diff options
100 files changed, 1642 insertions, 820 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim index 4ea68dc99..66fbe577c 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -10,7 +10,7 @@ # abstract syntax tree + symbol table import - msgs, hashes, nversion, options, strutils, securehash, ropes, idents, lists, + msgs, hashes, nversion, options, strutils, securehash, ropes, idents, intsets, idgen type @@ -736,13 +736,15 @@ type TLibKind* = enum libHeader, libDynamic - TLib* = object of lists.TListEntry # also misused for headers! + + TLib* = object # also misused for headers! kind*: TLibKind generated*: bool # needed for the backends: isOverriden*: bool name*: Rope path*: PNode # can be a string literal! + CompilesId* = int ## id that is used for the caching logic within ## ``system.compiles``. See the seminst module. TInstantiation* = object diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 8ea81ac65..7493a50ca 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -109,7 +109,7 @@ proc openArrayLoc(p: BProc, n: PNode): Rope = initLocExpr(p, n, a) case skipTypes(a.t, abstractVar).kind of tyOpenArray, tyVarargs: - result = "$1, $1Len0" % [rdLoc(a)] + result = "$1, $1Len_0" % [rdLoc(a)] of tyString, tySequence: if skipTypes(n.typ, abstractInst).kind == tyVar and not compileToCpp(p.module): @@ -200,8 +200,8 @@ proc genClosureCall(p: BProc, le, ri: PNode, d: var TLoc) = proc addComma(r: Rope): Rope = result = if r == nil: r else: r & ~", " - const PatProc = "$1.ClEnv? $1.ClPrc($3$1.ClEnv):(($4)($1.ClPrc))($2)" - const PatIter = "$1.ClPrc($3$1.ClEnv)" # we know the env exists + const PatProc = "$1.ClE_0? $1.ClP_0($3$1.ClE_0):(($4)($1.ClP_0))($2)" + const PatIter = "$1.ClP_0($3$1.ClE_0)" # we know the env exists var op: TLoc initLocExpr(p, ri.sons[0], op) var pl: Rope diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 6f7e83c18..309fb1f20 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -296,10 +296,10 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = of tyProc: if needsComplexAssignment(dest.t): # optimize closure assignment: - let a = optAsgnLoc(dest, dest.t, "ClEnv".rope) - let b = optAsgnLoc(src, dest.t, "ClEnv".rope) + let a = optAsgnLoc(dest, dest.t, "ClE_0".rope) + let b = optAsgnLoc(src, dest.t, "ClE_0".rope) genRefAssign(p, a, b, flags) - linefmt(p, cpsStmts, "$1.ClPrc = $2.ClPrc;$n", rdLoc(dest), rdLoc(src)) + linefmt(p, cpsStmts, "$1.ClP_0 = $2.ClP_0;$n", rdLoc(dest), rdLoc(src)) else: linefmt(p, cpsStmts, "$1 = $2;$n", rdLoc(dest), rdLoc(src)) of tyTuple: @@ -336,12 +336,12 @@ proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = # passed to an open array? if needsComplexAssignment(dest.t): linefmt(p, cpsStmts, # XXX: is this correct for arrays? - "#genericAssignOpenArray((void*)$1, (void*)$2, $1Len0, $3);$n", + "#genericAssignOpenArray((void*)$1, (void*)$2, $1Len_0, $3);$n", addrLoc(dest), addrLoc(src), genTypeInfo(p.module, dest.t)) else: useStringh(p.module) linefmt(p, cpsStmts, - "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($1[0])*$1Len0);$n", + "memcpy((void*)$1, (NIM_CONST void*)$2, sizeof($1[0])*$1Len_0);$n", rdLoc(dest), rdLoc(src)) of tySet: if mapType(ty) == ctArray: @@ -384,7 +384,7 @@ proc genDeepCopy(p: BProc; dest, src: TLoc) = addrLoc(dest), rdLoc(src), genTypeInfo(p.module, dest.t)) of tyOpenArray, tyVarargs: linefmt(p, cpsStmts, - "#genericDeepCopyOpenArray((void*)$1, (void*)$2, $1Len0, $3);$n", + "#genericDeepCopyOpenArray((void*)$1, (void*)$2, $1Len_0, $3);$n", addrLoc(dest), addrLocOrTemp(src), genTypeInfo(p.module, dest.t)) of tySet: if mapType(ty) == ctArray: @@ -602,14 +602,14 @@ proc genEqProc(p: BProc, e: PNode, d: var TLoc) = initLocExpr(p, e.sons[2], b) if a.t.skipTypes(abstractInst).callConv == ccClosure: putIntoDest(p, d, e.typ, - "($1.ClPrc == $2.ClPrc && $1.ClEnv == $2.ClEnv)" % [rdLoc(a), rdLoc(b)]) + "($1.ClP_0 == $2.ClP_0 && $1.ClE_0 == $2.ClE_0)" % [rdLoc(a), rdLoc(b)]) else: putIntoDest(p, d, e.typ, "($1 == $2)" % [rdLoc(a), rdLoc(b)]) proc genIsNil(p: BProc, e: PNode, d: var TLoc) = let t = skipTypes(e.sons[1].typ, abstractRange) if t.kind == tyProc and t.callConv == ccClosure: - unaryExpr(p, e, d, "($1.ClPrc == 0)") + unaryExpr(p, e, d, "($1.ClP_0 == 0)") else: unaryExpr(p, e, d, "($1 == 0)") @@ -861,7 +861,7 @@ proc genOpenArrayElem(p: BProc, x, y: PNode, d: var TLoc) = initLocExpr(p, x, a) initLocExpr(p, y, b) # emit range check: if optBoundsCheck in p.options: - linefmt(p, cpsStmts, "if ((NU)($1) >= (NU)($2Len0)) #raiseIndexError();$n", + linefmt(p, cpsStmts, "if ((NU)($1) >= (NU)($2Len_0)) #raiseIndexError();$n", rdLoc(b), rdLoc(a)) # BUGFIX: ``>=`` and not ``>``! if d.k == locNone: d.s = a.s putIntoDest(p, d, elemType(skipTypes(a.t, abstractVar)), @@ -944,7 +944,7 @@ proc genEcho(p: BProc, n: PNode) = # this unusal way of implementing it ensures that e.g. ``echo("hallo", 45)`` # is threadsafe. internalAssert n.kind == nkBracket - discard lists.includeStr(p.module.headerFiles, "<stdio.h>") + p.module.includeHeader("<stdio.h>") var args: Rope = nil var a: TLoc for i in countup(0, n.len-1): @@ -1322,7 +1322,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) = var b: TLoc case a.t.kind of tyOpenArray, tyVarargs: - putIntoDest(p, b, e.typ, "$1, $1Len0" % [rdLoc(a)], a.s) + putIntoDest(p, b, e.typ, "$1, $1Len_0" % [rdLoc(a)], a.s) of tyString, tySequence: putIntoDest(p, b, e.typ, "$1->data, $1->$2" % [rdLoc(a), lenField(p)], a.s) @@ -1362,8 +1362,8 @@ proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) = let typ = skipTypes(a.typ, abstractVar) case typ.kind of tyOpenArray, tyVarargs: - if op == mHigh: unaryExpr(p, e, d, "($1Len0-1)") - else: unaryExpr(p, e, d, "$1Len0") + if op == mHigh: unaryExpr(p, e, d, "($1Len_0-1)") + else: unaryExpr(p, e, d, "$1Len_0") of tyCString: useStringh(p.module) if op == mHigh: unaryExpr(p, e, d, "($1 ? (strlen($1)-1) : -1)") @@ -1851,11 +1851,11 @@ proc genClosure(p: BProc, n: PNode, d: var TLoc) = # tasyncawait.nim breaks with this optimization: when false: if d.k != locNone: - linefmt(p, cpsStmts, "$1.ClPrc = $2; $1.ClEnv = $3;$n", + linefmt(p, cpsStmts, "$1.ClP_0 = $2; $1.ClE_0 = $3;$n", d.rdLoc, a.rdLoc, b.rdLoc) else: getTemp(p, n.typ, tmp) - linefmt(p, cpsStmts, "$1.ClPrc = $2; $1.ClEnv = $3;$n", + linefmt(p, cpsStmts, "$1.ClP_0 = $2; $1.ClE_0 = $3;$n", tmp.rdLoc, a.rdLoc, b.rdLoc) putLocIntoDest(p, d, tmp) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 02119d63e..cc925b150 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -170,7 +170,7 @@ proc genBreakState(p: BProc, n: PNode) = else: initLocExpr(p, n.sons[0], a) # the environment is guaranteed to contain the 'state' field at offset 0: - lineF(p, cpsStmts, "if ((((NI*) $1.ClEnv)[0]) < 0) break;$n", [rdLoc(a)]) + lineF(p, cpsStmts, "if ((((NI*) $1.ClE_0)[0]) < 0) break;$n", [rdLoc(a)]) # lineF(p, cpsStmts, "if (($1) < 0) break;$n", [rdLoc(a)]) proc genVarPrototypeAux(m: BModule, sym: PSym) @@ -540,7 +540,7 @@ proc genBreakStmt(p: BProc, t: PNode) = # named break? assert(t.sons[0].kind == nkSym) var sym = t.sons[0].sym - assert(sym.loc.k == locOther) + doAssert(sym.loc.k == locOther) idx = sym.position-1 else: # an unnamed 'break' can only break a loop after 'transf' pass: @@ -884,7 +884,7 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) = # if not isEmptyType(t.typ) and d.k == locNone: getTemp(p, t.typ, d) - discard lists.includeStr(p.module.headerFiles, "<setjmp.h>") + p.module.includeHeader("<setjmp.h>") genLineDir(p, t) var safePoint = getTempName(p.module) if getCompilerProc("Exception") != nil: diff --git a/compiler/ccgtrav.nim b/compiler/ccgtrav.nim index 547504afb..457093c61 100644 --- a/compiler/ccgtrav.nim +++ b/compiler/ccgtrav.nim @@ -87,7 +87,7 @@ proc genTraverseProc(c: var TTraversalClosure, accessor: Rope, typ: PType) = lineCg(p, cpsStmts, c.visitorFrmt, accessor) of tyProc: if typ.callConv == ccClosure: - lineCg(p, cpsStmts, c.visitorFrmt, rfmt(nil, "$1.ClEnv", accessor)) + lineCg(p, cpsStmts, c.visitorFrmt, rfmt(nil, "$1.ClE_0", accessor)) else: discard diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 29d4e23c9..8fdd97428 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -413,7 +413,7 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var Rope, # this fixes the 'sort' bug: if param.typ.kind == tyVar: param.loc.s = OnUnknown # need to pass hidden parameter: - addf(params, ", NI $1Len$2", [param.loc.r, j.rope]) + addf(params, ", NI $1Len_$2", [param.loc.r, j.rope]) inc(j) arr = arr.sons[0] if t.sons[0] != nil and isInvalidReturnType(t.sons[0]): @@ -427,7 +427,7 @@ proc genProcParams(m: BModule, t: PType, rettype, params: var Rope, addf(params, " Result", []) if t.callConv == ccClosure and declareEnvironment: if params != nil: add(params, ", ") - add(params, "void* ClEnv") + add(params, "void* ClE_0") if tfVarargs in t.flags: if params != nil: add(params, ", ") add(params, "...") @@ -678,8 +678,8 @@ proc getTypeDescAux(m: BModule, origTyp: PType, check: var IntSet): Rope = [rope(CallingConvToStr[t.callConv]), rettype, result, desc]) else: addf(m.s[cfsTypes], "typedef struct {$n" & - "N_NIMCALL_PTR($2, ClPrc) $3;$n" & - "void* ClEnv;$n} $1;$n", + "N_NIMCALL_PTR($2, ClP_0) $3;$n" & + "void* ClE_0;$n} $1;$n", [result, rettype, desc]) of tySequence: # we cannot use getTypeForward here because then t would be associated @@ -815,8 +815,8 @@ proc getClosureType(m: BModule, t: PType, kind: TClosureTypeKind): Rope = [rope(CallingConvToStr[t.callConv]), rettype, result, desc]) else: addf(m.s[cfsTypes], "typedef struct {$n" & - "N_NIMCALL_PTR($2, ClPrc) $3;$n" & - "void* ClEnv;$n} $1;$n", + "N_NIMCALL_PTR($2, ClP_0) $3;$n" & + "void* ClE_0;$n} $1;$n", [result, rettype, desc]) proc finishTypeDescriptions(m: BModule) = diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index d42f0438f..46a9177ad 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -10,7 +10,7 @@ # This module declares some helpers for the C code generator. import - ast, astalgo, ropes, lists, hashes, strutils, types, msgs, wordrecg, + ast, astalgo, ropes, hashes, strutils, types, msgs, wordrecg, platform, trees proc getPragmaStmt*(n: PNode, w: TSpecialWord): PNode = @@ -181,7 +181,7 @@ proc mangle*(name: string): string = of '_': # we generate names like 'foo_9' for scope disambiguations and so # disallow this here: - if i < name.len-1 and name[i] in Digits: + if i < name.len-1 and name[i+1] in Digits: discard else: add(result, c) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 63a7cda0e..c829a463e 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -11,7 +11,7 @@ import ast, astalgo, hashes, trees, platform, magicsys, extccomp, options, intsets, - nversion, nimsets, msgs, securehash, bitsets, idents, lists, types, + nversion, nimsets, msgs, securehash, bitsets, idents, types, ccgutils, os, ropes, math, passes, rodread, wordrecg, treetab, cgmeth, condsyms, rodutils, renderer, idgen, cgendata, ccgmerge, semfold, aliases, lowerings, semparallel, tables, sets, ndi @@ -75,12 +75,13 @@ proc isSimpleConst(typ: PType): bool = proc useStringh(m: BModule) = if includesStringh notin m.flags: incl m.flags, includesStringh - discard lists.includeStr(m.headerFiles, "<string.h>") + m.includeHeader("<string.h>") proc useHeader(m: BModule, sym: PSym) = if lfHeader in sym.loc.flags: assert(sym.annex != nil) - discard lists.includeStr(m.headerFiles, getStr(sym.annex.path)) + let str = getStr(sym.annex.path) + m.includeHeader(str) proc cgsym(m: BModule, name: string): Rope @@ -594,15 +595,14 @@ proc cgsym(m: BModule, name: string): Rope = proc generateHeaders(m: BModule) = add(m.s[cfsHeaders], tnl & "#include \"nimbase.h\"" & tnl) - var it = PStrEntry(m.headerFiles.head) - while it != nil: - if it.data[0] == '#': - add(m.s[cfsHeaders], rope(it.data.replace('`', '"') & tnl)) - elif it.data[0] notin {'\"', '<'}: - addf(m.s[cfsHeaders], "#include \"$1\"$N", [rope(it.data)]) + + for it in m.headerFiles: + if it[0] == '#': + add(m.s[cfsHeaders], rope(it.replace('`', '"') & tnl)) + elif it[0] notin {'\"', '<'}: + addf(m.s[cfsHeaders], "#include \"$1\"$N", [rope(it)]) else: - addf(m.s[cfsHeaders], "#include $1$N", [rope(it.data)]) - it = PStrEntry(it.next) + addf(m.s[cfsHeaders], "#include $1$N", [rope(it)]) add(m.s[cfsHeaders], "#undef linux" & tnl) proc initFrame(p: BProc, procname, filename: Rope): Rope = @@ -628,7 +628,7 @@ proc closureSetup(p: BProc, prc: PSym) = #echo "created environment: ", env.id, " for ", prc.name.s assignLocalVar(p, env) # generate cast assignment: - linefmt(p, cpsStmts, "$1 = ($2) ClEnv;$n", + linefmt(p, cpsStmts, "$1 = ($2) ClE_0;$n", rdLoc(env.loc), getTypeDesc(p.module, env.typ)) proc easyResultAsgn(n: PNode): PNode = @@ -974,7 +974,7 @@ proc genMainProc(m: BModule) = else: nimMain = WinNimDllMain otherMain = WinCDllMain - discard lists.includeStr(m.headerFiles, "<windows.h>") + m.includeHeader("<windows.h>") elif optGenDynLib in gGlobalOptions: nimMain = PosixNimDllMain otherMain = PosixCDllMain @@ -1129,7 +1129,7 @@ proc initProcOptions(m: BModule): TOptions = proc rawNewModule(g: BModuleList; module: PSym, filename: string): BModule = new(result) result.tmpBase = rope("TM" & $hashOwner(module) & "_") - initLinkedList(result.headerFiles) + result.headerFiles = @[] result.declaredThings = initIntSet() result.declaredProtos = initIntSet() result.cfilename = filename @@ -1166,7 +1166,7 @@ proc nullify[T](arr: var T) = proc resetModule*(m: BModule) = # between two compilations in CAAS mode, we can throw # away all the data that was written to disk - initLinkedList(m.headerFiles) + m.headerFiles = @[] m.declaredProtos = initIntSet() m.forwTypeCache = initTable[SigHash, Rope]() m.initProc = newProc(nil, m) @@ -1366,7 +1366,7 @@ proc updateCachedModule(m: BModule) = cf.flags = {CfileFlag.Cached} addFileToCompile(cf) -proc myClose(b: PPassContext, n: PNode): PNode = +proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = result = n if b == nil or passes.skipCodegen(n): return var m = BModule(b) @@ -1377,9 +1377,10 @@ proc myClose(b: PPassContext, n: PNode): PNode = registerModuleToMain(m.g, m.module) if sfMainModule in m.module.flags: - incl m.flags, objHasKidsValid - var disp = generateMethodDispatchers() - for i in 0..sonsLen(disp)-1: genProcAux(m, disp.sons[i].sym) + if m.g.forwardedProcsCounter == 0: + incl m.flags, objHasKidsValid + let disp = generateMethodDispatchers(graph) + for x in disp: genProcAux(m, x.sym) genMainProc(m) proc cgenWriteModules*(backend: RootRef, config: ConfigRef) = diff --git a/compiler/cgendata.nim b/compiler/cgendata.nim index 565399ead..be087095f 100644 --- a/compiler/cgendata.nim +++ b/compiler/cgendata.nim @@ -10,7 +10,7 @@ ## This module contains the data structures for the C code generation phase. import - ast, astalgo, ropes, passes, options, intsets, lists, platform, sighashes, + ast, astalgo, ropes, passes, options, intsets, platform, sighashes, tables, ndi from msgs import TLineInfo @@ -130,7 +130,7 @@ type forwTypeCache*: TypeCache # cache for forward declarations of types declaredThings*: IntSet # things we have declared in this .c file declaredProtos*: IntSet # prototypes we have declared in this .c file - headerFiles*: TLinkedList # needed headers to include + headerFiles*: seq[string] # needed headers to include typeInfoMarker*: TypeCache # needed for generating type information initProc*: BProc # code for init procedure postInitProc*: BProc # code to be executed after the init proc @@ -148,9 +148,13 @@ type g*: BModuleList ndi*: NdiFile +proc includeHeader*(this: BModule; header: string) = + if not this.headerFiles.contains header: + this.headerFiles.add header + proc s*(p: BProc, s: TCProcSection): var Rope {.inline.} = # section in the current block - result = p.blocks[p.blocks.len - 1].sections[s] + result = p.blocks[^1].sections[s] proc procSec*(p: BProc, s: TCProcSection): var Rope {.inline.} = # top level proc sections diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim index eda80be30..d05e395b9 100644 --- a/compiler/cgmeth.nim +++ b/compiler/cgmeth.nim @@ -11,7 +11,7 @@ import intsets, options, ast, astalgo, msgs, idents, renderer, types, magicsys, - sempass2, strutils + sempass2, strutils, modulegraphs proc genConv(n: PNode, d: PType, downcast: bool): PNode = var dest = skipTypes(d, abstractPtrs) @@ -35,19 +35,24 @@ proc genConv(n: PNode, d: PType, downcast: bool): PNode = else: result = n +proc getDispatcher*(s: PSym): PSym = + ## can return nil if is has no dispatcher. + let dispn = lastSon(s.ast) + if dispn.kind == nkSym: + let disp = dispn.sym + if sfDispatcher in disp.flags: result = disp + proc methodCall*(n: PNode): PNode = result = n # replace ordinary method by dispatcher method: - var disp = lastSon(result.sons[0].sym.ast).sym - assert sfDispatcher in disp.flags - result.sons[0].sym = disp - # change the arguments to up/downcasts to fit the dispatcher's parameters: - for i in countup(1, sonsLen(result)-1): - result.sons[i] = genConv(result.sons[i], disp.typ.sons[i], true) - -# save for incremental compilation: -var - gMethods: seq[tuple[methods: TSymSeq, dispatcher: PSym]] = @[] + let disp = getDispatcher(result.sons[0].sym) + if disp != nil: + result.sons[0].sym = disp + # change the arguments to up/downcasts to fit the dispatcher's parameters: + for i in countup(1, sonsLen(result)-1): + result.sons[i] = genConv(result.sons[i], disp.typ.sons[i], true) + else: + localError(n.info, "'" & $result.sons[0] & "' lacks a dispatcher") type MethodResult = enum No, Invalid, Yes @@ -55,8 +60,8 @@ type proc sameMethodBucket(a, b: PSym): MethodResult = if a.name.id != b.name.id: return if sonsLen(a.typ) != sonsLen(b.typ): - return # check for return type: - if not sameTypeOrNil(a.typ.sons[0], b.typ.sons[0]): return + return + for i in countup(1, sonsLen(a.typ) - 1): var aa = a.typ.sons[i] var bb = b.typ.sons[i] @@ -84,6 +89,14 @@ proc sameMethodBucket(a, b: PSym): MethodResult = return No else: return No + if result == Yes: + # check for return type: + if not sameTypeOrNil(a.typ.sons[0], b.typ.sons[0]): + if b.typ.sons[0] != nil and b.typ.sons[0].kind == tyExpr: + # infer 'auto' from the base to make it consistent: + b.typ.sons[0] = a.typ.sons[0] + else: + return No proc attachDispatcher(s: PSym, dispatcher: PNode) = var L = s.ast.len-1 @@ -144,27 +157,28 @@ proc fixupDispatcher(meth, disp: PSym) = if disp.typ.lockLevel < meth.typ.lockLevel: disp.typ.lockLevel = meth.typ.lockLevel -proc methodDef*(s: PSym, fromCache: bool) = - let L = len(gMethods) +proc methodDef*(g: ModuleGraph; s: PSym, fromCache: bool) = + let L = len(g.methods) var witness: PSym for i in countup(0, L - 1): - let disp = gMethods[i].dispatcher + let disp = g.methods[i].dispatcher case sameMethodBucket(disp, s) of Yes: - add(gMethods[i].methods, s) + add(g.methods[i].methods, s) attachDispatcher(s, lastSon(disp.ast)) fixupDispatcher(s, disp) #echo "fixup ", disp.name.s, " ", disp.id when useEffectSystem: checkMethodEffects(disp, s) - if sfBase in s.flags and gMethods[i].methods[0] != s: + if {sfBase, sfFromGeneric} * s.flags == {sfBase} and + g.methods[i].methods[0] != s: # already exists due to forwarding definition? localError(s.info, "method is not a base") return of No: discard of Invalid: - if witness.isNil: witness = gMethods[i].methods[0] + if witness.isNil: witness = g.methods[i].methods[0] # create a new dispatcher: - add(gMethods, (methods: @[s], dispatcher: createDispatcher(s))) + add(g.methods, (methods: @[s], dispatcher: createDispatcher(s))) #echo "adding ", s.info #if fromCache: # internalError(s.info, "no method dispatcher found") @@ -258,12 +272,12 @@ proc genDispatcher(methods: TSymSeq, relevantCols: IntSet): PSym = disp = ret result.ast.sons[bodyPos] = disp -proc generateMethodDispatchers*(): PNode = +proc generateMethodDispatchers*(g: ModuleGraph): PNode = result = newNode(nkStmtList) - for bucket in countup(0, len(gMethods) - 1): + for bucket in countup(0, len(g.methods) - 1): var relevantCols = initIntSet() - for col in countup(1, sonsLen(gMethods[bucket].methods[0].typ) - 1): - if relevantCol(gMethods[bucket].methods, col): incl(relevantCols, col) - sortBucket(gMethods[bucket].methods, relevantCols) + for col in countup(1, sonsLen(g.methods[bucket].methods[0].typ) - 1): + if relevantCol(g.methods[bucket].methods, col): incl(relevantCols, col) + sortBucket(g.methods[bucket].methods, relevantCols) addSon(result, - newSymNode(genDispatcher(gMethods[bucket].methods, relevantCols))) + newSymNode(genDispatcher(g.methods[bucket].methods, relevantCols))) diff --git a/compiler/commands.nim b/compiler/commands.nim index 8dd7439b6..6fdca27fc 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -26,8 +26,8 @@ bootSwitch(usedGoGC, defined(gogc), "--gc:go") bootSwitch(usedNoGC, defined(nogc), "--gc:none") import - os, msgs, options, nversion, condsyms, strutils, extccomp, platform, lists, - wordrecg, parseutils, nimblecmd, idents, parseopt + os, msgs, options, nversion, condsyms, strutils, extccomp, platform, + wordrecg, parseutils, nimblecmd, idents, parseopt, sequtils # but some have deps to imported modules. Yay. bootSwitch(usedTinyC, hasTinyCBackend, "-d:tinyc") @@ -335,12 +335,14 @@ proc processSwitch(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; of "excludepath": expectArg(switch, arg, pass, info) let path = processPath(arg, info) - lists.excludePath(options.searchPaths, path) - lists.excludePath(options.lazyPaths, path) + + options.searchPaths.keepItIf( cmpPaths(it, path) != 0 ) + options.lazyPaths.keepItIf( cmpPaths(it, path) != 0 ) + if (len(path) > 0) and (path[len(path) - 1] == DirSep): let strippedPath = path[0 .. (len(path) - 2)] - lists.excludePath(options.searchPaths, strippedPath) - lists.excludePath(options.lazyPaths, strippedPath) + options.searchPaths.keepItIf( cmpPaths(it, strippedPath) != 0 ) + options.lazyPaths.keepItIf( cmpPaths(it, strippedPath) != 0 ) of "nimcache": expectArg(switch, arg, pass, info) options.nimcacheDir = processPath(arg, info, true) diff --git a/compiler/compilerlog.nim b/compiler/compilerlog.nim new file mode 100644 index 000000000..fbf8cd9aa --- /dev/null +++ b/compiler/compilerlog.nim @@ -0,0 +1,9 @@ + +from os import getHomeDir, `/` + +proc logStr*(line: string) = + var f: File + if open(f, getHomeDir() / "nimsuggest.log", fmAppend): + f.writeLine(line) + f.close() + diff --git a/compiler/docgen2.nim b/compiler/docgen2.nim index 9504ab52f..118f1c7c5 100644 --- a/compiler/docgen2.nim +++ b/compiler/docgen2.nim @@ -33,11 +33,11 @@ template closeImpl(body: untyped) {.dirty.} = except IOError: discard -proc close(p: PPassContext, n: PNode): PNode = +proc close(graph: ModuleGraph; p: PPassContext, n: PNode): PNode = closeImpl: writeOutput(g.doc, g.module.filename, HtmlExt, useWarning) -proc closeJson(p: PPassContext, n: PNode): PNode = +proc closeJson(graph: ModuleGraph; p: PPassContext, n: PNode): PNode = closeImpl: writeOutputJson(g.doc, g.module.filename, ".json", useWarning) diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index a84d8ae22..70cd411fe 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -13,7 +13,7 @@ # nim files. import - lists, ropes, os, strutils, osproc, platform, condsyms, options, msgs, + ropes, os, strutils, osproc, platform, condsyms, options, msgs, securehash, streams #from debuginfo import writeDebugInfo @@ -360,8 +360,8 @@ type CfileList = seq[Cfile] var - externalToLink: TLinkedList # files to link in addition to the file - # we compiled + externalToLink: seq[string] = @[] # files to link in addition to the file + # we compiled linkOptionsCmd: string = "" compileOptionsCmd: seq[string] = @[] linkOptions: string = "" @@ -456,11 +456,10 @@ proc resetCompilationLists* = # when the module is loaded/unloaded it adds/removes its items # That's because we still need to hash check the external files # Maybe we can do that in checkDep on the other hand? - initLinkedList(externalToLink) + externalToLink.setLen 0 proc addExternalFileToLink*(filename: string) = - prependStr(externalToLink, filename) - # BUGFIX: was ``appendStr`` + externalToLink.insert(filename, 0) proc execWithEcho(cmd: string, msg = hintExecuting): int = rawMessage(msg, cmd) @@ -497,9 +496,6 @@ proc noAbsolutePaths: bool {.inline.} = # `optGenMapping` is included here for niminst. result = gGlobalOptions * {optGenScript, optGenMapping} != {} -proc add(s: var string, many: openArray[string]) = - s.add many.join - proc cFileSpecificOptions(cfilename: string): string = result = compileOptions for option in compileOptionsCmd: @@ -530,7 +526,7 @@ proc getLinkOptions: string = for linkedLib in items(cLinkedLibs): result.add(CC[cCompiler].linkLibCmd % linkedLib.quoteShell) for libDir in items(cLibs): - result.add([CC[cCompiler].linkDirCmd, libDir.quoteShell]) + result.add(join([CC[cCompiler].linkDirCmd, libDir.quoteShell])) proc needsExeExt(): bool {.inline.} = result = (optGenScript in gGlobalOptions and targetOS == osWindows) or @@ -566,7 +562,7 @@ proc getCompileCFileCmd*(cfile: Cfile): string = includeCmd = CC[c].includeCmd & quoteShell(libpath) for includeDir in items(cIncludes): - includeCmd.add([CC[c].includeCmd, includeDir.quoteShell]) + includeCmd.add(join([CC[c].includeCmd, includeDir.quoteShell])) compilePattern = joinPath(ccompilerpath, exe) else: @@ -741,14 +737,12 @@ proc callCCompiler*(projectfile: string) = rawMessage(errExecutionOfProgramFailed, cmds.join()) if optNoLinking notin gGlobalOptions: # call the linker: - var it = PStrEntry(externalToLink.head) var objfiles = "" - while it != nil: - let objFile = if noAbsolutePaths(): it.data.extractFilename else: it.data + for it in externalToLink: + let objFile = if noAbsolutePaths(): it.extractFilename else: it add(objfiles, ' ') add(objfiles, quoteShell( addFileExt(objFile, CC[cCompiler].objExt))) - it = PStrEntry(it.next) for x in toCompile: add(objfiles, ' ') add(objfiles, quoteShell(x.obj)) @@ -791,15 +785,14 @@ proc writeJsonBuildInstructions*(projectfile: string) = else: lit "],\L" - proc linkfiles(f: File; buf, objfiles: var string; toLink: TLinkedList) = - var it = PStrEntry(toLink.head) - while it != nil: - let objfile = addFileExt(it.data, CC[cCompiler].objExt) - str objfile + proc linkfiles(f: File; buf, objfiles: var string; toLink: seq[string]) = + for i, it in toLink: + let objfile = addFileExt(it, CC[cCompiler].objExt) + str(objfile) add(objfiles, ' ') add(objfiles, quoteShell(objfile)) - it = PStrEntry(it.next) - if it == nil: + + if i == toLink.high: lit "\L" else: lit ",\L" diff --git a/compiler/hlo.nim b/compiler/hlo.nim index de0fa6216..9491eef83 100644 --- a/compiler/hlo.nim +++ b/compiler/hlo.nim @@ -84,7 +84,7 @@ proc hlo(c: PContext, n: PNode): PNode = if isEmptyType(n.typ) and isEmptyType(result.typ): discard else: - result = fitNode(c, n.typ, result) + result = fitNode(c, n.typ, result, n.info) # optimization has been applied so check again: result = commonOptimizations(c.module, result) result = hlo(c, result) diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index 29a72c86e..8f819686d 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -31,7 +31,7 @@ implements the required case distinction. import ast, astalgo, strutils, hashes, trees, platform, magicsys, extccomp, options, - nversion, nimsets, msgs, securehash, bitsets, idents, lists, types, os, + nversion, nimsets, msgs, securehash, bitsets, idents, types, os, times, ropes, math, passes, ccgutils, wordrecg, renderer, rodread, rodutils, intsets, cgmeth, lowerings @@ -2237,13 +2237,13 @@ proc myProcess(b: PPassContext, n: PNode): PNode = add(p.g.code, p.body) globals.unique = p.unique -proc wholeCode*(m: BModule): Rope = +proc wholeCode(graph: ModuleGraph; m: BModule): Rope = for prc in globals.forwarded: if not globals.generatedSyms.containsOrIncl(prc.id): var p = newProc(globals, m, nil, m.module.options) attachProc(p, prc) - var disp = generateMethodDispatchers() + var disp = generateMethodDispatchers(graph) for i in 0..sonsLen(disp)-1: let prc = disp.sons[i].sym if not globals.generatedSyms.containsOrIncl(prc.id): @@ -2277,7 +2277,7 @@ proc genClass(obj: PType; content: Rope; ext: string) = let outfile = changeFileExt(completeCFilePath($cls), ext) discard writeRopeIfNotEqual(result, outfile) -proc myClose(b: PPassContext, n: PNode): PNode = +proc myClose(graph: ModuleGraph; b: PPassContext, n: PNode): PNode = if passes.skipCodegen(n): return n result = myProcess(b, n) var m = BModule(b) @@ -2285,7 +2285,7 @@ proc myClose(b: PPassContext, n: PNode): PNode = let ext = if m.target == targetJS: "js" else: "php" let f = if globals.classes.len == 0: m.module.filename else: "nimsystem" - let code = wholeCode(m) + let code = wholeCode(graph, m) let outfile = if options.outFile.len > 0: if options.outFile.isAbsolute: options.outFile diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim index 8d4badb4e..cd2ccfe53 100644 --- a/compiler/lambdalifting.nim +++ b/compiler/lambdalifting.nim @@ -10,7 +10,7 @@ # This include file implements lambda lifting for the transformator. import - intsets, strutils, lists, options, ast, astalgo, trees, treetab, msgs, os, + intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os, idents, renderer, types, magicsys, rodread, lowerings, tables discard """ diff --git a/compiler/lists.nim b/compiler/lists.nim index 27e373342..bfd052204 100644 --- a/compiler/lists.nim +++ b/compiler/lists.nim @@ -7,113 +7,22 @@ # distribution, for details about the copyright. # -# This module implements a generic doubled linked list. -# TODO Remove this and replace it with something sensible -import os -type - PListEntry* = ref TListEntry - TListEntry* = object of RootObj - prev*, next*: PListEntry - - TStrEntry* = object of TListEntry - data*: string - - PStrEntry* = ref TStrEntry - TLinkedList* = object # for the "find" operation: - head*, tail*: PListEntry - counter*: int - - TCompareProc* = proc (entry: PListEntry, closure: pointer): bool {.nimcall.} - -proc initLinkedList*(list: var TLinkedList) = - list.counter = 0 - list.head = nil - list.tail = nil - -proc append*(list: var TLinkedList, entry: PListEntry) = - inc(list.counter) - entry.next = nil - entry.prev = list.tail - if list.tail != nil: - assert(list.tail.next == nil) - list.tail.next = entry - list.tail = entry - if list.head == nil: list.head = entry - -proc contains*(list: TLinkedList, data: string): bool = - var it = list.head - while it != nil: - if PStrEntry(it).data == data: - return true - it = it.next +# This module is deprecated, don't use it. +# TODO Remove this -proc newStrEntry(data: string): PStrEntry = - new(result) - result.data = data - -proc appendStr*(list: var TLinkedList, data: string) = - append(list, newStrEntry(data)) - -proc includeStr*(list: var TLinkedList, data: string): bool = - if contains(list, data): return true - appendStr(list, data) # else: add to list - -proc prepend*(list: var TLinkedList, entry: PListEntry) = - inc(list.counter) - entry.prev = nil - entry.next = list.head - if list.head != nil: - assert(list.head.prev == nil) - list.head.prev = entry - list.head = entry - if list.tail == nil: list.tail = entry - -proc prependStr*(list: var TLinkedList, data: string) = - prepend(list, newStrEntry(data)) +import os -proc insertBefore*(list: var TLinkedList, pos, entry: PListEntry) = - assert(pos != nil) - if pos == list.head: - prepend(list, entry) - else: - inc(list.counter) - entry.next = pos - entry.prev = pos.prev - if pos.prev != nil: pos.prev.next = entry - pos.prev = entry +static: + echo "WARNING: imported deprecated module compiler/lists.nim, use seq ore lists from the standard library" -proc remove*(list: var TLinkedList, entry: PListEntry) = - dec(list.counter) - if entry == list.tail: - list.tail = entry.prev - if entry == list.head: - list.head = entry.next - if entry.next != nil: entry.next.prev = entry.prev - if entry.prev != nil: entry.prev.next = entry.next +proc appendStr*(list: var seq[string]; data: string) {.deprecated.} = + # just use system.add + list.add(data) -proc bringToFront*(list: var TLinkedList, entry: PListEntry) = - when true: - list.remove entry - list.prepend entry +proc includeStr(list: var seq[string]; data: string): bool {.deprecated.} = + if list.contains(data): + result = true else: - if entry == list.head: return - if entry == list.tail: list.tail = entry.prev - if entry.next != nil: entry.next.prev = entry.prev - if entry.prev != nil: entry.prev.next = entry.next - entry.prev = nil - entry.next = list.head - list.head = entry - -proc excludePath*(list: var TLinkedList, data: string) = - var it = list.head - while it != nil: - let nxt = it.next - if cmpPaths(PStrEntry(it).data, data) == 0: - remove(list, it) - it = nxt + result = false + list.add data -proc find*(list: TLinkedList, fn: TCompareProc, closure: pointer): PListEntry = - result = list.head - while result != nil: - if fn(result, closure): return - result = result.next diff --git a/compiler/lookups.nim b/compiler/lookups.nim index 19a4da07b..089e69ff9 100644 --- a/compiler/lookups.nim +++ b/compiler/lookups.nim @@ -115,7 +115,7 @@ proc errorSym*(c: PContext, n: PNode): PSym = considerQuotedIdent(m) else: getIdent("err:" & renderTree(m)) - result = newSym(skError, ident, getCurrOwner(), n.info) + result = newSym(skError, ident, getCurrOwner(c), n.info) result.typ = errorType(c) incl(result.flags, sfDiscardable) # pretend it's imported from some unknown module to prevent cascading errors: diff --git a/compiler/main.nim b/compiler/main.nim index 2acb7620c..5f86e6188 100644 --- a/compiler/main.nim +++ b/compiler/main.nim @@ -15,7 +15,7 @@ import wordrecg, sem, semdata, idents, passes, docgen, extccomp, cgen, jsgen, json, nversion, platform, nimconf, importer, passaux, depends, vm, vmdef, types, idgen, - docgen2, service, parser, modules, ccgutils, sigmatch, ropes, lists, + docgen2, service, parser, modules, ccgutils, sigmatch, ropes, modulegraphs from magicsys import systemModule, resetSysTypes @@ -151,7 +151,7 @@ proc mainCommand*(graph: ModuleGraph; cache: IdentCache) = # In "nim serve" scenario, each command must reset the registered passes clearPasses() gLastCmdTime = epochTime() - appendStr(searchPaths, options.libpath) + searchPaths.add(options.libpath) when false: # gProjectFull.len != 0: # current path is always looked first for modules prependStr(searchPaths, gProjectPath) @@ -229,7 +229,7 @@ proc mainCommand*(graph: ModuleGraph; cache: IdentCache) = for s in definedSymbolNames(): definedSymbols.elems.add(%s) var libpaths = newJArray() - for dir in iterSearchPath(searchPaths): libpaths.elems.add(%dir) + for dir in searchPaths: libpaths.elems.add(%dir) var dumpdata = % [ (key: "version", val: %VersionAsString), @@ -245,7 +245,7 @@ proc mainCommand*(graph: ModuleGraph; cache: IdentCache) = for s in definedSymbolNames(): msgWriteln(s, {msgStdout, msgSkipHook}) msgWriteln("-- end of list --", {msgStdout, msgSkipHook}) - for it in iterSearchPath(searchPaths): msgWriteln(it) + for it in searchPaths: msgWriteln(it) of "check": gCmd = cmdCheck commandCheck(graph, cache) diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index 87a35b290..c081a099a 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -40,9 +40,16 @@ type # module dependencies. backend*: RootRef # minor hack so that a backend can extend this easily config*: ConfigRef + doStopCompile*: proc(): bool {.closure.} + usageSym*: PSym # for nimsuggest + owners*: seq[PSym] + methods*: seq[tuple[methods: TSymSeq, dispatcher: PSym]] {.this: g.} +proc stopCompile*(g: ModuleGraph): bool {.inline.} = + result = doStopCompile != nil and doStopCompile() + proc newModuleGraph*(config: ConfigRef = nil): ModuleGraph = result = ModuleGraph() initStrTable(result.packageSyms) @@ -54,6 +61,8 @@ proc newModuleGraph*(config: ConfigRef = nil): ModuleGraph = result.config = newConfigRef() else: result.config = config + result.owners = @[] + result.methods = @[] proc resetAllModules*(g: ModuleGraph) = initStrTable(packageSyms) @@ -61,6 +70,9 @@ proc resetAllModules*(g: ModuleGraph) = modules = @[] importStack = @[] inclToMod = initTable[int32, int32]() + usageSym = nil + owners = @[] + methods = @[] proc getModule*(g: ModuleGraph; fileIdx: int32): PSym = if fileIdx >= 0 and fileIdx < modules.len: @@ -73,7 +85,7 @@ proc addDep*(g: ModuleGraph; m: PSym, dep: int32) = deps.incl m.position.dependsOn(dep) # we compute the transitive closure later when quering the graph lazily. # this improve efficiency quite a lot: - invalidTransitiveClosure = true + #invalidTransitiveClosure = true proc addIncludeDep*(g: ModuleGraph; module, includeFile: int32) = discard hasKeyOrPut(inclToMod, includeFile, module) diff --git a/compiler/msgs.nim b/compiler/msgs.nim index 2523958f0..0d30651bb 100644 --- a/compiler/msgs.nim +++ b/compiler/msgs.nim @@ -563,6 +563,15 @@ proc newFileInfo(fullPath, projPath: string): TFileInfo = if optEmbedOrigSrc in gGlobalOptions or true: result.lines = @[] +proc fileInfoKnown*(filename: string): bool = + var + canon: string + try: + canon = canonicalizePath(filename) + except: + canon = filename + result = filenameToIndexTbl.hasKey(canon) + proc fileInfoIdx*(filename: string; isKnownFile: var bool): int32 = var canon: string diff --git a/compiler/nimblecmd.nim b/compiler/nimblecmd.nim index 9c5c17287..e6466fc24 100644 --- a/compiler/nimblecmd.nim +++ b/compiler/nimblecmd.nim @@ -9,11 +9,11 @@ ## Implements some helper procs for Nimble (Nim's package manager) support. -import parseutils, strutils, strtabs, os, options, msgs, lists +import parseutils, strutils, strtabs, os, options, msgs proc addPath*(path: string, info: TLineInfo) = - if not contains(options.searchPaths, path): - lists.prependStr(options.searchPaths, path) + if not options.searchPaths.contains(path): + options.searchPaths.insert(path, 0) proc versionSplitPos(s: string): int = result = s.len-2 @@ -61,7 +61,7 @@ iterator chosen(packages: StringTableRef): string = proc addNimblePath(p: string, info: TLineInfo) = if not contains(options.searchPaths, p): message(info, hintPath, p) - lists.prependStr(options.lazyPaths, p) + options.lazyPaths.insert(p, 0) proc addPathRec(dir: string, info: TLineInfo) = var packages = newStringTable(modeStyleInsensitive) diff --git a/compiler/nimeval.nim b/compiler/nimeval.nim index 2872bdade..aca03fc16 100644 --- a/compiler/nimeval.nim +++ b/compiler/nimeval.nim @@ -10,7 +10,7 @@ ## exposes the Nim VM to clients. import ast, modules, passes, passaux, condsyms, - options, nimconf, lists, sem, semdata, llstream, vm, modulegraphs, idents + options, nimconf, sem, semdata, llstream, vm, modulegraphs, idents proc execute*(program: string) = passes.gIncludeFile = includeModule @@ -25,7 +25,7 @@ proc execute*(program: string) = registerPass(semPass) registerPass(evalPass) - appendStr(searchPaths, options.libpath) + searchPaths.add options.libpath var graph = newModuleGraph() var cache = newIdentCache() var m = makeStdinModule(graph) diff --git a/compiler/nimfix/nimfix.nim b/compiler/nimfix/nimfix.nim index 4afb16912..a97d88078 100644 --- a/compiler/nimfix/nimfix.nim +++ b/compiler/nimfix/nimfix.nim @@ -13,7 +13,7 @@ import strutils, os, parseopt import compiler/[options, commands, modules, sem, passes, passaux, nimfix/pretty, msgs, nimconf, - extccomp, condsyms, lists, + extccomp, condsyms, modulegraphs, idents] const Usage = """ @@ -39,10 +39,10 @@ proc mainCommand = registerPass verbosePass registerPass semPass gCmd = cmdPretty - appendStr(searchPaths, options.libpath) + searchPaths.add options.libpath if gProjectFull.len != 0: # current path is always looked first for modules - prependStr(searchPaths, gProjectPath) + searchPaths.insert(gProjectPath, 0) compileProject(newModuleGraph(), newIdentCache()) pretty.overwriteFiles() diff --git a/compiler/options.nim b/compiler/options.nim index 2295bbf93..6281980ff 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -8,7 +8,7 @@ # import - os, lists, strutils, strtabs, osproc, sets + os, strutils, strtabs, osproc, sets const hasTinyCBackend* = defined(tinyc) @@ -100,7 +100,7 @@ type IdeCmd* = enum ideNone, ideSug, ideCon, ideDef, ideUse, ideDus, ideChk, ideMod, - ideHighlight, ideOutline + ideHighlight, ideOutline, ideKnown ConfigRef* = ref object ## eventually all global configuration should be moved here cppDefines*: HashSet[string] @@ -129,7 +129,8 @@ var gExitcode*: int8 gCmd*: TCommands = cmdNone # the command gSelectedGC* = gcRefc # the selected GC - searchPaths*, lazyPaths*: TLinkedList + searchPaths*: seq[string] = @[] + lazyPaths*: seq[string] = @[] outFile*: string = "" docSeeSrcUrl*: string = "" # if empty, no seeSrc will be generated. \ # The string uses the formatting variables `path` and `line`. @@ -267,9 +268,7 @@ proc removeTrailingDirSep*(path: string): string = proc disableNimblePath*() = gNoNimblePath = true - lazyPaths.head = nil - lazyPaths.tail = nil - lazyPaths.counter = 0 + lazyPaths.setLen(0) include packagehandling @@ -336,27 +335,22 @@ proc completeGeneratedFilePath*(f: string, createSubDir: bool = true): string = result = joinPath(subdir, tail) #echo "completeGeneratedFilePath(", f, ") = ", result -iterator iterSearchPath*(searchPaths: TLinkedList): string = - var it = PStrEntry(searchPaths.head) - while it != nil: - yield it.data - it = PStrEntry(it.next) - proc rawFindFile(f: string): string = - for it in iterSearchPath(searchPaths): + for it in searchPaths: result = joinPath(it, f) if existsFile(result): return result.canonicalizePath result = "" proc rawFindFile2(f: string): string = - var it = PStrEntry(lazyPaths.head) - while it != nil: - result = joinPath(it.data, f) + for i, it in lazyPaths: + result = joinPath(it, f) if existsFile(result): - bringToFront(lazyPaths, it) + # bring to front + for j in countDown(i,1): + swap(lazyPaths[j], lazyPaths[j-1]) + return result.canonicalizePath - it = PStrEntry(it.next) result = "" template patchModule() {.dirty.} = @@ -442,6 +436,7 @@ proc parseIdeCmd*(s: string): IdeCmd = of "mod": ideMod of "highlight": ideHighlight of "outline": ideOutline + of "known": ideKnown else: ideNone proc `$`*(c: IdeCmd): string = @@ -456,3 +451,4 @@ proc `$`*(c: IdeCmd): string = of ideNone: "none" of ideHighlight: "highlight" of ideOutline: "outline" + of ideKnown: "known" diff --git a/compiler/parser.nim b/compiler/parser.nim index 902bf0fcb..272c1b15f 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -514,12 +514,12 @@ proc parsePar(p: var TParser): PNode = var a = simpleExpr(p) if p.tok.tokType == tkEquals: # special case: allow assignments + let asgn = newNodeP(nkAsgn, p) getTok(p) optInd(p, result) let b = parseExpr(p) - let asgn = newNodeI(nkAsgn, a.info, 2) - asgn.sons[0] = a - asgn.sons[1] = b + asgn.add a + asgn.add b result.add(asgn) if p.tok.tokType == tkSemiColon: semiStmtList(p, result) @@ -1184,10 +1184,10 @@ proc parseExprStmt(p: var TParser): PNode = #| ))? var a = simpleExpr(p) if p.tok.tokType == tkEquals: + result = newNodeP(nkAsgn, p) getTok(p) optInd(p, result) var b = parseExpr(p) - result = newNodeI(nkAsgn, a.info) addSon(result, a) addSon(result, b) else: diff --git a/compiler/passaux.nim b/compiler/passaux.nim index eeaf12953..2065d5893 100644 --- a/compiler/passaux.nim +++ b/compiler/passaux.nim @@ -29,21 +29,3 @@ proc verboseProcess(context: PPassContext, n: PNode): PNode = message(n.info, hintProcessing, $idgen.gFrontendId) const verbosePass* = makePass(open = verboseOpen, process = verboseProcess) - -proc cleanUp(c: PPassContext, n: PNode): PNode = - result = n - # we cannot clean up if dead code elimination is activated - if optDeadCodeElim in gGlobalOptions or n == nil: return - case n.kind - of nkStmtList: - for i in countup(0, sonsLen(n) - 1): discard cleanUp(c, n.sons[i]) - of nkProcDef, nkMethodDef: - if n.sons[namePos].kind == nkSym: - var s = n.sons[namePos].sym - if sfDeadCodeElim notin getModule(s).flags and not astNeeded(s): - s.ast.sons[bodyPos] = ast.emptyNode # free the memory - else: - discard - -const cleanupPass* = makePass(process = cleanUp, close = cleanUp) - diff --git a/compiler/passes.nim b/compiler/passes.nim index 3cc15147e..7966ee88d 100644 --- a/compiler/passes.nim +++ b/compiler/passes.nim @@ -11,7 +11,7 @@ # `TPass` interface. import - strutils, lists, options, ast, astalgo, llstream, msgs, platform, os, + strutils, options, ast, astalgo, llstream, msgs, platform, os, condsyms, idents, renderer, types, extccomp, math, magicsys, nversion, nimsets, syntaxes, times, rodread, idgen, modulegraphs @@ -24,7 +24,7 @@ type TPassOpen* = proc (graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext {.nimcall.} TPassOpenCached* = proc (graph: ModuleGraph; module: PSym, rd: PRodReader): PPassContext {.nimcall.} - TPassClose* = proc (p: PPassContext, n: PNode): PNode {.nimcall.} + TPassClose* = proc (graph: ModuleGraph; p: PPassContext, n: PNode): PNode {.nimcall.} TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.} TPass* = tuple[open: TPassOpen, openCached: TPassOpenCached, @@ -94,7 +94,7 @@ proc carryPass*(g: ModuleGraph; p: TPass, module: PSym; cache: IdentCache; m: TPassData): TPassData = var c = p.open(g, module, cache) result.input = p.process(c, m.input) - result.closeOutput = if p.close != nil: p.close(c, m.closeOutput) + result.closeOutput = if p.close != nil: p.close(g, c, m.closeOutput) else: m.closeOutput proc carryPasses*(g: ModuleGraph; nodes: PNode, module: PSym; @@ -121,10 +121,10 @@ proc openPassesCached(g: ModuleGraph; a: var TPassContextArray, module: PSym, else: a[i] = nil -proc closePasses(a: var TPassContextArray) = +proc closePasses(graph: ModuleGraph; a: var TPassContextArray) = var m: PNode = nil for i in countup(0, gPassesLen - 1): - if not isNil(gPasses[i].close): m = gPasses[i].close(a[i], m) + if not isNil(gPasses[i].close): m = gPasses[i].close(graph, a[i], m) a[i] = nil # free the memory here proc processTopLevelStmt(n: PNode, a: var TPassContextArray): bool = @@ -142,11 +142,11 @@ proc processTopLevelStmtCached(n: PNode, a: var TPassContextArray) = for i in countup(0, gPassesLen - 1): if not isNil(gPasses[i].openCached): m = gPasses[i].process(a[i], m) -proc closePassesCached(a: var TPassContextArray) = +proc closePassesCached(graph: ModuleGraph; a: var TPassContextArray) = var m: PNode = nil for i in countup(0, gPassesLen - 1): if not isNil(gPasses[i].openCached) and not isNil(gPasses[i].close): - m = gPasses[i].close(a[i], m) + m = gPasses[i].close(graph, a[i], m) a[i] = nil # free the memory here proc resolveMod(module, relativeTo: string): int32 = @@ -171,6 +171,7 @@ proc processImplicits(implicits: seq[string], nodeKind: TNodeKind, proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, rd: PRodReader; cache: IdentCache): bool {.discardable.} = + if graph.stopCompile(): return true var p: TParsers a: TPassContextArray @@ -198,6 +199,7 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, processImplicits implicitIncludes, nkIncludeStmt, a, module while true: + if graph.stopCompile(): break var n = parseTopLevelStmt(p) if n.kind == nkEmpty: break if sfNoForward in module.flags: @@ -213,12 +215,14 @@ proc processModule*(graph: ModuleGraph; module: PSym, stream: PLLStream, elif not processTopLevelStmt(n, a): break closeParsers(p) if s.kind != llsStdIn: break - closePasses(a) + closePasses(graph, a) # id synchronization point for more consistent code generation: idSynchronizationPoint(1000) else: openPassesCached(graph, a, module, rd) var n = loadInitSection(rd) - for i in countup(0, sonsLen(n) - 1): processTopLevelStmtCached(n.sons[i], a) - closePassesCached(a) + for i in countup(0, sonsLen(n) - 1): + if graph.stopCompile(): break + processTopLevelStmtCached(n.sons[i], a) + closePassesCached(graph, a) result = true diff --git a/compiler/patterns.nim b/compiler/patterns.nim index 09b8d3305..859fe2a81 100644 --- a/compiler/patterns.nim +++ b/compiler/patterns.nim @@ -289,7 +289,7 @@ proc applyRule*(c: PContext, s: PSym, n: PNode): PNode = # constraint not fulfilled: if not ok: return nil - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) if ctx.subMatch: assert m.len == 3 m.sons[1] = result diff --git a/compiler/plugins/locals/locals.nim b/compiler/plugins/locals/locals.nim index 8a3f67dd4..338e7bcac 100644 --- a/compiler/plugins/locals/locals.nim +++ b/compiler/plugins/locals/locals.nim @@ -28,7 +28,7 @@ proc semLocals(c: PContext, n: PNode): PNode = it.typ.skipTypes({tyGenericInst, tyVar}).kind notin {tyVarargs, tyOpenArray, tyTypeDesc, tyStatic, tyExpr, tyStmt, tyEmpty}: - var field = newSym(skField, it.name, getCurrOwner(), n.info) + var field = newSym(skField, it.name, getCurrOwner(c), n.info) field.typ = it.typ.skipTypes({tyGenericInst, tyVar}) field.position = counter inc(counter) diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index bcb0461f2..4476dab72 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -11,7 +11,7 @@ import os, platform, condsyms, ast, astalgo, idents, semdata, msgs, renderer, - wordrecg, ropes, options, strutils, lists, extccomp, math, magicsys, trees, + wordrecg, ropes, options, strutils, extccomp, math, magicsys, trees, rodread, types, lookups const @@ -218,20 +218,19 @@ proc processCallConv(c: PContext, n: PNode) = var sw = whichKeyword(n.sons[1].ident) case sw of FirstCallConv..LastCallConv: - POptionEntry(c.optionStack.tail).defaultCC = wordToCallConv(sw) + c.optionStack[^1].defaultCC = wordToCallConv(sw) else: localError(n.info, errCallConvExpected) else: localError(n.info, errCallConvExpected) proc getLib(c: PContext, kind: TLibKind, path: PNode): PLib = - var it = PLib(c.libs.head) - while it != nil: - if it.kind == kind: - if trees.exprStructuralEquivalent(it.path, path): return it - it = PLib(it.next) + for it in c.libs: + if it.kind == kind and trees.exprStructuralEquivalent(it.path, path): + return it + result = newLib(kind) result.path = path - append(c.libs, result) + c.libs.add result if path.kind in {nkStrLit..nkTripleStrLit}: result.isOverriden = options.isDynlibOverride(path.strVal) @@ -254,7 +253,7 @@ proc processDynLib(c: PContext, n: PNode, sym: PSym) = if (sym == nil) or (sym.kind == skModule): let lib = getLib(c, libDynamic, expectDynlibNode(c, n)) if not lib.isOverriden: - POptionEntry(c.optionStack.tail).dynlib = lib + c.optionStack[^1].dynlib = lib else: if n.kind == nkExprColonExpr: var lib = getLib(c, libDynamic, expectDynlibNode(c, n)) @@ -350,12 +349,12 @@ proc processPush(c: PContext, n: PNode, start: int) = if n.sons[start-1].kind == nkExprColonExpr: localError(n.info, errGenerated, "':' after 'push' not supported") var x = newOptionEntry() - var y = POptionEntry(c.optionStack.tail) + var y = c.optionStack[^1] x.options = gOptions x.defaultCC = y.defaultCC x.dynlib = y.dynlib x.notes = gNotes - append(c.optionStack, x) + c.optionStack.add(x) for i in countup(start, sonsLen(n) - 1): if processOption(c, n.sons[i]): # simply store it somewhere: @@ -365,12 +364,12 @@ proc processPush(c: PContext, n: PNode, start: int) = #localError(n.info, errOptionExpected) proc processPop(c: PContext, n: PNode) = - if c.optionStack.counter <= 1: + if c.optionStack.len <= 1: localError(n.info, errAtPopWithoutPush) else: - gOptions = POptionEntry(c.optionStack.tail).options - gNotes = POptionEntry(c.optionStack.tail).notes - remove(c.optionStack, c.optionStack.tail) + gOptions = c.optionStack[^1].options + gNotes = c.optionStack[^1].notes + c.optionStack.setLen(c.optionStack.len - 1) proc processDefine(c: PContext, n: PNode) = if (n.kind == nkExprColonExpr) and (n.sons[1].kind == nkIdent): @@ -977,8 +976,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, validPragmas: TSpecialWords) = if sym != nil and sym.kind != skModule: - var it = POptionEntry(c.optionStack.head) - while it != nil: + for it in c.optionStack: let o = it.otherPragmas if not o.isNil: pushInfoContext(n.info) @@ -986,11 +984,10 @@ proc implicitPragmas*(c: PContext, sym: PSym, n: PNode, if singlePragma(c, sym, o, i, validPragmas): internalError(n.info, "implicitPragmas") popInfoContext() - it = it.next.POptionEntry if lfExportLib in sym.loc.flags and sfExportc notin sym.flags: localError(n.info, errDynlibRequiresExportc) - var lib = POptionEntry(c.optionStack.tail).dynlib + var lib = c.optionStack[^1].dynlib if {lfDynamicLib, lfHeader} * sym.loc.flags == {} and sfImportc in sym.flags and lib != nil: incl(sym.loc.flags, lfDynamicLib) diff --git a/compiler/renderer.nim b/compiler/renderer.nim index 1602988dd..5ce8414d6 100644 --- a/compiler/renderer.nim +++ b/compiler/renderer.nim @@ -10,7 +10,7 @@ # This module implements the renderer of the standard Nim representation. import - lexer, options, idents, strutils, ast, msgs, lists + lexer, options, idents, strutils, ast, msgs type TRenderFlag* = enum diff --git a/compiler/rodwrite.nim b/compiler/rodwrite.nim index 4ce9e616a..d61d817dd 100644 --- a/compiler/rodwrite.nim +++ b/compiler/rodwrite.nim @@ -641,7 +641,7 @@ proc myOpen(g: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = rawAddInterfaceSym(w, module) result = w -proc myClose(c: PPassContext, n: PNode): PNode = +proc myClose(graph: ModuleGraph; c: PPassContext, n: PNode): PNode = result = process(c, n) var w = PRodWriter(c) writeRod(w) diff --git a/compiler/scriptconfig.nim b/compiler/scriptconfig.nim index 9e94f1c19..0c31eadbe 100644 --- a/compiler/scriptconfig.nim +++ b/compiler/scriptconfig.nim @@ -12,7 +12,7 @@ import ast, modules, idents, passes, passaux, condsyms, - options, nimconf, lists, sem, semdata, llstream, vm, vmdef, commands, msgs, + options, nimconf, sem, semdata, llstream, vm, vmdef, commands, msgs, os, times, osproc, wordrecg, strtabs, modulegraphs # we support 'cmpIgnoreStyle' natively for efficiency: @@ -153,7 +153,7 @@ proc runNimScript*(cache: IdentCache; scriptName: string; registerPass(semPass) registerPass(evalPass) - appendStr(searchPaths, options.libpath) + searchPaths.add(options.libpath) var m = graph.makeModule(scriptName) incl(m.flags, sfMainModule) diff --git a/compiler/sem.nim b/compiler/sem.nim index fc7736b07..8da5d4707 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -10,7 +10,7 @@ # This module implements the semantic checking pass. import - ast, strutils, hashes, lists, options, lexer, astalgo, trees, treetab, + ast, strutils, hashes, options, lexer, astalgo, trees, treetab, wordrecg, ropes, msgs, os, condsyms, idents, renderer, types, platform, math, magicsys, parser, nversion, nimsets, semfold, importer, procfind, lookups, rodread, pragmas, passes, semdata, semtypinst, sigmatch, @@ -32,7 +32,7 @@ proc semExprNoType(c: PContext, n: PNode): PNode proc semExprNoDeref(c: PContext, n: PNode, flags: TExprFlags = {}): PNode proc semProcBody(c: PContext, n: PNode): PNode -proc fitNode(c: PContext, formal: PType, arg: PNode): PNode +proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode proc changeType(n: PNode, newType: PType, check: bool) proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode @@ -69,7 +69,7 @@ template semIdeForTemplateOrGeneric(c: PContext; n: PNode; # echo "passing to safeSemExpr: ", renderTree(n) discard safeSemExpr(c, n) -proc fitNode(c: PContext, formal: PType, arg: PNode): PNode = +proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode = if arg.typ.isNil: localError(arg.info, errExprXHasNoType, renderTree(arg, {renderNoComments})) @@ -79,7 +79,7 @@ proc fitNode(c: PContext, formal: PType, arg: PNode): PNode = else: result = indexTypesMatch(c, formal, arg.typ, arg) if result == nil: - typeMismatch(arg, formal, arg.typ) + typeMismatch(info, formal, arg.typ) # error correction: result = copyTree(arg) result.typ = formal @@ -166,7 +166,7 @@ proc commonType*(x, y: PType): PType = result.addSonSkipIntLit(r) proc newSymS(kind: TSymKind, n: PNode, c: PContext): PSym = - result = newSym(kind, considerQuotedIdent(n), getCurrOwner(), n.info) + result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info) proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym = proc `$`(kind: TSymKind): string = substr(system.`$`(kind), 2).toLowerAscii @@ -186,9 +186,9 @@ proc newSymG*(kind: TSymKind, n: PNode, c: PContext): PSym = # when there is a nested proc inside a template, semtmpl # will assign a wrong owner during the first pass over the # template; we must fix it here: see #909 - result.owner = getCurrOwner() + result.owner = getCurrOwner(c) else: - result = newSym(kind, considerQuotedIdent(n), getCurrOwner(), n.info) + result = newSym(kind, considerQuotedIdent(n), getCurrOwner(c), n.info) #if kind in {skForVar, skLet, skVar} and result.owner.kind == skModule: # incl(result.flags, sfGlobal) @@ -367,7 +367,7 @@ proc semAfterMacroCall(c: PContext, n: PNode, s: PSym, #result = symNodeFromType(c, typ, n.info) else: result = semExpr(c, result, flags) - result = fitNode(c, s.typ.sons[0], result) + result = fitNode(c, s.typ.sons[0], result, result.info) #GlobalError(s.info, errInvalidParamKindX, typeToString(s.typ.sons[0])) dec(evalTemplateCounter) discard c.friendModules.pop() @@ -376,7 +376,7 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, flags: TExprFlags = {}): PNode = pushInfoContext(nOrig.info) - markUsed(n.info, sym) + markUsed(n.info, sym, c.graph.usageSym) styleCheckUse(n.info, sym) if sym == c.p.owner: globalError(n.info, errRecursiveDependencyX, sym.name.s) @@ -390,12 +390,12 @@ proc semMacroExpr(c: PContext, n, nOrig: PNode, sym: PSym, popInfoContext() proc forceBool(c: PContext, n: PNode): PNode = - result = fitNode(c, getSysType(tyBool), n) + result = fitNode(c, getSysType(tyBool), n, n.info) if result == nil: result = n proc semConstBoolExpr(c: PContext, n: PNode): PNode = let nn = semExprWithType(c, n) - result = fitNode(c, getSysType(tyBool), nn) + result = fitNode(c, getSysType(tyBool), nn, nn.info) if result == nil: localError(n.info, errConstExprExpected) return nn @@ -434,7 +434,7 @@ proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = c.instTypeBoundOp = sigmatch.instTypeBoundOp pushProcCon(c, module) - pushOwner(c.module) + pushOwner(c, c.module) c.importTable = openScope(c) c.importTable.addSym(module) # a module knows itself if sfSystemModule in module.flags: @@ -450,7 +450,7 @@ proc myOpen(graph: ModuleGraph; module: PSym; cache: IdentCache): PPassContext = proc myOpenCached(graph: ModuleGraph; module: PSym; rd: PRodReader): PPassContext = result = myOpen(graph, module, rd.cache) - for m in items(rd.methods): methodDef(m, true) + for m in items(rd.methods): methodDef(graph, m, true) proc isImportSystemStmt(n: PNode): bool = if magicsys.systemModule == nil: return false @@ -502,7 +502,7 @@ proc recoverContext(c: PContext) = # faster than wrapping every stack operation in a 'try finally' block and # requires far less code. c.currentScope = c.topLevelScope - while getCurrOwner().kind != skModule: popOwner() + while getCurrOwner(c).kind != skModule: popOwner(c) while c.p != nil and c.p.owner.kind != skModule: c.p = c.p.next proc myProcess(context: PPassContext, n: PNode): PNode = @@ -523,7 +523,7 @@ proc myProcess(context: PPassContext, n: PNode): PNode = else: result = ast.emptyNode #if gCmd == cmdIdeTools: findSuggest(c, n) -proc myClose(context: PPassContext, n: PNode): PNode = +proc myClose(graph: ModuleGraph; context: PPassContext, n: PNode): PNode = var c = PContext(context) closeScope(c) # close module's scope rawCloseScope(c) # imported symbols; don't check for unused ones! @@ -533,7 +533,7 @@ proc myClose(context: PPassContext, n: PNode): PNode = addCodeForGenerics(c, result) if c.module.ast != nil: result.add(c.module.ast) - popOwner() + popOwner(c) popProcCon(c) const semPass* = makePass(myOpen, myOpenCached, myProcess, myClose) diff --git a/compiler/semasgn.nim b/compiler/semasgn.nim index c4dc13624..70765d087 100644 --- a/compiler/semasgn.nim +++ b/compiler/semasgn.nim @@ -105,7 +105,7 @@ proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool = of attachedDestructor: let op = t.destructor if op != nil: - markUsed(c.info, op) + markUsed(c.info, op, c.c.graph.usageSym) styleCheckUse(c.info, op) body.add newDestructorCall(op, x) result = true @@ -123,14 +123,14 @@ proc considerOverloadedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool = op = t.assignment if op == nil: op = liftBody(c.c, t, c.info) - markUsed(c.info, op) + markUsed(c.info, op, c.c.graph.usageSym) styleCheckUse(c.info, op) body.add newAsgnCall(c.c, op, x, y) result = true of attachedDeepCopy: let op = t.deepCopy if op != nil: - markUsed(c.info, op) + markUsed(c.info, op, c.c.graph.usageSym) styleCheckUse(c.info, op) body.add newDeepCopyCall(op, x, y) result = true @@ -248,6 +248,7 @@ proc addParam(procType: PType; param: PSym) = proc liftBody(c: PContext; typ: PType; info: TLineInfo): PSym = var a: TLiftCtx a.info = info + a.c = c let body = newNodeI(nkStmtList, info) result = newSym(skProc, getIdent":lifted=", typ.owner, info) a.fn = result diff --git a/compiler/semcall.nim b/compiler/semcall.nim index ae7bab89d..98667b085 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -327,7 +327,7 @@ proc inferWithMetatype(c: PContext, formal: PType, result.typ = generateTypeInstance(c, m.bindings, arg.info, formal.skipTypes({tyCompositeTypeClass})) else: - typeMismatch(arg, formal, arg.typ) + typeMismatch(arg.info, formal, arg.typ) # error correction: result = copyTree(arg) result.typ = formal @@ -335,7 +335,7 @@ proc inferWithMetatype(c: PContext, formal: PType, proc semResolvedCall(c: PContext, n: PNode, x: TCandidate): PNode = assert x.state == csMatch var finalCallee = x.calleeSym - markUsed(n.sons[0].info, finalCallee) + markUsed(n.sons[0].info, finalCallee, c.graph.usageSym) styleCheckUse(n.sons[0].info, finalCallee) assert finalCallee.ast != nil if x.hasFauxMatch: @@ -411,7 +411,7 @@ proc explicitGenericSym(c: PContext, n: PNode, s: PSym): PNode = let tm = typeRel(m, formal, arg, true) if tm in {isNone, isConvertible}: return nil var newInst = generateInstance(c, s, m.bindings, n.info) - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(newInst, n.info) diff --git a/compiler/semdata.nim b/compiler/semdata.nim index 77a530a15..77d461007 100644 --- a/compiler/semdata.nim +++ b/compiler/semdata.nim @@ -10,15 +10,14 @@ ## This module contains the data structures for the semantic checking phase. import - strutils, lists, intsets, options, lexer, ast, astalgo, trees, treetab, + strutils, intsets, options, lexer, ast, astalgo, trees, treetab, wordrecg, ropes, msgs, platform, os, condsyms, idents, renderer, types, extccomp, math, magicsys, nversion, nimsets, parser, times, passes, rodread, vmdef, modulegraphs type - TOptionEntry* = object of lists.TListEntry # entries to put on a - # stack for pragma parsing + TOptionEntry* = object # entries to put on a stack for pragma parsing options*: TOptions defaultCC*: TCallingConvention dynlib*: PLib @@ -79,10 +78,10 @@ type inGenericInst*: int # > 0 if we are instantiating a generic converters*: TSymSeq # sequence of converters patterns*: TSymSeq # sequence of pattern matchers - optionStack*: TLinkedList + optionStack*: seq[POptionEntry] symMapping*: TIdTable # every gensym'ed symbol needs to be mapped # to some new symbol in a generic instantiation - libs*: TLinkedList # all libs used by this module + libs*: seq[PLib] # all libs used by this module semConstExpr*: proc (c: PContext, n: PNode): PNode {.nimcall.} # for the pragmas semExpr*: proc (c: PContext, n: PNode, flags: TExprFlags = {}): PNode {.nimcall.} semTryExpr*: proc (c: PContext, n: PNode,flags: TExprFlags = {}): PNode {.nimcall.} @@ -125,26 +124,23 @@ proc scopeDepth*(c: PContext): int {.inline.} = result = if c.currentScope != nil: c.currentScope.depthLevel else: 0 -var gOwners*: seq[PSym] = @[] - -proc getCurrOwner*(): PSym = +proc getCurrOwner*(c: PContext): PSym = # owner stack (used for initializing the # owner field of syms) # the documentation comment always gets # assigned to the current owner - # BUGFIX: global array is needed! - result = gOwners[high(gOwners)] + result = c.graph.owners[^1] -proc pushOwner*(owner: PSym) = - add(gOwners, owner) +proc pushOwner*(c: PContext; owner: PSym) = + add(c.graph.owners, owner) -proc popOwner*() = - var length = len(gOwners) - if length > 0: setLen(gOwners, length - 1) +proc popOwner*(c: PContext) = + var length = len(c.graph.owners) + if length > 0: setLen(c.graph.owners, length - 1) else: internalError("popOwner") proc lastOptionEntry*(c: PContext): POptionEntry = - result = POptionEntry(c.optionStack.tail) + result = c.optionStack[^1] proc popProcCon*(c: PContext) {.inline.} = c.p = c.p.next @@ -187,9 +183,9 @@ proc newOptionEntry*(): POptionEntry = proc newContext*(graph: ModuleGraph; module: PSym; cache: IdentCache): PContext = new(result) result.ambiguousSymbols = initIntSet() - initLinkedList(result.optionStack) - initLinkedList(result.libs) - append(result.optionStack, newOptionEntry()) + result.optionStack = @[] + result.libs = @[] + result.optionStack.add(newOptionEntry()) result.module = module result.friendModules = @[module] result.converters = @[] @@ -226,7 +222,7 @@ proc addToLib*(lib: PLib, sym: PSym) = sym.annex = lib proc newTypeS*(kind: TTypeKind, c: PContext): PType = - result = newType(kind, getCurrOwner()) + result = newType(kind, getCurrOwner(c)) proc makePtrType*(c: PContext, baseType: PType): PType = result = newTypeS(tyPtr, c) @@ -245,7 +241,7 @@ proc makeTypeDesc*(c: PContext, typ: PType): PType = proc makeTypeSymNode*(c: PContext, typ: PType, info: TLineInfo): PNode = let typedesc = makeTypeDesc(c, typ) - let sym = newSym(skType, c.cache.idAnon, getCurrOwner(), info).linkTo(typedesc) + let sym = newSym(skType, c.cache.idAnon, getCurrOwner(c), info).linkTo(typedesc) return newSymNode(sym, info) proc makeTypeFromExpr*(c: PContext, n: PNode): PType = @@ -255,7 +251,7 @@ proc makeTypeFromExpr*(c: PContext, n: PNode): PType = proc newTypeWithSons*(c: PContext, kind: TTypeKind, sons: seq[PType]): PType = - result = newType(kind, getCurrOwner()) + result = newType(kind, getCurrOwner(c)) result.sons = sons proc makeStaticExpr*(c: PContext, n: PNode): PNode = @@ -326,7 +322,7 @@ proc errorNode*(c: PContext, n: PNode): PNode = proc fillTypeS*(dest: PType, kind: TTypeKind, c: PContext) = dest.kind = kind - dest.owner = getCurrOwner() + dest.owner = getCurrOwner(c) dest.size = - 1 proc makeRangeType*(c: PContext; first, last: BiggestInt; diff --git a/compiler/semdestruct.nim b/compiler/semdestruct.nim index a8873bbe2..b09404b39 100644 --- a/compiler/semdestruct.nim +++ b/compiler/semdestruct.nim @@ -51,7 +51,7 @@ proc doDestructorStuff(c: PContext, s: PSym, n: PNode) = let destructableT = instantiateDestructor(c, t.sons[i]) if destructableT != nil: n.sons[bodyPos].addSon(newNode(nkCall, t.sym.info, @[ - useSym(destructableT.destructor), + useSym(destructableT.destructor, c.graph.usageSym), n.sons[paramsPos][1][0]])) proc destroyFieldOrFields(c: PContext, field: PNode, holder: PNode): PNode @@ -60,8 +60,8 @@ proc destroySym(c: PContext, field: PSym, holder: PNode): PNode = let destructableT = instantiateDestructor(c, field.typ) if destructableT != nil: result = newNode(nkCall, field.info, @[ - useSym(destructableT.destructor), - newNode(nkDotExpr, field.info, @[holder, useSym(field)])]) + useSym(destructableT.destructor, c.graph.usageSym), + newNode(nkDotExpr, field.info, @[holder, useSym(field, c.graph.usageSym)])]) proc destroyCase(c: PContext, n: PNode, holder: PNode): PNode = var nonTrivialFields = 0 @@ -181,7 +181,8 @@ proc createDestructorCall(c: PContext, s: PSym): PNode = let destructableT = instantiateDestructor(c, varTyp) if destructableT != nil: let call = semStmt(c, newNode(nkCall, s.info, @[ - useSym(destructableT.destructor), useSym(s)])) + useSym(destructableT.destructor, c.graph.usageSym), + useSym(s, c.graph.usageSym)])) result = newNode(nkDefer, s.info, @[call]) proc insertDestructors(c: PContext, @@ -233,8 +234,8 @@ proc insertDestructors(c: PContext, tryStmt.addSon( newNode(nkFinally, info, @[ semStmt(c, newNode(nkCall, info, @[ - useSym(destructableT.destructor), - useSym(varId.sym)]))])) + useSym(destructableT.destructor, c.graph.usageSym), + useSym(varId.sym, c.graph.usageSym)]))])) result.outer = newNodeI(nkStmtList, info) varSection.sons.setLen(j+1) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 542d7b4e3..ba60442d6 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -12,10 +12,10 @@ proc semTemplateExpr(c: PContext, n: PNode, s: PSym, flags: TExprFlags = {}): PNode = - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) pushInfoContext(n.info) - result = evalTemplate(n, s, getCurrOwner(), efFromHlo in flags) + result = evalTemplate(n, s, getCurrOwner(c), efFromHlo in flags) if efNoSemCheck notin flags: result = semAfterMacroCall(c, result, s, flags) popInfoContext() @@ -194,13 +194,13 @@ proc semConv(c: PContext, n: PNode): PNode = of convOK: # handle SomeProcType(SomeGenericProc) if op.kind == nkSym and op.sym.isGenericRoutine: - result.sons[1] = fitNode(c, result.typ, result.sons[1]) + result.sons[1] = fitNode(c, result.typ, result.sons[1], result.info) elif op.kind == nkPar and targetType.kind == tyTuple: - op = fitNode(c, targetType, op) + op = fitNode(c, targetType, op, result.info) of convNotNeedeed: message(n.info, hintConvFromXtoItselfNotNeeded, result.typ.typeToString) of convNotLegal: - result = fitNode(c, result.typ, result.sons[1]) + result = fitNode(c, result.typ, result.sons[1], result.info) if result == nil: localError(n.info, errGenerated, msgKindToString(errIllegalConvFromXtoY)% [op.typ.typeToString, result.typ.typeToString]) @@ -209,7 +209,7 @@ proc semConv(c: PContext, n: PNode): PNode = let it = op.sons[i] let status = checkConvertible(c, result.typ, it.typ) if status in {convOK, convNotNeedeed}: - markUsed(n.info, it.sym) + markUsed(n.info, it.sym, c.graph.usageSym) styleCheckUse(n.info, it.sym) markIndirect(c, it.sym) return it @@ -445,7 +445,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = x = n.sons[i] if x.kind == nkExprColonExpr and sonsLen(x) == 2: var idx = semConstExpr(c, x.sons[0]) - idx = fitNode(c, indexType, idx) + idx = fitNode(c, indexType, idx, x.info) if lastIndex+1 != getOrdValue(idx): localError(x.info, errInvalidOrderInArrayConstructor) x = x.sons[1] @@ -458,7 +458,7 @@ proc semArrayConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = inc(lastIndex) addSonSkipIntLit(result.typ, typ) for i in 0 .. <result.len: - result.sons[i] = fitNode(c, typ, result.sons[i]) + result.sons[i] = fitNode(c, typ, result.sons[i], result.sons[i].info) result.typ.sons[0] = makeRangeType(c, 0, sonsLen(result) - 1, n.info) proc fixAbstractType(c: PContext, n: PNode) = @@ -926,7 +926,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = let s = getGenSym(c, sym) case s.kind of skConst: - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) case skipTypes(s.typ, abstractInst-{tyTypeDesc}).kind of tyNil, tyChar, tyInt..tyInt64, tyFloat..tyFloat128, @@ -950,20 +950,20 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = result = newSymNode(s, n.info) of skMacro: if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0: - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) else: result = semMacroExpr(c, n, n, s, flags) of skTemplate: if efNoEvaluateGeneric in flags and s.ast[genericParamsPos].len > 0: - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) else: result = semTemplateExpr(c, n, s, flags) of skParam: - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) if s.typ.kind == tyStatic and s.typ.n != nil: # XXX see the hack in sigmatch.nim ... @@ -985,7 +985,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = if s.magic == mNimvm: localError(n.info, "illegal context for 'nimvm' magic") - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) # We cannot check for access to outer vars for example because it's still @@ -1003,7 +1003,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = n.typ = s.typ return n of skType: - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) if s.typ.kind == tyStatic and s.typ.n != nil: return s.typ.n @@ -1025,7 +1025,7 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = if f != nil and fieldVisible(c, f): # is the access to a public field or in the same module or in a friend? doAssert f == s - markUsed(n.info, f) + markUsed(n.info, f, c.graph.usageSym) styleCheckUse(n.info, f) result = newNodeIT(nkDotExpr, n.info, f.typ) result.add makeDeref(newSymNode(p.selfSym)) @@ -1038,11 +1038,11 @@ proc semSym(c: PContext, n: PNode, sym: PSym, flags: TExprFlags): PNode = if ty.sons[0] == nil: break ty = skipTypes(ty.sons[0], skipPtrs) # old code, not sure if it's live code: - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) else: - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) styleCheckUse(n.info, s) result = newSymNode(s, n.info) @@ -1062,7 +1062,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = result = symChoice(c, n, s, scClosed) if result.kind == nkSym: result = semSym(c, n, s, flags) else: - markUsed(n.sons[1].info, s) + markUsed(n.sons[1].info, s, c.graph.usageSym) result = semSym(c, n, s, flags) styleCheckUse(n.sons[1].info, s) return @@ -1087,7 +1087,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = result = newSymNode(f) result.info = n.info result.typ = ty - markUsed(n.info, f) + markUsed(n.info, f, c.graph.usageSym) styleCheckUse(n.info, f) return of tyTypeParamsHolders: @@ -1120,7 +1120,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = if f != nil: if fieldVisible(c, f): # is the access to a public field or in the same module or in a friend? - markUsed(n.sons[1].info, f) + markUsed(n.sons[1].info, f, c.graph.usageSym) styleCheckUse(n.sons[1].info, f) n.sons[0] = makeDeref(n.sons[0]) n.sons[1] = newSymNode(f) # we now have the correct field @@ -1134,7 +1134,7 @@ proc builtinFieldAccess(c: PContext, n: PNode, flags: TExprFlags): PNode = elif ty.kind == tyTuple and ty.n != nil: f = getSymFromList(ty.n, i) if f != nil: - markUsed(n.sons[1].info, f) + markUsed(n.sons[1].info, f, c.graph.usageSym) styleCheckUse(n.sons[1].info, f) n.sons[0] = makeDeref(n.sons[0]) n.sons[1] = newSymNode(f) @@ -1393,9 +1393,9 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode = c.p.resultSym.typ = rhs.typ c.p.owner.typ.sons[0] = rhs.typ else: - typeMismatch(n, lhs.typ, rhs.typ) + typeMismatch(n.info, lhs.typ, rhs.typ) - n.sons[1] = fitNode(c, le, rhs) + n.sons[1] = fitNode(c, le, rhs, n.info) if tfHasAsgn in lhs.typ.flags and not lhsIsResult and mode != noOverloadedAsgn: return overloadedAsgn(c, lhs, n.sons[1]) @@ -1494,7 +1494,7 @@ proc semYield(c: PContext, n: PNode): PNode = let restype = iterType.sons[0] if restype != nil: if restype.kind != tyExpr: - n.sons[0] = fitNode(c, restype, n.sons[0]) + n.sons[0] = fitNode(c, restype, n.sons[0], n.info) if n.sons[0].typ == nil: internalError(n.info, "semYield") if resultTypeIsInferrable(restype): @@ -1581,9 +1581,8 @@ proc getMagicSym(magic: TMagic): PSym = result = newSym(skProc, getIdent($magic), systemModule, gCodegenLineInfo) result.magic = magic -proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo, - owner = getCurrOwner()): PSym = - result = newSym(kind, c.cache.idAnon, owner, info) +proc newAnonSym(c: PContext; kind: TSymKind, info: TLineInfo): PSym = + result = newSym(kind, c.cache.idAnon, getCurrOwner(c), info) result.flags = {sfGenSym} proc semExpandToAst(c: PContext, n: PNode): PNode = @@ -1592,7 +1591,7 @@ proc semExpandToAst(c: PContext, n: PNode): PNode = if expandedSym.kind == skError: return n macroCall.sons[0] = newSymNode(expandedSym, macroCall.info) - markUsed(n.info, expandedSym) + markUsed(n.info, expandedSym, c.graph.usageSym) styleCheckUse(n.info, expandedSym) for i in countup(1, macroCall.len-1): @@ -1683,7 +1682,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = # open a scope for temporary symbol inclusions: let oldScope = c.currentScope openScope(c) - let oldOwnerLen = len(gOwners) + let oldOwnerLen = len(c.graph.owners) let oldGenerics = c.generics let oldErrorOutputs = errorOutputs errorOutputs = {} @@ -1709,7 +1708,7 @@ proc tryExpr(c: PContext, n: PNode, flags: TExprFlags = {}): PNode = c.inGenericInst = oldInGenericInst c.p = oldProcCon msgs.setInfoContextLen(oldContextLen) - setLen(gOwners, oldOwnerLen) + setLen(c.graph.owners, oldOwnerLen) c.currentScope = oldScope errorOutputs = oldErrorOutputs msgs.gErrorCounter = oldErrorCount @@ -1930,13 +1929,14 @@ proc semSetConstr(c: PContext, n: PNode): PNode = addSonSkipIntLit(result.typ, typ) for i in countup(0, sonsLen(n) - 1): var m: PNode + let info = n.sons[i].info if isRange(n.sons[i]): - m = newNodeI(nkRange, n.sons[i].info) - addSon(m, fitNode(c, typ, n.sons[i].sons[1])) - addSon(m, fitNode(c, typ, n.sons[i].sons[2])) + m = newNodeI(nkRange, info) + addSon(m, fitNode(c, typ, n.sons[i].sons[1], info)) + addSon(m, fitNode(c, typ, n.sons[i].sons[2], info)) elif n.sons[i].kind == nkRange: m = n.sons[i] # already semchecked else: - m = fitNode(c, typ, n.sons[i]) + m = fitNode(c, typ, n.sons[i], info) addSon(result, m) proc semTableConstr(c: PContext, n: PNode): PNode = @@ -2082,7 +2082,7 @@ proc semObjConstr(c: PContext, n: PNode, flags: TExprFlags): PNode = t = skipTypes(t.sons[0], skipPtrs) if f != nil and fieldVisible(c, f): it.sons[0] = newSymNode(f) - e = fitNode(c, f.typ, e) + e = fitNode(c, f.typ, e, it.info) # small hack here in a nkObjConstr the ``nkExprColonExpr`` node can have # 3 children the last being the field check if check != nil: @@ -2107,8 +2107,8 @@ proc semBlock(c: PContext, n: PNode): PNode = var labl = newSymG(skLabel, n.sons[0], c) if sfGenSym notin labl.flags: addDecl(c, labl) - n.sons[0] = newSymNode(labl, n.sons[0].info) - suggestSym(n.sons[0].info, labl) + n.sons[0] = newSymNode(labl, n.sons[0].info) + suggestSym(n.sons[0].info, labl, c.graph.usageSym) styleCheckDef(labl) n.sons[1] = semExpr(c, n.sons[1]) n.typ = n.sons[1].typ diff --git a/compiler/semfields.nim b/compiler/semfields.nim index 9d8cea862..06826ef75 100644 --- a/compiler/semfields.nim +++ b/compiler/semfields.nim @@ -108,7 +108,7 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode = var trueSymbol = strTableGet(magicsys.systemModule.tab, getIdent"true") if trueSymbol == nil: localError(n.info, errSystemNeeds, "true") - trueSymbol = newSym(skUnknown, getIdent"true", getCurrOwner(), n.info) + trueSymbol = newSym(skUnknown, getIdent"true", getCurrOwner(c), n.info) trueSymbol.typ = getSysType(tyBool) result.sons[0] = newSymNode(trueSymbol, n.info) @@ -128,7 +128,7 @@ proc semForFields(c: PContext, n: PNode, m: TMagic): PNode = for i in 1..call.len-1: var tupleTypeB = skipTypes(call.sons[i].typ, abstractVar-{tyTypeDesc}) if not sameType(tupleTypeA, tupleTypeB): - typeMismatch(call.sons[i], tupleTypeA, tupleTypeB) + typeMismatch(call.sons[i].info, tupleTypeA, tupleTypeB) inc(c.p.nestedLoopCounter) if tupleTypeA.kind == tyTuple: diff --git a/compiler/semfold.nim b/compiler/semfold.nim index 749deded1..58239d23e 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -11,7 +11,7 @@ # and evaluation phase import - strutils, lists, options, ast, astalgo, trees, treetab, nimsets, times, + strutils, options, ast, astalgo, trees, treetab, nimsets, times, nversion, platform, math, msgs, os, condsyms, idents, renderer, types, commands, magicsys, saturate diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index bc80c41ad..9c05ab2f9 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -174,7 +174,11 @@ proc semGenericStmt(c: PContext, n: PNode, # XXX for example: ``result.add`` -- ``add`` needs to be looked up here... var dummy: bool result = fuzzyLookup(c, n, flags, ctx, dummy) - of nkEmpty, nkSym..nkNilLit: + of nkSym: + let a = n.sym + let b = getGenSym(c, a) + if b != a: n.sym = b + of nkEmpty, succ(nkSym)..nkNilLit: # see tests/compile/tgensymgeneric.nim: # We need to open the gensym'ed symbol again so that the instantiation # creates a fresh copy; but this is wrong the very first reason for gensym diff --git a/compiler/seminst.nim b/compiler/seminst.nim index acaade494..78dd7efe5 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -61,7 +61,7 @@ iterator instantiateGenericParamList(c: PContext, n: PNode, pt: TIdTable): PSym if q.typ.kind notin {tyTypeDesc, tyGenericParam, tyStatic}+tyTypeClasses: continue let symKind = if q.typ.kind == tyStatic: skConst else: skType - var s = newSym(symKind, q.name, getCurrOwner(), q.info) + var s = newSym(symKind, q.name, getCurrOwner(c), q.info) s.flags = s.flags + {sfUsed, sfFromGeneric} var t = PType(idTableGet(pt, q.typ)) if t == nil: @@ -209,7 +209,7 @@ proc instantiateProcType(c: PContext, pt: TIdTable, param.owner = prc param.typ = result.sons[i] if oldParam.ast != nil: - param.ast = fitNode(c, param.typ, oldParam.ast) + param.ast = fitNode(c, param.typ, oldParam.ast, oldParam.ast.info) # don't be lazy here and call replaceTypeVarsN(cl, originalParams[i])! result.n.sons[i] = newSymNode(param) @@ -255,7 +255,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, incl(result.flags, sfFromGeneric) result.owner = fn result.ast = n - pushOwner(result) + pushOwner(c, result) openScope(c) let gp = n.sons[genericParamsPos] @@ -304,7 +304,7 @@ proc generateInstance(c: PContext, fn: PSym, pt: TIdTable, popProcCon(c) popInfoContext() closeScope(c) # close scope for parameters - popOwner() + popOwner(c) c.currentScope = oldScope discard c.friendModules.pop() dec(c.instCounter) diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index e72172c81..5eed1e702 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -107,7 +107,7 @@ proc semTypeTraits(c: PContext, n: PNode): PNode = if t.sonsLen > 0: # This is either a type known to sem or a typedesc # param to a regular proc (again, known at instantiation) - result = evalTypeTrait(n[0], t, getCurrOwner()) + result = evalTypeTrait(n[0], t, getCurrOwner(c)) else: # a typedesc variable, pass unmodified to evals result = n diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index e2b0f4b7a..0507ed504 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -30,12 +30,13 @@ proc semBreakOrContinue(c: PContext, n: PNode): PNode = of nkIdent: s = lookUp(c, n.sons[0]) of nkSym: s = n.sons[0].sym else: illFormedAst(n) + s = getGenSym(c, s) if s.kind == skLabel and s.owner.id == c.p.owner.id: var x = newSymNode(s) x.info = n.info incl(s.flags, sfUsed) n.sons[0] = x - suggestSym(x.info, s) + suggestSym(x.info, s, c.graph.usageSym) styleCheckUse(x.info, s) else: localError(n.info, errInvalidControlFlowX, s.name.s) @@ -190,7 +191,7 @@ proc semIf(c: PContext, n: PNode): PNode = else: for it in n: let j = it.len-1 - it.sons[j] = fitNode(c, typ, it.sons[j]) + it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) result.kind = nkIfExpr result.typ = typ @@ -256,7 +257,7 @@ proc semCase(c: PContext, n: PNode): PNode = for i in 1..n.len-1: var it = n.sons[i] let j = it.len-1 - it.sons[j] = fitNode(c, typ, it.sons[j]) + it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) result.typ = typ proc semTry(c: PContext, n: PNode): PNode = @@ -329,15 +330,15 @@ proc semTry(c: PContext, n: PNode): PNode = result.typ = enforceVoidContext else: if n.lastSon.kind == nkFinally: discardCheck(c, n.lastSon.lastSon) - n.sons[0] = fitNode(c, typ, n.sons[0]) + n.sons[0] = fitNode(c, typ, n.sons[0], n.sons[0].info) for i in 1..last: var it = n.sons[i] let j = it.len-1 - it.sons[j] = fitNode(c, typ, it.sons[j]) + it.sons[j] = fitNode(c, typ, it.sons[j], it.sons[j].info) result.typ = typ proc fitRemoveHiddenConv(c: PContext, typ: PType, n: PNode): PNode = - result = fitNode(c, typ, n) + result = fitNode(c, typ, n, n.info) if result.kind in {nkHiddenStdConv, nkHiddenSubConv}: let r1 = result.sons[1] if r1.kind in {nkCharLit..nkUInt64Lit} and typ.skipTypes(abstractRange).kind in {tyFloat..tyFloat128}: @@ -366,11 +367,13 @@ proc semIdentDef(c: PContext, n: PNode, kind: TSymKind): PSym = if isTopLevel(c): result = semIdentWithPragma(c, kind, n, {sfExported}) incl(result.flags, sfGlobal) + #if kind in {skVar, skLet}: + # echo "global variable here ", n.info, " ", result.name.s else: result = semIdentWithPragma(c, kind, n, {}) if result.owner.kind == skModule: incl(result.flags, sfGlobal) - suggestSym(n.info, result) + suggestSym(n.info, result, c.graph.usageSym) styleCheckDef(result) proc checkNilable(v: PSym) = @@ -489,7 +492,7 @@ proc semVarOrLet(c: PContext, n: PNode, symkind: TSymKind): PNode = else: # BUGFIX: ``fitNode`` is needed here! # check type compatibility between def.typ and typ - def = fitNode(c, typ, def) + def = fitNode(c, typ, def, def.info) #changeType(def.skipConv, typ, check=true) else: typ = skipIntLit(def.typ) @@ -621,7 +624,7 @@ proc semForVars(c: PContext, n: PNode): PNode = if iter.kind != tyTuple or length == 3: if length == 3: var v = symForVar(c, n.sons[0]) - if getCurrOwner().kind == skModule: incl(v.flags, sfGlobal) + if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal) # BUGFIX: don't use `iter` here as that would strip away # the ``tyGenericInst``! See ``tests/compile/tgeneric.nim`` # for an example: @@ -635,7 +638,7 @@ proc semForVars(c: PContext, n: PNode): PNode = else: for i in countup(0, length - 3): var v = symForVar(c, n.sons[i]) - if getCurrOwner().kind == skModule: incl(v.flags, sfGlobal) + if getCurrOwner(c).kind == skModule: incl(v.flags, sfGlobal) v.typ = iter.sons[i] n.sons[i] = newSymNode(v) if sfGenSym notin v.flags and not isDiscardUnderscore(v): @@ -745,7 +748,7 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = # We have a generic type declaration here. In generic types, # symbol lookup needs to be done here. openScope(c) - pushOwner(s) + pushOwner(c, s) if s.magic == mNone: s.typ.kind = tyGenericBody # XXX for generic type aliases this is not correct! We need the # underlying Id really: @@ -769,11 +772,11 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = body.sym = s body.size = -1 # could not be computed properly s.typ.sons[sonsLen(s.typ) - 1] = body - popOwner() + popOwner(c) closeScope(c) elif a.sons[2].kind != nkEmpty: # process the type's body: - pushOwner(s) + pushOwner(c, s) var t = semTypeNode(c, a.sons[2], s.typ) if s.typ == nil: s.typ = t @@ -782,7 +785,7 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = assignType(s.typ, t) #debug s.typ s.ast = a - popOwner() + popOwner(c) let aa = a.sons[2] if aa.kind in {nkRefTy, nkPtrTy} and aa.len == 1 and aa.sons[0].kind == nkObjectTy: @@ -793,7 +796,7 @@ proc typeSectionRightSidePass(c: PContext, n: PNode) = internalAssert st.lastSon.sym == nil incl st.flags, tfRefsAnonObj let obj = newSym(skType, getIdent(s.name.s & ":ObjectType"), - getCurrOwner(), s.info) + getCurrOwner(c), s.info) obj.typ = st.lastSon st.lastSon.sym = obj @@ -927,7 +930,7 @@ proc semBorrow(c: PContext, n: PNode, s: PSym) = proc addResult(c: PContext, t: PType, info: TLineInfo, owner: TSymKind) = if t != nil: - var s = newSym(skResult, getIdent"result", getCurrOwner(), info) + var s = newSym(skResult, getIdent"result", getCurrOwner(c), info) s.typ = t incl(s.flags, sfUsed) addParamOrResult(c, s, owner) @@ -1002,12 +1005,12 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = checkSonsLen(n, bodyPos + 1) var s: PSym if n[namePos].kind != nkSym: - s = newSym(skProc, c.cache.idAnon, getCurrOwner(), n.info) + s = newSym(skProc, c.cache.idAnon, getCurrOwner(c), n.info) s.ast = n n.sons[namePos] = newSymNode(s) else: s = n[namePos].sym - pushOwner(s) + pushOwner(c, s) openScope(c) var gp: PNode if n.sons[genericParamsPos].kind != nkEmpty: @@ -1047,7 +1050,7 @@ proc semLambda(c: PContext, n: PNode, flags: TExprFlags): PNode = else: localError(n.info, errImplOfXexpected, s.name.s) closeScope(c) # close scope for parameters - popOwner() + popOwner(c) result.typ = s.typ proc semDo(c: PContext, n: PNode, flags: TExprFlags): PNode = @@ -1081,7 +1084,7 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode = params[i].sym.name.s) #params[i].sym.owner = s openScope(c) - pushOwner(s) + pushOwner(c, s) addParams(c, params, skProc) pushProcCon(c, s) addResult(c, n.typ.sons[0], n.info, skProc) @@ -1089,7 +1092,7 @@ proc semInferredLambda(c: PContext, pt: TIdTable, n: PNode): PNode = let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) n.sons[bodyPos] = transformBody(c.module, semBody, s) popProcCon(c) - popOwner() + popOwner(c) closeScope(c) # alternative variant (not quite working): @@ -1177,11 +1180,58 @@ proc semOverride(c: PContext, s: PSym, n: PNode) = localError(n.info, errGenerated, "'destroy' or 'deepCopy' expected for 'override'") +proc cursorInProcAux(n: PNode): bool = + if inCheckpoint(n.info) != cpNone: return true + for i in 0..<n.safeLen: + if cursorInProcAux(n[i]): return true + +proc cursorInProc(n: PNode): bool = + if n.info.fileIndex == gTrackPos.fileIndex: + result = cursorInProcAux(n) + type TProcCompilationSteps = enum stepRegisterSymbol, stepDetermineType, +import compilerlog + +proc hasObjParam(s: PSym): bool = + var t = s.typ + for col in countup(1, sonsLen(t)-1): + if skipTypes(t.sons[col], skipPtrs).kind == tyObject: + return true + +proc finishMethod(c: PContext, s: PSym) = + if hasObjParam(s): + methodDef(c.graph, s, false) + +proc semMethodPrototype(c: PContext; s: PSym; n: PNode) = + if isGenericRoutine(s): + let tt = s.typ + var foundObj = false + # we start at 1 for now so that tparsecombnum continues to compile. + # XXX Revisit this problem later. + for col in countup(1, sonsLen(tt)-1): + let t = tt.sons[col] + if t != nil and t.kind == tyGenericInvocation: + var x = skipTypes(t.sons[0], {tyVar, tyPtr, tyRef, tyGenericInst, + tyGenericInvocation, tyGenericBody, + tyAlias}) + if x.kind == tyObject and t.len-1 == n.sons[genericParamsPos].len: + foundObj = true + x.methods.safeAdd((col,s)) + if not foundObj: + message(n.info, warnDeprecated, "generic method not attachable to object type") + else: + # why check for the body? bug #2400 has none. Checking for sfForward makes + # no sense either. + # and result.sons[bodyPos].kind != nkEmpty: + if hasObjParam(s): + methodDef(c.graph, s, fromCache=false) + else: + localError(n.info, errXNeedsParamObjectType, "method") + proc semProcAux(c: PContext, n: PNode, kind: TSymKind, validPragmas: TSpecialWords, phase = stepRegisterSymbol): PNode = @@ -1196,7 +1246,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, assert phase == stepRegisterSymbol if n[namePos].kind == nkEmpty: - s = newSym(kind, c.cache.idAnon, getCurrOwner(), n.info) + s = newSym(kind, c.cache.idAnon, getCurrOwner(c), n.info) incl(s.flags, sfUsed) isAnon = true else: @@ -1213,7 +1263,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, return else: s = n[namePos].sym - s.owner = getCurrOwner() + s.owner = getCurrOwner(c) typeIsDetermined = s.typ == nil s.ast = n #s.scope = c.currentScope @@ -1222,7 +1272,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, # where the proc was declared let oldScope = c.currentScope #c.currentScope = s.scope - pushOwner(s) + pushOwner(c, s) openScope(c) var gp: PNode if n.sons[genericParamsPos].kind != nkEmpty: @@ -1288,8 +1338,8 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, if importantComments() and not isNil(proto.ast.comment): n.comment = proto.ast.comment proto.ast = n # needed for code generation - popOwner() - pushOwner(s) + popOwner(c) + pushOwner(c, s) s.options = gOptions if sfOverriden in s.flags or s.name.s[0] == '=': semOverride(c, s, n) if s.name.s[0] in {'.', '('}: @@ -1303,30 +1353,40 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, # Macros and Templates can have generic parameters, but they are # only used for overload resolution (there is no instantiation of # the symbol, so we must process the body now) - pushProcCon(c, s) - if n.sons[genericParamsPos].kind == nkEmpty or usePseudoGenerics: - if not usePseudoGenerics: paramsTypeCheck(c, s.typ) - - c.p.wasForwarded = proto != nil - maybeAddResult(c, s, n) - if lfDynamicLib notin s.loc.flags: - # no semantic checking for importc: - let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) - # unfortunately we cannot skip this step when in 'system.compiles' - # context as it may even be evaluated in 'system.compiles': - n.sons[bodyPos] = transformBody(c.module, semBody, s) + if not usePseudoGenerics and gIdeCmd in {ideSug, ideCon} and not + cursorInProc(n.sons[bodyPos]): + discard "speed up nimsuggest" + if s.kind == skMethod: semMethodPrototype(c, s, n) else: - if s.typ.sons[0] != nil and kind != skIterator: - addDecl(c, newSym(skUnknown, getIdent"result", nil, n.info)) - openScope(c) - n.sons[bodyPos] = semGenericStmt(c, n.sons[bodyPos]) - closeScope(c) - fixupInstantiatedSymbols(c, s) - if sfImportc in s.flags: - # so we just ignore the body after semantic checking for importc: - n.sons[bodyPos] = ast.emptyNode - popProcCon(c) + pushProcCon(c, s) + if n.sons[genericParamsPos].kind == nkEmpty or usePseudoGenerics: + if not usePseudoGenerics: paramsTypeCheck(c, s.typ) + + c.p.wasForwarded = proto != nil + maybeAddResult(c, s, n) + if s.kind == skMethod: semMethodPrototype(c, s, n) + + if lfDynamicLib notin s.loc.flags: + # no semantic checking for importc: + let semBody = hloBody(c, semProcBody(c, n.sons[bodyPos])) + # unfortunately we cannot skip this step when in 'system.compiles' + # context as it may even be evaluated in 'system.compiles': + n.sons[bodyPos] = transformBody(c.module, semBody, s) + else: + if s.typ.sons[0] != nil and kind != skIterator: + addDecl(c, newSym(skUnknown, getIdent"result", nil, n.info)) + + openScope(c) + n.sons[bodyPos] = semGenericStmt(c, n.sons[bodyPos]) + closeScope(c) + fixupInstantiatedSymbols(c, s) + if s.kind == skMethod: semMethodPrototype(c, s, n) + if sfImportc in s.flags: + # so we just ignore the body after semantic checking for importc: + n.sons[bodyPos] = ast.emptyNode + popProcCon(c) else: + if s.kind == skMethod: semMethodPrototype(c, s, n) if proto != nil: localError(n.info, errImplOfXexpected, proto.name.s) if {sfImportc, sfBorrow} * s.flags == {} and s.magic == mNone: incl(s.flags, sfForward) @@ -1334,7 +1394,7 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind, sideEffectsCheck(c, s) closeScope(c) # close scope for parameters # c.currentScope = oldScope - popOwner() + popOwner(c) if n.sons[patternPos].kind != nkEmpty: c.patterns.add(s) if isAnon: result.typ = s.typ @@ -1353,7 +1413,7 @@ proc semIterator(c: PContext, n: PNode): PNode = let isAnon = n[namePos].kind == nkEmpty if n[namePos].kind == nkSym: # gensym'ed iterators might need to become closure iterators: - n[namePos].sym.owner = getCurrOwner() + n[namePos].sym.owner = getCurrOwner(c) n[namePos].sym.kind = skIterator result = semProcAux(c, n, skIterator, iteratorPragmas) var s = result.sons[namePos].sym @@ -1380,46 +1440,22 @@ proc semIterator(c: PContext, n: PNode): PNode = proc semProc(c: PContext, n: PNode): PNode = result = semProcAux(c, n, skProc, procPragmas) -proc hasObjParam(s: PSym): bool = - var t = s.typ - for col in countup(1, sonsLen(t)-1): - if skipTypes(t.sons[col], skipPtrs).kind == tyObject: - return true - -proc finishMethod(c: PContext, s: PSym) = - if hasObjParam(s): - methodDef(s, false) - proc semMethod(c: PContext, n: PNode): PNode = if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "method") result = semProcAux(c, n, skMethod, methodPragmas) - # macros can transform methods to nothing: + # macros can transform converters to nothing: if namePos >= result.safeLen: return result var s = result.sons[namePos].sym - if isGenericRoutine(s): - let tt = s.typ - var foundObj = false - # we start at 1 for now so that tparsecombnum continues to compile. - # XXX Revisit this problem later. - for col in countup(1, sonsLen(tt)-1): - let t = tt.sons[col] - if t != nil and t.kind == tyGenericInvocation: - var x = skipTypes(t.sons[0], {tyVar, tyPtr, tyRef, tyGenericInst, - tyGenericInvocation, tyGenericBody, - tyAlias}) - if x.kind == tyObject and t.len-1 == result.sons[genericParamsPos].len: - foundObj = true - x.methods.safeAdd((col,s)) - if not foundObj: - message(n.info, warnDeprecated, "generic method not attachable to object type") - else: - # why check for the body? bug #2400 has none. Checking for sfForward makes - # no sense either. - # and result.sons[bodyPos].kind != nkEmpty: - if hasObjParam(s): - methodDef(s, fromCache=false) - else: - localError(n.info, errXNeedsParamObjectType, "method") + # we need to fix the 'auto' return type for the dispatcher here (see tautonotgeneric + # test case): + let disp = getDispatcher(s) + # auto return type? + if disp != nil and disp.typ.sons[0] != nil and disp.typ.sons[0].kind == tyExpr: + let ret = s.typ.sons[0] + disp.typ.sons[0] = ret + if disp.ast[resultPos].kind == nkSym: + if isEmptyType(ret): disp.ast.sons[resultPos] = emptyNode + else: disp.ast[resultPos].sym.typ = ret proc semConverterDef(c: PContext, n: PNode): PNode = if not isTopLevel(c): localError(n.info, errXOnlyAtModuleScope, "converter") diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index 8819f17cc..d7134402f 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -60,7 +60,7 @@ proc symChoice(c: PContext, n: PNode, s: PSym, r: TSymChoiceRule): PNode = # (s.kind notin routineKinds or s.magic != mNone): # for instance 'nextTry' is both in tables.nim and astalgo.nim ... result = newSymNode(s, n.info) - markUsed(n.info, s) + markUsed(n.info, s, c.graph.usageSym) else: # semantic checking requires a type; ``fitNode`` deals with it # appropriately @@ -384,11 +384,13 @@ proc semTemplBody(c: var TemplCtx, n: PNode): PNode = checkSonsLen(n, 2) openScope(c) if n.sons[0].kind != nkEmpty: - # labels are always 'gensym'ed: - let s = newGenSym(skLabel, n.sons[0], c) - addPrelimDecl(c.c, s) - styleCheckDef(s) - n.sons[0] = newSymNode(s, n.sons[0].info) + addLocalDecl(c, n.sons[0], skLabel) + when false: + # labels are always 'gensym'ed: + let s = newGenSym(skLabel, n.sons[0], c) + addPrelimDecl(c.c, s) + styleCheckDef(s) + n.sons[0] = newSymNode(s, n.sons[0].info) n.sons[1] = semTemplBody(c, n.sons[1]) closeScope(c) of nkTryStmt: @@ -559,7 +561,7 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = styleCheckDef(s) # check parameter list: #s.scope = c.currentScope - pushOwner(s) + pushOwner(c, s) openScope(c) n.sons[namePos] = newSymNode(s, n.sons[namePos].info) if n.sons[pragmasPos].kind != nkEmpty: @@ -613,7 +615,7 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = # only parameters are resolved, no type checking is performed semIdeForTemplateOrGeneric(c, n.sons[bodyPos], ctx.cursorInBody) closeScope(c) - popOwner() + popOwner(c) s.ast = n result = n if n.sons[bodyPos].kind == nkEmpty: @@ -758,7 +760,7 @@ proc semPattern(c: PContext, n: PNode): PNode = ctx.toMixin = initIntSet() ctx.toInject = initIntSet() ctx.c = c - ctx.owner = getCurrOwner() + ctx.owner = getCurrOwner(c) result = flattenStmts(semPatternBody(ctx, n)) if result.kind in {nkStmtList, nkStmtListExpr}: if result.len == 1: diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index 440edd226..77ccce5d4 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -306,7 +306,7 @@ proc semTypeIdent(c: PContext, n: PNode): PSym = else: result = qualifiedLookUp(c, n, {checkAmbiguity, checkUndeclared}) if result != nil: - markUsed(n.info, result) + markUsed(n.info, result, c.graph.usageSym) styleCheckUse(n.info, result) if result.kind == skParam and result.typ.kind == tyTypeDesc: # This is a typedesc param. is it already bound? @@ -449,8 +449,8 @@ proc semBranchRange(c: PContext, t, a, b: PNode, covered: var BiggestInt): PNode checkMinSonsLen(t, 1) let ac = semConstExpr(c, a) let bc = semConstExpr(c, b) - let at = fitNode(c, t.sons[0].typ, ac).skipConvTakeType - let bt = fitNode(c, t.sons[0].typ, bc).skipConvTakeType + let at = fitNode(c, t.sons[0].typ, ac, ac.info).skipConvTakeType + let bt = fitNode(c, t.sons[0].typ, bc, bc.info).skipConvTakeType result = newNodeI(nkRange, a.info) result.add(at) @@ -472,7 +472,7 @@ proc semCaseBranchSetElem(c: PContext, t, b: PNode, checkSonsLen(b, 2) result = semBranchRange(c, t, b.sons[0], b.sons[1], covered) else: - result = fitNode(c, t.sons[0].typ, b) + result = fitNode(c, t.sons[0].typ, b, b.info) inc(covered) proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int, @@ -493,7 +493,7 @@ proc semCaseBranch(c: PContext, t, branch: PNode, branchIndex: int, return elif r.kind notin {nkCurly, nkBracket} or len(r) == 0: checkMinSonsLen(t, 1) - branch.sons[i] = skipConv(fitNode(c, t.sons[0].typ, r)) + branch.sons[i] = skipConv(fitNode(c, t.sons[0].typ, r, r.info)) inc(covered) else: # first element is special and will overwrite: branch.sons[i]: @@ -608,7 +608,7 @@ proc semRecordNodeAux(c: PContext, n: PNode, check: var IntSet, pos: var int, let rec = rectype.sym for i in countup(0, sonsLen(n)-3): var f = semIdentWithPragma(c, skField, n.sons[i], {sfExported}) - suggestSym(n.sons[i].info, f) + suggestSym(n.sons[i].info, f, c.graph.usageSym) f.typ = typ f.position = pos if (rec != nil) and ({sfImportc, sfExportc} * rec.flags != {}) and @@ -750,7 +750,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, return genericParams.sons[i].typ let owner = if typeClass.sym != nil: typeClass.sym - else: getCurrOwner() + else: getCurrOwner(c) var s = newSym(skType, finalTypId, owner, info) if typId == nil: s.flags.incl(sfAnon) s.linkTo(typeClass) @@ -850,7 +850,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, of tyGenericInst: if paramType.lastSon.kind == tyUserTypeClass: - var cp = copyType(paramType, getCurrOwner(), false) + var cp = copyType(paramType, getCurrOwner(c), false) cp.kind = tyUserTypeClassInst return addImplicitGeneric(cp) @@ -876,10 +876,10 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode, result = liftingWalk(expanded, true) of tyUserTypeClass, tyBuiltInTypeClass, tyAnd, tyOr, tyNot: - result = addImplicitGeneric(copyType(paramType, getCurrOwner(), true)) + result = addImplicitGeneric(copyType(paramType, getCurrOwner(c), true)) of tyGenericParam: - markUsed(info, paramType.sym) + markUsed(info, paramType.sym, c.graph.usageSym) styleCheckUse(info, paramType.sym) if tfWildcard in paramType.flags: paramType.flags.excl tfWildcard @@ -947,7 +947,7 @@ proc semProcTypeNode(c: PContext, n, genericParams: PNode, # example code that triggers it: # proc sort[T](cmp: proc(a, b: T): int = cmp) if not containsGenericType(typ): - def = fitNode(c, typ, def) + def = fitNode(c, typ, def, def.info) if not hasType and not hasDefault: if isType: localError(a.info, "':' expected") if kind in {skTemplate, skMacro}: @@ -1267,7 +1267,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = of mExpr: result = semTypeNode(c, n.sons[0], nil) if result != nil: - result = copyType(result, getCurrOwner(), false) + result = copyType(result, getCurrOwner(c), false) for i in countup(1, n.len - 1): result.rawAddSon(semTypeNode(c, n.sons[i], nil)) of mDistinct: @@ -1330,7 +1330,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = else: assignType(prev, t) result = prev - markUsed(n.info, n.sym) + markUsed(n.info, n.sym, c.graph.usageSym) styleCheckUse(n.info, n.sym) else: if s.kind != skError: localError(n.info, errTypeExpected) @@ -1479,7 +1479,7 @@ proc semGenericParamList(c: PContext, n: PNode, father: PType = nil): PNode = # from manyloc/named_argument_bug/triengine: def.typ = def.typ.skipTypes({tyTypeDesc}) if not containsGenericType(def.typ): - def = fitNode(c, typ, def) + def = fitNode(c, typ, def, def.info) if typ == nil: typ = newTypeS(tyGenericParam, c) diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index d3c142954..f2caab41f 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -73,7 +73,7 @@ type const isNilConversion = isConvertible # maybe 'isIntConv' fits better? -proc markUsed*(info: TLineInfo, s: PSym) +proc markUsed*(info: TLineInfo, s: PSym; usageSym: var PSym) template hasFauxMatch*(c: TCandidate): bool = c.fauxMatch != tyNone @@ -543,7 +543,7 @@ proc procTypeRel(c: var TCandidate, f, a: PType): TTypeRelation = else: return isNone when useEffectSystem: - if not compatibleEffects(f, a): return isNone + if compatibleEffects(f, a) != efCompat: return isNone of tyNil: result = f.allowsNil @@ -1291,7 +1291,7 @@ proc userConvMatch(c: PContext, m: var TCandidate, f, a: PType, dest = generateTypeInstance(c, m.bindings, arg, dest) let fdest = typeRel(m, f, dest) if fdest in {isEqual, isGeneric}: - markUsed(arg.info, c.converters[i]) + markUsed(arg.info, c.converters[i], c.graph.usageSym) var s = newSymNode(c.converters[i]) s.typ = c.converters[i].typ s.info = arg.info @@ -1551,7 +1551,7 @@ proc paramTypesMatch*(m: var TCandidate, f, a: PType, else: result = nil else: # only one valid interpretation found: - markUsed(arg.info, arg.sons[best].sym) + markUsed(arg.info, arg.sons[best].sym, m.c.graph.usageSym) styleCheckUse(arg.info, arg.sons[best].sym) result = paramTypesMatchAux(m, f, arg.sons[best].typ, arg.sons[best], argOrig) diff --git a/compiler/suggest.nim b/compiler/suggest.nim index 66876b9b5..9ae427583 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -28,7 +28,7 @@ type column*: int # Starts at 0 doc*: string # Not escaped (yet) symkind*: TSymKind - forth*: string # XXX TODO object on symkind + forth*: string # type quality*: range[0..100] # matching quality isGlobal*: bool # is a global variable tokenLen*: int @@ -88,7 +88,7 @@ proc symToSuggest(s: PSym, isLocal: bool, section: string, li: TLineInfo; when not defined(noDocgen): result.doc = s.extractDocComment -proc `$`(suggest: Suggest): string = +proc `$`*(suggest: Suggest): string = result = $suggest.section result.add(sep) if suggest.section == ideHighlight: @@ -131,7 +131,7 @@ proc suggestResult(s: Suggest) = if not isNil(suggestionResultHook): suggestionResultHook(s) else: - suggestWriteln($(s)) + suggestWriteln($s) proc filterSym(s: PSym): bool {.inline.} = result = s.kind != skModule @@ -297,10 +297,10 @@ proc suggestFieldAccess(c: PContext, n: PNode, outputs: var int) = suggestOperations(c, n, typ, outputs) type - TCheckPointResult = enum + TCheckPointResult* = enum cpNone, cpFuzzy, cpExact -proc inCheckpoint(current: TLineInfo): TCheckPointResult = +proc inCheckpoint*(current: TLineInfo): TCheckPointResult = if current.fileIndex == gTrackPos.fileIndex: if current.line == gTrackPos.line and abs(current.col-gTrackPos.col) < 4: @@ -353,10 +353,10 @@ when defined(nimsuggest): s.allUsages.add(info) var - usageSym*: PSym + #usageSym*: PSym lastLineInfo*: TLineInfo -proc findUsages(info: TLineInfo; s: PSym) = +proc findUsages(info: TLineInfo; s: PSym; usageSym: var PSym) = if suggestVersion < 2: if usageSym == nil and isTracked(info, s.name.s.len): usageSym = s @@ -385,7 +385,7 @@ proc ensureIdx[T](x: var T, y: int) = proc ensureSeq[T](x: var seq[T]) = if x == nil: newSeq(x, 0) -proc suggestSym*(info: TLineInfo; s: PSym; isDecl=true) {.inline.} = +proc suggestSym*(info: TLineInfo; s: PSym; usageSym: var PSym; isDecl=true) {.inline.} = ## misnamed: should be 'symDeclared' when defined(nimsuggest): if suggestVersion == 2: @@ -395,20 +395,20 @@ proc suggestSym*(info: TLineInfo; s: PSym; isDecl=true) {.inline.} = s.addNoDup(info) if gIdeCmd == ideUse: - findUsages(info, s) + findUsages(info, s, usageSym) elif gIdeCmd == ideDef: findDefinition(info, s) elif gIdeCmd == ideDus and s != nil: if isTracked(info, s.name.s.len): suggestResult(symToSuggest(s, isLocal=false, $ideDef, 100)) - findUsages(info, s) + findUsages(info, s, usageSym) elif gIdeCmd == ideHighlight and info.fileIndex == gTrackPos.fileIndex: suggestResult(symToSuggest(s, isLocal=false, $ideHighlight, info, 100)) elif gIdeCmd == ideOutline and info.fileIndex == gTrackPos.fileIndex and isDecl: suggestResult(symToSuggest(s, isLocal=false, $ideOutline, info, 100)) -proc markUsed(info: TLineInfo; s: PSym) = +proc markUsed(info: TLineInfo; s: PSym; usageSym: var PSym) = incl(s.flags, sfUsed) if s.kind == skEnumField and s.owner != nil: incl(s.owner.flags, sfUsed) @@ -416,11 +416,11 @@ proc markUsed(info: TLineInfo; s: PSym) = if sfDeprecated in s.flags: message(info, warnDeprecated, s.name.s) if sfError in s.flags: localError(info, errWrongSymbolX, s.name.s) when defined(nimsuggest): - suggestSym(info, s, false) + suggestSym(info, s, usageSym, false) -proc useSym*(sym: PSym): PNode = +proc useSym*(sym: PSym; usageSym: var PSym): PNode = result = newSymNode(sym) - markUsed(result.info, sym) + markUsed(result.info, sym, usageSym) proc safeSemExpr*(c: PContext, n: PNode): PNode = # use only for idetools support! diff --git a/compiler/transf.nim b/compiler/transf.nim index 2103d48bf..0c53c0cbf 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -19,7 +19,7 @@ # * transforms 'defer' into a 'try finally' statement import - intsets, strutils, lists, options, ast, astalgo, trees, treetab, msgs, os, + intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os, idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread, lambdalifting, sempass2, lowerings, lookups @@ -694,9 +694,10 @@ proc transformCall(c: PTransf, n: PNode): PTransNode = # bugfix: check after 'transformSons' if it's still a method call: # use the dispatcher for the call: if s.sons[0].kind == nkSym and s.sons[0].sym.kind == skMethod: - let t = lastSon(s.sons[0].sym.ast) - if t.kind != nkSym or sfDispatcher notin t.sym.flags: - methodDef(s.sons[0].sym, false) + when false: + let t = lastSon(s.sons[0].sym.ast) + if t.kind != nkSym or sfDispatcher notin t.sym.flags: + methodDef(s.sons[0].sym, false) result = methodCall(s).PTransNode else: result = s.PTransNode diff --git a/compiler/types.nim b/compiler/types.nim index d5ec2972a..df1d3e3ca 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1377,7 +1377,16 @@ proc compatibleEffectsAux(se, re: PNode): bool = return false result = true -proc compatibleEffects*(formal, actual: PType): bool = +type + EffectsCompat* = enum + efCompat + efRaisesDiffer + efRaisesUnknown + efTagsDiffer + efTagsUnknown + efLockLevelsDiffer + +proc compatibleEffects*(formal, actual: PType): EffectsCompat = # for proc type compatibility checking: assert formal.kind == tyProc and actual.kind == tyProc internalAssert formal.n.sons[0].kind == nkEffectList @@ -1393,18 +1402,21 @@ proc compatibleEffects*(formal, actual: PType): bool = # 'r.msgHandler = if isNil(msgHandler): defaultMsgHandler else: msgHandler' if not isNil(se) and se.kind != nkArgList: # spec requires some exception or tag, but we don't know anything: - if real.len == 0: return false - result = compatibleEffectsAux(se, real.sons[exceptionEffects]) - if not result: return + if real.len == 0: return efRaisesUnknown + let res = compatibleEffectsAux(se, real.sons[exceptionEffects]) + if not res: return efRaisesDiffer let st = spec.sons[tagEffects] if not isNil(st) and st.kind != nkArgList: # spec requires some exception or tag, but we don't know anything: - if real.len == 0: return false - result = compatibleEffectsAux(st, real.sons[tagEffects]) - if not result: return - result = formal.lockLevel.ord < 0 or - actual.lockLevel.ord <= formal.lockLevel.ord + if real.len == 0: return efTagsUnknown + let res = compatibleEffectsAux(st, real.sons[tagEffects]) + if not res: return efTagsDiffer + if formal.lockLevel.ord < 0 or + actual.lockLevel.ord <= formal.lockLevel.ord: + result = efCompat + else: + result = efLockLevelsDiffer proc isCompileTimeOnly*(t: PType): bool {.inline.} = result = t.kind in {tyTypeDesc, tyStatic} @@ -1503,11 +1515,26 @@ proc skipHiddenSubConv*(n: PNode): PNode = else: result = n -proc typeMismatch*(n: PNode, formal, actual: PType) = +proc typeMismatch*(info: TLineInfo, formal, actual: PType) = if formal.kind != tyError and actual.kind != tyError: let named = typeToString(formal) let desc = typeToString(formal, preferDesc) let x = if named == desc: named else: named & " = " & desc - localError(n.info, errGenerated, msgKindToString(errTypeMismatch) & - typeToString(actual) & ") " & - `%`(msgKindToString(errButExpectedX), [x])) + var msg = msgKindToString(errTypeMismatch) & + typeToString(actual) & ") " & + msgKindToString(errButExpectedX) % [x] + + if formal.kind == tyProc and actual.kind == tyProc: + case compatibleEffects(formal, actual) + of efCompat: discard + of efRaisesDiffer: + msg.add "\n.raise effects differ" + of efRaisesUnknown: + msg.add "\n.raise effect is 'can raise any'" + of efTagsDiffer: + msg.add "\n.tag effects differ" + of efTagsUnknown: + msg.add "\n.tag effect is 'any tag allowed'" + of efLockLevelsDiffer: + msg.add "\nlock levels differ" + localError(info, errGenerated, msg) diff --git a/compiler/vm.nim b/compiler/vm.nim index 5d4784281..6a9545193 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -1556,7 +1556,10 @@ proc myProcess(c: PPassContext, n: PNode): PNode = result = n oldErrorCount = msgs.gErrorCounter -const evalPass* = makePass(myOpen, nil, myProcess, myProcess) +proc myClose(graph: ModuleGraph; c: PPassContext, n: PNode): PNode = + myProcess(c, n) + +const evalPass* = makePass(myOpen, nil, myProcess, myClose) proc evalConstExprAux(module: PSym; cache: IdentCache; prc: PSym, n: PNode, mode: TEvalMode): PNode = diff --git a/doc/manual/threads.txt b/doc/manual/threads.txt index 95bd93b98..fe88123d2 100644 --- a/doc/manual/threads.txt +++ b/doc/manual/threads.txt @@ -62,8 +62,6 @@ be used: {.gcsafe.}: deepCopy(perThread, someGlobal) - onThreadCreation(setPerThread) - Future directions: diff --git a/lib/core/macros.nim b/lib/core/macros.nim index 3adf4670d..83776f16b 100644 --- a/lib/core/macros.nim +++ b/lib/core/macros.nim @@ -742,6 +742,8 @@ proc `$`*(node: NimNode): string {.compileTime.} = result = $node.symbol of nnkOpenSymChoice, nnkClosedSymChoice: result = $node[0] + of nnkAccQuoted: + result = $node[0] else: badNodeKind node.kind, "$" diff --git a/lib/pure/asyncdispatch.nim b/lib/pure/asyncdispatch.nim index d97214d15..7fa686f00 100644 --- a/lib/pure/asyncdispatch.nim +++ b/lib/pure/asyncdispatch.nim @@ -1387,6 +1387,17 @@ proc send*(socket: AsyncFD, data: string, # -- Await Macro include asyncmacro +proc readAll*(future: FutureStream[string]): Future[string] {.async.} = + ## Returns a future that will complete when all the string data from the + ## specified future stream is retrieved. + result = "" + while true: + let (hasValue, value) = await future.read() + if hasValue: + result.add(value) + else: + break + proc recvLine*(socket: AsyncFD): Future[string] {.async, deprecated.} = ## Reads a line of data from ``socket``. Returned future will complete once ## a full line is read or an error occurs. diff --git a/lib/pure/asyncfile.nim b/lib/pure/asyncfile.nim index 0241e4796..488b8276e 100644 --- a/lib/pure/asyncfile.nim +++ b/lib/pure/asyncfile.nim @@ -476,3 +476,16 @@ proc close*(f: AsyncFile) = if close(f.fd.cint) == -1: raiseOSError(osLastError()) +proc writeFromStream*(f: AsyncFile, fs: FutureStream[string]) {.async.} = + ## Reads data from the specified future stream until it is completed. + ## The data which is read is written to the file immediately and + ## freed from memory. + ## + ## This procedure is perfect for saving streamed data to a file without + ## wasting memory. + while true: + let (hasValue, value) = await fs.read() + if hasValue: + await f.write(value) + else: + break diff --git a/lib/pure/asynchttpserver.nim b/lib/pure/asynchttpserver.nim index a558d9d7e..672eb34a0 100644 --- a/lib/pure/asynchttpserver.nim +++ b/lib/pure/asynchttpserver.nim @@ -31,7 +31,7 @@ ## ## waitFor server.serve(Port(8080), cb) -import tables, asyncnet, asyncdispatch, parseutils, uri, strutils +import tables, asyncnet, asyncdispatch, parseutils, uri, strutils, nativesockets import httpcore export httpcore except parseHeader @@ -241,7 +241,7 @@ proc serve*(server: AsyncHttpServer, port: Port, ## specified address and port. ## ## When a request is made by a client the specified callback will be called. - server.socket = newAsyncSocket() + server.socket = newAsyncSocket(AF_INET6) if server.reuseAddr: server.socket.setSockOpt(OptReuseAddr, true) if server.reusePort: diff --git a/lib/pure/asyncmacro.nim b/lib/pure/asyncmacro.nim index f74881c6d..f0837d67d 100644 --- a/lib/pure/asyncmacro.nim +++ b/lib/pure/asyncmacro.nim @@ -33,8 +33,10 @@ template createCb(retFutureSym, iteratorNameSym, if not nameIterVar.finished: var next = nameIterVar() if next == nil: - assert retFutureSym.finished, "Async procedure's (" & - name & ") return Future was not finished." + if not retFutureSym.finished: + let msg = "Async procedure ($1) yielded `nil`, are you await'ing a " & + "`nil` Future?" + raise newException(AssertionError, msg % name) else: next.callback = cb except: @@ -281,6 +283,14 @@ proc getFutureVarIdents(params: NimNode): seq[NimNode] {.compileTime.} = ($params[i][1][0].ident).normalize == "futurevar": result.add(params[i][0]) +proc isInvalidReturnType(typeName: string): bool = + return typeName notin ["Future"] #, "FutureStream"] + +proc verifyReturnType(typeName: string) {.compileTime.} = + if typeName.isInvalidReturnType: + error("Expected return type of 'Future' got '$1'" % + typeName) + proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = ## This macro transforms a single procedure into a closure iterator. ## The ``async`` macro supports a stmtList holding multiple async procedures. @@ -295,18 +305,16 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = # Verify that the return type is a Future[T] if returnType.kind == nnkBracketExpr: let fut = repr(returnType[0]) - if fut != "Future": - error("Expected return type of 'Future' got '" & fut & "'") + verifyReturnType(fut) baseType = returnType[1] elif returnType.kind in nnkCallKinds and $returnType[0] == "[]": let fut = repr(returnType[1]) - if fut != "Future": - error("Expected return type of 'Future' got '" & fut & "'") + verifyReturnType(fut) baseType = returnType[2] elif returnType.kind == nnkEmpty: baseType = returnType else: - error("Expected return type of 'Future' got '" & repr(returnType) & "'") + verifyReturnType(repr(returnType)) let subtypeIsVoid = returnType.kind == nnkEmpty or (baseType.kind == nnkIdent and returnType[1].ident == !"void") @@ -390,7 +398,7 @@ proc asyncSingleProc(prc: NimNode): NimNode {.compileTime.} = if procBody.kind != nnkEmpty: result[6] = outerProcBody #echo(treeRepr(result)) - #if prc[0].getName == "testInfix": + #if prc[0].getName == "beta": # echo(toStrLit(result)) macro async*(prc: untyped): untyped = @@ -451,13 +459,12 @@ proc stripAwait(node: NimNode): NimNode = for i in 0 .. <result.len: result[i] = stripAwait(result[i]) -proc splitParams(param: NimNode, async: bool): NimNode = - expectKind(param, nnkIdentDefs) - result = param - if param[1].kind == nnkInfix and $param[1][0].ident in ["|", "or"]: - let firstType = param[1][1] +proc splitParamType(paramType: NimNode, async: bool): NimNode = + result = paramType + if paramType.kind == nnkInfix and $paramType[0].ident in ["|", "or"]: + let firstType = paramType[1] let firstTypeName = $firstType.ident - let secondType = param[1][2] + let secondType = paramType[2] let secondTypeName = $secondType.ident # Make sure that at least one has the name `async`, otherwise we shouldn't @@ -468,22 +475,21 @@ proc splitParams(param: NimNode, async: bool): NimNode = if async: if firstTypeName.normalize.startsWith("async"): - result = newIdentDefs(param[0], param[1][1]) + result = paramType[1] elif secondTypeName.normalize.startsWith("async"): - result = newIdentDefs(param[0], param[1][2]) + result = paramType[2] else: if not firstTypeName.normalize.startsWith("async"): - result = newIdentDefs(param[0], param[1][1]) + result = paramType[1] elif not secondTypeName.normalize.startsWith("async"): - result = newIdentDefs(param[0], param[1][2]) + result = paramType[2] proc stripReturnType(returnType: NimNode): NimNode = # Strip out the 'Future' from 'Future[T]'. result = returnType if returnType.kind == nnkBracketExpr: let fut = repr(returnType[0]) - if fut != "Future": - error("Expected return type of 'Future' got '" & fut & "'") + verifyReturnType(fut) result = returnType[1] proc splitProc(prc: NimNode): (NimNode, NimNode) = @@ -491,15 +497,24 @@ proc splitProc(prc: NimNode): (NimNode, NimNode) = ## for example: proc (socket: Socket | AsyncSocket). ## It transforms them so that ``proc (socket: Socket)`` and ## ``proc (socket: AsyncSocket)`` are returned. + result[0] = prc.copyNimTree() - result[0][3][0] = stripReturnType(result[0][3][0]) + # Retrieve the `T` inside `Future[T]`. + let returnType = stripReturnType(result[0][3][0]) + result[0][3][0] = splitParamType(returnType, async=false) for i in 1 .. <result[0][3].len: - result[0][3][i] = splitParams(result[0][3][i], false) + # Sync proc (0) -> FormalParams (3) -> IdentDefs, the parameter (i) -> + # parameter type (1). + result[0][3][i][1] = splitParamType(result[0][3][i][1], async=false) result[0][6] = stripAwait(result[0][6]) result[1] = prc.copyNimTree() + if result[1][3][0].kind == nnkBracketExpr: + result[1][3][0][1] = splitParamType(result[1][3][0][1], async=true) for i in 1 .. <result[1][3].len: - result[1][3][i] = splitParams(result[1][3][i], true) + # Async proc (1) -> FormalParams (3) -> IdentDefs, the parameter (i) -> + # parameter type (1). + result[1][3][i][1] = splitParamType(result[1][3][i][1], async=true) macro multisync*(prc: untyped): untyped = ## Macro which processes async procedures into both asynchronous and @@ -512,4 +527,4 @@ macro multisync*(prc: untyped): untyped = let (sync, asyncPrc) = splitProc(prc) result = newStmtList() result.add(asyncSingleProc(asyncPrc)) - result.add(sync) + result.add(sync) \ No newline at end of file diff --git a/lib/pure/collections/deques.nim b/lib/pure/collections/deques.nim index 495d7896c..d42679f06 100644 --- a/lib/pure/collections/deques.nim +++ b/lib/pure/collections/deques.nim @@ -129,7 +129,7 @@ proc expandIfNeeded[T](deq: var Deque[T]) = var cap = deq.mask + 1 if unlikely(deq.count >= cap): var n = newSeq[T](cap * 2) - for i, x in deq: # don't use copyMem because the GC and because it's slower. + for i, x in pairs(deq): # don't use copyMem because the GC and because it's slower. shallowCopy(n[i], x) shallowCopy(deq.data, n) deq.mask = cap * 2 - 1 diff --git a/lib/pure/collections/queues.nim b/lib/pure/collections/queues.nim index 0490ae494..401422162 100644 --- a/lib/pure/collections/queues.nim +++ b/lib/pure/collections/queues.nim @@ -144,7 +144,7 @@ proc add*[T](q: var Queue[T], item: T) = var cap = q.mask+1 if unlikely(q.count >= cap): var n = newSeq[T](cap*2) - for i, x in q: # don't use copyMem because the GC and because it's slower. + for i, x in pairs(q): # don't use copyMem because the GC and because it's slower. shallowCopy(n[i], x) shallowCopy(q.data, n) q.mask = cap*2 - 1 diff --git a/lib/pure/hashes.nim b/lib/pure/hashes.nim index 17d1c6442..d5759e507 100644 --- a/lib/pure/hashes.nim +++ b/lib/pure/hashes.nim @@ -112,6 +112,14 @@ proc hash*(x: int64): Hash {.inline.} = ## efficient hashing of int64 integers result = toU32(x) +proc hash*(x: uint): Hash {.inline.} = + ## efficient hashing of unsigned integers + result = cast[int](x) + +proc hash*(x: uint64): Hash {.inline.} = + ## efficient hashing of uint64 integers + result = toU32(cast[int](x)) + proc hash*(x: char): Hash {.inline.} = ## efficient hashing of characters result = ord(x) diff --git a/lib/pure/httpclient.nim b/lib/pure/httpclient.nim index 1ded540ec..e88847004 100644 --- a/lib/pure/httpclient.nim +++ b/lib/pure/httpclient.nim @@ -84,6 +84,9 @@ ## .. code-block:: Nim ## client.onProgressChanged = nil ## +## **Warning:** The ``total`` reported by httpclient may be 0 in some cases. +## +## ## SSL/TLS support ## =============== ## This requires the OpenSSL library, fortunately it's widely used and installed @@ -117,20 +120,28 @@ ## only basic authentication is supported at the moment. import net, strutils, uri, parseutils, strtabs, base64, os, mimetypes, - math, random, httpcore, times, tables -import asyncnet, asyncdispatch + math, random, httpcore, times, tables, streams +import asyncnet, asyncdispatch, asyncfile import nativesockets export httpcore except parseHeader # TODO: The ``except`` doesn't work type - Response* = object + Response* = ref object + version*: string + status*: string + headers*: HttpHeaders + body: string + bodyStream*: Stream + + AsyncResponse* = ref object version*: string status*: string headers*: HttpHeaders - body*: string + body: string + bodyStream*: FutureStream[string] -proc code*(response: Response): HttpCode +proc code*(response: Response | AsyncResponse): HttpCode {.raises: [ValueError, OverflowError].} = ## Retrieves the specified response's ``HttpCode``. ## @@ -138,6 +149,27 @@ proc code*(response: Response): HttpCode ## corresponding ``HttpCode``. return response.status[0 .. 2].parseInt.HttpCode +proc body*(response: Response): string = + ## Retrieves the specified response's body. + ## + ## The response's body stream is read synchronously. + if response.body.isNil(): + response.body = response.bodyStream.readAll() + return response.body + +proc `body=`*(response: Response, value: string) {.deprecated.} = + ## Setter for backward compatibility. + ## + ## **This is deprecated and should not be used**. + response.body = value + +proc body*(response: AsyncResponse): Future[string] {.async.} = + ## Reads the response's body and caches it. The read is performed only + ## once. + if response.body.isNil: + response.body = await readAll(response.bodyStream) + return response.body + type Proxy* = ref object url*: Uri @@ -249,6 +281,7 @@ proc parseBody(s: Socket, headers: HttpHeaders, httpVersion: string, timeout: in result.add(buf) proc parseResponse(s: Socket, getBody: bool, timeout: int): Response = + new result var parsedStatus = false var linei = 0 var fullyRead = false @@ -604,7 +637,7 @@ proc post*(url: string, extraHeaders = "", body = "", ## **Deprecated since version 0.15.0**: use ``HttpClient.post`` instead. let (mpHeaders, mpBody) = format(multipart) - template withNewLine(x): expr = + template withNewLine(x): untyped = if x.len > 0 and not x.endsWith("\c\L"): x & "\c\L" else: @@ -653,10 +686,13 @@ proc postContent*(url: string, extraHeaders = "", body = "", proc downloadFile*(url: string, outputFilename: string, sslContext: SSLContext = defaultSSLContext, timeout = -1, userAgent = defUserAgent, - proxy: Proxy = nil) = + proxy: Proxy = nil) {.deprecated.} = ## | Downloads ``url`` and saves it to ``outputFilename`` ## | An optional timeout can be specified in milliseconds, if reading from the ## server takes longer than specified an ETimeout exception will be raised. + ## + ## **Deprecated since version 0.16.2**: use ``HttpClient.downloadFile`` + ## instead. var f: File if open(f, outputFilename, fmWrite): f.write(getContent(url, sslContext = sslContext, timeout = timeout, @@ -735,6 +771,11 @@ type contentProgress: BiggestInt oneSecondProgress: BiggestInt lastProgressReport: float + when SocketType is AsyncSocket: + bodyStream: FutureStream[string] + else: + bodyStream: Stream + getBody: bool ## When `false`, the body is never read in requestAux. type HttpClient* = HttpClientBase[Socket] @@ -764,6 +805,8 @@ proc newHttpClient*(userAgent = defUserAgent, result.proxy = proxy result.timeout = timeout result.onProgressChanged = nil + result.bodyStream = newStringStream() + result.getBody = true when defined(ssl): result.sslContext = sslContext @@ -794,6 +837,8 @@ proc newAsyncHttpClient*(userAgent = defUserAgent, result.proxy = proxy result.timeout = -1 # TODO result.onProgressChanged = nil + result.bodyStream = newFutureStream[string]("newAsyncHttpClient") + result.getBody = true when defined(ssl): result.sslContext = sslContext @@ -815,14 +860,14 @@ proc reportProgress(client: HttpClient | AsyncHttpClient, client.oneSecondProgress = 0 client.lastProgressReport = epochTime() -proc recvFull(client: HttpClient | AsyncHttpClient, - size: int, timeout: int): Future[string] {.multisync.} = +proc recvFull(client: HttpClient | AsyncHttpClient, size: int, timeout: int, + keep: bool): Future[int] {.multisync.} = ## Ensures that all the data requested is read and returned. - result = "" + var readLen = 0 while true: - if size == result.len: break + if size == readLen: break - let remainingSize = size - result.len + let remainingSize = size - readLen let sizeToRecv = min(remainingSize, net.BufferSize) when client.socket is Socket: @@ -830,13 +875,17 @@ proc recvFull(client: HttpClient | AsyncHttpClient, else: let data = await client.socket.recv(sizeToRecv) if data == "": break # We've been disconnected. - result.add data + + readLen.inc(data.len) + if keep: + await client.bodyStream.write(data) await reportProgress(client, data.len) -proc parseChunks(client: HttpClient | AsyncHttpClient): Future[string] + return readLen + +proc parseChunks(client: HttpClient | AsyncHttpClient): Future[void] {.multisync.} = - result = "" while true: var chunkSize = 0 var chunkSizeStr = await client.socket.recvLine() @@ -861,25 +910,27 @@ proc parseChunks(client: HttpClient | AsyncHttpClient): Future[string] httpError("Invalid chunk size: " & chunkSizeStr) inc(i) if chunkSize <= 0: - discard await recvFull(client, 2, client.timeout) # Skip \c\L + discard await recvFull(client, 2, client.timeout, false) # Skip \c\L break - result.add await recvFull(client, chunkSize, client.timeout) - discard await recvFull(client, 2, client.timeout) # Skip \c\L + discard await recvFull(client, chunkSize, client.timeout, true) + discard await recvFull(client, 2, client.timeout, false) # Skip \c\L # Trailer headers will only be sent if the request specifies that we want # them: http://tools.ietf.org/html/rfc2616#section-3.6.1 proc parseBody(client: HttpClient | AsyncHttpClient, headers: HttpHeaders, - httpVersion: string): Future[string] {.multisync.} = - result = "" + httpVersion: string): Future[void] {.multisync.} = # Reset progress from previous requests. client.contentTotal = 0 client.contentProgress = 0 client.oneSecondProgress = 0 client.lastProgressReport = 0 + when client is AsyncHttpClient: + assert(not client.bodyStream.finished) + if headers.getOrDefault"Transfer-Encoding" == "chunked": - result = await parseChunks(client) + await parseChunks(client) else: # -REGION- Content-Length # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.3 @@ -888,26 +939,31 @@ proc parseBody(client: HttpClient | AsyncHttpClient, var length = contentLengthHeader.parseint() client.contentTotal = length if length > 0: - result = await client.recvFull(length, client.timeout) - if result == "": + let recvLen = await client.recvFull(length, client.timeout, true) + if recvLen == 0: httpError("Got disconnected while trying to read body.") - if result.len != length: + if recvLen != length: httpError("Received length doesn't match expected length. Wanted " & - $length & " got " & $result.len) + $length & " got " & $recvLen) else: # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.4 TODO # -REGION- Connection: Close # (http://tools.ietf.org/html/rfc2616#section-4.4) NR.5 if headers.getOrDefault"Connection" == "close" or httpVersion == "1.0": - var buf = "" while true: - buf = await client.recvFull(4000, client.timeout) - if buf == "": break - result.add(buf) + let recvLen = await client.recvFull(4000, client.timeout, true) + if recvLen == 0: break + + when client is AsyncHttpClient: + client.bodyStream.complete() + else: + client.bodyStream.setPosition(0) proc parseResponse(client: HttpClient | AsyncHttpClient, - getBody: bool): Future[Response] {.multisync.} = + getBody: bool): Future[Response | AsyncResponse] + {.multisync.} = + new result var parsedStatus = false var linei = 0 var fullyRead = false @@ -955,10 +1011,14 @@ proc parseResponse(client: HttpClient | AsyncHttpClient, if not fullyRead: httpError("Connection was closed before full request has been made") + if getBody: - result.body = await parseBody(client, result.headers, result.version) - else: - result.body = "" + when client is HttpClient: + client.bodyStream = newStringStream() + else: + client.bodyStream = newFutureStream[string]("parseResponse") + await parseBody(client, result.headers, result.version) + result.bodyStream = client.bodyStream proc newConnection(client: HttpClient | AsyncHttpClient, url: Uri) {.multisync.} = @@ -1006,8 +1066,9 @@ proc override(fallback, override: HttpHeaders): HttpHeaders = result[k] = vs proc requestAux(client: HttpClient | AsyncHttpClient, url: string, - httpMethod: string, body = "", - headers: HttpHeaders = nil): Future[Response] {.multisync.} = + httpMethod: string, body = "", + headers: HttpHeaders = nil): Future[Response | AsyncResponse] + {.multisync.} = # Helper that actually makes the request. Does not handle redirects. let connectionUrl = if client.proxy.isNil: parseUri(url) else: client.proxy.url @@ -1047,16 +1108,17 @@ proc requestAux(client: HttpClient | AsyncHttpClient, url: string, if body != "": await client.socket.send(body) - result = await parseResponse(client, - httpMethod.toLower() notin ["head", "connect"]) + let getBody = httpMethod.toLowerAscii() notin ["head", "connect"] and + client.getBody + result = await parseResponse(client, getBody) # Restore the clients proxy in case it was overwritten. client.proxy = savedProxy - proc request*(client: HttpClient | AsyncHttpClient, url: string, httpMethod: string, body = "", - headers: HttpHeaders = nil): Future[Response] {.multisync.} = + headers: HttpHeaders = nil): Future[Response | AsyncResponse] + {.multisync.} = ## Connects to the hostname specified by the URL and performs a request ## using the custom method string specified by ``httpMethod``. ## @@ -1078,7 +1140,8 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string, proc request*(client: HttpClient | AsyncHttpClient, url: string, httpMethod = HttpGET, body = "", - headers: HttpHeaders = nil): Future[Response] {.multisync.} = + headers: HttpHeaders = nil): Future[Response | AsyncResponse] + {.multisync.} = ## Connects to the hostname specified by the URL and performs a request ## using the method specified. ## @@ -1088,11 +1151,10 @@ proc request*(client: HttpClient | AsyncHttpClient, url: string, ## ## When a request is made to a different hostname, the current connection will ## be closed. - result = await request(client, url, $httpMethod, body, - headers = headers) + result = await request(client, url, $httpMethod, body, headers) proc get*(client: HttpClient | AsyncHttpClient, - url: string): Future[Response] {.multisync.} = + url: string): Future[Response | AsyncResponse] {.multisync.} = ## Connects to the hostname specified by the URL and performs a GET request. ## ## This procedure will follow redirects up to a maximum number of redirects @@ -1112,17 +1174,18 @@ proc getContent*(client: HttpClient | AsyncHttpClient, if resp.code.is4xx or resp.code.is5xx: raise newException(HttpRequestError, resp.status) else: - return resp.body + return await resp.bodyStream.readAll() proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "", - multipart: MultipartData = nil): Future[Response] {.multisync.} = + multipart: MultipartData = nil): Future[Response | AsyncResponse] + {.multisync.} = ## Connects to the hostname specified by the URL and performs a POST request. ## ## This procedure will follow redirects up to a maximum number of redirects ## specified in ``client.maxRedirects``. let (mpHeader, mpBody) = format(multipart) - - template withNewLine(x): expr = + # TODO: Support FutureStream for `body` parameter. + template withNewLine(x): untyped = if x.len > 0 and not x.endsWith("\c\L"): x & "\c\L" else: @@ -1134,16 +1197,14 @@ proc post*(client: HttpClient | AsyncHttpClient, url: string, body = "", headers["Content-Type"] = mpHeader.split(": ")[1] headers["Content-Length"] = $len(xb) - result = await client.requestAux(url, $HttpPOST, xb, - headers = headers) + result = await client.requestAux(url, $HttpPOST, xb, headers) # Handle redirects. var lastURL = url for i in 1..client.maxRedirects: if result.status.redirection(): let redirectTo = getNewLocation(lastURL, result.headers) var meth = if result.status != "307": HttpGet else: HttpPost - result = await client.requestAux(redirectTo, $meth, xb, - headers = headers) + result = await client.requestAux(redirectTo, $meth, xb, headers) lastURL = redirectTo proc postContent*(client: HttpClient | AsyncHttpClient, url: string, @@ -1161,4 +1222,30 @@ proc postContent*(client: HttpClient | AsyncHttpClient, url: string, if resp.code.is4xx or resp.code.is5xx: raise newException(HttpRequestError, resp.status) else: - return resp.body + return await resp.bodyStream.readAll() + +proc downloadFile*(client: HttpClient | AsyncHttpClient, + url: string, filename: string): Future[void] {.multisync.} = + ## Downloads ``url`` and saves it to ``filename``. + client.getBody = false + let resp = await client.get(url) + + when client is HttpClient: + client.bodyStream = newFileStream(filename, fmWrite) + if client.bodyStream.isNil: + fileError("Unable to open file") + parseBody(client, resp.headers, resp.version) + client.bodyStream.close() + else: + client.bodyStream = newFutureStream[string]("downloadFile") + var file = openAsync(filename, fmWrite) + # Let `parseBody` write response data into client.bodyStream in the + # background. + asyncCheck parseBody(client, resp.headers, resp.version) + # The `writeFromStream` proc will complete once all the data in the + # `bodyStream` has been written to the file. + await file.writeFromStream(client.bodyStream) + file.close() + + if resp.code.is4xx or resp.code.is5xx: + raise newException(HttpRequestError, resp.status) diff --git a/lib/pure/includes/asyncfutures.nim b/lib/pure/includes/asyncfutures.nim index c83228014..6af5bf3cf 100644 --- a/lib/pure/includes/asyncfutures.nim +++ b/lib/pure/includes/asyncfutures.nim @@ -16,6 +16,12 @@ type FutureVar*[T] = distinct Future[T] + FutureStream*[T] = ref object of FutureBase ## Special future that acts as + ## a queue. Its API is still + ## experimental and so is + ## subject to change. + queue: Deque[T] + FutureError* = object of Exception cause*: FutureBase @@ -26,11 +32,7 @@ when not defined(release): proc callSoon*(cbproc: proc ()) {.gcsafe.} -proc newFuture*[T](fromProc: string = "unspecified"): Future[T] = - ## Creates a new future. - ## - ## Specifying ``fromProc``, which is a string specifying the name of the proc - ## that this future belongs to, is a good habit as it helps with debugging. +template setupFutureBase(fromProc: string) = new(result) result.finished = false when not defined(release): @@ -39,6 +41,13 @@ proc newFuture*[T](fromProc: string = "unspecified"): Future[T] = result.fromProc = fromProc currentID.inc() +proc newFuture*[T](fromProc: string = "unspecified"): Future[T] = + ## Creates a new future. + ## + ## Specifying ``fromProc``, which is a string specifying the name of the proc + ## that this future belongs to, is a good habit as it helps with debugging. + setupFutureBase(fromProc) + proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] = ## Create a new ``FutureVar``. This Future type is ideally suited for ## situations where you want to avoid unnecessary allocations of Futures. @@ -47,6 +56,22 @@ proc newFutureVar*[T](fromProc = "unspecified"): FutureVar[T] = ## that this future belongs to, is a good habit as it helps with debugging. result = FutureVar[T](newFuture[T](fromProc)) +proc newFutureStream*[T](fromProc = "unspecified"): FutureStream[T] = + ## Create a new ``FutureStream``. This future's callback is activated when + ## two events occur: + ## + ## * New data is written into the future stream. + ## * The future stream is completed (this means that no more data will be + ## written). + ## + ## Specifying ``fromProc``, which is a string specifying the name of the proc + ## that this future belongs to, is a good habit as it helps with debugging. + ## + ## **Note:** The API of FutureStream is still new and so has a higher + ## likelihood of changing in the future. + setupFutureBase(fromProc) + result.queue = initDeque[T]() + proc clean*[T](future: FutureVar[T]) = ## Resets the ``finished`` status of ``future``. Future[T](future).finished = false @@ -107,12 +132,18 @@ proc complete*[T](future: FutureVar[T], val: T) = ## Any previously stored value will be overwritten. template fut: untyped = Future[T](future) checkFinished(fut) - assert(fut.error == nil) + assert(fut.error.isNil()) fut.finished = true fut.value = val - if fut.cb != nil: + if not fut.cb.isNil(): fut.cb() +proc complete*[T](future: FutureStream[T]) = + ## Completes a ``FutureStream`` signalling the end of data. + future.finished = true + if not future.cb.isNil(): + future.cb() + proc fail*[T](future: Future[T], error: ref Exception) = ## Completes ``future`` with ``error``. #assert(not future.finished, "Future already finished, cannot finish twice.") @@ -149,6 +180,20 @@ proc `callback=`*[T](future: Future[T], ## If future has already completed then ``cb`` will be called immediately. future.callback = proc () = cb(future) +proc `callback=`*[T](future: FutureStream[T], + cb: proc (future: FutureStream[T]) {.closure,gcsafe.}) = + ## Sets the callback proc to be called when data was placed inside the + ## future stream. + ## + ## The callback is also called when the future is completed. So you should + ## use ``finished`` to check whether data is available. + ## + ## If the future stream already has data or is finished then ``cb`` will be + ## called immediately. + future.cb = proc () = cb(future) + if future.queue.len > 0 or future.finished: + callSoon(future.cb) + proc injectStacktrace[T](future: Future[T]) = # TODO: Come up with something better. when not defined(release): @@ -195,12 +240,18 @@ proc mget*[T](future: FutureVar[T]): var T = ## Future has not been finished. result = Future[T](future).value -proc finished*[T](future: Future[T] | FutureVar[T]): bool = +proc finished*[T](future: Future[T] | FutureVar[T] | FutureStream[T]): bool = ## Determines whether ``future`` has completed. ## ## ``True`` may indicate an error or a value. Use ``failed`` to distinguish. + ## + ## For a ``FutureStream`` a ``true`` value means that no more data will be + ## placed inside the stream _and_ that there is no data waiting to be + ## retrieved. when future is FutureVar[T]: result = (Future[T](future)).finished + elif future is FutureStream[T]: + result = future.finished and future.queue.len == 0 else: result = future.finished @@ -208,6 +259,57 @@ proc failed*(future: FutureBase): bool = ## Determines whether ``future`` completed with an error. return future.error != nil +proc write*[T](future: FutureStream[T], value: T): Future[void] = + ## Writes the specified value inside the specified future stream. + ## + ## This will raise ``ValueError`` if ``future`` is finished. + result = newFuture[void]("FutureStream.put") + if future.finished: + let msg = "FutureStream is finished and so no longer accepts new data." + result.fail(newException(ValueError, msg)) + return + # TODO: Implement limiting of the streams storage to prevent it growing + # infinitely when no reads are occuring. + future.queue.addLast(value) + if not future.cb.isNil: future.cb() + result.complete() + +proc read*[T](future: FutureStream[T]): Future[(bool, T)] = + ## Returns a future that will complete when the ``FutureStream`` has data + ## placed into it. The future will be completed with the oldest + ## value stored inside the stream. The return value will also determine + ## whether data was retrieved, ``false`` means that the future stream was + ## completed and no data was retrieved. + ## + ## This function will remove the data that was returned from the underlying + ## ``FutureStream``. + var resFut = newFuture[(bool, T)]("FutureStream.take") + let savedCb = future.cb + future.callback = + proc (fs: FutureStream[T]) = + # We don't want this callback called again. + future.cb = nil + + # The return value depends on whether the FutureStream has finished. + var res: (bool, T) + if finished(fs): + # Remember, this callback is called when the FutureStream is completed. + res[0] = false + else: + res[0] = true + res[1] = fs.queue.popFirst() + + if not resFut.finished: + resFut.complete(res) + + # If the saved callback isn't nil then let's call it. + if not savedCb.isNil: savedCb() + return resFut + +proc len*[T](future: FutureStream[T]): int = + ## Returns the amount of data pieces inside the stream. + future.queue.len + proc asyncCheck*[T](future: Future[T]) = ## Sets a callback on ``future`` which raises an exception if the future ## finished with an error. diff --git a/lib/pure/json.nim b/lib/pure/json.nim index 5e36a2aa1..c7b581a85 100644 --- a/lib/pure/json.nim +++ b/lib/pure/json.nim @@ -952,7 +952,7 @@ proc newIndent(curr, indent: int, ml: bool): int = else: return indent proc nl(s: var string, ml: bool) = - if ml: s.add("\n") + s.add(if ml: "\n" else: " ") proc escapeJson*(s: string; result: var string) = ## Converts a string `s` to its JSON representation. @@ -986,15 +986,14 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true, lstArr = false, currIndent = 0) = case node.kind of JObject: - if currIndent != 0 and not lstArr: result.nl(ml) - result.indent(currIndent) # Indentation + if lstArr: result.indent(currIndent) # Indentation if node.fields.len > 0: result.add("{") result.nl(ml) # New line var i = 0 for key, val in pairs(node.fields): if i > 0: - result.add(", ") + result.add(",") result.nl(ml) # New Line inc i # Need to indent more than { @@ -1030,7 +1029,7 @@ proc toPretty(result: var string, node: JsonNode, indent = 2, ml = true, result.nl(ml) for i in 0..len(node.elems)-1: if i > 0: - result.add(", ") + result.add(",") result.nl(ml) # New Line toPretty(result, node.elems[i], indent, ml, true, newIndent(currIndent, indent, ml)) diff --git a/lib/pure/logging.nim b/lib/pure/logging.nim index 5544a4b3f..65724f75a 100644 --- a/lib/pure/logging.nim +++ b/lib/pure/logging.nim @@ -46,6 +46,8 @@ ## ## **Warning:** The global list of handlers is a thread var, this means that ## the handlers must be re-added in each thread. +## **Warning:** When logging on disk or console, only error and fatal messages +## are flushed out immediately. Use flushFile() where needed. import strutils, times when not defined(js): diff --git a/lib/pure/random.nim b/lib/pure/random.nim index 8d463576a..1f750edcd 100644 --- a/lib/pure/random.nim +++ b/lib/pure/random.nim @@ -95,7 +95,7 @@ proc random*(max: float): float {.benign.} = proc random*[T](x: Slice[T]): T = ## For a slice `a .. b` returns a value in the range `a .. b-1`. - result = random(x.b - x.a) + x.a + result = T(random(x.b - x.a)) + x.a proc random*[T](a: openArray[T]): T = ## returns a random element from the openarray `a`. diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 9c205a54f..9b2526337 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -898,7 +898,7 @@ proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, proc toHex*[T](x: T): string = ## Shortcut for ``toHex(x, T.sizeOf * 2)`` - toHex(x, T.sizeOf * 2) + toHex(BiggestInt(x), T.sizeOf * 2) proc intToStr*(x: int, minchars: Positive = 1): string {.noSideEffect, rtl, extern: "nsuIntToStr".} = @@ -1652,6 +1652,7 @@ proc escape*(s: string, prefix = "\"", suffix = "\""): string {.noSideEffect, ## The procedure has been designed so that its output is usable for many ## different common syntaxes. The resulting string is prefixed with ## `prefix` and suffixed with `suffix`. Both may be empty strings. + ## **Note**: This is not correct for producing Ansi C code! result = newStringOfCap(s.len + s.len shr 2) result.add(prefix) for c in items(s): diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index cdca02ed7..49e24037a 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -24,7 +24,7 @@ ## echo "run before each test" ## ## teardown: -## echo "run after each test": +## echo "run after each test" ## ## test "essential truths": ## # give up and stop if this fails @@ -95,7 +95,7 @@ proc shouldRun(testName: string): bool = result = true proc startSuite(name: string) = - template rawPrint() = echo("\n[Suite] ", name) + template rawPrint() = echo("\n[Suite] ", name) when not defined(ECMAScript): if colorOutput: styledEcho styleBright, fgBlue, "\n[Suite] ", resetStyle, name @@ -134,15 +134,15 @@ template suite*(name, body) {.dirty.} = ## [OK] (2 + -2) != 4 block: bind startSuite - template setup(setupBody: untyped) {.dirty.} = - var testSetupIMPLFlag = true + template setup(setupBody: untyped) {.dirty, used.} = + var testSetupIMPLFlag {.used.} = true template testSetupIMPL: untyped {.dirty.} = setupBody - template teardown(teardownBody: untyped) {.dirty.} = - var testTeardownIMPLFlag = true + template teardown(teardownBody: untyped) {.dirty, used.} = + var testTeardownIMPLFlag {.used.} = true template testTeardownIMPL: untyped {.dirty.} = teardownBody - let testInSuiteImplFlag = true + let testInSuiteImplFlag {.used.} = true startSuite name body diff --git a/lib/system.nim b/lib/system.nim index b37d74357..74dca461a 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2763,6 +2763,7 @@ when not defined(JS): #and not defined(nimscript): # we use binary mode on Windows: c_setmode(c_fileno(stdin), O_BINARY) c_setmode(c_fileno(stdout), O_BINARY) + c_setmode(c_fileno(stderr), O_BINARY) when defined(endb): proc endbStep() @@ -3168,20 +3169,20 @@ when not defined(JS): #and not defined(nimscript): ## retrieves the raw proc pointer of the closure `x`. This is ## useful for interfacing closures with C. {.emit: """ - `result` = `x`.ClPrc; + `result` = `x`.ClP_0; """.} proc rawEnv*[T: proc](x: T): pointer {.noSideEffect, inline.} = ## retrieves the raw environment pointer of the closure `x`. This is ## useful for interfacing closures with C. {.emit: """ - `result` = `x`.ClEnv; + `result` = `x`.ClE_0; """.} proc finished*[T: proc](x: T): bool {.noSideEffect, inline.} = ## can be used to determine if a first class iterator has finished. {.emit: """ - `result` = *((NI*) `x`.ClEnv) < 0; + `result` = *((NI*) `x`.ClE_0) < 0; """.} elif defined(JS): diff --git a/lib/system/alloc.nim b/lib/system/alloc.nim index a124d7537..f28a124d2 100644 --- a/lib/system/alloc.nim +++ b/lib/system/alloc.nim @@ -292,10 +292,15 @@ proc writeFreeList(a: MemRegion) = it, it.next, it.prev, it.size) it = it.next +const nimMaxHeap {.intdefine.} = 0 + proc requestOsChunks(a: var MemRegion, size: int): PBigChunk = when not defined(emscripten): if not a.blockChunkSizeIncrease: let usedMem = a.currMem # - a.freeMem + when nimMaxHeap != 0: + if usedMem > nimMaxHeap * 1024 * 1024: + raiseOutOfMem() if usedMem < 64 * 1024: a.nextChunkSize = PageSize*4 else: diff --git a/lib/system/threads.nim b/lib/system/threads.nim index 6e58638e9..003969a79 100644 --- a/lib/system/threads.nim +++ b/lib/system/threads.nim @@ -355,58 +355,22 @@ type {.deprecated: [TThread: Thread, TThreadId: ThreadId].} var - threadCreationHandlers: array[60, proc () {.nimcall, gcsafe.}] - countThreadCreationHandlers: int - - threadDestructionHandlers: array[60, proc () {.nimcall, gcsafe.}] - countThreadDestructionHandlers: int - -proc onThreadCreation*(handler: proc () {.nimcall, gcsafe.}) = - ## Registers a global handler that is called at thread creation. - ## This can be used to initialize thread local variables properly. - ## Note that the handler has to be .gcafe and so the typical usage - ## looks like: - ## - ## .. code-block:: nim - ## - ## var - ## someGlobal: string = "some string here" - ## perThread {.threadvar.}: string - ## - ## proc setPerThread() = - ## {.gcsafe.}: - ## deepCopy(perThread, someGlobal) - ## - ## onThreadCreation(setPerThread) - ## - ## **Note**: The registration is currently not threadsafe! Better - ## call ``onThreadCreation`` before any thread started its work! - threadCreationHandlers[countThreadCreationHandlers] = handler - inc countThreadCreationHandlers - -proc onThreadDestruction*(handler: proc () {.nimcall, gcsafe.}) = - ## Registers a global handler that is called at thread destruction. - ## Threads are destructed when the ``.thread`` proc returns - ## normally or raises an exception. Note that unhandled exceptions - ## in a thread nevertheless cause the whole process to die. - threadDestructionHandlers[countThreadDestructionHandlers] = handler - inc countThreadDestructionHandlers + threadDestructionHandlers {.rtlThreadVar.}: seq[proc () {.closure, gcsafe.}] -template beforeThreadRuns() = - for i in 0..countThreadCreationHandlers-1: - threadCreationHandlers[i]() +proc onThreadDestruction*(handler: proc () {.closure, gcsafe.}) = + ## Registers a *thread local* handler that is called at the thread's + ## destruction. + ## A thread is destructed when the ``.thread`` proc returns + ## normally or when it raises an exception. Note that unhandled exceptions + ## in a thread nevertheless cause the whole process to die. + if threadDestructionHandlers.isNil: + threadDestructionHandlers = @[] + threadDestructionHandlers.add handler template afterThreadRuns() = - for i in countdown(countThreadDestructionHandlers-1, 0): + for i in countdown(threadDestructionHandlers.len-1, 0): threadDestructionHandlers[i]() -proc runOnThreadCreationHandlers*() = - ## This runs every registered ``onThreadCreation`` handler and is usually - ## used to initialize thread local storage for the main thread. Since the - ## main thread is **not** created via ``createThread`` it doesn't run the - ## handlers automatically. - beforeThreadRuns() - when not defined(boehmgc) and not hasSharedHeap and not defined(gogc) and not defined(gcstack): proc deallocOsPages() @@ -421,7 +385,6 @@ when defined(boehmgc): proc threadProcWrapDispatch[TArg](sb: pointer, thrd: pointer) {.noconv.} = boehmGC_register_my_thread(sb) - beforeThreadRuns() try: let thrd = cast[ptr Thread[TArg]](thrd) when TArg is void: @@ -433,7 +396,6 @@ when defined(boehmgc): boehmGC_unregister_my_thread() else: proc threadProcWrapDispatch[TArg](thrd: ptr Thread[TArg]) = - beforeThreadRuns() try: when TArg is void: thrd.dataFn() diff --git a/lib/upcoming/asyncdispatch.nim b/lib/upcoming/asyncdispatch.nim index d384cd05e..d2de4a465 100644 --- a/lib/upcoming/asyncdispatch.nim +++ b/lib/upcoming/asyncdispatch.nim @@ -9,7 +9,7 @@ include "system/inclrtl" -import os, oids, tables, strutils, times, heapqueue, lists +import os, oids, tables, strutils, times, heapqueue, lists, queues import nativesockets, net, deques @@ -1664,6 +1664,17 @@ proc accept*(socket: AsyncFD, # -- Await Macro include asyncmacro +proc readAll*(future: FutureStream[string]): Future[string] {.async.} = + ## Returns a future that will complete when all the string data from the + ## specified future stream is retrieved. + result = "" + while true: + let (hasValue, value) = await future.read() + if hasValue: + result.add(value) + else: + break + proc recvLine*(socket: AsyncFD): Future[string] {.async.} = ## Reads a line of data from ``socket``. Returned future will complete once ## a full line is read or an error occurs. diff --git a/tests/async/tfuturestream.nim b/tests/async/tfuturestream.nim new file mode 100644 index 000000000..9a8e986a0 --- /dev/null +++ b/tests/async/tfuturestream.nim @@ -0,0 +1,53 @@ +discard """ + file: "tfuturestream.nim" + exitcode: 0 + output: ''' +0 +1 +2 +3 +4 +5 +Done +Finished +''' +""" +import asyncdispatch + +var fs = newFutureStream[int]() + +proc alpha() {.async.} = + for i in 0 .. 5: + await sleepAsync(1000) + await fs.write(i) + + echo("Done") + fs.complete() + +proc beta() {.async.} = + while not fs.finished: + let (hasValue, value) = await fs.read() + if hasValue: + echo(value) + + echo("Finished") + +asyncCheck alpha() +waitFor beta() + +# TODO: Something like this should work eventually. +# proc delta(): FutureStream[string] {.async.} = +# for i in 0 .. 5: +# await sleepAsync(1000) +# result.put($i) + +# return "" + +# proc omega() {.async.} = +# let fut = delta() +# while not fut.finished(): +# echo(await fs.takeAsync()) + +# echo("Finished") + +# waitFor omega() \ No newline at end of file diff --git a/tests/ccgbugs/twrong_method.nim b/tests/ccgbugs/twrong_method.nim new file mode 100644 index 000000000..9879c6114 --- /dev/null +++ b/tests/ccgbugs/twrong_method.nim @@ -0,0 +1,27 @@ +discard """ + cmd: "nim c -d:release $file" + output: '''correct method''' +""" +# bug #5439 +type + Control* = ref object of RootObj + + ControlImpl* = ref object of Control + + Container* = ref object of ControlImpl + + ContainerImpl* = ref object of Container + +method testProc*(control: Control) {.base.} = echo "wrong method" + +method testProc*(container: Container) = echo "correct method" + +proc main() + +main() # wrong method called + +proc main() = + var container = new ContainerImpl + container.testProc() + +# main() # correct method called diff --git a/tests/collections/thashes.nim b/tests/collections/thashes.nim index b9c639414..76b99313c 100644 --- a/tests/collections/thashes.nim +++ b/tests/collections/thashes.nim @@ -72,4 +72,19 @@ block: var t = initTable[int, int]() t[0] = 0 +# Check hashability of all integer types (issue #5429) +block: + let intTables = ( + newTable[int, string](), + newTable[int8, string](), + newTable[int16, string](), + newTable[int32, string](), + newTable[int64, string](), + newTable[uint, string](), + newTable[uint8, string](), + newTable[uint16, string](), + newTable[uint32, string](), + newTable[uint64, string](), + ) + echo "true" diff --git a/tests/errmsgs/tshow_asgn.nim b/tests/errmsgs/tshow_asgn.nim new file mode 100644 index 000000000..250f786e2 --- /dev/null +++ b/tests/errmsgs/tshow_asgn.nim @@ -0,0 +1,15 @@ +discard """ + errormsg: "type mismatch: got (int) but expected 'cshort = int16'" + line: 12 + column: 10 + file: "tshow_asgn.nim" +""" + +# bug #5430 + +proc random*[T](x: Slice[T]): T = + ## For a slice `a .. b` returns a value in the range `a .. b-1`. + result = int(x.b - x.a) + x.a + +let slice = 10.cshort..15.cshort +discard slice.random diff --git a/tests/metatype/tautonotgeneric.nim b/tests/method/tautonotgeneric.nim index a55ae488e..f0d6932f9 100644 --- a/tests/metatype/tautonotgeneric.nim +++ b/tests/method/tautonotgeneric.nim @@ -1,15 +1,24 @@ discard """ - output: "wof!" + output: '''wof! +wof!''' """ # bug #1659 type Animal = ref object {.inheritable.} type Dog = ref object of Animal -method say(a: Animal): auto = "wat!" +method say(a: Animal): auto {.base.} = "wat!" method say(a: Dog): auto = "wof!" proc saySomething(a: Animal): auto = a.say() + +method ec(a: Animal): auto {.base.} = echo "wat!" +method ec(a: Dog): auto = echo "wof!" + +proc ech(a: Animal): auto = a.ec() + + var a = Dog() echo saySomething(a) +ech a diff --git a/tests/method/tgeneric_methods2.nim b/tests/method/tgeneric_methods2.nim new file mode 100644 index 000000000..6e761dc48 --- /dev/null +++ b/tests/method/tgeneric_methods2.nim @@ -0,0 +1,15 @@ +#5432 +type + Iterator[T] = ref object of RootObj + +# base methods with `T` in the return type are okay +method methodThatWorks*[T](i: Iterator[T]): T {.base.} = + discard + +# base methods without `T` (void or basic types) fail +method methodThatFails*[T](i: Iterator[T]) {.base.} = + discard + +type + SpecificIterator1 = ref object of Iterator[string] + SpecificIterator2 = ref object of Iterator[int] diff --git a/tests/misc/parsecomb.nim b/tests/misc/parsecomb.nim index 05fe97ad1..4ff2f65d2 100644 --- a/tests/misc/parsecomb.nim +++ b/tests/misc/parsecomb.nim @@ -29,10 +29,10 @@ method runInput[T, O](self: Parser[T, O], inp: Input[T]): Result[T, O] = # XXX: above needed for now, as without the `tmp` bit below, it compiles to invalid C. tmp(self)(inp) -method run*[T, O](self: Parser[T, O], toks: seq[T]): Result[T, O] = +proc run*[T, O](self: Parser[T, O], toks: seq[T]): Result[T, O] = self.runInput(Input[T](toks: toks, index: 0)) -method chain*[T, O1, O2](self: Parser[T, O1], nextp: proc (v: O1): Parser[T, O2]): Parser[T, O2] = +proc chain*[T, O1, O2](self: Parser[T, O1], nextp: proc (v: O1): Parser[T, O2]): Parser[T, O2] = result = proc (inp: Input[T]): Result[T, O2] = let r = self.runInput(inp) case r.kind: @@ -41,7 +41,7 @@ method chain*[T, O1, O2](self: Parser[T, O1], nextp: proc (v: O1): Parser[T, O2] of rkFailure: Result[T, O2](kind: rkFailure) -method skip[T](self: Input[T], n: int): Input[T] = +method skip[T](self: Input[T], n: int): Input[T] {.base.} = Input[T](toks: self.toks, index: self.index + n) proc pskip*[T](n: int): Parser[T, tuple[]] = @@ -69,11 +69,11 @@ proc `+`*[T, O](first: Parser[T, O], second: Parser[T, O]): Parser[T, O] = # end of primitives (definitions involving Parser(..)) -method map*[T, O1, O2](self: Parser[T, O1], p: proc (v: O1): O2): Parser[T, O2] = +proc map*[T, O1, O2](self: Parser[T, O1], p: proc (v: O1): O2): Parser[T, O2] = self.chain(proc (v: O1): Parser[T, O2] = unit[T, O2](p(v))) -method then*[T, O1, O2](self: Parser[T, O1], next: Parser[T, O2]): Parser[T, O2] = +proc then*[T, O1, O2](self: Parser[T, O1], next: Parser[T, O2]): Parser[T, O2] = self.chain(proc (v: O1): Parser[T, O2] = next) diff --git a/tests/osproc/ta.nim b/tests/osproc/ta.nim deleted file mode 100644 index 5ebcc7f14..000000000 --- a/tests/osproc/ta.nim +++ /dev/null @@ -1,3 +0,0 @@ -import strutils -let x = stdin.readLine() -echo x.parseInt + 5 diff --git a/tests/osproc/ta_in.nim b/tests/osproc/ta_in.nim new file mode 100644 index 000000000..b46890f6e --- /dev/null +++ b/tests/osproc/ta_in.nim @@ -0,0 +1,5 @@ +# This file is prefixed with an "a", because other tests +# depend on it and it must be compiled first. +import strutils +let x = stdin.readLine() +echo x.parseInt + 5 diff --git a/tests/osproc/ta_out.nim b/tests/osproc/ta_out.nim new file mode 100644 index 000000000..f7091a7f6 --- /dev/null +++ b/tests/osproc/ta_out.nim @@ -0,0 +1,16 @@ +# This file is prefixed with an "a", because other tests +# depend on it and it must be compiled first. +stdout.writeLine("to stdout") +stdout.flushFile() +stdout.writeLine("to stdout") +stdout.flushFile() + +stderr.writeLine("to stderr") +stderr.flushFile() +stderr.writeLine("to stderr") +stderr.flushFile() + +stdout.writeLine("to stdout") +stdout.flushFile() +stdout.writeLine("to stdout") +stdout.flushFile() diff --git a/tests/osproc/tstdin.nim b/tests/osproc/tstdin.nim index b491c2500..d94c34192 100644 --- a/tests/osproc/tstdin.nim +++ b/tests/osproc/tstdin.nim @@ -4,7 +4,7 @@ discard """ """ import osproc, os, streams -const filename = when defined(Windows): "ta.exe" else: "ta" +const filename = when defined(Windows): "ta_in.exe" else: "ta_in" doAssert fileExists(getCurrentDir() / "tests" / "osproc" / filename) diff --git a/tests/osproc/tstdout.nim b/tests/osproc/tstdout.nim new file mode 100644 index 000000000..0cb5bd9c0 --- /dev/null +++ b/tests/osproc/tstdout.nim @@ -0,0 +1,29 @@ +discard """ + output: '''-------------------------------------- +to stdout +to stdout +to stderr +to stderr +to stdout +to stdout +-------------------------------------- +''' +""" +import osproc, os, streams + +const filename = when defined(Windows): "ta_out.exe" else: "ta_out" + +doAssert fileExists(getCurrentDir() / "tests" / "osproc" / filename) + +var p = startProcess(filename, getCurrentDir() / "tests" / "osproc", + options={poStdErrToStdOut}) + +let outputStream = p.outputStream +var x = newStringOfCap(120) +var output = "" +while outputStream.readLine(x.TaintedString): + output.add(x & "\n") + +echo "--------------------------------------" +stdout.write output +echo "--------------------------------------" diff --git a/tests/pragmas/tused.nim b/tests/pragmas/tused.nim index f3126bd45..4a317f874 100644 --- a/tests/pragmas/tused.nim +++ b/tests/pragmas/tused.nim @@ -1,13 +1,35 @@ discard """ - output: '''8''' + nimout: ''' +compile start +tused.nim(15, 8) Hint: 'tused.echoSub(a: int, b: int)' is declared but not used [XDeclaredButNotUsed] +compile end''' + output: "8\n8" """ -template implementArithOps(T) = +static: + echo "compile start" + +template implementArithOpsOld(T) = + proc echoAdd(a, b: T) = + echo a + b + proc echoSub(a, b: T) = + echo a - b + +template implementArithOpsNew(T) = proc echoAdd(a, b: T) {.used.} = echo a + b proc echoSub(a, b: T) {.used.} = echo a - b -# no warning produced for the unused 'echoSub' -implementArithOps(int) -echoAdd 3, 5 +block: + # should produce warning for the unused 'echoSub' + implementArithOpsOld(int) + echoAdd 3, 5 + +block: + # no warning produced for the unused 'echoSub' + implementArithOpsNew(int) + echoAdd 3, 5 + +static: + echo "compile end" diff --git a/tests/stdlib/thttpclient.nim b/tests/stdlib/thttpclient.nim index 7b1111f9b..62c1ebee7 100644 --- a/tests/stdlib/thttpclient.nim +++ b/tests/stdlib/thttpclient.nim @@ -13,7 +13,9 @@ proc asyncTest() {.async.} = var client = newAsyncHttpClient() var resp = await client.request("http://example.com/") doAssert(resp.code.is2xx) - doAssert("<title>Example Domain</title>" in resp.body) + var body = await resp.body + body = await resp.body # Test caching + doAssert("<title>Example Domain</title>" in body) resp = await client.request("http://example.com/404") doAssert(resp.code.is4xx) @@ -47,7 +49,8 @@ proc asyncTest() {.async.} = echo("Downloaded ", progress, " of ", total) echo("Current rate: ", speed div 1000, "kb/s") client.onProgressChanged = onProgressChanged - discard await client.getContent("http://speedtest-ams2.digitalocean.com/100mb.test") + await client.downloadFile("http://speedtest-ams2.digitalocean.com/100mb.test", + "100mb.test") client.close() @@ -94,7 +97,8 @@ proc syncTest() = echo("Downloaded ", progress, " of ", total) echo("Current rate: ", speed div 1000, "kb/s") client.onProgressChanged = onProgressChanged - discard client.getContent("http://speedtest-ams2.digitalocean.com/100mb.test") + client.downloadFile("http://speedtest-ams2.digitalocean.com/100mb.test", + "100mb.test") client.close() diff --git a/tests/stdlib/tunittest.nim b/tests/stdlib/tunittest.nim index e87cd3508..3f8601323 100644 --- a/tests/stdlib/tunittest.nim +++ b/tests/stdlib/tunittest.nim @@ -1,5 +1,11 @@ +discard """ + nimout: "compile start\ncompile end" +""" + import unittest, sequtils +static: + echo "compile start" proc doThings(spuds: var int): int = spuds = 24 @@ -10,9 +16,9 @@ test "#964": check spuds == 24 -from strutils import toUpper +from strutils import toUpperAscii test "#1384": - check(@["hello", "world"].map(toUpper) == @["HELLO", "WORLD"]) + check(@["hello", "world"].map(toUpperAscii) == @["HELLO", "WORLD"]) import options @@ -57,7 +63,7 @@ suite "suite with only teardown": suite "suite with only setup": setup: - var testVar = "from setup" + var testVar {.used.} = "from setup" test "unittest with only setup 1": check testVar == "from setup" @@ -89,3 +95,6 @@ suite "bug #4494": var tags = @[1, 2, 3, 4, 5] check: allIt(0..3, tags[it] != tags[it + 1]) + +static: + echo "compile end" diff --git a/tests/template/mgensym_generic_cross_module.nim b/tests/template/mgensym_generic_cross_module.nim new file mode 100644 index 000000000..80b681db4 --- /dev/null +++ b/tests/template/mgensym_generic_cross_module.nim @@ -0,0 +1,14 @@ + +template makeDomElement(x: untyped, name: string = nil) = + const tag {.gensym.} = if name == nil: astToStr(x) else: name + + proc x*(p: int|float) = + echo tag, p + + proc x*(p: string|cstring) = + echo tag, p + +#proc wrappedUp[T](x: T) = +# mixin foo, bar +makeDomElement(foo, "foo") +makeDomElement(bar) diff --git a/tests/template/tgensym_generic_cross_module.nim b/tests/template/tgensym_generic_cross_module.nim new file mode 100644 index 000000000..856ab676d --- /dev/null +++ b/tests/template/tgensym_generic_cross_module.nim @@ -0,0 +1,14 @@ +discard """ + output: '''foo55 +foo8.0 +fooaha +bar7''' +""" +# bug #5419 +import mgensym_generic_cross_module + +foo(55) +foo 8.0 +foo "aha" +bar 7 + diff --git a/tests/template/tgensym_label.nim b/tests/template/tgensym_label.nim new file mode 100644 index 000000000..fd3b0a1ee --- /dev/null +++ b/tests/template/tgensym_label.nim @@ -0,0 +1,18 @@ + +# bug #5417 +import macros + +macro genBody: untyped = + let sbx = genSym(nskLabel, "test") + when true: + result = quote do: + block `sbx`: + break `sbx` + else: + template foo(s1, s2) = + block s1: + break s2 + result = getAst foo(sbx, sbx) + +proc test() = + genBody() diff --git a/tests/threads/tonthreadcreation.nim b/tests/threads/tonthreadcreation.nim index 5d9b777b8..f588a21c9 100644 --- a/tests/threads/tonthreadcreation.nim +++ b/tests/threads/tonthreadcreation.nim @@ -7,19 +7,16 @@ var someGlobal: string = "some string here" perThread {.threadvar.}: string -proc setPerThread() = - {.gcsafe.}: - deepCopy(perThread, someGlobal) - -proc threadDied() {.gcsafe} = +proc threadDied() {.gcsafe.} = echo "dying ", perThread proc foo() {.thread.} = + onThreadDestruction threadDied + {.gcsafe.}: + deepCopy(perThread, someGlobal) echo perThread proc main = - onThreadCreation setPerThread - onThreadDestruction threadDied var t: Thread[void] createThread[void](t, foo) t.joinThread() diff --git a/tools/downloader.nim b/tools/downloader.nim index 48331e97a..3d866f5d7 100644 --- a/tools/downloader.nim +++ b/tools/downloader.nim @@ -36,10 +36,7 @@ proc download(pkg: string; c: Controls) {.async.} = c.bar.value = clamp(int(progress*100 div total), 0, 100) client.onProgressChanged = onProgressChanged - # XXX give a destination filename instead - let contents = await client.getContent("https://nim-lang.org/download/" & pkg & ".7z") - # XXX make this async somehow: - writeFile(z, contents) + await client.downloadFile("https://nim-lang.org/download/" & pkg & ".7z", z) c.bar.value = 100 let p = osproc.startProcess("7zG.exe", getCurrentDir() / r"..\dist", ["x", pkg & ".7z"]) diff --git a/tools/nimsuggest/nimsuggest.nim b/tools/nimsuggest/nimsuggest.nim index e11bb560c..137ac4219 100644 --- a/tools/nimsuggest/nimsuggest.nim +++ b/tools/nimsuggest/nimsuggest.nim @@ -13,11 +13,11 @@ import strutils, os, parseopt, parseutils, sequtils, net, rdstdin, sexp # Do NOT import suggest. It will lead to wierd bugs with # suggestionResultHook, because suggest.nim is included by sigmatch. # So we import that one instead. -import compiler/options, compiler/commands, compiler/modules, compiler/sem, - compiler/passes, compiler/passaux, compiler/msgs, compiler/nimconf, - compiler/extccomp, compiler/condsyms, compiler/lists, - compiler/sigmatch, compiler/ast, compiler/scriptconfig, - compiler/idents, compiler/modulegraphs +import compiler / [options, commands, modules, sem, + passes, passaux, msgs, nimconf, + extccomp, condsyms, + sigmatch, ast, scriptconfig, + idents, modulegraphs, compilerlog, vm] when defined(windows): import winlean @@ -40,6 +40,7 @@ Options: --log enable verbose logging to nimsuggest.log file --v2 use version 2 of the protocol; more features and much faster + --refresh perform automatic refreshes to keep the analysis precise --tester implies --v2 and --stdin and outputs a line '""" & DummyEof & """' for the tester @@ -57,10 +58,11 @@ var gMode: Mode gEmitEof: bool # whether we write '!EOF!' dummy lines gLogging = false + gRefresh: bool const seps = {':', ';', ' ', '\t'} - Help = "usage: sug|con|def|use|dus|chk|mod|highlight|outline file.nim[;dirtyfile.nim]:line:col\n" & + Help = "usage: sug|con|def|use|dus|chk|mod|highlight|outline|known file.nim[;dirtyfile.nim]:line:col\n" & "type 'quit' to quit\n" & "type 'debug' to toggle debug mode on/off\n" & "type 'terse' to toggle terse mode on/off" @@ -68,12 +70,6 @@ const type EUnexpectedCommand = object of Exception -proc logStr(line: string) = - var f: File - if open(f, getHomeDir() / "nimsuggest.log", fmAppend): - f.writeLine(line) - f.close() - proc parseQuoted(cmd: string; outp: var string; start: int): int = var i = start i += skipWhitespace(cmd, i) @@ -150,7 +146,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; gTrackPos = newLineInfo(dirtyIdx, line, col) gErrorCounter = 0 if suggestVersion < 2: - usageSym = nil + graph.usageSym = nil if not isKnownFile: graph.compileProject(cache) if suggestVersion == 2 and gIdeCmd in {ideUse, ideDus} and @@ -163,7 +159,7 @@ proc execute(cmd: IdeCmd, file, dirtyfile: string, line, col: int; if gIdeCmd != ideMod: graph.compileProject(cache, modIdx) if gIdeCmd in {ideUse, ideDus}: - let u = if suggestVersion >= 2: graph.symFromInfo(gTrackPos) else: usageSym + let u = if suggestVersion >= 2: graph.symFromInfo(gTrackPos) else: graph.usageSym if u != nil: listUsages(u) else: @@ -180,7 +176,7 @@ proc executeEpc(cmd: IdeCmd, args: SexpNode; dirtyfile = args[3].getStr(nil) execute(cmd, file, dirtyfile, int(line), int(column), graph, cache) -proc returnEpc(socket: var Socket, uid: BiggestInt, s: SexpNode|string, +proc returnEpc(socket: Socket, uid: BiggestInt, s: SexpNode|string, return_symbol = "return") = let response = $convertSexp([newSSymbol(return_symbol), uid, s]) socket.send(toHex(len(response), 6)) @@ -198,7 +194,7 @@ template sendEpc(results: typed, tdef, hook: untyped) = let res = sexp(results) if gLogging: logStr($res) - returnEPC(client, uid, res) + returnEpc(client, uid, res) template checkSanity(client, sizeHex, size, messageBuffer: typed) = if client.recv(sizeHex, 6) != 6: @@ -208,6 +204,48 @@ template checkSanity(client, sizeHex, size, messageBuffer: typed) = if client.recv(messageBuffer, size) != size: raise newException(ValueError, "didn't get all the bytes") +var + requests: Channel[string] + results: Channel[Suggest] + +proc toStdout() {.gcsafe.} = + while true: + let res = results.recv() + case res.section + of ideNone: break + of ideChk: echo res.doc + of ideKnown: echo res.quality == 1 + else: echo res + +proc toSocket(stdoutSocket: Socket) {.gcsafe.} = + while true: + let res = results.recv() + case res.section + of ideNone: break + of ideChk: stdoutSocket.send(res.doc & "\c\L") + of ideKnown: stdoutSocket.send($(res.quality == 1) & "\c\L") + else: stdoutSocket.send($res & "\c\L") + +proc toEpc(client: Socket; uid: BiggestInt) {.gcsafe.} = + var list = newSList() + while true: + let res = results.recv() + case res.section + of ideNone: break + of ideChk: + list.add sexp(res.doc) + of ideKnown: + list.add sexp(res.quality == 1) + else: + list.add sexp(res) + returnEpc(client, uid, list) + +proc writelnToChannel(line: string) = + results.send(Suggest(section: ideChk, doc: line)) + +proc sugResultHook(s: Suggest) = + results.send(s) + template setVerbosity(level: typed) = gVerbosity = level gNotes = NotesVerbosity[gVerbosity] @@ -217,16 +255,128 @@ proc connectToNextFreePort(server: Socket, host: string): Port = let (_, port) = server.getLocalAddr result = port -proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) = +type + ThreadParams = tuple[port: Port; address: string] + +proc replStdin(x: ThreadParams) {.thread.} = + if gEmitEof: + echo DummyEof + while true: + let line = readLine(stdin) + requests.send line + toStdout() + echo DummyEof + flushFile(stdout) + else: + echo Help + var line = "" + while readLineFromStdin("> ", line): + requests.send line + toStdout() + echo "" + flushFile(stdout) + +proc replTcp(x: ThreadParams) {.thread.} = + var server = newSocket() + server.bindAddr(x.port, x.address) + var inp = "".TaintedString + server.listen() + while true: + var stdoutSocket = newSocket() + accept(server, stdoutSocket) + + stdoutSocket.readLine(inp) + requests.send inp + toSocket(stdoutSocket) + stdoutSocket.send("\c\L") + stdoutSocket.close() + +proc argsToStr(x: SexpNode): string = + if x.kind != SList: return x.getStr + doAssert x.kind == SList + doAssert x.len >= 4 + let file = x[0].getStr + let line = x[1].getNum + let col = x[2].getNum + let dirty = x[3].getStr + result = x[0].getStr.escape + if dirty.len > 0: + result.add ';' + result.add dirty.escape + result.add ':' + result.add line + result.add ':' + result.add col + +proc replEpc(x: ThreadParams) {.thread.} = + var server = newSocket() + let port = connectToNextFreePort(server, "localhost") + server.listen() + echo port + + var client = newSocket() + # Wait for connection + accept(server, client) + while true: + var + sizeHex = "" + size = 0 + messageBuffer = "" + checkSanity(client, sizeHex, size, messageBuffer) + let + message = parseSexp($messageBuffer) + epcApi = message[0].getSymbol + case epcApi + of "call": + let + uid = message[1].getNum + args = message[3] + + gIdeCmd = parseIdeCmd(message[2].getSymbol) + case gIdeCmd + of ideChk: + setVerbosity(1) + # Use full path because other emacs plugins depends it + gListFullPaths = true + incl(gGlobalOptions, optIdeDebug) + of ideSug, ideCon, ideDef, ideUse, ideDus, ideOutline, ideHighlight: + setVerbosity(0) + else: discard + let cmd = $gIdeCmd & " " & args.argsToStr + if gLogging: + logStr "MSG CMD: " & cmd + requests.send(cmd) + toEpc(client, uid) + of "methods": + returnEpc(client, message[1].getNum, listEpc()) + of "epc-error": + # an unhandled exception forces down the whole process anyway, so we + # use 'quit' here instead of 'raise' + quit("recieved epc error: " & $messageBuffer) + else: + let errMessage = case epcApi + of "return", "return-error": + "no return expected" + else: + "unexpected call: " & epcAPI + quit errMessage + +proc execCmd(cmd: string; graph: ModuleGraph; cache: IdentCache) = + template sentinel() = + # send sentinel for the input reading thread: + results.send(Suggest(section: ideNone)) + template toggle(sw) = if sw in gGlobalOptions: excl(gGlobalOptions, sw) else: incl(gGlobalOptions, sw) + sentinel() return template err() = echo Help + sentinel() return var opc = "" @@ -246,6 +396,7 @@ proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) = of "quit": quit() of "debug": toggle optIdeDebug of "terse": toggle optIdeTerse + of "known": gIdeCmd = ideKnown else: err() var dirtyfile = "" var orig = "" @@ -259,25 +410,74 @@ proc parseCmdLine(cmd: string; graph: ModuleGraph; cache: IdentCache) = i += skipWhile(cmd, seps, i) i += parseInt(cmd, col, i) - execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache) + if gIdeCmd == ideKnown: + results.send(Suggest(section: ideKnown, quality: ord(fileInfoKnown(orig)))) + else: + execute(gIdeCmd, orig, dirtyfile, line, col-1, graph, cache) + sentinel() + +proc recompileFullProject(graph: ModuleGraph; cache: IdentCache) = + echo "recompiling full project" + resetSystemArtifacts() + vm.globalCtx = nil + graph.resetAllModules() + GC_fullcollect() + compileProject(graph, cache) + echo GC_getStatistics() + +proc mainThread(graph: ModuleGraph; cache: IdentCache) = + if gLogging: + for it in searchPaths: + logStr(it) + + proc wrHook(line: string) {.closure.} = + if gMode == mepc: + if gLogging: logStr(line) + else: + writelnToChannel(line) + + msgs.writelnHook = wrHook + suggestionResultHook = sugResultHook + graph.doStopCompile = proc (): bool = requests.peek() > 0 + var idle = 0 + while true: + let (hasData, req) = requests.tryRecv() + if hasData: + msgs.writelnHook = wrHook + suggestionResultHook = sugResultHook -proc serveStdin(graph: ModuleGraph; cache: IdentCache) = + execCmd(req, graph, cache) + idle = 0 + else: + os.sleep 250 + idle += 1 + if idle == 20 and gRefresh: + # we use some nimsuggest activity to enable a lazy recompile: + gIdeCmd = ideChk + msgs.writelnHook = proc (s: string) = discard + suggestionResultHook = proc (s: Suggest) = discard + recompileFullProject(graph, cache) + +var + inputThread: Thread[ThreadParams] + +proc serveStdin(graph: ModuleGraph; cache: IdentCache) {.deprecated.} = if gEmitEof: echo DummyEof while true: let line = readLine(stdin) - parseCmdLine line, graph, cache + execCmd line, graph, cache echo DummyEof flushFile(stdout) else: echo Help var line = "" while readLineFromStdin("> ", line): - parseCmdLine line, graph, cache + execCmd line, graph, cache echo "" flushFile(stdout) -proc serveTcp(graph: ModuleGraph; cache: IdentCache) = +proc serveTcp(graph: ModuleGraph; cache: IdentCache) {.deprecated.} = var server = newSocket() server.bindAddr(gPort, gAddress) var inp = "".TaintedString @@ -291,20 +491,18 @@ proc serveTcp(graph: ModuleGraph; cache: IdentCache) = accept(server, stdoutSocket) stdoutSocket.readLine(inp) - parseCmdLine inp.string, graph, cache + execCmd inp.string, graph, cache stdoutSocket.send("\c\L") stdoutSocket.close() -proc serveEpc(server: Socket; graph: ModuleGraph; cache: IdentCache) = +proc serveEpc(server: Socket; graph: ModuleGraph; cache: IdentCache) {.deprecated.} = var client = newSocket() # Wait for connection accept(server, client) if gLogging: - var it = searchPaths.head - while it != nil: - logStr(PStrEntry(it).data) - it = it.next + for it in searchPaths: + logStr(it) msgs.writelnHook = proc (line: string) = logStr(line) while true: @@ -357,33 +555,49 @@ proc mainCommand(graph: ModuleGraph; cache: IdentCache) = incl gGlobalOptions, optCaasEnabled isServing = true wantMainModule() - appendStr(searchPaths, options.libpath) + add(searchPaths, options.libpath) #if gProjectFull.len != 0: # current path is always looked first for modules # prependStr(searchPaths, gProjectPath) # do not stop after the first error: msgs.gErrorMax = high(int) + # compile the project before showing any input so that we already + # can answer questions right away: + compileProject(graph, cache) + + open(requests) + open(results) case gMode - of mstdin: - compileProject(graph, cache) - #modules.gFuzzyGraphChecking = false - serveStdin(graph, cache) - of mtcp: - # until somebody accepted the connection, produce no output (logging is too - # slow for big projects): - msgs.writelnHook = proc (msg: string) = discard - compileProject(graph, cache) - #modules.gFuzzyGraphChecking = false - serveTcp(graph, cache) - of mepc: - var server = newSocket() - let port = connectToNextFreePort(server, "localhost") - server.listen() - echo port - compileProject(graph, cache) - serveEpc(server, graph, cache) + of mstdin: createThread(inputThread, replStdin, (gPort, gAddress)) + of mtcp: createThread(inputThread, replTcp, (gPort, gAddress)) + of mepc: createThread(inputThread, replEpc, (gPort, gAddress)) + mainThread(graph, cache) + joinThread(inputThread) + close(requests) + close(results) + + when false: + case gMode + of mstdin: + compileProject(graph, cache) + #modules.gFuzzyGraphChecking = false + serveStdin(graph, cache) + of mtcp: + # until somebody accepted the connection, produce no output (logging is too + # slow for big projects): + msgs.writelnHook = proc (msg: string) = discard + compileProject(graph, cache) + #modules.gFuzzyGraphChecking = false + serveTcp(graph, cache) + of mepc: + var server = newSocket() + let port = connectToNextFreePort(server, "localhost") + server.listen() + echo port + compileProject(graph, cache) + serveEpc(server, graph, cache) proc processCmdLine*(pass: TCmdLinePass, cmd: string) = var p = parseopt.initOptParser(cmd) @@ -403,16 +617,14 @@ proc processCmdLine*(pass: TCmdLinePass, cmd: string) = of "epc": gMode = mepc gVerbosity = 0 # Port number gotta be first. - of "debug": - incl(gGlobalOptions, optIdeDebug) - of "v2": - suggestVersion = 2 + of "debug": incl(gGlobalOptions, optIdeDebug) + of "v2": suggestVersion = 2 of "tester": suggestVersion = 2 gMode = mstdin gEmitEof = true - of "log": - gLogging = true + of "log": gLogging = true + of "refresh": gRefresh = true else: processSwitch(pass, p) of cmdArgument: options.gProjectName = unixToNativePath(p.key) diff --git a/tools/nimsuggest/nimsuggest.nim.cfg b/tools/nimsuggest/nimsuggest.nim.cfg index 949bd18e8..2e14a4dd3 100644 --- a/tools/nimsuggest/nimsuggest.nim.cfg +++ b/tools/nimsuggest/nimsuggest.nim.cfg @@ -8,9 +8,18 @@ path:"$lib/packages/docutils" define:useStdoutAsStdmsg define:nimsuggest +# die when nimsuggest uses more than 4GB: +@if cpu32: + define:"nimMaxHeap=2000" +@else: + define:"nimMaxHeap=4000" +@end #cs:partial #define:useNodeIds #define:booting #define:noDocgen --path:"$nim" +--threads:on +--noNimblePath +--path:"../../compiler" diff --git a/tools/nimsuggest/sexp.nim b/tools/nimsuggest/sexp.nim index cf08111d7..61bba10bc 100644 --- a/tools/nimsuggest/sexp.nim +++ b/tools/nimsuggest/sexp.nim @@ -561,16 +561,16 @@ proc toPretty(result: var string, node: SexpNode, indent = 2, ml = true, result.add(escapeJson(node.str)) of SInt: if lstArr: result.indent(currIndent) - result.add($node.num) + result.add(node.num) of SFloat: if lstArr: result.indent(currIndent) - result.add($node.fnum) + result.add(node.fnum) of SNil: if lstArr: result.indent(currIndent) result.add("nil") of SSymbol: if lstArr: result.indent(currIndent) - result.add($node.symbol) + result.add(node.symbol) of SList: if lstArr: result.indent(currIndent) if len(node.elems) != 0: diff --git a/tools/nimsuggest/tester.nim b/tools/nimsuggest/tester.nim index 156d3ddb9..c4542e089 100644 --- a/tools/nimsuggest/tester.nim +++ b/tools/nimsuggest/tester.nim @@ -3,7 +3,7 @@ # before 'nimsuggest' is invoked to ensure this token doesn't make a # crucial difference for Nim's parser. -import os, osproc, strutils, streams, re +import os, osproc, strutils, streams, re, sexp, net type Test = object @@ -17,7 +17,7 @@ const template tpath(): untyped = getAppDir() / "tests" -proc parseTest(filename: string): Test = +proc parseTest(filename: string; epcMode=false): Test = const cursorMarker = "#[!]#" let nimsug = curDir & addFileExt("nimsuggest", ExeExt) result.dest = getTempDir() / extractFilename(filename) @@ -31,7 +31,10 @@ proc parseTest(filename: string): Test = for x in lines(filename): let marker = x.find(cursorMarker)+1 if marker > 0: - markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker + if epcMode: + markers.add "(\"" & filename & "\" " & $i & " " & $marker & " \"" & result.dest & "\")" + else: + markers.add "\"" & filename & "\";\"" & result.dest & "\":" & $i & ":" & $marker tmp.writeLine x.replace(cursorMarker, "") else: tmp.writeLine x @@ -133,6 +136,126 @@ proc smartCompare(pattern, x: string): bool = if pattern.contains('*'): result = match(x, re(escapeRe(pattern).replace("\\x2A","(.*)"), {})) +proc sendEpcStr(socket: Socket; cmd: string) = + let s = cmd.find(' ') + doAssert s > 0 + var args = cmd.substr(s+1) + if not args.startsWith("("): args = escapeJson(args) + let c = "(call 567 " & cmd.substr(0, s) & args & ")" + socket.send toHex(c.len, 6) + socket.send c + +proc recvEpc(socket: Socket): string = + var L = newStringOfCap(6) + if socket.recv(L, 6) != 6: + raise newException(ValueError, "recv A failed") + let x = parseHexInt(L) + result = newString(x) + if socket.recv(result, x) != x: + raise newException(ValueError, "recv B failed") + +proc sexpToAnswer(s: SexpNode): string = + result = "" + doAssert s.kind == SList + doAssert s.len >= 3 + let m = s[2] + if m.kind != SList: + echo s + doAssert m.kind == SList + for a in m: + doAssert a.kind == SList + var first = true + #s.section, + #s.symkind, + #s.qualifiedPath.map(newSString), + #s.filePath, + #s.forth, + #s.line, + #s.column, + #s.doc + if a.len >= 8: + let section = a[0].getStr + let symk = a[1].getStr + let qp = a[2] + let file = a[3].getStr + let typ = a[4].getStr + let line = a[5].getNum + let col = a[6].getNum + let doc = a[7].getStr.escape + result.add section + result.add '\t' + result.add symk + result.add '\t' + var i = 0 + for aa in qp: + if i > 0: result.add '.' + result.add aa.getStr + inc i + result.add '\t' + result.add typ + result.add '\t' + result.add file + result.add '\t' + result.add line + result.add '\t' + result.add col + result.add '\t' + result.add doc + result.add '\t' + # for now Nim EPC does not return the quality + result.add "100" + result.add '\L' + +proc doReport(filename, answer, resp: string; report: var string) = + if resp != answer and not smartCompare(resp, answer): + report.add "\nTest failed: " & filename + var hasDiff = false + for i in 0..min(resp.len-1, answer.len-1): + if resp[i] != answer[i]: + report.add "\n Expected: " & resp.substr(i) + report.add "\n But got: " & answer.substr(i) + hasDiff = true + break + if not hasDiff: + report.add "\n Expected: " & resp + report.add "\n But got: " & answer + +proc runEpcTest(filename: string): int = + let s = parseTest(filename, true) + for cmd in s.startup: + if not runCmd(cmd, s.dest): + quit "invalid command: " & cmd + let epccmd = s.cmd.replace("--tester", "--epc --v2") + let cl = parseCmdLine(epccmd) + var p = startProcess(command=cl[0], args=cl[1 .. ^1], + options={poStdErrToStdOut, poUsePath, + poInteractive, poDemon}) + let outp = p.outputStream + let inp = p.inputStream + var report = "" + var a = newStringOfCap(120) + try: + # read the port number: + if outp.readLine(a): + let port = parseInt(a) + var socket = newSocket() + socket.connect("localhost", Port(port)) + for req, resp in items(s.script): + if not runCmd(req, s.dest): + socket.sendEpcStr(req) + let sx = parseSexp(socket.recvEpc()) + if not req.startsWith("mod "): + let answer = sexpToAnswer(sx) + doReport(filename, answer, resp, report) + else: + raise newException(ValueError, "cannot read port number") + finally: + close(p) + if report.len > 0: + echo "==== EPC ========================================" + echo report + result = report.len + proc runTest(filename: string): int = let s = parseTest filename for cmd in s.startup: @@ -159,31 +282,28 @@ proc runTest(filename: string): int = if a == DummyEof: break answer.add a answer.add '\L' - if resp != answer and not smartCompare(resp, answer): - report.add "\nTest failed: " & filename - var hasDiff = false - for i in 0..min(resp.len-1, answer.len-1): - if resp[i] != answer[i]: - report.add "\n Expected: " & resp.substr(i) - report.add "\n But got: " & answer.substr(i) - hasDiff = true - break - if not hasDiff: - report.add "\n Expected: " & resp - report.add "\n But got: " & answer + doReport(filename, answer, resp, report) finally: inp.writeLine("quit") inp.flush() close(p) if report.len > 0: + echo "==== STDIN ======================================" echo report result = report.len proc main() = var failures = 0 - for x in walkFiles(getAppDir() / "tests/t*.nim"): - echo "Test ", x - failures += runTest(expandFilename(x)) + when false: + let x = getAppDir() / "tests/twithin_macro.nim" + let xx = expandFilename x + failures += runEpcTest(xx) + else: + for x in walkFiles(getAppDir() / "tests/t*.nim"): + echo "Test ", x + let xx = expandFilename x + failures += runTest(xx) + failures += runEpcTest(xx) if failures > 0: quit 1 diff --git a/tools/nimsuggest/tests/twithin_macro.nim b/tools/nimsuggest/tests/twithin_macro.nim index d67984707..7392dd605 100644 --- a/tools/nimsuggest/tests/twithin_macro.nim +++ b/tools/nimsuggest/tests/twithin_macro.nim @@ -206,8 +206,8 @@ $nimsuggest --tester $file >sug $1 sug;;skField;;name;;string;;$file;;166;;6;;"";;100 sug;;skField;;age;;int;;$file;;167;;6;;"";;100 -sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int{.noSideEffect, gcsafe, locks: 0.};;$file;;169;;9;;"";;100 +sug;;skMethod;;twithin_macro.age_human_yrs;;proc (self: Animal): int;;$file;;169;;9;;"";;100 sug;;skMacro;;twithin_macro.class;;proc (head: untyped, body: untyped): untyped{.gcsafe, locks: <unknown>.};;$file;;4;;6;;"Iterates over the children of the NimNode ``n``.";;100 -sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string{.noSideEffect, gcsafe, locks: 0.};;$file;;168;;9;;"";;100 -sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string{.noSideEffect, gcsafe, locks: 0.};;$file;;184;;9;;"";;100* +sug;;skMethod;;twithin_macro.vocalize;;proc (self: Animal): string;;$file;;168;;9;;"";;100 +sug;;skMethod;;twithin_macro.vocalize;;proc (self: Rabbit): string;;$file;;184;;9;;"";;100* """ diff --git a/web/news/e031_version_0_16_2.rst b/web/news/e031_version_0_16_2.rst index 785285eaf..3f111b503 100644 --- a/web/news/e031_version_0_16_2.rst +++ b/web/news/e031_version_0_16_2.rst @@ -27,6 +27,8 @@ Changes affecting backwards compatibility Library Additions ----------------- +- Added ``system.onThreadDestruction``. + Tool Additions -------------- @@ -67,7 +69,8 @@ In the near future we will be converting all exception types to refs to remove the need for the ``newException`` template. - A new pragma ``.used`` can be used for symbols to prevent -the "declared but not used" warning. More details can be found `here <http://nim-lang.org/docs/manual.html#pragmas-used-pragma>`_. +the "declared but not used" warning. More details can be +found `here <http://nim-lang.org/docs/manual.html#pragmas-used-pragma>`_. Bugfixes |