Merge branch 'devel' into pr_field

This commit is contained in:
Andreas Rumpf
2026-06-08 22:59:51 +02:00
committed by GitHub
75 changed files with 1111 additions and 90 deletions

43
tests/arc/t25595.nim Normal file
View File

@@ -0,0 +1,43 @@
discard """
matrix: "--mm:orc; --mm:arc; --mm:refc"
"""
# bug #25595: cursor inference must not borrow a case object whose source can be
# mutated through the cursor's own ref across a call. `let c = h.w` was inferred as a
# non-owning cursor; `clear(c.r)` overwrites `h.w` via the cursor's back-reference,
# freeing the ref while the borrow still uses it -> use-after-free. Detected here
# deterministically: the element's destructor must not run during the call.
var destroyed = false
type
O = ref object
value: int
home: H
W = object
case k: bool
of true: r: O
of false: discard
H = ref object
w: W
proc `=destroy`(o: var typeof(O()[])) =
destroyed = true
proc clear(o: O): int =
o.home.w = W()
doAssert not destroyed, "use-after-free: element destroyed during the call"
result = o.value
proc go(h: H): int =
let c = h.w
result = clear(c.r)
proc main =
let h = H()
let o = O(value: 42)
o.home = h
h.w = W(k: true, r: o)
doAssert go(h) == 42
main()

47
tests/arc/t25850.nim Normal file
View File

@@ -0,0 +1,47 @@
discard """
cmd: '''nim c --mm:orc --expandArc:uIf --expandArc:uCase $file'''
nimout: '''
--expandArc: uIf
block :tmp:
let s = w()
if true:
r[] = s
else:
r[] = s
-- end of expandArc ------------------------
--expandArc: uCase
block :tmp:
let s = w()
case n
of 0:
r[] = s
else:
r[] = w()
-- end of expandArc ------------------------
'''
"""
# bug #25850
# Assigning an expression-based control flow construct (an `if`/`case` nested in
# a `block`) must distribute the assignment directly into the leaf branches
# instead of creating redundant intermediate temporaries per branch.
proc w(): array[1000, byte] {.noinline.} = discard
proc uIf(r: ptr array[1000, byte]) =
r[] = (block:
let s = w()
if true: s else: s)
proc uCase(r: ptr array[1000, byte], n: int) =
r[] = (block:
let s = w()
case n
of 0: s
else: w())
var d: array[1000, byte]
uIf(addr d)
uCase(addr d, 0)

View File

@@ -0,0 +1,36 @@
discard """
matrix: "; --panics:on"
"""
# issue #25851: --panics:on must not drop the nimErr_ check after a closure
# call whose result is consumed directly (e.g. `result.add elem(src)`).
# Regression from #25295.
type
Overrun = object of CatchableError
Source = object
data: seq[bool]
cursor: int
ElemFn = proc(src: var Source): bool {.closure.}
proc drawBool(src: var Source): bool =
if src.cursor >= src.data.len: raise newException(Overrun, "exhausted")
result = src.data[src.cursor]; inc src.cursor
proc listRun(elem: ElemFn, src: var Source): seq[bool] =
result = @[]
while true:
if not src.drawBool(): break
result.add elem(src) # closure call the result flows straight
# into `add`, which previously caused the
# compiler to skip the nimErr_ check.
let elem: ElemFn = proc(src: var Source): bool = src.drawBool()
# Both --panics:on and --panics:off must propagate the Overrun.
var caught = false
try:
var src = Source(data: @[true])
discard listRun(elem, src)
except Overrun:
caught = true
doAssert caught, "Overrun exception was swallowed"

View File

@@ -0,0 +1,13 @@
template a(T: type): int =
when T is uint64: 1 else: 2
type
M*[T] = object
data*: seq[T]
b: seq[int]
indices*: array[a(T), int64]
U = distinct uint64
D* = object
c: M[U]
v: array[180000, int64]
g*: M[uint64]

View File

@@ -0,0 +1,5 @@
import ./c
proc p*(): D =
let c = M[uint64](data: @[0], indices: [1])
result = D(g: c)

7
tests/ccgbugs2/m25800.h Normal file
View File

@@ -0,0 +1,7 @@
/*TYPESECTION*/
struct CppRef {
int* data;
CppRef() : data(new int(42)) {}
~CppRef() { delete data; data = nullptr; }
void reset() { delete data; data = nullptr; }
};

18
tests/ccgbugs2/t25294.nim Normal file
View File

@@ -0,0 +1,18 @@
discard """
matrix: "--mm:refc; --mm:orc"
"""
import ./m25294/[c, t]
block:
let a = new D
a[] = p()
discard a[]
block:
let a = new D
a[] = p()
discard a[]
block:
let a = new D
a[] = p()
discard a[]

23
tests/ccgbugs2/t25800.nim Normal file
View File

@@ -0,0 +1,23 @@
discard """
cmd: "nim cpp $file"
action: "compile"
"""
# Bug Report 1: {.importcpp.} on =wasMoved generates invalid preprocessor directive #.
type CppRef* {.importcpp, bycopy, noInit, header: "m25800.h".} = object
proc `=destroy`(x: var CppRef) {.importcpp: "#.~CppRef()".}
proc `=wasMoved`(x: var CppRef) {.importcpp: "#.reset()".}
proc `=copy`(dest: var CppRef; src: CppRef) {.importcpp: "dest = src".}
proc `=sink`(dest: var CppRef; src: CppRef) {.importcpp: "dest = std::move(src)".}
# This triggers =wasMoved when passing to sink parameter
proc consume(x: sink CppRef) = discard
proc test() =
var x: CppRef
consume(move(x)) # =wasMoved MUST be called here after the move
test()

View File

@@ -12,3 +12,51 @@ proc foo =
doAssert m.id == 999
foo()
block:
type Foo = object
a,b,c: int
var dest: Foo
# proc `=wasMoved`(x: var Foo) =
# debugEcho "wasMoved called"
proc main() =
var x = Foo(a:11, b:12, c:13)
dest = move(x)
main()
block:
type Foo = object
a,b,c: int
var dest: Foo
proc `=wasMoved`(x: var Foo) =
discard "wasMoved called"
proc main() =
var x = Foo(a:11, b:12, c:13)
dest = move(x)
main()
import std/threadpool
block:
type Foo = object
data: string
proc `=wasMoved`(x: var Foo) =
discard
proc work(x: Foo) =
discard
var x = Foo(data: "hello")
spawn work(x)
sync()

View File

@@ -0,0 +1,74 @@
from std/os import osLastError, osErrorMsg, OSErrorCode, raiseOSError,
newOSError, `==`
{.push raises: [].}
const
EPERM* = OSErrorCode(1)
ECONNABORTED* = OSErrorCode(53)
ETIMEDOUT* = OSErrorCode(60)
ENOTCONN* = OSErrorCode(107)
EMFILE* = OSErrorCode(24)
ENFILE* = OSErrorCode(23)
ENOBUFS* = OSErrorCode(55)
ENOMEM* = OSErrorCode(12)
type
AsyncError* = object of CatchableError
TransportErrorBase* = object of AsyncError
TransportOsError* = object of TransportErrorBase
code*: OSErrorCode
TransportTooManyError* = object of TransportErrorBase
TransportAbortedError* = object of TransportErrorBase
template getConnectionAbortedError*(
code: OSErrorCode
): ref TransportAbortedError =
let msg =
case code
of OSErrorCode(0), ECONNABORTED:
"[ECONNABORTED] Connection has been aborted before being accepted"
of EPERM:
"[EPERM] Firewall rules forbid connection"
of ETIMEDOUT:
"[ETIMEDOUT] Operation has been timed out"
of ENOTCONN:
"[ENOTCONN] Transport endpoint is not connected"
else:
"[" & $int(code) & "] Connection has been aborted"
newException(TransportAbortedError, msg)
template getTransportTooManyError*(
code = OSErrorCode(0)
): ref TransportTooManyError =
let msg =
case code
of OSErrorCode(0):
"Too many open transports"
of EMFILE:
"[EMFILE] Too many open files in the process"
of ENFILE:
"[ENFILE] Too many open files in system"
of ENOBUFS:
"[ENOBUFS] No buffer space available"
of ENOMEM:
"[ENOMEM] Not enough memory availble"
else:
"[" & $int(code) & "] Too many open transports"
newException(TransportTooManyError, msg)
template getTransportError*(ecode: OSErrorCode): untyped =
case ecode
of ECONNABORTED, EPERM, ETIMEDOUT, ENOTCONN:
getConnectionAbortedError(ecode)
of EMFILE, ENFILE, ENOBUFS, ENOMEM:
getTransportTooManyError(ecode)
else:
(ref TransportOsError)(code: ecode,
msg: "(" & $int(ecode) & ") " & osErrorMsg(ecode))
proc raiseTransportError*(err: OSErrorCode) {.
raises: [TransportAbortedError, TransportTooManyError, TransportOsError],
noreturn.} =
## Raises transport specific OS error.
raise getTransportError(err)

View File

@@ -0,0 +1,8 @@
discard """
errormsg: "cast(raises: ValueError) can raise an unlisted exception: ValueError"
line: 7
"""
proc fff() {.raises: [].} =
{.cast(raises: ValueError).}:
discard

View File

@@ -0,0 +1,30 @@
discard """
targets: "cpp"
matrix: "--mm:arc; --mm:orc; --mm:refc"
output: '''
finally
after
'''
"""
# Regression test: typeless `except:` followed by `finally:` must not
# trigger ReraiseDefect at the end of the proc.
#
# Previously, `genTryCpp` only emitted `T_ = nullptr;` in the *typed*
# except branches, leaving the typeless `except:` path with a still-set
# `T_`. After the handler body and `popCurrentException`, the trailing
# `if (T_) std::rethrow_exception(T_);` in the finally block would still
# fire — but with the Nim exception stack already popped, the rethrow
# bubbled up as a `ReraiseDefect: no exception to reraise`.
proc test() =
try:
raise newException(CatchableError, "x")
except:
let e = getCurrentException()
discard e
finally:
echo "finally"
test()
echo "after"

16
tests/generics/t20811.nim Normal file
View File

@@ -0,0 +1,16 @@
discard """
output: '''42
42'''
"""
proc outer(j: int) =
proc genericInner[T](): int =
j
proc plainInner(): int =
j
echo genericInner[int]()
echo plainInner()
outer(42)

19
tests/init/t25857.nim Normal file
View File

@@ -0,0 +1,19 @@
discard """
output: "1"
"""
# Regression for #25857: `typeof(result)` inside `result`'s initializer must not be
# treated as a use-before-initialization of `result`. `typeof` is a type query and
# never evaluates its operand, so this compiles and runs.
# (Before the fix this errored: "'result' requires explicit initialization" on
# {.requiresInit.} return types, breaking the `ok(typeof(result), v)` idiom.)
type Box[T] {.requiresInit.} = object
v: T
func make[T](_: typedesc[Box[T]], v: T): Box[T] = Box[T](v: v)
proc f(): Box[int] =
make(typeof(result), 1)
echo f().v

View File

@@ -465,4 +465,24 @@ block: # bug #25724
else: yield 1
for w in c():
let n = w
(proc() = discard n)()
(proc() = discard n)()
block:
iterator c(): int =
yield 1
yield 1
for w in c():
proc p(s: int) =
let sap = s
p(0)
block: # bug #25725
iterator c(): int =
when nimvm: yield 0
else: yield 1
for w in c():
let n = w
proc p(s: int) =
let s = s; discard n
p(0)

View File

@@ -0,0 +1,38 @@
discard """
targets: "c cpp js"
"""
template sameAddress(a, b): bool =
when defined(js):
a == b
else:
a.unsafeAddr == b.unsafeAddr
proc main() =
block:
let a = [10, 11, 12]
for ai in items(a):
doAssert sameAddress(ai, a[0])
break
block:
let a = [[1, 2], [1, 2], [1, 2]]
for ai in items(a):
doAssert sameAddress(ai[0], a[0][0])
break
block:
let s = @[(1, 2), (3, 4), (5, 6)]
doAssert (3, 4) in s
main()
static:
main()
block: # issue #25849
static:
const key = "NIM_TESTS_TOSENV_KEY"
for val in ["val", "", "\xc3\x86"]:
let s = @[(key, "val"), (key, ""), (key, "\xc3\x86")]
doAssert (key, val) in s

View File

@@ -45,3 +45,13 @@ block:
r: R
func f(o: O): int = 42
block:
iterator j(x: array[1, int]): lent int = yield x[0]
iterator g(): int {.closure.} =
let a = 1
for w in j([a]):
yield 0
doAssert w == 1
for _ in g(): discard

View File

@@ -0,0 +1,5 @@
type
VtableBaseA* = ref object of RootObj
method say*(a: VtableBaseA): string {.base.} =
"base"

View File

@@ -0,0 +1,7 @@
import mvtables_reentry_a
type
VtableDerivedB* = ref object of VtableBaseA
method say*(d: VtableDerivedB): string =
"derived"

View File

@@ -0,0 +1,19 @@
discard """
targets: "c cpp"
"""
import mvtables_reentry_a
type
MainType = ref object of VtableBaseA
method say*(m: MainType): string =
"main"
when isMainModule:
import mvtables_reentry_b
let a: VtableBaseA = VtableDerivedB()
doAssert a.say() == "derived"
let m: VtableBaseA = MainType()
doAssert m.say() == "main"

View File

@@ -20,3 +20,25 @@ block: # issue #24021
discard
else:
discard foo.z
# bug #22791
type Foo = object
case a: bool
of false:
discard
of true:
case b: bool
of false:
discard
of true:
c: bool
const f = Foo(a: true, b: true, c: true)
case f.a
of true:
case f.b
of true:
echo f.c
else: discard
else: discard

View File

@@ -0,0 +1,12 @@
discard """
cmd: "nim check $file"
action: compile
nimout: '''
tinvalid_cmp_op1.nim(12, 1) Warning: define `<=` instead of `>=` to implement user defined comparison operator. it allows you to use `>=` automatically. [InvalidCmpOp]
'''
"""
# issue #25655
type Foo = distinct int
func `>=`(a, b: Foo): bool = int(a) >= int(b)

View File

@@ -0,0 +1,12 @@
discard """
cmd: "nim check $file"
action: compile
nimout: '''
tinvalid_cmp_op2.nim(12, 1) Warning: define `<` instead of `>` to implement user defined comparison operator. it allows you to use `>` automatically. [InvalidCmpOp]
'''
"""
# issue #25655
type Foo = distinct int
func `>`(a, b: Foo): bool = int(a) > int(b)

View File

@@ -0,0 +1,12 @@
discard """
cmd: "nim check $file"
action: compile
nimout: '''
tinvalid_cmp_op3.nim(12, 1) Warning: define `==` instead of `!=` to implement user defined comparison operator. it allows you to use `!=` automatically. [InvalidCmpOp]
'''
"""
# issue #25655
type Foo = distinct int
func `!=`(a, b: Foo): bool = int(a) != int(b)

View File

@@ -29,3 +29,30 @@ block tnestprc:
result = x + y
result = add(x, 3)
doAssert Add3(7) == 10
block:
type A = object
c: int
type H = proc(): lent A {.nimcall.}
const u = A(c: 0)
proc e(T: typedesc): lent A = u
proc y(T: typedesc): H =
proc(): lent A {.nimcall.} = T.e
discard y(int)
block:
type A = object
c: int
type H = proc(): lent A {.nimcall.}
let u = A(c: 0)
proc y(_: int | int): H =
proc(): lent A {.nimcall.} = u
discard y(0)
block:
type A = object
c: int
type H = proc(): lent A {.nimcall.}
let u = A()
let _: H = proc(): lent A {.nimcall.} = u

View File

@@ -241,3 +241,12 @@ proc main() =
static: main()
main()
# https://github.com/nim-lang/Nim/issues/18583
# $ separator must be emitted even when the item's string repr is empty
type EmptyStr18583 = object
proc `$`(x: EmptyStr18583): string = ""
block:
var d = [EmptyStr18583(), EmptyStr18583()].toDeque
doAssert $d == "[, ]", "got: " & $d

View File

@@ -104,3 +104,15 @@ template main() =
static: main()
main()
# https://github.com/nim-lang/Nim/issues/18583
type EmptyStr18583HeapQ = object
proc `$`(x: EmptyStr18583HeapQ): string = ""
proc `<`(a, b: EmptyStr18583HeapQ): bool = false
block:
var h = initHeapQueue[EmptyStr18583HeapQ]()
push(h, EmptyStr18583HeapQ())
push(h, EmptyStr18583HeapQ())
let s = $h
doAssert s == "[, ]", "got: " & s

View File

@@ -287,3 +287,14 @@ template main =
static: main()
main()
# https://github.com/nim-lang/Nim/issues/18583
type EmptyStr18583List = object
proc `$`(x: EmptyStr18583List): string = ""
block:
var L: SinglyLinkedList[EmptyStr18583List]
L.prepend(EmptyStr18583List())
L.prepend(EmptyStr18583List())
let s = $L
doAssert s == "[, ]", "got: " & s

View File

@@ -259,6 +259,11 @@ block:
doAssert match("EINE ÜBERSICHT UND AUSSERDEM", peg"(\upper \white*)+")
doAssert(not match("456678", peg"(\letter)+"))
block:
doAssert match("CAFÉ", peg"\i café")
doAssert match("Café", peg"\i café")
doAssert "two cafés: Café and CAFÉ".findAll(peg"\i café").len == 3
doAssert("var1 = key; var2 = key2".replacef(
peg"\skip(\s*) {\ident}'='{\ident}", "$1<-$2$2") ==
"var1<-keykey;var2<-key2key2")

View File

@@ -544,6 +544,12 @@ proc main() =
var x = 5
doAssert fmt"{(x=7;123.456)=:13e}" == "(x=7;123.456)= 1.234560e+02"
doAssert x==7
block: # binary operators in interpolated expressions
let n = 1
doAssert &"{n-1}" == "0"
doAssert fmt"{n-1}" == "0"
block: #curly bracket expressions and tuples
proc formatValue(result: var string; value:Table|bool|JsonNode; specifier:string) = result.add $value

View File

@@ -289,6 +289,15 @@ template main() =
var foo = parseUri("http://example.com") / "foo" ? {"do": "do", "bar": ""}
var foo1 = parseUri("http://example.com/foo?do=do&bar")
doAssert foo == foo1
block: # issue #19782: appends to existing query string
var foo = parseUri("http://example.com/foo?existing=1") ? {"bar": "qux"}
doAssert $foo == "http://example.com/foo?existing=1&bar=qux"
block: # issue #19782: empty params list preserves existing query
var foo = parseUri("http://example.com/foo?existing=1") ? {:}
doAssert $foo == "http://example.com/foo?existing=1"
block: # issue #19782: empty params on uri without query is a no-op
var foo = parseUri("http://example.com/foo") ? {:}
doAssert $foo == "http://example.com/foo"
block: # getDataUri, dataUriBase64
doAssert getDataUri("", "text/plain") == "data:text/plain;charset=utf-8;base64,"

View File

@@ -135,6 +135,19 @@ block: # issue #22605 for templates, original complex example
doAssert g2(int) == "error"
block: # issue #20811
template injectError(body: untyped): untyped =
template error: untyped {.used, inject.} = "injected"
body
proc outerOpen(error: string): string =
injectError:
proc genericInner[T](): string =
error
genericInner[int]()
doAssert outerOpen("captured") == "injected"
block: # issue #23865 for templates
type Xxx = enum
error

View File

@@ -0,0 +1,77 @@
discard """
output: '''ok
ok
ok'''
"""
# bug #25693
proc d() = discard @[0]
proc f(a: var seq[int], _: string) =
let p = @[0]
d()
a = p
block: # scalar `untyped` parameter
template g(b: untyped) {.dirty.} =
template t: untyped = b
proc g(_: int) = discard
let q = "a"
g:
var a: seq[int]
try:
f(a, q & "1")
except CatchableError:
discard
try:
f(a, q & "1")
except CatchableError:
discard
block: t()
block: t()
echo "ok"
block: # `typed` parameter captured and re-emitted: each emission gets its own
# symbols, otherwise the destructor/liveness pass miscompiles the shared
# local `a` and the program crashes at runtime
template g(b: typed) {.dirty.} =
template t: untyped = b
let q = "a"
g:
var a: seq[int]
try:
f(a, q & "1")
except CatchableError:
discard
try:
f(a, q & "1")
except CatchableError:
discard
block: t()
block: t()
echo "ok"
block: # `varargs[untyped]` parameter takes the same pristine-AST path
template g(b: varargs[untyped]) {.dirty.} =
template t: untyped = b
proc g(_: int) = discard
let q = "a"
g:
var a: seq[int]
try:
f(a, q & "1")
except CatchableError:
discard
try:
f(a, q & "1")
except CatchableError:
discard
block: t()
block: t()
echo "ok"

View File

@@ -5,3 +5,31 @@ type
proc newFoo[T](): Foo[T] = Foo[T](newSeq[T]())
var x = newFoo[Bar[int]]()
# issue #22936
import std/macros
type
InternalFutureBase = object of RootObj
FutureBase = ref object of InternalFutureBase
Future[T] = ref object of FutureBase
internalValue: T
B[T, E] = ref object of Future[T]
proc take[F: Future](fut: F) = discard
proc takeMany[F: Future](futs: seq[F]) = discard
macro checkFutures[F: Future](futs: seq[F]): untyped =
newEmptyNode()
var future: B[void, void]
var futures: seq[B[void, void]]
take(future)
takeMany(futures)
checkFutures(futures)

16
tests/vm/t25849.nim Normal file
View File

@@ -0,0 +1,16 @@
discard """
targets: "c cpp js"
"""
import std/os
from std/sequtils import toSeq
iterator items(a: array[3, string]): lent string {.inline.} =
for i in 0..2:
yield a[i]
static:
const key = "NIM_TESTS_TOSENV_KEY"
for val in items(["a", "b", "c"]):
putEnv(key, val)
doAssert (key, val) in toSeq(envPairs())