diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index fc3b520374..dcde4e2707 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -1587,20 +1587,24 @@ proc genInOp(p: BProc, e: PNode, d: var TLoc) = e.sons[2] initLocExpr(p, ea, a) initLoc(b, locExpr, e, OnUnknown) - b.r = rope("(") var length = sonsLen(e.sons[1]) - for i in countup(0, length - 1): - let it = e.sons[1].sons[i] - if it.kind == nkRange: - initLocExpr(p, it.sons[0], x) - initLocExpr(p, it.sons[1], y) - addf(b.r, "$1 >= $2 && $1 <= $3", - [rdCharLoc(a), rdCharLoc(x), rdCharLoc(y)]) - else: - initLocExpr(p, it, x) - addf(b.r, "$1 == $2", [rdCharLoc(a), rdCharLoc(x)]) - if i < length - 1: add(b.r, " || ") - add(b.r, ")") + if length > 0: + b.r = rope("(") + for i in countup(0, length - 1): + let it = e.sons[1].sons[i] + if it.kind == nkRange: + initLocExpr(p, it.sons[0], x) + initLocExpr(p, it.sons[1], y) + addf(b.r, "$1 >= $2 && $1 <= $3", + [rdCharLoc(a), rdCharLoc(x), rdCharLoc(y)]) + else: + initLocExpr(p, it, x) + addf(b.r, "$1 == $2", [rdCharLoc(a), rdCharLoc(x)]) + if i < length - 1: add(b.r, " || ") + add(b.r, ")") + else: + # handle the case of an empty set + b.r = rope("0") putIntoDest(p, d, e, b.r) else: assert(e.sons[1].typ != nil) diff --git a/compiler/lexer.nim b/compiler/lexer.nim index cd2778d3c7..375bf227dd 100644 --- a/compiler/lexer.nim +++ b/compiler/lexer.nim @@ -344,7 +344,8 @@ proc getNumber(L: var TLexer, result: var TToken) = if buf[pos] == '_': if buf[pos+1] notin chars: lexMessage(L, errGenerated, - "only single underscores may occur in a token: '__' is invalid") + "only single underscores may occur in a token and token may not " & + "end with an underscore: e.g. '1__1' and '1_' are invalid") break add(tok.literal, '_') inc(pos) diff --git a/compiler/sem.nim b/compiler/sem.nim index 0adcccfbe5..b242e4db62 100644 --- a/compiler/sem.nim +++ b/compiler/sem.nim @@ -76,7 +76,7 @@ template semIdeForTemplateOrGeneric(c: PContext; n: PNode; proc fitNodePostMatch(c: PContext, formal: PType, arg: PNode): PNode = result = arg let x = result.skipConv - if x.kind in {nkPar, nkTupleConstr} and formal.kind != tyExpr: + if x.kind in {nkPar, nkTupleConstr, nkCurly} and formal.kind != tyExpr: changeType(c, x, formal, check=true) else: result = skipHiddenSubConv(result) diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index 5d6eaf6529..f5f5915d42 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -393,6 +393,8 @@ proc semIs(c: PContext, n: PNode, flags: TExprFlags): PNode = # When the right-hand side is an explicit type, we must # not allow regular values to be matched against the type: liftLhs = false + else: + n.sons[2] = semExpr(c, n[2]) var lhsType = n[1].typ if lhsType.kind != tyTypeDesc: @@ -906,19 +908,6 @@ proc buildEchoStmt(c: PContext, n: PNode): PNode = proc semExprNoType(c: PContext, n: PNode): PNode = result = semExpr(c, n, {efWantStmt}) - # make an 'if' expression an 'if' statement again for backwards - # compatibility (.discardable was a bad idea!); bug #6980 - var isStmt = false - if result.kind == nkIfExpr: - isStmt = true - for condActionPair in result: - let action = condActionPair.lastSon - if not implicitlyDiscardable(action) and not - endsInNoReturn(action): - isStmt = false - if isStmt: - result.kind = nkIfStmt - result.typ = nil discardCheck(c, result) proc isTypeExpr(n: PNode): bool = diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index 0218096d6e..e3c750f5b7 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -54,7 +54,8 @@ template macroToExpand(s): untyped = s.kind in {skMacro, skTemplate} and (s.typ.len == 1 or sfAllUntyped in s.flags) template macroToExpandSym(s): untyped = - s.kind in {skMacro, skTemplate} and (s.typ.len == 1) and not fromDotExpr + sfCustomPragma notin s.flags and s.kind in {skMacro, skTemplate} and + (s.typ.len == 1) and not fromDotExpr template isMixedIn(sym): bool = let s = sym diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index 945bcd9e17..bf004a5314 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -111,13 +111,15 @@ proc semExprBranchScope(c: PContext, n: PNode): PNode = const skipForDiscardable = {nkIfStmt, nkIfExpr, nkCaseStmt, nkOfBranch, nkElse, nkStmtListExpr, nkTryStmt, nkFinally, nkExceptBranch, - nkElifBranch, nkElifExpr, nkElseExpr, nkBlockStmt, nkBlockExpr} + nkElifBranch, nkElifExpr, nkElseExpr, nkBlockStmt, nkBlockExpr, + nkHiddenStdConv} proc implicitlyDiscardable(n: PNode): bool = var n = n while n.kind in skipForDiscardable: n = n.lastSon - result = isCallExpr(n) and n.sons[0].kind == nkSym and - sfDiscardable in n.sons[0].sym.flags + result = n.kind == nkRaiseStmt or + (isCallExpr(n) and n.sons[0].kind == nkSym and + sfDiscardable in n.sons[0].sym.flags) proc fixNilType(c: PContext; n: PNode) = if isAtom(n): @@ -132,11 +134,8 @@ proc discardCheck(c: PContext, result: PNode) = if c.matchedConcept != nil: return if result.typ != nil and result.typ.kind notin {tyStmt, tyVoid}: if implicitlyDiscardable(result): - var n = result - result.typ = nil - while n.kind in skipForDiscardable: - n = n.lastSon - n.typ = nil + var n = newNodeI(nkDiscardStmt, result.info, 1) + n[0] = result elif result.typ.kind != tyError and c.config.cmd != cmdInteractive: var n = result while n.kind in skipForDiscardable: n = n.lastSon diff --git a/compiler/types.nim b/compiler/types.nim index c5af855cc5..4d3f99c310 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -623,7 +623,7 @@ proc firstOrd*(conf: ConfigRef; t: PType): BiggestInt = assert(t.n.sons[0].kind == nkSym) result = t.n.sons[0].sym.position of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, - tyStatic, tyInferred, tyUserTypeClassInst: + tyStatic, tyInferred, tyUserTypeClasses: result = firstOrd(conf, lastSon(t)) of tyOrdinal: if t.len > 0: result = firstOrd(conf, lastSon(t)) @@ -642,7 +642,7 @@ proc firstFloat*(t: PType): BiggestFloat = getFloatValue(t.n.sons[0]) of tyVar: firstFloat(t.sons[0]) of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, - tyStatic, tyInferred: + tyStatic, tyInferred, tyUserTypeClasses: firstFloat(lastSon(t)) else: internalError(newPartialConfigRef(), "invalid kind for firstFloat(" & $t.kind & ')') @@ -679,7 +679,7 @@ proc lastOrd*(conf: ConfigRef; t: PType; fixedUnsigned = false): BiggestInt = assert(t.n.sons[sonsLen(t.n) - 1].kind == nkSym) result = t.n.sons[sonsLen(t.n) - 1].sym.position of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, - tyStatic, tyInferred: + tyStatic, tyInferred, tyUserTypeClasses: result = lastOrd(conf, lastSon(t)) of tyProxy: result = 0 of tyOrdinal: @@ -699,7 +699,7 @@ proc lastFloat*(t: PType): BiggestFloat = assert(t.n.kind == nkRange) getFloatValue(t.n.sons[1]) of tyGenericInst, tyDistinct, tyTypeDesc, tyAlias, tySink, - tyStatic, tyInferred: + tyStatic, tyInferred, tyUserTypeClasses: lastFloat(lastSon(t)) else: internalError(newPartialConfigRef(), "invalid kind for lastFloat(" & $t.kind & ')') @@ -707,7 +707,7 @@ proc lastFloat*(t: PType): BiggestFloat = proc lengthOrd*(conf: ConfigRef; t: PType): BiggestInt = - case t.kind + case t.skipTypes(tyUserTypeClasses).kind of tyInt64, tyInt32, tyInt: result = lastOrd(conf, t) of tyDistinct: result = lengthOrd(conf, t.sons[0]) else: @@ -717,7 +717,7 @@ proc lengthOrd*(conf: ConfigRef; t: PType): BiggestInt = if last == high(BiggestInt) and first <= 0: result = last else: - result = lastOrd(conf, t) - firstOrd(conf, t) + 1 + result = last - first + 1 # -------------- type equality ----------------------------------------------- diff --git a/compiler/vm.nim b/compiler/vm.nim index b16eb0fd40..7f0dd80edc 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -209,7 +209,7 @@ proc writeField(n: var PNode, x: TFullReg) = of rkNone: discard of rkInt: n.intVal = x.intVal of rkFloat: n.floatVal = x.floatVal - of rkNode: n = x.node + of rkNode: n = copyValue(x.node) of rkRegisterAddr: writeField(n, x.regAddr[]) of rkNodeAddr: n = x.nodeAddr[] @@ -622,6 +622,13 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = stackTrace(c, tos, pc, errNilAccess) regs[ra].nodeAddr[][] = n[] regs[ra].nodeAddr[].flags.incl nfIsRef + # `var object` parameters are sent as rkNodeAddr. When they are mutated + # vmgen generates opcWrDeref, which means that we must dereference + # twice. + # TODO: This should likely be handled differently in vmgen. + elif (nfIsRef notin regs[ra].nodeAddr[].flags and + nfIsRef notin n.flags): + regs[ra].nodeAddr[][] = n[] else: regs[ra].nodeAddr[] = n of rkRegisterAddr: regs[ra].regAddr[] = regs[rc] @@ -912,6 +919,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = if a.kind == nkSym: regs[ra].node = if a.sym.ast.isNil: newNode(nkNilLit) else: copyTree(a.sym.ast) + regs[ra].node.flags.incl nfIsRef else: stackTrace(c, tos, pc, "node is not a symbol") of opcEcho: @@ -1210,8 +1218,14 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = of opcIsNil: decodeB(rkInt) let node = regs[rb].node - regs[ra].intVal = ord(node.kind == nkNilLit or - (node.kind in {nkStrLit..nkTripleStrLit} and node.strVal.isNil)) + regs[ra].intVal = ord( + # Note that `nfIsRef` + `nkNilLit` represents an allocated + # reference with the value `nil`, so `isNil` should be false! + (node.kind == nkNilLit and nfIsRef notin node.flags) or + (node.kind in {nkStrLit..nkTripleStrLit} and node.strVal.isNil) or + (not node.typ.isNil and node.typ.kind == tyProc and + node.typ.callConv == ccClosure and node.sons[0].kind == nkNilLit and + node.sons[1].kind == nkNilLit)) of opcNBindSym: decodeBx(rkNode) regs[ra].node = copyTree(c.constants.sons[rbx]) @@ -1462,6 +1476,7 @@ proc rawExecute(c: PCtx, start: int, tos: PStackFrame): TFullReg = else: regs[ra].node = newNodeI(nkIdent, c.debug[pc]) regs[ra].node.ident = getIdent(c.cache, regs[rb].node.strVal) + regs[ra].node.flags.incl nfIsRef of opcSetType: if regs[ra].kind != rkNode: internalError(c.config, c.debug[pc], "cannot set type") diff --git a/lib/impure/nre.nim b/lib/impure/nre.nim index 6058128ddf..889210f627 100644 --- a/lib/impure/nre.nim +++ b/lib/impure/nre.nim @@ -654,17 +654,17 @@ template replaceImpl(str: string, pattern: Regex, proc replace*(str: string, pattern: Regex, subproc: proc (match: RegexMatch): string): string = - ## Replaces each match of Regex in the string with ``sub``, which should + ## Replaces each match of Regex in the string with ``subproc``, which should ## never be or return ``nil``. ## - ## If ``sub`` is a ``proc (RegexMatch): string``, then it is executed with + ## If ``subproc`` is a ``proc (RegexMatch): string``, then it is executed with ## each match and the return value is the replacement value. ## - ## If ``sub`` is a ``proc (string): string``, then it is executed with the + ## If ``subproc`` is a ``proc (string): string``, then it is executed with the ## full text of the match and and the return value is the replacement ## value. ## - ## If ``sub`` is a string, the syntax is as follows: + ## If ``subproc`` is a string, the syntax is as follows: ## ## - ``$$`` - literal ``$`` ## - ``$123`` - capture number ``123`` diff --git a/lib/pure/osproc.nim b/lib/pure/osproc.nim index e8bca4bdd1..f86acfc492 100644 --- a/lib/pure/osproc.nim +++ b/lib/pure/osproc.nim @@ -884,11 +884,9 @@ elif not defined(useNimRtl): chck posix_spawn_file_actions_adddup2(fops, data.pStdin[readIdx], readIdx) chck posix_spawn_file_actions_addclose(fops, data.pStdout[readIdx]) chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], writeIdx) - chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx]) if (poStdErrToStdOut in data.options): + chck posix_spawn_file_actions_addclose(fops, data.pStderr[readIdx]) chck posix_spawn_file_actions_adddup2(fops, data.pStdout[writeIdx], 2) - else: - chck posix_spawn_file_actions_adddup2(fops, data.pStderr[writeIdx], 2) var res: cint if data.workingDir.len > 0: diff --git a/lib/pure/strformat.nim b/lib/pure/strformat.nim index 36404cdf7c..247b9ec5cb 100644 --- a/lib/pure/strformat.nim +++ b/lib/pure/strformat.nim @@ -258,7 +258,9 @@ template callFormat(res, arg) {.dirty.} = # workaround in order to circumvent 'strutils.format' which matches # too but doesn't adhere to our protocol. res.add arg - elif compiles(format(arg, res)): + elif compiles(format(arg, res)) and + # Check if format returns void + not (compiles do: discard format(arg, res)): format(arg, res) elif compiles(format(arg)): res.add format(arg) @@ -684,6 +686,9 @@ when isMainModule: var nullTime: DateTime check &"{nullTime:yyyy-mm-dd}", "0000-00-00" + var tm = fromUnix(0) + discard &"{tm}" + # Unicode string tests check &"""{"αβγ"}""", "αβγ" check &"""{"αβγ":>5}""", " αβγ" diff --git a/lib/pure/times.nim b/lib/pure/times.nim index 7fd60b818f..a134faef2d 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -489,18 +489,16 @@ proc toParts*(dur: Duration): DurationParts = result[unit] = quantity -proc stringifyUnit*(value: int | int64, unit: string): string = +proc stringifyUnit(value: int | int64, unit: TimeUnit): string = ## Stringify time unit with it's name, lowercased - runnableExamples: - doAssert stringifyUnit(2, "Seconds") == "2 seconds" - doAssert stringifyUnit(1, "Years") == "1 year" + let strUnit = $unit result = "" result.add($value) result.add(" ") if abs(value) != 1: - result.add(unit.toLowerAscii()) + result.add(strUnit.toLowerAscii()) else: - result.add(unit[0..^2].toLowerAscii()) + result.add(strUnit[0..^2].toLowerAscii()) proc humanizeParts(parts: seq[string]): string = ## Make date string parts human-readable @@ -530,7 +528,7 @@ proc `$`*(dur: Duration): string = for unit in countdown(Weeks, Nanoseconds): let quantity = numParts[unit] if quantity != 0.int64: - parts.add(stringifyUnit(quantity, $unit)) + parts.add(stringifyUnit(quantity, unit)) result = humanizeParts(parts) @@ -1024,7 +1022,7 @@ proc `$`*(ti: TimeInterval): string = var tiParts = toParts(ti) for unit in countdown(Years, Nanoseconds): if tiParts[unit] != 0: - parts.add(stringifyUnit(tiParts[unit], $unit)) + parts.add(stringifyUnit(tiParts[unit], unit)) result = humanizeParts(parts) diff --git a/tests/concepts/t7952.nim b/tests/concepts/t7952.nim new file mode 100644 index 0000000000..96ff47e4a7 --- /dev/null +++ b/tests/concepts/t7952.nim @@ -0,0 +1,12 @@ +discard """ + output: 5 +""" + +type + HasLen = concept iter + len(iter) is int + +proc echoLen(x: HasLen) = + echo len(x) + +echoLen([1, 2, 3, 4, 5]) diff --git a/tests/exprs/tstmtexprs.nim b/tests/exprs/tstmtexprs.nim index 577f314ec2..e91c204586 100644 --- a/tests/exprs/tstmtexprs.nim +++ b/tests/exprs/tstmtexprs.nim @@ -151,3 +151,13 @@ if true: fooBool() else: raise newException(ValueError, "argh") + +# bug #5374 + +proc test1(): int64 {.discardable.} = discard +proc test2(): int {.discardable.} = discard + +if true: + test1() +else: + test2() diff --git a/tests/iter/t8041.nim b/tests/iter/t8041.nim new file mode 100644 index 0000000000..c87134cfca --- /dev/null +++ b/tests/iter/t8041.nim @@ -0,0 +1,7 @@ +iterator xy[T](a: T, b: set[T]): T = + if a in b: + yield a + +for a in xy(1'i8, {}): + for b in xy(a, {}): + echo a diff --git a/tests/pragmas/tcustom_pragma.nim b/tests/pragmas/tcustom_pragma.nim index 33a4a7e65e..cd1edccf67 100644 --- a/tests/pragmas/tcustom_pragma.nim +++ b/tests/pragmas/tcustom_pragma.nim @@ -137,4 +137,12 @@ block: static: assert hasIntSerKey assert strSerKey == "string" - assert nestedItemDefVal == "Nimmers of the world, unite!" \ No newline at end of file + assert nestedItemDefVal == "Nimmers of the world, unite!" + +block: + template simpleAttr {.pragma.} + + type Annotated {.simpleAttr.} = object + + proc generic_proc[T]() = + assert Annotated.hasCustomPragma(simpleAttr) \ No newline at end of file diff --git a/tests/sets/t2669.nim b/tests/sets/t2669.nim new file mode 100644 index 0000000000..6b8eb0f54c --- /dev/null +++ b/tests/sets/t2669.nim @@ -0,0 +1,6 @@ +discard """ +line: 6 +errormsg: "cannot convert 6 to range 1..5(int8)" +""" + +var c: set[range[1i8..5i8]] = {1i8, 2i8, 6i8} diff --git a/tests/vm/tvmmisc.nim b/tests/vm/tvmmisc.nim index 80f5aeee00..6a8084647f 100644 --- a/tests/vm/tvmmisc.nim +++ b/tests/vm/tvmmisc.nim @@ -89,4 +89,62 @@ block: proc f(size: int): int = var some = newStringOfCap(size) result = size - doAssert f(4) == 4 \ No newline at end of file + doAssert f(4) == 4 + +# #6689 +block: + static: + proc foo(): int = 0 + var f: proc(): int + doAssert f.isNil + f = foo + doAssert(not f.isNil) + +block: + static: + var x: ref ref int + new(x) + doAssert(not x.isNil) + +# #7871 +static: + type Obj = object + field: int + var s = newSeq[Obj](1) + var o = Obj() + s[0] = o + o.field = 2 + doAssert s[0].field == 0 + +# #8125 +static: + let def_iter_var = ident("it") + +# #8142 +static: + type Obj = object + names: string + + proc pushName(o: var Obj) = + var s = "" + s.add("FOOBAR") + o.names.add(s) + + var o = Obj() + o.names = "" + o.pushName() + o.pushName() + doAssert o.names == "FOOBARFOOBAR" + +# #8154 +import parseutils + +static: + type Obj = object + i: int + + proc foo(): Obj = + discard parseInt("1", result.i, 0) + + static: + doAssert foo().i == 1