Compare commits

..

10 Commits

Author SHA1 Message Date
ringabout
15d1fafc71 progress 2026-08-05 22:27:37 +08:00
ringabout
f24b316f13 adds more test cases 2026-08-05 21:55:45 +08:00
ringabout
52b9b1c5ca fixes #24848; presumably-invalid discard R[[R[int]]]() with type R[C] = ref object / b: C generates Error: internal erro 2026-08-05 20:50:31 +08:00
Ryan McConnell
2d81149294 unwrap typedesc in semSet to enable stuff like set[T.distinctBase] (#25924)
`distinctBase` results in typedesc, so `set[T.distinctBase]` received
`typedesc[range[...]]` as its element type, which `isOrdinalType`
rejects. Strip the wrapper in `semSet` before storing the element type
and checking ordinality.

Also add `tyFromExpr` to the deferred-check set so the error doesn't
fire prematurely inside generic bodies - same pattern already used by
`semArray`.
2026-07-26 18:08:44 +02:00
pacien
0021205854 std/xmltree/constructor macro: fix quoting in output (#26039) (#26040)
`toStrLit()` uses `repr()` internally, which forwards quotes and messes
with dashes in the output. Let's use `newStrLitNode()` directly instead.

GitHub: fixes https://github.com/nim-lang/Nim/issues/26039
2026-07-25 17:08:45 +02:00
SirOlaf
f17755782a Asyncdispatch: Process callbacks before timers (CI issue) (#26032)
Should fix
https://github.com/nim-lang/Nim/blob/devel/tests/async/tasyncclosestall.nim
(the flaky one) in CI.

Previously CI was somehow slow enough to race on completion through
multiple callback layers.

Also increased the message size to hopefully fill the socket's buffer
quicker
2026-07-24 22:33:56 +02:00
Tomohiro
9bc0887755 makes testament.nim compiles with --experimental:strictDefs (#26037) 2026-07-24 22:32:23 +02:00
ringabout
99a696e0c4 fixes #26010; Double destroy with {.cursor.} (#26031)
fixes #26010

Cursors do not own their values and therefore cannot transfer ownership
through move.
Reject move(cursor) during semantic analysis and share the
cursor-location check
between semantic analysis and destructor injection.
2026-07-24 14:07:15 +02:00
cryo2010
8e8f8de1ab fix: exception leak in closure iterator typed except branches (#23615) (#26034)
Fixes #23615

## Root cause

The leak does not require async at all -- this minimal closure iterator
leaks the exception and its stacktrace seq under ARC/ORC:

```nim
iterator it(): int {.closure.} =
  try:
    yield 1                              # try spanning a yield => closureiters transform
    raise newException(ValueError, "x")
  except ValueError:                     # typed except => generated `of` check
    discard
  yield 2
```

A bare `except:` does not leak; a *typed* `except` does:

1. `collectExceptState` in `compiler/closureiters.nim` generates the
except-branch type check as `of(getCurrentException(), T)`, using the
raw generic magic sym from `getSysMagic("of", mOf)`.
2. `injectdestructors` skips call arguments whose *formal* parameter
type is `isCompileTimeOnly`, and the raw generic `of` sym's formal
params are `tyGenericParam` so both arguments of the generated `of` call
are never processed.
3. `getCurrentException()` increfs `currException` via `=copy` into its
result. Since the arc pass never wraps that owned temp in a destroy
(`--expandArc` shows the condition left untouched, while a user-written
`if f() of ValueError` in the same iterator gets a `:tmpD` +
`=destroy`), the caught exception's refcount stays +1 forever.

Every `try: await x() except SomeError` in async code has this shape, so
each caught async exception leaked once.

## Fix

The state-machine wrapper already stores the active exception in the
`:curExc` env field before jumping to the except landing state, and
`currException == :curExc` on every path into that state. The generated
condition now references the env field via `ctx.newCurExcAccess()`
instead of calling `getCurrentException()` again -- no ownership
transfer, no temp to destroy, one fewer runtime call.

Note: the underlying `injectdestructors` behavior (skipping args of
calls whose formal params are raw `tyGenericParam`, e.g. from
`getSysMagic`) is a separate latent gap that could affect other
compiler-generated code; it is intentionally left untouched here.

## Valgrind, before and after

Exact code and command from the issue, on Linux (Valgrind 3.19):

```
nim c -d:danger --mm:orc --debugger:native --threads:off -d:useMalloc bug.nim
valgrind --leak-check=full --show-leak-kinds=all ./bug
```

Before (devel):

```
==14663== HEAP SUMMARY:
==14663==     in use at exit: 136 bytes in 2 blocks
==14663==   total heap usage: 22 allocs, 20 frees, 116,466 bytes allocated
==14663==
==14663== 56 bytes in 1 blocks are indirectly lost in loss record 1 of 2
==14663==    at 0x488A1C4: realloc (vg_replace_malloc.c:1437)
==14663==    by 0x10C663: prepareSeqAddUninit (seqs_v2.nim:212)
==14663==    by 0x10CF6F: raiseExceptionEx (excpt.nim:538)
==14663==    by 0x11661F: amain::amainX20X28AsyncX29_(Future<void>) (bug.nim:10)
==14663==    ...
==14663==
==14663== 136 (80 direct, 56 indirect) bytes in 1 blocks are definitely lost in loss record 2 of 2
==14663==    at 0x48850C8: malloc (vg_replace_malloc.c:381)
==14663==    by 0x10C8E3: nimNewObj (arc.nim:122)
==14663==    by 0x115403: err::errX20X28AsyncX29_(Future<void>) (asyncmacro.nim:274)
==14663==    by 0x116027: err::errNimAsyncContinue(Future<void>, ClosureIt<void>) (asyncmacro.nim:44)
==14663==    by 0x1163D3: bug::err (bug.nim:3)
==14663==    ...
==14663==
==14663== LEAK SUMMARY:
==14663==    definitely lost: 80 bytes in 1 blocks
==14663==    indirectly lost: 56 bytes in 1 blocks
==14663==      possibly lost: 0 bytes in 0 blocks
==14663==    still reachable: 0 bytes in 0 blocks
==14663== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
```

After (this PR):

```
==14675== HEAP SUMMARY:
==14675==     in use at exit: 0 bytes in 0 blocks
==14675==   total heap usage: 22 allocs, 22 frees, 116,466 bytes allocated
==14675==
==14675== All heap blocks were freed -- no leaks are possible
==14675==
==14675== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
```

## Testing

- New `tests/async/t23615.nim` (modeled on `t23212.nim`: `valgrind:
true` + alloc-stats assertion) covers both the pure closure-iterator
form and the async form from the issue, with the caught exception looped
50x so the leak blows well past the slack threshold. It passes with this
PR and fails against devel.
- Testament categories `async`, `arc`, `iter`, `exception` all pass with
the patched compiler (323 tests).
- Behavior is unchanged on a sanity program covering multi-branch
dispatch, `as e` binding, nested try, and re-raise across yields: output
is byte-identical to devel; the patched build just frees 2 more blocks
per caught exception.
2026-07-24 14:06:27 +02:00
Andreas Rumpf
cd3e9a46b2 run async tests under --mm:yrc (#26033) 2026-07-24 14:04:45 +02:00
27 changed files with 285 additions and 153 deletions

View File

@@ -336,9 +336,14 @@ proc collectExceptState(ctx: var Ctx, n: PNode): PNode {.inline.} =
var cond: PNode = nil
for i in 0..<c.len - 1:
assert(c[i].kind == nkType)
# Use the :curExc env field (set by the wrapper before entering the
# except landing state) instead of calling getCurrentException():
# injectdestructors does not process the args of this raw generic
# `of` magic call, so an owning getCurrentException() temp would
# never be destroyed and the caught exception would leak (#23615).
let nextCond = newTreeIT(nkCall, c.info, ctx.g.getSysType(c.info, tyBool),
newSymNode(g.getSysMagic(c.info, "of", mOf)),
g.callCodegenProc("getCurrentException"),
ctx.newCurExcAccess(),
c[i])
cond = if cond.isNil: nextCond

View File

@@ -24,7 +24,7 @@ import std/[strtabs, tables, strutils, intsets]
when defined(nimPreviewSlimSystem):
import std/assertions
from trees import exprStructuralEquivalent, getRoot, whichPragma, getPotentialWrites
from trees import exprStructuralEquivalent, getRoot, isCursor, whichPragma, getPotentialWrites
type
Con = object
@@ -180,17 +180,6 @@ proc isFirstWrite(n: PNode; c: var Con): bool =
let m = skipConvDfa(n)
result = nfFirstWrite in m.flags
proc isCursor(n: PNode): bool =
case n.kind
of nkSym:
sfCursor in n.sym.flags
of nkDotExpr:
isCursor(n[1])
of nkCheckedFieldExpr:
isCursor(n[0])
else:
false
template isFullyUnpackedTuple(n: PNode): bool =
## we move out all elements of unpacked tuples,
## hence unpacked tuples themselves don't need to be destroyed

View File

@@ -94,11 +94,21 @@ proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
body.add genBuiltin(c, mWasMoved, "wasMoved", x)
proc genAddr(c: var TLiftCtx; x: PNode): PNode =
if x.kind == nkHiddenDeref:
# These synthesized addresses are always passed to codegen procs that expect a
# genuine pointer (nimAsgnYrc, nimSinkYrc, destructors, ...). `addr(deref x)`
# collapses to `x` only when `x` is a real pointer; on the C++ backend a `var`
# parameter is a C++ reference, so we must keep the `nkHiddenAddr` to actually
# take its address (`&dest`) instead of passing the reference's value. Likewise
# `tfVarIsPtr` keeps the C++ backend from lowering the synthesized address back
# to a reference and dropping the `&` (e.g. a closure's `tyPointer` env). See
# #26026 CI (yrc + cpp).
if x.kind == nkHiddenDeref and c.g.config.backend != backendCpp:
checkSonsLen(x, 1, c.g.config)
result = x[0]
else:
result = newNodeIT(nkHiddenAddr, x.info, makeVarType(x.typ.owner, x.typ, c.idgen))
let addrTyp = makeVarType(x.typ.owner, x.typ, c.idgen)
addrTyp.incl tfVarIsPtr
result = newNodeIT(nkHiddenAddr, x.info, addrTyp)
result.add x
proc genWhileLoop(c: var TLiftCtx; i, dest: PNode): PNode =

View File

@@ -108,16 +108,6 @@ proc fitNodePostMatch(c: PContext, formal: PType, arg: PNode): PNode =
markUsed(c, a.info, a[0].sym)
template isAutoReturnType(t: PType): bool =
# `auto` return types are copied and marked so they are not generic params.
t.kind == tyAnything and tfRetType in t.flags
template isUnresolvedAutoReturnType(c: PContext; t: PType): bool =
# During return-type inference a recursive call has the routine's exact
# `auto` placeholder type. It contributes no type information of its own.
c.p != nil and c.p.owner != nil and c.p.owner.typ != nil and
c.p.owner.typ.returnType == t and isAutoReturnType(t)
proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode =
if arg.typ.isNil:
localError(c.config, arg.info, "expression has no type: " &
@@ -135,10 +125,6 @@ proc fitNode(c: PContext, formal: PType, arg: PNode; info: TLineInfo): PNode =
if sameType(ch.typ.skipTypes({tyVar, tyLent}), formal):
return ch
typeMismatch(c.config, info, formal, arg.typ, arg)
elif isUnresolvedAutoReturnType(c, arg.typ):
# A concrete sibling branch supplies the missing type for this branch.
result = arg
changeType(c, result, formal, check=true)
else:
result = indexTypesMatch(c, formal, arg.typ, arg)
if result == nil:
@@ -172,10 +158,8 @@ proc commonType*(c: PContext; x, y: PType): PType =
var a = skipTypes(x, {tyGenericInst, tyAlias, tySink})
var b = skipTypes(y, {tyGenericInst, tyAlias, tySink})
result = x
# Recursive calls cannot contribute to their own `auto` return type, so let
# the other branch determine the common type when it has concrete evidence.
if a.kind in {tyUntyped, tyNil} or isUnresolvedAutoReturnType(c, a): result = y
elif b.kind in {tyUntyped, tyNil} or isUnresolvedAutoReturnType(c, b): result = x
if a.kind in {tyUntyped, tyNil}: result = y
elif b.kind in {tyUntyped, tyNil}: result = x
elif a.kind == tyTyped: result = a
elif b.kind == tyTyped: result = b
elif a.kind == tyTypeDesc:

View File

@@ -924,11 +924,6 @@ proc semResolvedCall(c: PContext, x: var TCandidate,
result[0] = newSymNode(finalCallee, getCallLineInfo(result[0]))
if finalCallee.magic notin {mArrGet, mArrPut}:
result.typ = finalCallee.typ.returnType
# Remember that this body contains a self-call still sharing its unresolved
# `auto` placeholder; a later concrete return must resolve that placeholder.
if c.p != nil and result.typ != nil and finalCallee == c.p.owner and
isAutoReturnType(result.typ):
c.p.hasUnresolvedAutoCall = true
updateDefaultParams(c, result)
proc canDeref(n: PNode): bool {.inline.} =

View File

@@ -43,7 +43,6 @@ type
mapping*: SymMapping
caseContext*: seq[tuple[n: PNode, idx: int]]
localBindStmts*: seq[PNode]
hasUnresolvedAutoCall*: bool # a self-call still uses the `auto` return placeholder
TMatchedConcept* = object
candidateType*: PType

View File

@@ -2125,15 +2125,6 @@ proc semAsgn(c: PContext, n: PNode; mode=asgnNormal): PNode =
internalAssert c.config, c.p.resultSym != nil
# Make sure the type is valid for the result variable
typeAllowedCheck(c, n.info, rhsTyp, skResult)
# Earlier self-calls retain the old placeholder pointer. Resolve it
# in place as an alias before the routine switches to the concrete
# type, so those already-typed calls see the inferred type too.
if c.p.hasUnresolvedAutoCall and not rhsTyp.isMetaType and
isAutoReturnType(lhs.sym.typ):
let resolved = newTypeS(tyAlias, c)
rawAddSon(resolved, rhsTyp)
assignType(lhs.sym.typ, resolved)
c.p.hasUnresolvedAutoCall = false
lhs.typ = rhsTyp
c.p.resultSym.typ = rhsTyp
c.p.owner.typ.setReturnType rhsTyp
@@ -2205,11 +2196,7 @@ proc semProcBody(c: PContext, n: PNode; expectedType: PType = nil): PNode =
" flags=", c.p.resultSym.typ.flags,
" uid=", c.p.resultSym.typ.uniqueId.module, ".", c.p.resultSym.typ.uniqueId.item,
" state=", c.p.resultSym.typ.state
# With no concrete return, the recursive placeholder is still circular.
if c.p.hasUnresolvedAutoCall:
localError(c.config, c.p.resultSym.info, errCannotInferReturnType %
c.p.owner.name.s)
elif isEmptyType(result.typ):
if isEmptyType(result.typ):
# we inferred a 'void' return type:
c.p.resultSym.typ = errorType(c)
c.p.owner.typ.setReturnType nil

View File

@@ -693,5 +693,10 @@ proc magicsAfterOverloadResolution(c: PContext, n: PNode,
if n[1].kind in {nkStmtListExpr, nkBlockExpr,
nkIfExpr, nkCaseStmt, nkTryStmt}:
localError(c.config, n.info, "Nested expressions cannot be moved: '" & $n[1] & "'")
of mMove:
result = n
if isCursor(n[1]):
localError(c.config, n.info, errFailedMove,
"cannot move cursor '" & $n[1] & "'; a cursor does not own its value")
else:
result = n

View File

@@ -219,9 +219,10 @@ proc semSet(c: PContext, n: PNode, prev: PType): PType =
result = newOrPrevType(tySet, prev, c)
if n.len == 2 and n[1].kind != nkEmpty:
var base = semTypeNode(c, n[1], nil)
if base.kind == tyTypeDesc: base = base.base # unwrap from type traits like distinctBase
addSonSkipIntLit(result, base, c.idgen)
if base.kind in {tyGenericInst, tyAlias, tySink}: base = skipModifier(base)
if base.kind notin {tyGenericParam, tyGenericInvocation}:
if base.kind notin {tyGenericParam, tyGenericInvocation, tyFromExpr}:
if base.kind == tyForward:
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
elif not isOrdinalType(base, allowEnumWithHoles = true):

View File

@@ -12,7 +12,7 @@
import std / tables
import ast, astalgo, msgs, types, magicsys, semdata, renderer, options,
lineinfos, modulegraphs, layeredtable
lineinfos, modulegraphs, layeredtable, typeallowed
when defined(nimPreviewSlimSystem):
import std/assertions
@@ -296,6 +296,19 @@ proc replaceTypeVarsN(cl: var TReplTypeVars, n: PNode; start=0; expectedType: PT
replaceTypeVarsS(cl, n.sym, replaceTypeVarsT(cl, n.sym.typ))
if result.sym.kind == skField and
(cl.owner == nil or result.sym.owner == cl.owner):
let invalidType =
if not cl.allowMetaTypes and result.typ != nil and result.typ.isMetaType and
result.sym.owner != nil and result.sym.owner.kind == skType:
typeAllowed(result.typ, skVar, cl.c, {taProcContextIsNotMacro})
else:
nil
# Constrained types can remain unresolved during overload matching. Only
# reject the type-valued storage that can reach code generation (#24848).
if invalidType != nil and invalidType.kind == tyTypeDesc:
localError(cl.c.config, result.info,
"'" & invalidType.typeToString & "' is not a concrete type")
result.typ = errorType(cl.c)
result.sym.typ = result.typ
if result.sym.ast != nil:
# instantiate default value of object/tuple field
var n = result.sym.ast

View File

@@ -225,6 +225,17 @@ proc getRoot*(n: PNode): PSym =
else: result = nil
else: result = nil
proc isCursor*(n: PNode): bool =
case n.kind
of nkSym:
sfCursor in n.sym.flags
of nkDotExpr:
isCursor(n[1])
of nkCheckedFieldExpr:
isCursor(n[0])
else:
false
proc stupidStmtListExpr*(n: PNode): bool =
for i in 0..<n.len-1:
if n[i].kind notin {nkEmpty, nkCommentStmt}: return false

View File

@@ -50,9 +50,23 @@ cycle collector's overhead
but `--mm:orc` also produces more machine code than `--mm:arc`, so if you're on a target
where code size matters and you know that your code does not produce cycles, you can
use `--mm:arc`. Notice that the default `async`:idx: implementation produces cycles
and leaks memory with `--mm:arc`, in other words, for `async` you need to use `--mm:orc`.
and leaks memory with `--mm:arc`, in other words, for `async` you need to use `--mm:orc`
or `--mm:yrc`.
Atomic ARC/YRC
--------------
ARC/ORC are not threadsafe if `ref` or other automatically managed types are
accessed across thread boundaries.
Moving isolated subgraphs between threads is supported for ARC/ORC and the language has support
for that in the form of `isolate`. The modes `mm:atomicArc` and `mm:yrc` do offer this thread safety -- at the cost of atomic instructions. Whether that cost is acceptable depends on your program, it hard to give general guidelines. On a modern CPU the potential speedups in the form of increased multi-threading capabilities should outweigh the costs of atomic instructions by far. On an embedded device the atomics would probably only hurt though.
`mm:atomicArc` is a threadsafe variant of ARC: All the optimizations in the form of move semantics etc are still applied. `mm:yrc` is the threadsafe variant of ORC.
YRC is a novel concurrent cycle collection algorithm -- these are beasts to verify
and to get correct so there are dragons lurking here, use at your own risk.
Other MM modes
--------------
@@ -66,7 +80,7 @@ Other MM modes
Heaps are thread-local.
--mm:boehm Boehm based garbage collector, it offers a shared heap.
--mm:go Go's garbage collector, useful for interoperability with Go.
Offers a shared heap.
Offers a shared heap. Note that `mm:go` has seen little real world use. Use at your own risk.
--mm:none No memory management strategy nor a garbage collector. Allocated memory is
simply never freed. You should use `--mm:arc` instead.
@@ -76,6 +90,7 @@ Here is a comparison of the different memory management modes:
================== ======== ================= ============== ====== =================== ===================
Memory Management Heap Reference Cycles Stop-The-World Atomic Valgrind compatible Command line switch
================== ======== ================= ============== ====== =================== ===================
YRC Shared Cycle Collector No Yes Yes `--mm:yrc`
ORC Shared Cycle Collector No No Yes `--mm:orc`
ARC Shared Leak No No Yes `--mm:arc`
Atomic ARC Shared Leak No Yes Yes `--mm:atomicArc`

View File

@@ -269,6 +269,23 @@ proc processPendingCallbacks(p: PDispatcherBase; didSomeWork: var bool) =
cb()
didSomeWork = true
proc processTimersBeforePoll(
p: PDispatcherBase, didSomeWork: var bool
): Option[int] {.inline.} =
# Do not let an expired timeout overtake completion callbacks which are
# already pending. `adjustTimeout` makes the I/O poll non-blocking when the
# callback queue is non-empty.
if p.callbacks.len == 0:
result = processTimers(p, didSomeWork)
proc processCallbacksAndTimers(p: PDispatcherBase; didSomeWork: var bool) =
# A completed operation can take multiple queued callbacks to propagate
# through its public future. Process the whole chain before expired timers.
processPendingCallbacks(p, didSomeWork)
discard processTimers(p, didSomeWork)
# Timer futures must still propagate within this dispatcher iteration.
processPendingCallbacks(p, didSomeWork)
proc adjustTimeout(
p: PDispatcherBase, pollTimeout: int, nextTimer: Option[int]
): int {.inline.} =
@@ -399,7 +416,7 @@ when defined(windows) or defined(nimdoc):
"No handles or timers registered in dispatcher.")
result = false
let nextTimer = processTimers(p, result)
let nextTimer = processTimersBeforePoll(p, result)
let at = adjustTimeout(p, timeout, nextTimer)
var llTimeout =
if at == -1: winlean.INFINITE
@@ -450,10 +467,7 @@ when defined(windows) or defined(nimdoc):
result = false
else: raiseOSError(errCode)
# Timer processing.
discard processTimers(p, result)
# Callback queue processing
processPendingCallbacks(p, result)
processCallbacksAndTimers(p, result)
var acceptEx: WSAPROC_ACCEPTEX
@@ -1404,7 +1418,7 @@ else:
result = false
var keys: array[64, ReadyKey]
let nextTimer = processTimers(p, result)
let nextTimer = processTimersBeforePoll(p, result)
var count =
p.selector.selectInto(adjustTimeout(p, timeout, nextTimer), keys)
for i in 0..<count:
@@ -1447,10 +1461,7 @@ else:
if writeCbListCount > 0: incl(newEvents, Event.Write)
p.selector.updateHandle(SocketHandle(fd), newEvents)
# Timer processing.
discard processTimers(p, result)
# Callback queue processing
processPendingCallbacks(p, result)
processCallbacksAndTimers(p, result)
proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): owned(Future[string]) =

View File

@@ -913,17 +913,14 @@ proc findAll*(n: XmlNode, tag: string, caseInsensitive = false): seq[XmlNode] =
proc xmlConstructor(a: NimNode): NimNode =
if a.kind == nnkCall:
result = newCall("newXmlTree", toStrLit(a[0]))
result = newCall("newXmlTree", newStrLitNode($a[0]))
var attrs = newNimNode(nnkBracket, a)
var newStringTabCall = newCall(bindSym"newStringTable", attrs,
bindSym"modeCaseSensitive")
var elements = newNimNode(nnkBracket, a)
for i in 1..a.len-1:
if a[i].kind == nnkExprEqExpr:
# In order to support attributes like `data-lang` we have to
# replace whitespace because `toStrLit` gives `data - lang`.
let attrName = toStrLit(a[i][0]).strVal.replace(" ", "")
attrs.add(newStrLitNode(attrName))
attrs.add(newStrLitNode($a[i][0]))
attrs.add(a[i][1])
#echo repr(attrs)
else:

View File

@@ -190,8 +190,10 @@ proc ioTests(r: var TResults, cat: Category, options: string) =
# ------------------------- async tests ---------------------------------------
proc asyncTests(r: var TResults, cat: Category, options: string) =
# Run async with yrc instead of the default orc; the CI already runs long
# enough that we cannot afford to test both.
template test(filename: untyped) =
testSpec r, makeTest(filename, options, cat)
testSpec r, makeTest(filename, options & " --mm:yrc", cat)
for t in os.walkFiles("tests/async/t*.nim"):
test(t)
@@ -528,6 +530,7 @@ proc mmRaise(kind: TResultEnum, expected, given: string) =
raise e
proc isMetamorphicIcTest(content: string): bool =
result = false
for line in content.splitLines:
if line.strip == "#? metamorphic": return true
@@ -559,7 +562,7 @@ proc stableBinary(path: string): string =
## so two builds seconds apart differ there even with identical codegen. Skipping
## a generous fixed window keeps the clean-vs-incremental check about codegen.
const headerSkip = 4096
var f: File
var f: File = nil
if not open(f, path, fmRead):
raise newException(IOError, "cannot open: " & path)
defer: close(f)

23
tests/arc/t26010.nim Normal file
View File

@@ -0,0 +1,23 @@
discard """
action: reject
matrix: "--mm:orc; --mm:refc"
errormsg: "cannot move cursor 'a'; a cursor does not own its value"
"""
# bug #26010: a cursor is a non-owning alias and cannot transfer ownership.
type Xxx = object
proc `=destroy`(v: var Xxx) =
debugEcho "dest"
proc test(v: ref Xxx) =
var a {.cursor.} = v
var b = move(a)
discard
proc main() =
var x = new Xxx
test(x)
main()

52
tests/async/t23615.nim Normal file
View File

@@ -0,0 +1,52 @@
discard """
valgrind: true
cmd: '''nim c --mm:orc -d:nimAllocStats -d:useMalloc $file'''
output: '''ok'''
"""
# bug #23615: exceptions caught by a typed except branch in a closure
# iterator (and thus in any async proc) leaked under ARC/ORC.
import std/[asyncdispatch, importutils]
privateAccess(AllocStats)
block: # pure closure iterator, the minimal form of the bug
proc runIter() =
iterator it(): int {.closure.} =
try:
yield 1
raise newException(ValueError, "x")
except ValueError:
discard
yield 2
var f = it
doAssert f() == 1
doAssert f() == 2
let base = getAllocStats()
runIter()
GC_fullCollect()
let after = getAllocStats()
doAssert after.allocCount - after.deallocCount ==
base.allocCount - base.deallocCount, $base & " " & $after
block: # the async incarnation from the issue
proc err {.async.} =
raise newException(ValueError, "err1")
proc amain {.async.} =
await sleepAsync(1)
for _ in 0..<50:
try:
await err()
except ValueError:
discard
waitFor amain()
doAssert not hasPendingOperations()
setGlobalDispatcher(nil)
GC_fullCollect()
let stats = getAllocStats()
doAssert stats.allocCount - stats.deallocCount < 10, $stats
echo "ok"

View File

@@ -4,6 +4,7 @@ discard """
exitcode: 0
"""
import asyncdispatch, asyncnet
import std/strutils
when defined(windows):
from winlean import ERROR_NETNAME_DELETED
@@ -14,6 +15,7 @@ else:
# even when the socket is closed.
const
timeout = 2000
messagePaddingSize = 64 * 1024
var port = Port(0)
var sent = 0
@@ -31,10 +33,12 @@ proc isExpectedDisconnectionError(errCode: int32): bool =
errCode == EBADF or errCode == ECONNRESET or errCode == EPIPE
proc keepSendingTo(c: AsyncSocket) {.async.} =
let messagePadding = repeat('x', messagePaddingSize)
while true:
# This write will eventually get stuck because the client is not reading
# its messages.
let sendFut = c.send("Foobar" & $sent & "\n", flags = {})
# Larger writes reach socket backpressure quickly even on slow CI machines.
# This write will eventually get stuck because the client is not reading.
# Keep the padding after the newline so recvLine does not drain it.
let sendFut = c.send("Foobar" & $sent & "\n" & messagePadding, flags = {})
var sendTimedOut = false
try:
# On some platforms (notably macOS ARM64), the kernel may return

View File

@@ -0,0 +1,32 @@
discard """
action: run
"""
import asyncdispatch, os
proc wrap(fut: Future[void]): Future[void] =
result = newFuture[void]("wrap")
let retFuture = result
fut.addCallback proc () =
if fut.failed:
retFuture.fail(fut.error)
else:
retFuture.complete()
block:
let root = newFuture[void]("root")
let wrapped = wrap(wrap(wrap(root)))
let completedBeforeDeadline = withTimeout(wrapped, 20)
# Completion has happened at the bottom of the future chain, but its
# callbacks cannot propagate until control reaches the dispatcher.
root.complete()
sleep(40)
doAssert waitFor(completedBeforeDeadline)
block:
var callbackRan = false
sleepAsync(0).addCallback proc () = callbackRan = true
poll(0)
doAssert callbackRan

View File

@@ -1,9 +0,0 @@
discard """
errormsg: "cannot infer the return type of 'foo'"
line: 6
"""
proc foo(n: int): auto =
return foo(n + 1)
discard foo(0)

View File

@@ -1,12 +0,0 @@
discard """
errormsg: "cannot infer the return type of 'foo'"
line: 6
"""
proc foo(n: int): auto =
if n > 0:
foo(n - 1)
else:
foo(n + 1)
discard foo(1)

View File

@@ -1,9 +0,0 @@
discard """
errormsg: "cannot infer the return type of 'foo'"
line: 6
"""
proc foo[T](x: T): auto =
foo(x)
discard foo(1)

12
tests/generics/t21601.nim Normal file
View File

@@ -0,0 +1,12 @@
discard """
errormsg: "'typedesc' is not a concrete type"
line: 10
"""
# issue #21601
type Person = object
type Builder* = ref object of RootObj
class*: typedesc
echo Builder(class: Person).repr

10
tests/generics/t24848.nim Normal file
View File

@@ -0,0 +1,10 @@
discard """
errormsg: "'typedesc[R[system.int]]' is not a concrete type"
line: 8
"""
# issue #24848
type R[C] = ref object
b: C
discard R[[R[int]]]()

View File

@@ -1,46 +0,0 @@
proc byReturn(n: int): auto =
if n < 5:
return byReturn(n + 1)
else:
return 9
proc byResult(n: int): auto =
if n < 5:
result = byResult(n + 1)
else:
result = 9
proc byExpression(n: int): auto =
if n < 5:
byExpression(n + 1)
else:
9
proc generic[T](x: T; n: int): auto =
if n < 5:
return generic(x, n + 1)
else:
return x
proc concreteFirst(n: int): auto =
if n >= 5:
return 9
else:
return concreteFirst(n + 1)
proc multipleRecursiveBranches(n: int): auto =
if n < 0:
return multipleRecursiveBranches(n + 1)
elif n < 5:
return multipleRecursiveBranches(n + 1)
else:
return 9
doAssert byReturn(3) == 9
doAssert byResult(3) == 9
doAssert byExpression(3) == 9
doAssert generic("ok", 3) == "ok"
doAssert concreteFirst(3) == 9
doAssert multipleRecursiveBranches(-1) == 9

View File

@@ -0,0 +1,39 @@
# Test that set[] accepts range types via typedesc[R], and set[typedesc[R]]
# must unwrap the typedesc wrapper before checking ordinality.
import std/typetraits
type
TestDistinctRange = distinct range[0 .. 63]
block: # explicit range type as set base
type S = set[range[0 .. 63]]
var s: S = {0, 1}
doAssert 0 in s
block: # distinctBase result as set base (non-generic)
type S = set[TestDistinctRange.distinctBase]
var s: S = {0, 1}
doAssert 0 in s
block: # range alias as set base
type RangeAlias = range[0 .. 63]
type S = set[RangeAlias]
var s: S = {0, 1}
doAssert 0 in s
block: # set[T.distinctBase] in generic body type position
proc test[T: TestDistinctRange]() =
var s: set[T.distinctBase]
s = {0, 1}
doAssert 0 in s
test[TestDistinctRange]()
block: # passing set[T.distinctBase] to a proc expecting set[0..63]
proc accept(x: typedesc[set[0 .. 63]]) = discard
proc pass[T: TestDistinctRange](p: typedesc[set[T]]) =
accept(set[T.distinctBase])
pass(set[TestDistinctRange])

View File

@@ -118,3 +118,14 @@ block: #21541
doAssert temp.text == "Hello!"
temp.text = "Hola!"
doAssert temp.text == "Hola!"
block: #26039
let tree = <>rss(
"xmlns:atom" = "http://www.w3.org/2005/Atom",
<>"atom:link"(
`data-dummy` = "test",
),
)
doAssert $tree == """<rss xmlns:atom="http://www.w3.org/2005/Atom">
<atom:link data-dummy="test" />
</rss>"""