Compare commits

..

2 Commits

Author SHA1 Message Date
ringabout
e292ff93cb improve handling of auto return types in recursive calls 2026-07-30 20:59:12 +08:00
ringabout
02da2cf4ae fixes #13736; Compiler crash with auto return type and recursion 2026-07-29 22:38:57 +08:00
25 changed files with 153 additions and 273 deletions

View File

@@ -336,14 +336,9 @@ 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)),
ctx.newCurExcAccess(),
g.callCodegenProc("getCurrentException"),
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, isCursor, whichPragma, getPotentialWrites
from trees import exprStructuralEquivalent, getRoot, whichPragma, getPotentialWrites
type
Con = object
@@ -180,6 +180,17 @@ 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

@@ -75,11 +75,6 @@ proc newAsgnStmt(le, ri: PNode): PNode =
result[0] = le
result[1] = ri
proc newSinkAsgnStmt(le, ri: PNode): PNode =
result = newNodeI(nkSinkAsgn, le.info, 2)
result[0] = le
result[1] = ri
proc genBuiltin*(g: ModuleGraph; idgen: IdGenerator; magic: TMagic; name: string; i: PNode): PNode =
result = newNodeI(nkCall, i.info)
result.add createMagic(g, idgen, name, magic).newSymNode
@@ -89,9 +84,7 @@ proc genBuiltin(c: var TLiftCtx; magic: TMagic; name: string; i: PNode): PNode =
result = genBuiltin(c.g, c.idgen, magic, name, i)
proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
if c.kind == attachedSink:
body.add newSinkAsgnStmt(x, y)
elif c.kind in {attachedAsgn, attachedDeepCopy, attachedDup}:
if c.kind in {attachedAsgn, attachedDeepCopy, attachedSink, attachedDup}:
body.add newAsgnStmt(x, y)
elif c.kind == attachedDestructor and c.addMemReset:
let call = genBuiltin(c, mDefault, "default", x)
@@ -101,21 +94,11 @@ 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 =
# 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:
if x.kind == nkHiddenDeref:
checkSonsLen(x, 1, c.g.config)
result = x[0]
else:
let addrTyp = makeVarType(x.typ.owner, x.typ, c.idgen)
addrTyp.incl tfVarIsPtr
result = newNodeIT(nkHiddenAddr, x.info, addrTyp)
result = newNodeIT(nkHiddenAddr, x.info, makeVarType(x.typ.owner, x.typ, c.idgen))
result.add x
proc genWhileLoop(c: var TLiftCtx; i, dest: PNode): PNode =

View File

@@ -108,6 +108,16 @@ 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: " &
@@ -125,6 +135,10 @@ 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:
@@ -158,8 +172,10 @@ proc commonType*(c: PContext; x, y: PType): PType =
var a = skipTypes(x, {tyGenericInst, tyAlias, tySink})
var b = skipTypes(y, {tyGenericInst, tyAlias, tySink})
result = x
if a.kind in {tyUntyped, tyNil}: result = y
elif b.kind in {tyUntyped, tyNil}: 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
elif a.kind == tyTyped: result = a
elif b.kind == tyTyped: result = b
elif a.kind == tyTypeDesc:

View File

@@ -924,6 +924,11 @@ 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,6 +43,7 @@ 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,6 +2125,15 @@ 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
@@ -2196,7 +2205,11 @@ 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
if isEmptyType(result.typ):
# 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):
# we inferred a 'void' return type:
c.p.resultSym.typ = errorType(c)
c.p.owner.typ.setReturnType nil

View File

@@ -693,10 +693,5 @@ 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,10 +219,9 @@ 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, tyFromExpr}:
if base.kind notin {tyGenericParam, tyGenericInvocation}:
if base.kind == tyForward:
c.forwardTypeUpdates.add (getCurrOwner(c), result, n)
elif not isOrdinalType(base, allowEnumWithHoles = true):

View File

@@ -225,17 +225,6 @@ 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,23 +50,9 @@ 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`
or `--mm:yrc`.
and leaks memory with `--mm:arc`, in other words, for `async` you need to use `--mm:orc`.
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
--------------
@@ -80,7 +66,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. Note that `mm:go` has seen little real world use. Use at your own risk.
Offers a shared heap.
--mm:none No memory management strategy nor a garbage collector. Allocated memory is
simply never freed. You should use `--mm:arc` instead.
@@ -90,7 +76,6 @@ 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,23 +269,6 @@ 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.} =
@@ -416,7 +399,7 @@ when defined(windows) or defined(nimdoc):
"No handles or timers registered in dispatcher.")
result = false
let nextTimer = processTimersBeforePoll(p, result)
let nextTimer = processTimers(p, result)
let at = adjustTimeout(p, timeout, nextTimer)
var llTimeout =
if at == -1: winlean.INFINITE
@@ -467,7 +450,10 @@ when defined(windows) or defined(nimdoc):
result = false
else: raiseOSError(errCode)
processCallbacksAndTimers(p, result)
# Timer processing.
discard processTimers(p, result)
# Callback queue processing
processPendingCallbacks(p, result)
var acceptEx: WSAPROC_ACCEPTEX
@@ -1418,7 +1404,7 @@ else:
result = false
var keys: array[64, ReadyKey]
let nextTimer = processTimersBeforePoll(p, result)
let nextTimer = processTimers(p, result)
var count =
p.selector.selectInto(adjustTimeout(p, timeout, nextTimer), keys)
for i in 0..<count:
@@ -1461,7 +1447,10 @@ else:
if writeCbListCount > 0: incl(newEvents, Event.Write)
p.selector.updateHandle(SocketHandle(fd), newEvents)
processCallbacksAndTimers(p, result)
# Timer processing.
discard processTimers(p, result)
# Callback queue processing
processPendingCallbacks(p, result)
proc recv*(socket: AsyncFD, size: int,
flags = {SocketFlag.SafeDisconn}): owned(Future[string]) =

View File

@@ -913,14 +913,17 @@ proc findAll*(n: XmlNode, tag: string, caseInsensitive = false): seq[XmlNode] =
proc xmlConstructor(a: NimNode): NimNode =
if a.kind == nnkCall:
result = newCall("newXmlTree", newStrLitNode($a[0]))
result = newCall("newXmlTree", toStrLit(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:
attrs.add(newStrLitNode($a[i][0]))
# 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(a[i][1])
#echo repr(attrs)
else:

View File

@@ -190,10 +190,8 @@ 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 & " --mm:yrc", cat)
testSpec r, makeTest(filename, options, cat)
for t in os.walkFiles("tests/async/t*.nim"):
test(t)
@@ -530,7 +528,6 @@ 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
@@ -562,7 +559,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 = nil
var f: File
if not open(f, path, fmRead):
raise newException(IOError, "cannot open: " & path)
defer: close(f)

View File

@@ -1,23 +0,0 @@
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()

View File

@@ -1,16 +0,0 @@
discard """
matrix: "--mm:orc; --mm:refc"
"""
type M = object
y: seq[int]
proc `=copy`(_: var M, _: M) {.error.}
proc `=dup`(_: M): M {.error.}
proc k(v: sink M): M = v
proc w() =
var t = M(y: @[0])
let s = addr t.y[0]
t = k(t)
s[] = 1
doAssert t.y[0] != 0
w()

View File

@@ -1,52 +0,0 @@
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,7 +4,6 @@ discard """
exitcode: 0
"""
import asyncdispatch, asyncnet
import std/strutils
when defined(windows):
from winlean import ERROR_NETNAME_DELETED
@@ -15,7 +14,6 @@ else:
# even when the socket is closed.
const
timeout = 2000
messagePaddingSize = 64 * 1024
var port = Port(0)
var sent = 0
@@ -33,12 +31,10 @@ 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:
# 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 = {})
# This write will eventually get stuck because the client is not reading
# its messages.
let sendFut = c.send("Foobar" & $sent & "\n", flags = {})
var sendTimedOut = false
try:
# On some platforms (notably macOS ARM64), the kernel may return

View File

@@ -1,32 +0,0 @@
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

9
tests/errmsgs/t13736.nim Normal file
View File

@@ -0,0 +1,9 @@
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

@@ -0,0 +1,12 @@
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

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

46
tests/misc/t13736.nim Normal file
View File

@@ -0,0 +1,46 @@
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

@@ -1,39 +0,0 @@
# 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,14 +118,3 @@ 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>"""