diff --git a/compiler/ast.nim b/compiler/ast.nim index 110ee79e3e39..2bd2028ddad5 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -1101,7 +1101,7 @@ template `[]`*(n: Indexable, i: BackwardsIndex): Indexable = n[n.len - i.int] template `[]=`*(n: Indexable, i: BackwardsIndex; x: Indexable) = n[n.len - i.int] = x when defined(useNodeIds): - const nodeIdToDebug* = -1 # 2322968 + const nodeIdToDebug* = -1 var gNodeId: int proc newNode*(kind: TNodeKind): PNode = diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index 47cf83715cc2..306738d47277 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -417,6 +417,7 @@ type currentLine: int firstItem: bool useColor: bool + compressBuiltinTypes: bool res: string proc indentMore(this: var DebugPrinter) = @@ -541,7 +542,23 @@ proc value(this: var DebugPrinter; value: PSym) = this.closeCurly +const CompressedBuiltinTypes = { + tyBool, tyChar, tyPointer, tyString,tyCString, tyInt, tyInt8, tyInt16, + tyInt32, tyInt64, tyFloat, tyFloat32, tyFloat64, tyUInt, tyUInt8, + tyUInt16, tyUInt32, tyUInt64 +} + proc value(this: var DebugPrinter; value: PType) = + # these shortcuts for builtin types are done before ``earlyExit`` + # because they are shorter that backreference links. + if this.compressBuiltinTypes and value != nil and + value.kind in CompressedBuiltinTypes: + if this.useColor: + this.res.add backrefStyle + this.res.add toLowerAscii("<" & ($value.kind)[2..^1] & ">") + if this.useColor: + this.res.add resetStyle + return earlyExit(this, value) this.openCurly @@ -628,12 +645,12 @@ proc value(this: var DebugPrinter; value: PNode) = this.closeCurly - proc debug(n: PSym; conf: ConfigRef) = var this: DebugPrinter this.visited = initTable[pointer, int]() this.renderSymType = true this.useColor = not defined(windows) + this.compressBuiltinTypes = true this.value(n) echo($this.res) @@ -642,6 +659,7 @@ proc debug(n: PType; conf: ConfigRef) = this.visited = initTable[pointer, int]() this.renderSymType = true this.useColor = not defined(windows) + this.compressBuiltinTypes = true this.value(n) echo($this.res) @@ -650,6 +668,7 @@ proc debug(n: PNode; conf: ConfigRef) = this.visited = initTable[pointer, int]() #this.renderSymType = true this.useColor = not defined(windows) + this.compressBuiltinTypes = true this.value(n) echo($this.res) @@ -1047,4 +1066,3 @@ proc listSymbolNames*(symbols: openArray[PSym]): string = if result.len > 0: result.add ", " result.add sym.name.s - diff --git a/compiler/condsyms.nim b/compiler/condsyms.nim index 4fc882ab7d76..8e065f0e3488 100644 --- a/compiler/condsyms.nim +++ b/compiler/condsyms.nim @@ -105,6 +105,7 @@ proc initDefines*(symbols: StringTableRef) = defineSymbol("nimHasExceptionsQuery") defineSymbol("nimHasIsNamedTuple") defineSymbol("nimHashOrdinalFixed") + defineSymbol("nimHasTypeIsRecursive") when defined(nimHasLibFFI): # Renaming as we can't conflate input vs output define flags; e.g. this diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index aad3ddfab988..324f4549c4ba 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -1021,12 +1021,9 @@ template hashNimExe(): string = $secureHashFile(os.getAppFilename()) proc writeJsonBuildInstructions*(conf: ConfigRef) = template lit(x: untyped) = f.write x template str(x: untyped) = - when compiles(escapeJson(x, buf)): - buf.setLen 0 - escapeJson(x, buf) - f.write buf - else: - f.write escapeJson(x) + buf.setLen 0 + escapeJson(x, buf) + f.write buf proc cfiles(conf: ConfigRef; f: File; buf: var string; clist: CfileList, isExternal: bool) = var comma = false @@ -1062,7 +1059,7 @@ proc writeJsonBuildInstructions*(conf: ConfigRef) = pastStart = true lit "\L" - proc depfiles(conf: ConfigRef; f: File) = + proc depfiles(conf: ConfigRef; f: File; buf: var string) = var i = 0 for it in conf.m.fileInfos: let path = it.fullPath.string @@ -1102,7 +1099,7 @@ proc writeJsonBuildInstructions*(conf: ConfigRef) = lit ",\L\"cmdline\": " str conf.commandLine lit ",\L\"depfiles\":[\L" - depfiles(conf, f) + depfiles(conf, f, buf) lit "],\L\"nimexe\": \L" str hashNimExe() lit "\L" diff --git a/compiler/guards.nim b/compiler/guards.nim index e4f87bae4977..2db3ba57ee10 100644 --- a/compiler/guards.nim +++ b/compiler/guards.nim @@ -794,12 +794,7 @@ macro `=~`(x: PNode, pat: untyped): bool = var conds = newTree(nnkBracket) m(x, pat, conds) - when compiles(nestList(ident"and", conds)): - result = nestList(ident"and", conds) - #elif declared(macros.toNimIdent): - # result = nestList(toNimIdent"and", conds) - else: - result = nestList(!"and", conds) + result = nestList(ident"and", conds) proc isMinusOne(n: PNode): bool = n.kind in {nkCharLit..nkUInt64Lit} and n.intVal == -1 diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index da2a222a4a13..b160a552c54b 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -82,11 +82,11 @@ type TPassClose* = proc (graph: ModuleGraph; p: PPassContext, n: PNode): PNode {.nimcall.} TPassProcess* = proc (p: PPassContext, topLevelStmt: PNode): PNode {.nimcall.} - TPass* = tuple[open: TPassOpen, - process: TPassProcess, - close: TPassClose, - isFrontend: bool] - + TPass* = object + open*: TPassOpen + process*: TPassProcess + close*: TPassClose + isFrontend*: bool const cb64 = [ @@ -137,24 +137,14 @@ proc hash*(x: FileIndex): Hash {.borrow.} when defined(nimfind): template onUse*(info: TLineInfo; s: PSym) = - when compiles(c.c.graph): - if c.c.graph.onUsage != nil: c.c.graph.onUsage(c.c.graph, s, info) - else: - if c.graph.onUsage != nil: c.graph.onUsage(c.graph, s, info) + if c.graph.onUsage != nil: c.graph.onUsage(c.graph, s, info) template onDef*(info: TLineInfo; s: PSym) = - when compiles(c.c.graph): - if c.c.graph.onDefinition != nil: c.c.graph.onDefinition(c.c.graph, s, info) - else: - if c.graph.onDefinition != nil: c.graph.onDefinition(c.graph, s, info) + if c.graph.onDefinition != nil: c.graph.onDefinition(c.graph, s, info) template onDefResolveForward*(info: TLineInfo; s: PSym) = - when compiles(c.c.graph): - if c.c.graph.onDefinitionResolveForward != nil: - c.c.graph.onDefinitionResolveForward(c.c.graph, s, info) - else: - if c.graph.onDefinitionResolveForward != nil: - c.graph.onDefinitionResolveForward(c.graph, s, info) + if c.graph.onDefinitionResolveForward != nil: + c.graph.onDefinitionResolveForward(c.graph, s, info) else: template onUse*(info: TLineInfo; s: PSym) = discard diff --git a/compiler/semmagic.nim b/compiler/semmagic.nim index 53a19fae6a5e..e2f367a9e10e 100644 --- a/compiler/semmagic.nim +++ b/compiler/semmagic.nim @@ -202,6 +202,10 @@ proc evalTypeTrait(c: PContext; traitCall: PNode, operand: PType, context: PSym) localError(c.config, traitCall.info, "distinctBase expects a distinct type as argument. The given type was " & typeToString(operand)) result = newType(tyError, context).toNode(traitCall.info) + of "isRecursivePointer": + let operand = operand.skipTypes({tyGenericInst}) + let cond = isRecursivePointer(operand) + result = newIntNodeT(toInt128(ord(cond)), traitCall, c.graph) else: localError(c.config, traitCall.info, "unknown trait: " & s) result = newNodeI(nkEmpty, traitCall.info) diff --git a/compiler/transf.nim b/compiler/transf.nim index cbf904255cb8..47da399eb717 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -554,14 +554,14 @@ proc putArgInto(arg: PNode, formal: PType): TPutArgInto = # inline context. if formal.kind == tyTypeDesc: return paDirectMapping if skipTypes(formal, abstractInst).kind in {tyOpenArray, tyVarargs}: - case arg.kind - of nkStmtListExpr: - return paComplexOpenarray + case arg.skipHidden.kind of nkBracket: return paFastAsgnTakeTypeFromArg - else: - # XXX incorrect, causes #13417 when `arg` has side effects. + of nkSym: return paDirectMapping + else: + # XXX potential performance problem as this creataes an intermediate variable copy + return paComplexOpenarray case arg.kind of nkEmpty..nkNilLit: result = paDirectMapping @@ -569,14 +569,14 @@ proc putArgInto(arg: PNode, formal: PType): TPutArgInto = result = putArgInto(arg[0], formal) of nkCurly, nkBracket: for i in 0..= ff.n.len: return result diff --git a/compiler/types.nim b/compiler/types.nim index ecbe371e2f12..e219656eb990 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -1641,6 +1641,23 @@ proc isTupleRecursive*(t: PType): bool = var cycleDetector = initIntSet() isTupleRecursive(t, cycleDetector) +proc isRecursivePointer(typ: PType, isPointer: bool): bool = + if typ == nil: + return false + case typ.kind + of tyObject, tyTuple: + return isPointer and canFormAcycle(typ) + of tyRef, tyPtr: + return isRecursivePointer(typ.lastSon, isPointer = true) + of tyAlias, tyGenericInst, tyVar, tyLent, tySink, tyArray, tyUncheckedArray, tySequence, tyDistinct: + return isRecursivePointer(typ.lastSon, isPointer) + else: + return false + +proc isRecursivePointer*(t: PType): bool = + isRecursivePointer(t, false) + + proc isException*(t: PType): bool = # check if `y` is object type and it inherits from Exception assert(t != nil) diff --git a/compiler/unittest_light.nim b/compiler/unittest_light.nim index d9842b3994c9..0ae497adc6d2 100644 --- a/compiler/unittest_light.nim +++ b/compiler/unittest_light.nim @@ -16,20 +16,19 @@ proc mismatch*[T](lhs: T, rhs: T): string = result.add "\n" result.add "lhs:{" & replaceInvisible( $lhs) & "}\nrhs:{" & replaceInvisible($rhs) & "}\n" - when compiles(lhs.len): + when T is (string | seq | array): if lhs.len != rhs.len: result.add "lhs.len: " & $lhs.len & " rhs.len: " & $rhs.len & "\n" - when compiles(lhs[0]): - var i = 0 - while i < lhs.len and i < rhs.len: - if lhs[i] != rhs[i]: break - i.inc - result.add "first mismatch index: " & $i & "\n" - if i < lhs.len and i < rhs.len: - result.add "lhs[i]: {" & quoted($lhs[i]) & "}\nrhs[i]: {" & quoted( - $rhs[i]) & "}\n" - result.add "lhs[0.. 0: if deq.mask == 0: initImpl(deq, defaultInitialSize) diff --git a/lib/pure/collections/sequtils.nim b/lib/pure/collections/sequtils.nim index ea02ff1fd376..debbff951ecd 100644 --- a/lib/pure/collections/sequtils.nim +++ b/lib/pure/collections/sequtils.nim @@ -80,29 +80,6 @@ import macros when not defined(nimhygiene): {.pragma: dirty.} - -macro evalOnceAs(expAlias, exp: untyped, - letAssigneable: static[bool]): untyped = - ## Injects ``expAlias`` in caller scope, to avoid bugs involving multiple - ## substitution in macro arguments such as - ## https://github.com/nim-lang/Nim/issues/7187 - ## ``evalOnceAs(myAlias, myExp)`` will behave as ``let myAlias = myExp`` - ## except when ``letAssigneable`` is false (e.g. to handle openArray) where - ## it just forwards ``exp`` unchanged - expectKind(expAlias, nnkIdent) - var val = exp - - result = newStmtList() - # If `exp` is not a symbol we evaluate it once here and then use the temporary - # symbol as alias - if exp.kind != nnkSym and letAssigneable: - val = genSym() - result.add(newLetStmt(val, exp)) - - result.add( - newProc(name = genSym(nskTemplate, $expAlias), params = [getType(untyped)], - body = val, procType = nnkTemplateDef)) - proc concat*[T](seqs: varargs[seq[T]]): seq[T] = ## Takes several sequences' items and returns them inside a new sequence. ## All sequences must be of the same type. @@ -703,51 +680,83 @@ template anyIt*(s, pred: untyped): bool = break result -template toSeq1(s: not iterator): untyped = - # overload for typed but not iterator - type OutType = type(items(s)) - when compiles(s.len): - block: - evalOnceAs(s2, s, compiles((let _ = s))) - var i = 0 - var result = newSeq[OutType](s2.len) - for it in s2: - result[i] = it - i += 1 - result - else: - var result: seq[OutType] = @[] - for it in s: - result.add(it) - result +template toSeqBuiltin[IX, T](arg: array[IX, T]): seq[T] = @arg -template toSeq2(iter: iterator): untyped = - # overload for iterator - evalOnceAs(iter2, iter(), false) - when compiles(iter2.len): - var i = 0 - var result = newSeq[type(iter2)](iter2.len) - for x in iter2: - result[i] = x - inc i - result +template toSeqBuiltin[T](arg: openArray[T]): seq[T] = @arg + +template toSeqBuiltin(arg: string): seq[char] = @arg + +template toSeqBuiltin[T](arg: seq[T]): seq[T] = arg + +proc toSeqBuiltin[T](arg: set[T]): seq[T] {.inline.} = + for it in arg: + result.add it + +proc toSeqBuiltin(a: cstring): seq[char] {.inline.} = + let len = a.len + result.setLen len + var i = 0 + while i < len: + result[i] = a[i] + inc i + +proc toSeqBuiltin[E: enum](t: typedesc[E]): seq[E] = + for it in t: + result.add it + +macro toSeqBuiltin(arg: iterator): untyped = + let typ = arg.getTypeInst + typ.expectKind nnkProcTy # only for closure iterators + let formalParams = typ[0] + formalParams.expectKind nnkFormalParams + formalParams.expectMinLen 1 + let resultTyp = nnkBracketExpr.newTree(bindSym"seq", formalParams[0]) + let tmpSym = genSym(nskVar) + result = nnkStmtListExpr.newTree + result.add nnkVarSection.newTree(nnkIdentDefs.newTree(tmpSym, resultTyp, newNimNode(nnkEmpty))) + result.add quote do: + for it in `arg`: + `tmpSym`.add it + result.add tmpSym + +template toSeqBuiltin[T: not proc](arg: T): untyped = + # fallback for ``IntSet`` and ``HashSet``. Explicit overload would + # be better, but this would require an additional import here. + var tmp: seq[typeof(items(arg), typeOfIter)] + for it in arg: + tmp.add it + tmp + +macro toSeqImpl(arg: typed): untyped = + let iteratorCall = arg[1] + let iteratorSym = iteratorCall[0] + if iteratorCall.len == 2 and iteratorSym.eqIdent("items") and iteratorSym.owner.owner.eqIdent("stdlib"): + let iteratorArg = iteratorCall[1] + result = newCall(bindSym"toSeqBuiltin", iteratorArg) else: - type OutType = type(iter2()) - var result: seq[OutType] = @[] - when compiles(iter2()): - evalOnceAs(iter4, iter, false) - let iter3 = iter4() - for x in iter3(): - result.add(x) - else: - for x in iter2(): - result.add(x) - result + let typ = iteratorCall.getTypeInst() + result = quote do: + var tmp: seq[`typ`] + for it in `iteratorCall`: + tmp.add it + tmp + +proc genCallToSeqImpl(arg: NimNode): NimNode = + newCall(bindSym"toSeqImpl", + nnkForStmt.newTree( + ident"x", arg, newStmtList())) + +macro toSeqImplSingleSym(arg: typed): untyped = + if arg.kind != nnkSym: + error "identifier not unique", arg # arg is symChoice + elif arg.symKind == nskIterator: + result = genCallToSeqImpl(newCall(arg)) + else: + result = genCallToSeqImpl(arg) -template toSeq*(iter: untyped): untyped = +macro toSeq*(arg: untyped): untyped = ## Transforms any iterable (anything that can be iterated over, e.g. with ## a for-loop) into a sequence. - ## runnableExamples: let myRange = 1..5 @@ -761,26 +770,10 @@ template toSeq*(iter: untyped): untyped = assert mySeq1 == @[1, 2, 3, 4, 5] assert mySeq2 == @[1'i8, 3, 5] - when compiles(toSeq1(iter)): - toSeq1(iter) - elif compiles(toSeq2(iter)): - toSeq2(iter) + if arg.kind in {nnkSym, nnkIdent, nnkOpenSymChoice, nnkClosedSymChoice}: + result = newCall(bindSym"toSeqImplSingleSym", arg) else: - # overload for untyped, e.g.: `toSeq(myInlineIterator(3))` - when compiles(iter.len): - block: - evalOnceAs(iter2, iter, true) - var result = newSeq[type(iter)](iter2.len) - var i = 0 - for x in iter2: - result[i] = x - inc i - result - else: - var result: seq[type(iter)] = @[] - for x in iter: - result.add(x) - result + result = genCallToSeqImpl(arg) template foldl*(sequence, operation: untyped): untyped = ## Template to fold a sequence from left to right, returning the accumulation. @@ -914,34 +907,14 @@ template mapIt*(s: typed, op: untyped): untyped = strings = nums.mapIt($(4 * it)) assert strings == @["4", "8", "12", "16"] - when defined(nimHasTypeof): - type OutType = typeof(( - block: - var it{.inject.}: typeof(items(s), typeOfIter); - op), typeOfProc) - else: - type OutType = type(( - block: - var it{.inject.}: type(items(s)); - op)) - when compiles(s.len): - block: # using a block avoids https://github.com/nim-lang/Nim/issues/8580 - - # BUG: `evalOnceAs(s2, s, false)` would lead to C compile errors - # (`error: use of undeclared identifier`) instead of Nim compile errors - evalOnceAs(s2, s, compiles((let _ = s))) - - var i = 0 - var result = newSeq[OutType](s2.len) - for it {.inject.} in s2: - result[i] = op - i += 1 - result - else: - var result: seq[OutType] = @[] - for it {.inject.} in s: - result.add(op) - result + type OutType = typeof(( + block: + var it{.inject.}: typeof(items(s), typeOfIter); + op), typeOfProc) + var result: seq[OutType] + for it {.inject.} in s: + result.add(op) + result template applyIt*(varSeq, op: untyped) = ## Convenience template around the mutable ``apply`` proc to reduce typing. @@ -1386,9 +1359,14 @@ when isMainModule: block: # tests https://github.com/nim-lang/Nim/issues/7187 counter = 0 - let ret = toSeq(@[1, 2, 3].identity().filter(proc (x: int): bool = x < 3)) - doAssert ret == @[1, 2] + let ret1 = toSeq(@[1, 2, 3].identity().filter(proc (x: int): bool = x < 3)) + doAssert ret1 == @[1, 2] doAssert counter == 1 + counter = 0 + let ret2 = toSeq([1, 2, 3].identity().filter(proc (x: int): bool = x < 3)) + doAssert ret2 == @[1, 2] + doAssert counter == 1 + block: # foldl tests let numbers = @[5, 9, 11] @@ -1422,8 +1400,8 @@ when isMainModule: strings = nums.identity.mapIt($(4 * it)) doAssert counter == 1 nums.applyIt(it * 3) - assert nums[0] + nums[3] == 15 - assert strings[2] == "12" + doAssert nums[0] + nums[3] == 15 + doAssert strings[2] == "12" block: # newSeqWith tests var seq2D = newSeqWith(4, newSeq[bool](2)) diff --git a/lib/pure/collections/sets.nim b/lib/pure/collections/sets.nim index 11639df3beeb..12e59055cea8 100644 --- a/lib/pure/collections/sets.nim +++ b/lib/pure/collections/sets.nim @@ -146,10 +146,7 @@ proc `[]`*[A](s: var HashSet[A], key: A): var A = var index = rawGet(s, key, hc) if index >= 0: result = s.data[index].key else: - when compiles($key): - raise newException(KeyError, "key not found: " & $key) - else: - raise newException(KeyError, "key not found") + raise newException(KeyError, "key not found: " & $key) proc contains*[A](s: HashSet[A], key: A): bool = ## Returns true if `key` is in `s`. diff --git a/lib/pure/collections/sharedtables.nim b/lib/pure/collections/sharedtables.nim index 2a1c0543f3b3..a8aad663ad78 100644 --- a/lib/pure/collections/sharedtables.nim +++ b/lib/pure/collections/sharedtables.nim @@ -29,6 +29,9 @@ type template maxHash(t): untyped = t.dataLen-1 +template checkIfInitialized() = + assert t.data != nil, "shared table use before initialization" + include tableimpl template st_maybeRehashPutImpl(enlarge) {.dirty.} = @@ -122,10 +125,7 @@ proc mget*[A, B](t: var SharedTable[A, B], key: A): var B = let hasKey = index >= 0 if hasKey: result = t.data[index].val if not hasKey: - when compiles($key): - raise newException(KeyError, "key not found: " & $key) - else: - raise newException(KeyError, "key not found") + raise newException(KeyError, "key not found: " & $key) proc mgetOrPut*[A, B](t: var SharedTable[A, B], key: A, val: B): var B = ## retrieves value at ``t[key]`` or puts ``val`` if not present, either way @@ -228,12 +228,10 @@ proc deinitSharedTable*[A, B](t: var SharedTable[A, B]) = deallocShared(t.data) deinitLock t.lock -proc initSharedTable*[A, B](initialSize = 64): SharedTable[A, B] {.deprecated: - "use 'init' instead".} = - ## This is not posix compliant, may introduce undefined behavior. - assert isPowerOfTwo(initialSize) - result.counter = 0 - result.dataLen = initialSize - result.data = cast[KeyValuePairSeq[A, B]](allocShared0( - sizeof(KeyValuePair[A, B]) * initialSize)) - initLock result.lock +proc initSharedTable*[A, B](initialSize = 64): SharedTable[A, B] = + ## creates a new hash table that is empty. + ## + ## `initialSize` needs to be a power of two. If you need to accept runtime + ## values for this you could use the ``nextPowerOfTwo`` proc from the + ## `math `_ module or the ``rightSize`` proc from this module.. + result.init(initialSize) diff --git a/lib/pure/collections/tableimpl.nim b/lib/pure/collections/tableimpl.nim index 47c14af93aa6..950c3dc25e7d 100644 --- a/lib/pure/collections/tableimpl.nim +++ b/lib/pure/collections/tableimpl.nim @@ -30,11 +30,6 @@ proc rawInsert[X, A, B](t: var X, data: var KeyValuePairSeq[A, B], key: A, val: B, hc: Hash, h: Hash) = rawInsertImpl() -template checkIfInitialized() = - when compiles(defaultInitialSize): - if t.dataLen == 0: - initImpl(t, defaultInitialSize) - template addImpl(enlarge) {.dirty.} = checkIfInitialized() if mustRehash(t): enlarge(t) @@ -108,28 +103,13 @@ template delImpl() {.dirty.} = template clearImpl() {.dirty.} = for i in 0 ..< t.dataLen: - when compiles(t.data[i].hcode): # CountTable records don't contain a hcode - t.data[i].hcode = 0 - t.data[i].key = default(type(t.data[i].key)) - t.data[i].val = default(type(t.data[i].val)) + t.data[i] = default(typeof(t.data[i])) t.counter = 0 -template ctAnd(a, b): bool = - when a: - when b: true - else: false - else: false - template initImpl(result: typed, size: int) = - when ctAnd(declared(SharedTable), type(result) is SharedTable): - init(result, size) - else: - assert isPowerOfTwo(size) - result.counter = 0 - newSeq(result.data, size) - when compiles(result.first): - result.first = -1 - result.last = -1 + assert isPowerOfTwo(size) + result.counter = 0 + newSeq(result.data, size) template insertImpl() = # for CountTable if t.dataLen == 0: initImpl(t, defaultInitialSize) diff --git a/lib/pure/collections/tables.nim b/lib/pure/collections/tables.nim index 131e0ad80360..84d1a5cbfef8 100644 --- a/lib/pure/collections/tables.nim +++ b/lib/pure/collections/tables.nim @@ -248,6 +248,10 @@ const template maxHash(t): untyped = high(t.data) template dataLen(t): untyped = len(t.data) +template checkIfInitialized() = + if t.dataLen == 0: + initImpl(t, defaultInitialSize) + include tableimpl template get(t, key): untyped = @@ -258,10 +262,7 @@ template get(t, key): untyped = var index = rawGet(t, key, hc) if index >= 0: result = t.data[index].val else: - when compiles($key): - raise newException(KeyError, "key not found: " & $key) - else: - raise newException(KeyError, "key not found") + raise newException(KeyError, "key not found: " & $key) proc enlarge[A, B](t: var Table[A, B]) = var n: KeyValuePairSeq[A, B] @@ -1278,6 +1279,8 @@ proc initOrderedTable*[A, B](initialSize = defaultInitialSize): OrderedTable[A, a = initOrderedTable[int, string]() b = initOrderedTable[char, seq[int]]() initImpl(result, initialSize) + result.first = -1 + result.last = -1 proc `[]=`*[A, B](t: var OrderedTable[A, B], key: A, val: B) = ## Inserts a ``(key, value)`` pair into ``t``. diff --git a/lib/pure/htmlgen.nim b/lib/pure/htmlgen.nim index e0518f5ee10a..e1a4023a80c5 100644 --- a/lib/pure/htmlgen.nim +++ b/lib/pure/htmlgen.nim @@ -113,10 +113,7 @@ proc xmlCheckedTag*(argsList: NimNode, tag: string, optAttr = "", reqAttr = "", result.add(newStrLitNode("")) - when compiles(nestList(ident"&", result)): - result = nestList(ident"&", result) - else: - result = nestList(!"&", result) + result = nestList(ident"&", result) macro a*(e: varargs[untyped]): untyped = ## generates the HTML ``a`` element. diff --git a/lib/pure/options.nim b/lib/pure/options.nim index dc5cfa4bf560..e5a603fbb363 100644 --- a/lib/pure/options.nim +++ b/lib/pure/options.nim @@ -486,7 +486,7 @@ when isMainModule: let tmp = option(intref) check(sizeof(tmp) == sizeof(ptr int)) - + var prc = proc (x: int): int = x + 1 check(option(prc).isSome) prc = nil @@ -513,4 +513,3 @@ when isMainModule: test "Ref type with overloaded `==`": let p = some(RefPerson.new()) check p.isSome - diff --git a/lib/pure/strtabs.nim b/lib/pure/strtabs.nim index c6b44a3caebf..29ffbd15721b 100644 --- a/lib/pure/strtabs.nim +++ b/lib/pure/strtabs.nim @@ -139,10 +139,7 @@ template get(t: StringTableRef, key: string) = var index = rawGet(t, key) if index >= 0: result = t.data[index].val else: - when compiles($key): - raise newException(KeyError, "key not found: " & $key) - else: - raise newException(KeyError, "key not found") + raise newException(KeyError, "key not found: " & key) proc len*(t: StringTableRef): int {.rtlFunc, extern: "nst$1".} = ## Returns the number of keys in `t`. diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index ee2c5fe22892..8b7565a0dccf 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -190,7 +190,7 @@ proc delOutputFormatter*(formatter: OutputFormatter) = proc resetOutputFormatters* {.since: (1, 1).} = formatters = @[] - + proc newConsoleOutputFormatter*(outputLevel: OutputLevel = OutputLevel.PRINT_ALL, colorOutput = true): ConsoleOutputFormatter = ConsoleOutputFormatter( @@ -621,8 +621,7 @@ macro check*(conditions: untyped): untyped = # preserve the semantics of var params template print(name: untyped, value: typed) = - when compiles(string($value)): - checkpoint(name & " was " & $value) + checkpoint(name & " was " & $value) proc inspectArgs(exp: NimNode): tuple[assigns, check, printOuts: NimNode] = result.check = copyNimTree(exp) diff --git a/lib/system.nim b/lib/system.nim index f9ebe26b4f33..6876153a994e 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -2041,6 +2041,94 @@ template unlikely*(val: bool): bool = else: unlikelyProc(val) +proc addEscapedChar*(s: var string, c: char) {.noSideEffect, inline.} = + ## Adds a char to string `s` and applies the following escaping: + ## + ## * replaces any ``\`` by ``\\`` + ## * replaces any ``'`` by ``\'`` + ## * replaces any ``"`` by ``\"`` + ## * replaces any ``\a`` by ``\\a`` + ## * replaces any ``\b`` by ``\\b`` + ## * replaces any ``\t`` by ``\\t`` + ## * replaces any ``\n`` by ``\\n`` + ## * replaces any ``\v`` by ``\\v`` + ## * replaces any ``\f`` by ``\\f`` + ## * replaces any ``\c`` by ``\\c`` + ## * replaces any ``\e`` by ``\\e`` + ## * replaces any other character not in the set ``{'\21..'\126'} + ## by ``\xHH`` where ``HH`` is its hexadecimal value. + ## + ## The procedure has been designed so that its output is usable for many + ## different common syntaxes. + ## + ## **Note**: This is **not correct** for producing Ansi C code! + case c + of '\a': s.add "\\a" # \x07 + of '\b': s.add "\\b" # \x08 + of '\t': s.add "\\t" # \x09 + of '\L': s.add "\\n" # \x0A + of '\v': s.add "\\v" # \x0B + of '\f': s.add "\\f" # \x0C + of '\c': s.add "\\c" # \x0D + of '\e': s.add "\\e" # \x1B + of '\\': s.add("\\\\") + of '\'': s.add("\\'") + of '\"': s.add("\\\"") + of {'\32'..'\126'} - {'\\', '\'', '\"'}: s.add(c) + else: + s.add("\\x") + const HexChars = "0123456789ABCDEF" + let n = ord(c) + s.add(HexChars[int((n and 0xF0) shr 4)]) + s.add(HexChars[int(n and 0xF)]) + +proc addQuoted*[T](s: var string, x: T) = + ## Appends `x` to string `s` in place, applying quoting and escaping + ## if `x` is a string or char. + ## + ## See `addEscapedChar <#addEscapedChar,string,char>`_ + ## for the escaping scheme. When `x` is a string, characters in the + ## range ``{\128..\255}`` are never escaped so that multibyte UTF-8 + ## characters are untouched (note that this behavior is different from + ## ``addEscapedChar``). + ## + ## The Nim standard library uses this function on the elements of + ## collections when producing a string representation of a collection. + ## It is recommended to use this function as well for user-side collections. + ## Users may overload `addQuoted` for custom (string-like) types if + ## they want to implement a customized element representation. + ## + ## .. code-block:: Nim + ## var tmp = "" + ## tmp.addQuoted(1) + ## tmp.add(", ") + ## tmp.addQuoted("string") + ## tmp.add(", ") + ## tmp.addQuoted('c') + ## assert(tmp == """1, "string", 'c'""") + when T is string or T is cstring: + when T is cstring: + if x == nil: s.add "nil"; return + s.add("\"") + for c in x: + # Only ASCII chars are escaped to avoid butchering + # multibyte UTF-8 characters. + if c <= 127.char: + s.addEscapedChar(c) + else: + s.add c + s.add("\"") + elif T is char: + s.add("'") + s.addEscapedChar(x) + s.add("'") + # prevent temporary string allocation + elif T is SomeSignedInt: + s.addInt(x) + elif T is SomeFloat: + s.addFloat(x) + else: + s.add($x) import system/dollars export dollars @@ -2717,95 +2805,6 @@ when hasAlloc or defined(nimscript): when declared(initDebugger): initDebugger() -proc addEscapedChar*(s: var string, c: char) {.noSideEffect, inline.} = - ## Adds a char to string `s` and applies the following escaping: - ## - ## * replaces any ``\`` by ``\\`` - ## * replaces any ``'`` by ``\'`` - ## * replaces any ``"`` by ``\"`` - ## * replaces any ``\a`` by ``\\a`` - ## * replaces any ``\b`` by ``\\b`` - ## * replaces any ``\t`` by ``\\t`` - ## * replaces any ``\n`` by ``\\n`` - ## * replaces any ``\v`` by ``\\v`` - ## * replaces any ``\f`` by ``\\f`` - ## * replaces any ``\c`` by ``\\c`` - ## * replaces any ``\e`` by ``\\e`` - ## * replaces any other character not in the set ``{'\21..'\126'} - ## by ``\xHH`` where ``HH`` is its hexadecimal value. - ## - ## The procedure has been designed so that its output is usable for many - ## different common syntaxes. - ## - ## **Note**: This is **not correct** for producing Ansi C code! - case c - of '\a': s.add "\\a" # \x07 - of '\b': s.add "\\b" # \x08 - of '\t': s.add "\\t" # \x09 - of '\L': s.add "\\n" # \x0A - of '\v': s.add "\\v" # \x0B - of '\f': s.add "\\f" # \x0C - of '\c': s.add "\\c" # \x0D - of '\e': s.add "\\e" # \x1B - of '\\': s.add("\\\\") - of '\'': s.add("\\'") - of '\"': s.add("\\\"") - of {'\32'..'\126'} - {'\\', '\'', '\"'}: s.add(c) - else: - s.add("\\x") - const HexChars = "0123456789ABCDEF" - let n = ord(c) - s.add(HexChars[int((n and 0xF0) shr 4)]) - s.add(HexChars[int(n and 0xF)]) - -proc addQuoted*[T](s: var string, x: T) = - ## Appends `x` to string `s` in place, applying quoting and escaping - ## if `x` is a string or char. - ## - ## See `addEscapedChar <#addEscapedChar,string,char>`_ - ## for the escaping scheme. When `x` is a string, characters in the - ## range ``{\128..\255}`` are never escaped so that multibyte UTF-8 - ## characters are untouched (note that this behavior is different from - ## ``addEscapedChar``). - ## - ## The Nim standard library uses this function on the elements of - ## collections when producing a string representation of a collection. - ## It is recommended to use this function as well for user-side collections. - ## Users may overload `addQuoted` for custom (string-like) types if - ## they want to implement a customized element representation. - ## - ## .. code-block:: Nim - ## var tmp = "" - ## tmp.addQuoted(1) - ## tmp.add(", ") - ## tmp.addQuoted("string") - ## tmp.add(", ") - ## tmp.addQuoted('c') - ## assert(tmp == """1, "string", 'c'""") - when T is string or T is cstring: - s.add("\"") - for c in x: - # Only ASCII chars are escaped to avoid butchering - # multibyte UTF-8 characters. - if c <= 127.char: - s.addEscapedChar(c) - else: - s.add c - s.add("\"") - elif T is char: - s.add("'") - s.addEscapedChar(x) - s.add("'") - # prevent temporary string allocation - elif T is SomeSignedInt: - s.addInt(x) - elif T is SomeFloat: - s.addFloat(x) - elif compiles(s.add(x)): - s.add(x) - else: - s.add($x) - proc locals*(): RootObj {.magic: "Plugin", noSideEffect.} = ## Generates a tuple constructor expression listing all the local variables ## in the current scope. diff --git a/lib/system/dollars.nim b/lib/system/dollars.nim index 664f1c30d55a..c2b322641d44 100644 --- a/lib/system/dollars.nim +++ b/lib/system/dollars.nim @@ -65,6 +65,13 @@ else: return true return false +type + SomePointer = ptr | ref | pointer + +when defined(nimHasTypeIsRecursive): + proc isRecursivePointer(t: typedesc): bool {.magic: "TypeTrait".} +else: + template isRecursivePointer(t: typedesc): bool = false proc `$`*[T: tuple|object](x: T): string = ## Generic ``$`` operator for tuples that is lifted from the components @@ -75,34 +82,32 @@ proc `$`*[T: tuple|object](x: T): string = ## $(a: 23, b: 45) == "(a: 23, b: 45)" ## $() == "()" result = "(" - var firstElement = true - const isNamed = T is object or isNamedTuple(T) - when not isNamed: - var count = 0 + const isNamed = x is object or isNamedTuple(T) + var count = 0 for name, value in fieldPairs(x): - if not firstElement: result.add(", ") - when isNamed: + if count != 0: + result.add(", ") + if isNamed: result.add(name) result.add(": ") - else: - count.inc - when compiles($value): - when value isnot string and value isnot seq and compiles(value.isNil): - if value.isNil: result.add "nil" - else: result.addQuoted(value) + + when isRecursivePointer(typeof(value)): + if cast[pointer](value) == nil: + # nil can always be printed safely + result.add "nil" else: - result.addQuoted(value) - firstElement = false + # value may cycle back to origin, don't print it. + result.add("...") else: - result.add("...") - firstElement = false - when not isNamed: - if count == 1: - result.add(",") # $(1,) should print as the semantically legal (1,) - result.add(")") + result.addQuoted(value) + inc count + if not isNamed and count == 1: + result.add(",") # $(1,) should print as the semantically legal (1,) + result.add(")") proc collectionToString[T](x: T, prefix, separator, suffix: string): string = + mixin addQuoted result = prefix var firstElement = true for value in items(x): @@ -111,7 +116,7 @@ proc collectionToString[T](x: T, prefix, separator, suffix: string): string = else: result.add(separator) - when value isnot string and value isnot seq and compiles(value.isNil): + when value is SomePointer: # this branch should not be necessary if value.isNil: result.add "nil" @@ -160,3 +165,44 @@ proc `$`*[T](x: openArray[T]): string = ## .. code-block:: Nim ## $(@[23, 45].toOpenArray(0, 1)) == "[23, 45]" collectionToString(x, "[", ", ", "]") + +proc `$`*[T: ref](arg: T): string = + if arg == nil: + "nil" + else: + $arg[] + +proc `$`*(arg: pointer): string = + if arg == nil: + return "nil" + else: + # maybe print something so people know it is a pointer. + result.add "0x" + const + HexChars = "0123456789ABCDEF" + var buffer: array[16, char] + var idx = 0 + var tmp = cast[uint](arg) + while tmp > 0: + buffer[idx] = HexChars[tmp and 0xf] + tmp = tmp shr 4 + inc idx + while idx > 0: + dec idx + result.add buffer[idx] + +proc `$`*[T: ptr](arg: T): string = + $cast[pointer](arg) + +proc distinctBase(T: typedesc): typedesc {.magic: "TypeTrait".} + +proc `$`*[T: distinct](arg: T): string = + $distinctBase(T)(arg) + +proc `$`*[T: proc](arg: T): string = + if arg == nil: + return "nil" + else: + return "..." + +proc `$`*[T](arg: UncheckedArray[T]): string = "[...]" diff --git a/tests/concepts/tconcepts_issues.nim b/tests/concepts/tconcepts_issues.nim index 65f6d46604e6..96f6d27f5675 100644 --- a/tests/concepts/tconcepts_issues.nim +++ b/tests/concepts/tconcepts_issues.nim @@ -114,6 +114,7 @@ block t976: # bug #6249 type Obj2 = ref object + # Note: since everything is Printable now, this test isn't really that useful anymore. PrintAble = concept x $x is string @@ -121,7 +122,7 @@ block t976: when T is PrintAble: result = "Printable" else: result = "Non Printable" - echo Obj2() + echo toObj1(Obj2()) diff --git a/tests/destructor/tdestructor3.nim b/tests/destructor/tdestructor3.nim index b68aedce91f3..8e9f08723c62 100644 --- a/tests/destructor/tdestructor3.nim +++ b/tests/destructor/tdestructor3.nim @@ -6,11 +6,11 @@ destroy 123 destroy Foo: 123 destroy Foo: 5 -(x1: (val: ...)) +test destroy --------------- app begin -(val: ...) +test2 destroy app end ''' @@ -96,7 +96,7 @@ proc newObj2(x:int, y: float): MyObject2 = proc test = let obj2 = newObj2(1, 1.0) - echo obj2 + echo "test" test() @@ -104,12 +104,12 @@ test() #------------------------------------------------------------ # Issue #12883 -type +type TopObject = object internal: UniquePtr[int] proc deleteTop(p: ptr TopObject) = - if p != nil: + if p != nil: `=destroy`(p[]) # !!! this operation used to leak the integer deallocshared(p) @@ -117,12 +117,12 @@ proc createTop(): ptr TopObject = result = cast[ptr TopObject](allocShared0(sizeof(TopObject))) result.internal = newUniquePtr(1) -proc test2() = +proc test2() = let x = createTop() - echo $x.internal + echo "test2" deleteTop(x) -echo "---------------" +echo "---------------" echo "app begin" test2() -echo "app end" \ No newline at end of file +echo "app end" diff --git a/tests/generics/tgeneric0.nim b/tests/generics/tgeneric0.nim index ae4820f5719e..64962268cf4b 100644 --- a/tests/generics/tgeneric0.nim +++ b/tests/generics/tgeneric0.nim @@ -4,7 +4,7 @@ discard """ 0 float32 float32 -(name: "Resource 1", readers: ..., writers: ...) +(name: "Resource 1", readers: @[], writers: @[]) ''' """ diff --git a/tests/js/trefbyvar.nim b/tests/js/trefbyvar.nim index 5b168044ee44..848e0bc655c7 100644 --- a/tests/js/trefbyvar.nim +++ b/tests/js/trefbyvar.nim @@ -66,4 +66,4 @@ proc initTypeA1(a: int; b: string; c: pointer = nil): TypeA1 = result.c_impl = c let x = initTypeA1(1, "a") -doAssert($x == "(a_impl: 1, b_impl: \"a\", c_impl: ...)") +doAssert($x == "(a_impl: 1, b_impl: \"a\", c_impl: nil)") diff --git a/tests/statictypes/tstackmatrix.nim b/tests/statictypes/tstackmatrix.nim index 2509d21f8d0c..e4a753421cdb 100644 --- a/tests/statictypes/tstackmatrix.nim +++ b/tests/statictypes/tstackmatrix.nim @@ -1,5 +1,5 @@ discard """ - output: "(M: 3, N: 3, fp: ...)" + output: "(M: 3, N: 3, fp: 1.0)" """ # bug #6843 @@ -26,4 +26,4 @@ var [7'f64, 8, 9] ] m = stackMatrix(data) -echo m \ No newline at end of file +echo m diff --git a/tests/system/tostring.nim b/tests/system/tostring.nim index ea4a44417f3d..cf4ea105c7c2 100644 --- a/tests/system/tostring.nim +++ b/tests/system/tostring.nim @@ -36,8 +36,7 @@ type var foo1: Foo -# nil string should be an some point in time equal to the empty string -doAssert(($foo1)[0..9] == "(a: 0, b: ") +doAssert $foo1 == "(a: 0, b: \"\")" const data = @['a','b', '\0', 'c','d'] @@ -46,7 +45,6 @@ const # ensure same result when on VM or when at program execution doAssert dataStr == $data -import strutils # array test let arr = ['H','e','l','l','o',' ','W','o','r','l','d','!','\0'] @@ -83,9 +81,9 @@ proc stringCompare() = doAssert b == "bee" b.add g doAssert b == "bee" - c.add 123.456 + c.addFloat 123.456 doAssert c == "123.456" - d.add 123456 + d.addInt 123456 doAssert d == "123456" doAssert e == "" @@ -116,5 +114,137 @@ block: s.addQuoted a2 doAssert s == "\"fo\\\"o2\"" +type + MyType = object + a: int + b: string + + MyRef = ref MyType + MyDistinct = distinct MyType + + MyRefDistinct = ref MyDistinct + MyDistinctRef = distinct MyRef + + MyCompoundObject = object + field0: MyType + field1: MyRef + field2: MyDistinct + field3: MyRefDistinct + field4: MyDistinctRef + +block: + let tmp0 = MyType(a: 1, b: "abc") + let tmp1 = MyRef(a: 1, b: "abc") + let tmp2 = MyDistinct MyType(a: 1, b: "abc") + let tmp3 = MyRefDistinct MyRef(a: 1, b: "abc") + let tmp4 = MyDistinctRef MyRef(a: 1, b: "abc") + + let compound = MyCompoundObject( + field0: tmp0, + field1: tmp1, + field2: tmp2, + field3: tmp3, + field4: tmp4, + ) + + doAssert $tmp0 == "(a: 1, b: \"abc\")" + doAssert $tmp1 == "(a: 1, b: \"abc\")" + doAssert $tmp2 == "(a: 1, b: \"abc\")" + doAssert $tmp3 == "(a: 1, b: \"abc\")" + doAssert $tmp4 == "(a: 1, b: \"abc\")" + + doAssert $compound == "(field0: (a: 1, b: \"abc\"), field1: (a: 1, b: \"abc\"), field2: (a: 1, b: \"abc\"), field3: (a: 1, b: \"abc\"), field4: (a: 1, b: \"abc\"))" + +type + CyclicDistinctRef = distinct CyclicDistinctRefInner + CyclicDistinctRefInner = ref object + name: string + field0: CyclicDistinctRef + + # Multiple distinct stacked on top of each other. Stupid but possible. + StupidMultiDistinctRefInner = ref object + name: string + field0: StupidMultiDistinctRef + StupidMultiDistinctRefMiddle = distinct StupidMultiDistinctRefInner + StupidMultiDistinctRef = distinct StupidMultiDistinctRefMiddle + +block: + let cyclicA: CyclicDistinctRef = CyclicDistinctRef(CyclicDistinctRefInner()) + let cyclicB: CyclicDistinctRef = CyclicDistinctRef(CyclicDistinctRefInner()) + cyclicA.CyclicDistinctRefInner.name = "A" + cyclicA.CyclicDistinctRefInner.field0 = cyclicB + cyclicB.CyclicDistinctRefInner.name = "B" + cyclicB.CyclicDistinctRefInner.field0 = cyclicA + + # ensure this does not crash in an infinite loop + doAssert $cyclicA == "(name: \"A\", field0: ...)" + doAssert $cyclicB == "(name: \"B\", field0: ...)" + +block: + let cyclicA: StupidMultiDistinctRef = StupidMultiDistinctRef(StupidMultiDistinctRefInner()) + let cyclicB: StupidMultiDistinctRef = StupidMultiDistinctRef(StupidMultiDistinctRefInner()) + cyclicA.StupidMultiDistinctRefInner.name = "A" + cyclicA.StupidMultiDistinctRefInner.field0 = cyclicB + cyclicB.StupidMultiDistinctRefInner.name = "B" + cyclicB.StupidMultiDistinctRefInner.field0 = cyclicA + + # ensure this does not crash in an infinite loop + doAssert $cyclicA == "(name: \"A\", field0: ...)" + doAssert $cyclicB == "(name: \"B\", field0: ...)" + +type + CyclicStuff = ref object + name: string + children: seq[CyclicStuff] + + TreeStuff = object + name: string + children: seq[TreeStuff] + + + CyclicStuff2 = ref object of RootObj + name: string + child: ref RootObj + +block: + let cycle1 = CyclicStuff(name: "name1") + cycle1.children.add cycle1 # very simple cycle + doAssert $cycle1 == "(name: \"name1\", children: ...)" + + var tree = TreeStuff(name: "name1") + var cpy = tree + tree.children.add cpy + cpy = tree + tree.children.add cpy + + doAssert $tree == "(name: \"name1\", children: @[(name: \"name1\", children: @[]), (name: \"name1\", children: @[(name: \"name1\", children: @[])])])" + + let cycle2 = CyclicStuff2(name: "name3") + cycle2.child = cycle2 + +type + MyTypeWithProc = ref object + name: string + fun: proc(arg: int): int + cstr: cstring + data: UncheckedArray[byte] + +block: + let tmp = MyTypeWithProc(name: "Some Name") + doAssert $tmp == "(name: \"Some Name\", fun: nil, cstr: nil, data: [...])" + +import strutils + +block: + + type Foo = object + age: int + s: string + internal: seq[ptr Foo] + + var foo = Foo(age: 20, s: "bob") + foo.internal = @[foo.addr] + doAssert contains($foo, "(age: 20, s: \"bob\", internal: @[0x") + echo "DONE: tostring.nim" diff --git a/tests/types/tissues_types.nim b/tests/types/tissues_types.nim index 7ed0547bfc7e..72f80411ba8d 100644 --- a/tests/types/tissues_types.nim +++ b/tests/types/tissues_types.nim @@ -6,8 +6,8 @@ true ptr Foo (member: "hello world") (member: 123.456) -(member: "hello world", x: ...) -(member: 123.456, x: ...) +(member: "hello world", x: nil) +(member: 123.456, x: nil) 0 false '''