mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-31 19:03:42 +00:00
Compare commits
10 Commits
pr_extend_
...
pr_oi2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15d1fafc71 | ||
|
|
f24b316f13 | ||
|
|
52b9b1c5ca | ||
|
|
2d81149294 | ||
|
|
0021205854 | ||
|
|
f17755782a | ||
|
|
9bc0887755 | ||
|
|
99a696e0c4 | ||
|
|
8e8f8de1ab | ||
|
|
cd3e9a46b2 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.} =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
19
doc/mm.md
19
doc/mm.md
@@ -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`
|
||||
|
||||
@@ -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]) =
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
23
tests/arc/t26010.nim
Normal 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
52
tests/async/t23615.nim
Normal 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"
|
||||
@@ -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
|
||||
|
||||
32
tests/async/tasyncdispatchordering.nim
Normal file
32
tests/async/tasyncdispatchordering.nim
Normal 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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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
12
tests/generics/t21601.nim
Normal 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
10
tests/generics/t24848.nim
Normal 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]]]()
|
||||
@@ -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
|
||||
39
tests/set/tset_range_trait.nim
Normal file
39
tests/set/tset_range_trait.nim
Normal 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])
|
||||
@@ -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>"""
|
||||
|
||||
Reference in New Issue
Block a user