Merge branch 'devel' into pr_field

This commit is contained in:
ringabout
2026-07-06 20:23:33 +08:00
committed by GitHub
192 changed files with 12088 additions and 1811 deletions

19
tests/arc/t19312.nim Normal file
View File

@@ -0,0 +1,19 @@
discard """
matrix: "--mm:orc"
output: '''(val: 1)
(val: 1)'''
"""
# Issue #19312: copied ref object is converted to nil if not used in declaration module under ARC/ORC
# https://github.com/nim-lang/Nim/issues/19312
type
Wrapper* = object
val: int
RefWrapper* = ref Wrapper
let
a* = RefWrapper(val: 1)
b* = a
echo b[]
echo a[]

View File

@@ -0,0 +1,12 @@
# Issue: genMagicExpr: mAsgn internal error when using Isolated[T] with primitive types
# in tuple assignment to pointer dereference
import std/isolation
proc main() =
var x: ptr Isolated[float]
x = cast[ptr Isolated[float]](alloc0(sizeof(Isolated[float])))
x[] = isolate(42.0)
dealloc(x)
main()

View File

@@ -0,0 +1,28 @@
discard """
output: '''a
b
c
a
b
c'''
"""
# A `var T` loop variable (here from `mitems`) declared inside an enclosing
# loop must not be reset by dereferencing: at module scope it is emitted as a
# global, and the in-loop reset path used `resetLoc`, which dereferenced the
# still-uninitialized borrowed pointer and crashed with a SIGSEGV.
for p in @["abc", "123"]:
var testA = @["a", "b", "c"]
for l in testA.mitems:
echo(l)
# also exercise actual mutation through the borrowed reference
block:
var ok = true
for p in @["x", "y"]:
var s = @[1, 2, 3]
for l in s.mitems:
l += 10
if s != @[11, 12, 13]: ok = false
doAssert ok

View File

@@ -13,7 +13,7 @@ import asyncdispatch, times
var done = false
proc somethingAsync() {.async.} =
yield sleepAsync 5000
yield sleepAsync 1000
echo "async done"
done = true
@@ -21,5 +21,5 @@ asyncCheck somethingAsync()
var count = 0
while not done:
count += 1
drain 1000
drain 200
echo "iteration: ", count

17
tests/async/t16416.nim Normal file
View File

@@ -0,0 +1,17 @@
discard """
output: '''done'''
"""
# Issue #16416: Can't call closure iterator from inside an async function
# https://github.com/nim-lang/Nim/issues/16416
import asyncdispatch
iterator x(): int {.closure.} =
yield 1
proc y() {.async.} =
for z in x():
discard
waitFor y()
echo "done"

View File

@@ -13,7 +13,7 @@ else:
# This reproduces a case where a socket remains stuck waiting for writes
# even when the socket is closed.
const
timeout = 8000
timeout = 2000
var port = Port(0)
var sent = 0

View File

@@ -30,3 +30,13 @@ block:
var x = M(x: 1)
doAssert(x.x == 1)
block: # bug #25931
type
N {.importc: "const void *".} = pointer
S = object
f: proc (_: N) {.cdecl.}
#var _: proc (_: pointer) {.cdecl.}
proc d(_: proc (_: pointer) {.cdecl.}) = discard
discard S()
d(proc (_: pointer) {.cdecl.} = discard)

View File

@@ -75,3 +75,15 @@ block: # importc type inheritance
doAssert(cast[cint](b) == 123)
var c = foo(b)
doAssert(cast[cint](c) == 123)
block: # bug #25945
var stateRefund = 0
let authCode =
if true:
if false:
stateRefund += 0
@[]
else:
@([1.byte])
discard (if true: (discard; @[]) else: @[0])

View File

@@ -80,3 +80,20 @@ block tissue7104:
sp do ():
inc i
echo "ok ", i
block: # bug #25903
iterator g: int {.closure.} =
discard try:
yield 0
0
except IOError, OSError:
0
let _ = g
block: # bug #25904
iterator w: int {.closure.} =
discard try: 0
except IOError, OSError:
yield 0
0
let _ = w

View File

@@ -1,7 +1,7 @@
discard """
targets: "c"
matrix: "--debugger:native --mangle:nim"
ccodecheck: "'testFunc__titaniummangle95nim_u'"
ccodecheck: "'testFunc_u' \\d+ '__titaniummangle95nim'"
"""
#When debugging this notice that if one check fails, it can be due to any of the above.

View File

@@ -0,0 +1,28 @@
discard """
action: "reject"
errormsg: "type mismatch"
"""
type
Dollarable = concept
proc `$`(x: Self): string
proc checkEqual(x, y: Dollarable) =
if x != y:
echo $x
echo $y
type
StateFlags = enum
sfMatch
sfSoft
MatchKind = enum
NoFurtherMatch
NoMatch
Match
AllFurtherMatch
proc `==`(a: set[StateFlags]; b: MatchKind): bool = true
checkEqual({sfMatch, sfSoft}, Match)

23
tests/concepts/t14913.nim Normal file
View File

@@ -0,0 +1,23 @@
discard """
output: '''done'''
"""
# Issue #14913: Compiler crash when using a default parameter value for a parameter whose type is a concept
# https://github.com/nim-lang/Nim/issues/14913
type
State = object
MoreState = object
StringRecord = concept x, type T
for k, v in fieldPairs(x):
k is string
v is string
StateStrings = object
a, b: string
proc combine(a: State, b: MoreState): StateStrings = discard
proc whoops[T: StringRecord](a: State, b: MoreState, c: T = a.combine(b)) =
discard
whoops(State(), MoreState())
echo "done"

View File

@@ -14,6 +14,8 @@ b
c
1
2
5
test
'''
"""
import conceptsv2_helper
@@ -600,3 +602,15 @@ block:
let test = MemMapFileStream()
spring(test)
# explicit negative "bind once"
type
Dollarable = concept
proc `$`(x: Self): string
proc checkEqual2[T: Dollarable; S: Dollarable](x: T, y: S) =
echo $x
echo $y
checkEqual2(5, "test")

View File

@@ -0,0 +1,30 @@
#ifndef TCPP_DEFAULT_CTOR_ASSIGNMENT_H
#define TCPP_DEFAULT_CTOR_ASSIGNMENT_H
struct AmbiguousAssign {
int x;
const char* y;
AmbiguousAssign(): x(0), y(nullptr) {}
AmbiguousAssign(int x, const char* y): x(x), y(y) {}
AmbiguousAssign& operator=(int v) {
x = v;
y = nullptr;
return *this;
}
AmbiguousAssign& operator=(const char* s) {
x = 0;
y = s;
return *this;
}
AmbiguousAssign& operator=(const AmbiguousAssign& other) {
x = other.x;
y = other.y;
return *this;
}
};
#endif

View File

@@ -0,0 +1,14 @@
discard """
cmd: "nim cpp $file"
"""
type
AmbiguousAssign {.importcpp, header: "tcpp_default_ctor_assignment.h".} = object
x: cint
y: cstring
proc main =
var xs = newSeq[AmbiguousAssign](3)
doAssert xs.len == 3
main()

View File

@@ -0,0 +1,20 @@
discard """
output: '''done'''
"""
# Issue #9617: Compiler error with sequences of destructible types
# https://github.com/nim-lang/Nim/issues/9617
type
Foo* = object
Bar = ref object
s: seq[Foo]
proc `=destroy`*(self: var Foo) = echo "hi"
proc test(b: Bar) =
for i in b.s:
discard
test(Bar())
echo "done"

View File

@@ -57,4 +57,8 @@ block:
except IOError as e:
raise
f()
f()
block:
static: doAssert IOError is Exception
proc r(e: ref Exception) {.raises: [IOError].} = raise (ref IOError)(e)

View File

@@ -13,4 +13,12 @@ proc send(x: string) =
let wrapper = Thing(x: x)
discard isolate(wrapper)
send("la")
send("la")
block:
func enqueue[T](buf: var array[10, T], elem: sink T) =
`=sink`(buf[0], elem)
var buf: array[10, int]
enqueue(buf, 42)
assert buf[0] == 42

View File

@@ -283,3 +283,13 @@ block: # bug #23952
doAssert s1 != s2
static: foo()
foo()
# bug #25908
type S {.pure.} = enum a, b
iterator foo(x: array[1, S]): lent S =
yield x[0]
iterator f(_: int | int): S =
for a in foo([S.b]): yield a
for v in f(0): doAssert v == S.b

11
tests/errmsgs/t16956.nim Normal file
View File

@@ -0,0 +1,11 @@
discard """
action: "reject"
errormsg: "invalid type: 'iterator (a: int, b: int, step: Positive): int{.inline, noSideEffect, gcsafe.}' for const"
line: 9
"""
# Issue #16956: Error: not unused depending on unrelated code changes
# https://github.com/nim-lang/Nim/issues/16956
const f2 = case true
of true: countup[int]
of false: countdown[int]

View File

@@ -0,0 +1,18 @@
{.experimental: "openSym".}
import std/hashes
template maxHash(t): untyped = high(t).Hash
template implCaptured() {.dirty.} =
result = maxHash(t)
template implInjected() {.dirty.} =
type Hash = uint8
result = high(t).Hash
proc usesCaptured*[T](t: T): auto =
implCaptured()
proc usesInjected*[T](t: T): auto =
implInjected()

35
tests/generics/t21252.nim Normal file
View File

@@ -0,0 +1,35 @@
discard """
output: '''done'''
"""
# Issue #21252: Compiler SIGSEGV when not instantiating generic proc correctly
# https://github.com/nim-lang/Nim/issues/21252
type
Addr = object
layerIdx: int
type Msg0 = object
address: Addr
selSample: tuple[inArrays: seq[seq[float64]], target: seq[float64], gradientStrength: float64]
type WeightUpdate = object
address: Addr
proc workerThread[
layer0StimulusWidth: static int
]() =
discard
proc z*[
layer0StimulusWidth: static int,
nUnitsPerLayer: static seq[int],
targetLen: static int
]() =
workerThread[layer0StimulusWidth]()
when isMainModule:
const layer0StimulusWidth: int = 5*29
const nUnitsPerLayer: seq[int] = @[50, 5]
const targetLen: int = 5
z[layer0StimulusWidth, nUnitsPerLayer, targetLen]()
echo "done"

View File

@@ -14,12 +14,6 @@ type WorkProc[A, B] = proc(a: A): Option[B] {.nimcall.}
proc worker[TArg](p: TArg) {.thread, nimcall.} =
discard
proc readFilesThread() =
type TArg[A, B] =
tuple[r: ptr Channel[Option[A]], w: ptr Channel[Option[B]], p: WorkProc[A, B]]
var readThread: Thread[TArg[int, SharedBuf]]
proc readFilesAd() {.async.} =
var readChan: Channel[Option[int]]
@@ -29,8 +23,6 @@ proc readFilesAd() {.async.} =
var readThread: Thread[TArg[int, SharedBuf]]
let test = await (addr readChan).recv()
joinThread(readThread)
waitFor readFilesAd()
type

View File

@@ -0,0 +1,12 @@
# the RHS of a dot expression can be wrapped in `nkOpenSym` by the generic
# prepass (e.g. a `x.T` type conversion expanded from a dirty template):
# the captured symbol (`hashes.Hash`, not in scope here) must be used when
# nothing is injected, while a symbol injected during instantiation still
# overrides it
{.experimental: "openSym".}
import mopensymdot
doAssert sizeof(usesCaptured(@[1, 2, 3])) == sizeof(int) # hashes.Hash
doAssert sizeof(usesInjected(@[1, 2, 3])) == 1 # injected uint8

16
tests/ic/mconvbase.nim Normal file
View File

@@ -0,0 +1,16 @@
# Helper for tconverterreexport.nim: defines a converter that a consumer reaches
# only through a re-export chain (mconvbase <- mconvmid <- test). Models
# faststreams' `implicitDeref: InputStreamHandle -> InputStream`.
type
Handle* = object
x*: int
Real* = object
x*: int
converter toReal*(h: Handle): Real = Real(x: h.x)
type Reader* = object
v: int
func init*(T: type Reader, r: Real): Reader = Reader(v: r.x + 100)
func readVal*(r: Reader): int = r.v

13
tests/ic/mconvmid.nim Normal file
View File

@@ -0,0 +1,13 @@
# Helper for tconverterreexport.nim: re-exports mconvbase and provides a
# `mixin`-using template whose expansion in the consumer relies on the
# re-exported `toReal` converter (Handle -> Real) for `init`. Mirrors
# ssz_serialization re-exporting serialization/faststreams and its `decode`
# template needing the implicit stream conversion at `init(Reader, stream)`.
import mconvbase
export mconvbase
template decode*(): auto =
mixin init, readVal
var h = Handle(x: 5)
var r = init(Reader, h) # requires the re-exported converter toReal
readVal(r)

28
tests/ic/mctglobal.nim Normal file
View File

@@ -0,0 +1,28 @@
# Helper module for tcompiletimeglobal.nim (not a test itself; no `discard`).
#
# Exercises `{.compileTime.}` module-level globals across the NIF boundary: under
# `nim ic` this module is compiled to its own NIF and *loaded* (not semchecked)
# by the importer, so its compile-time globals must be eagerly initialized at
# load time. A macro that splices such a global into a `quote do:` otherwise
# reads a nil VM slot.
import macros
let injectedName {.compileTime.} = ident "ctgValue"
proc genAssign*(): NimNode =
## The order-fragile case: the CT global is read inside a proc that the macro
## calls, not in the macro's own `quote`. The lazy VM init attaches to the
## first vmgen'd reference, which need not be the first one executed.
result = quote do:
`injectedName` = 42
macro defineCtgValue*(): untyped =
result = newStmtList()
# Read the global via the helper proc FIRST, then via the macro's own quote,
# so the proc's reference executes before the macro's: only eager init at load
# time makes both see the initialized value.
let assign = genAssign()
result.add quote do:
var `injectedName`: int
result.add assign

View File

@@ -0,0 +1,2 @@
converter toBool*(x: int): bool = x != 0

14
tests/ic/memit.nim Normal file
View File

@@ -0,0 +1,14 @@
# Helper for temit.nim: a NON-main module with a module-scope `{.emit.}` that
# introduces a C macro consumed by an `{.importc, nodecl.}` const. The IC backend
# reload used to drop top-level emit pragmas — writeToplevelNode wrote them to the
# by-symbol-index implementation section, where a symbol-less pragma is never
# reloaded — so the generated C lost the `#define` and failed to compile with
# "use of undeclared identifier". Mirrors lib/pure/concurrency/cpuinfo.nim's
# `#include <sys/sysctl.h>` + `CTL_HW`/`HW_NCPU` importc pattern.
{.emit: """/*TYPESECTION*/
#define NIM_IC_EMIT_ANSWER 42
""".}
let icEmitAnswer {.importc: "NIM_IC_EMIT_ANSWER", nodecl.}: cint
proc emitAnswer*(): int = int(icEmitAnswer)

View File

@@ -0,0 +1,9 @@
# Calls `emptyOwned` from a DIFFERENT module than the one that owns it, so the
# reference at link time must resolve to a definition the owning module emits.
import memptyowned
proc callEmpty*(cond: bool) =
if cond:
emptyOwned()
echo "called ", cond

12
tests/ic/memptyowned.nim Normal file
View File

@@ -0,0 +1,12 @@
# Helper for `temptyowned`: exports a concrete proc whose body folds to
# nothing (an `nkEmpty` body, like Nimbus' `extras.incInternalErrors` when the
# metrics counter's `.inc()` expands to a no-op under `-u:metrics`). The proc is
# NOT called within its own module — only `memptycaller` references it — so the
# per-module backend's owned-routine seeding is the ONLY thing that can emit it.
template maybe*(x: untyped) =
when false:
x
proc emptyOwned*() =
maybe(echo "unreachable")

3
tests/ic/mgofmc.nim Normal file
View File

@@ -0,0 +1,3 @@
# Helper for tgenericoffer.nim: a distinct type whose `==` lives in THIS module.
type MultiCodec* = distinct int
proc `==`*(a, b: MultiCodec): bool = int(a) == int(b)

10
tests/ic/mgofmh.nim Normal file
View File

@@ -0,0 +1,10 @@
# Helper for tgenericoffer.nim: imports mgofmc, builds a compile-time const
# Table[MultiCodec,int] and exposes a GENERIC proc whose (T-independent) body
# instantiates getOrDefault[MultiCodec] here, where mgofmc's `==` is visible.
import tables, mgofmc
proc buildTab(): Table[MultiCodec, int] =
result[MultiCodec(1)] = 100
result[MultiCodec(2)] = 200
const CodeHashes = buildTab()
proc digest*[T](x: T): int =
CodeHashes.getOrDefault(MultiCodec(2))

6
tests/ic/mgofpid.nim Normal file
View File

@@ -0,0 +1,6 @@
# Helper for tgenericoffer.nim: imports mgofmh but NOT mgofmc, then calls the
# generic `digest`. Under IC this re-instantiates digest here, where mgofmc's
# `==` is NOT in scope — so getOrDefault[MultiCodec] must be REUSED from mgofmh's
# offer, not re-instantiated, or `==(MultiCodec)` resolution fails.
import mgofmh
proc callDigest*(): int = digest(5)

29
tests/ic/minitordera.nim Normal file
View File

@@ -0,0 +1,29 @@
# Helper for tinitorder (not a test itself; no `discard`).
#
# Imports minitorderb and, in its OWN init, reads the state minitorderb set up.
# With the wrong (importer-first) init order `gBState` is still 0 here. It also
# allocates a seq in its init so that under `--mm:refc` the GC (set up by the
# system module's init) must already be live — i.e. the system module's init has
# to be ordered first.
import minitorderb
var
gASawB = -1
gAItems: seq[int]
proc getASawB*(): int = gASawB
proc getACount*(): int = gAItems.len
proc recordA() =
gASawB = getBState()
gAItems = @[1, 2, 3]
# Allocate (and drop) enough garbage to force a GC cycle DURING module init.
# Under refc that runs a conservative stack scan, which needs the main
# thread's stack bottom already set — i.e. `initStackBottomWith` must run
# before the module inits, not after them.
for i in 0 ..< 100_000:
let s = @[i, i + 1, i + 2]
doAssert s.len == 3
recordA()

15
tests/ic/minitorderb.nim Normal file
View File

@@ -0,0 +1,15 @@
# Helper for tinitorder (not a test itself; no `discard`).
#
# An imported module whose INIT sets module-level state at runtime. Under the
# per-module backend its init must run BEFORE any importer's init (the imported
# module is a dependency → post-order). With the buggy importer-first order this
# module's `setupB` runs too late and importers observe `gBState == 0`.
var gBState: int
proc getBState*(): int = gBState
proc setupB() =
gBState = 42
setupB()

15
tests/ic/mmethanimal.nim Normal file
View File

@@ -0,0 +1,15 @@
# Helper for tmethitanium: the OWNER module of a `{.base.}` method. Its concrete
# base body is emitted here; the whole-program dispatcher is synthesized into the
# main module. Under `--debugger:native` the backend uses the Itanium mangling
# scheme, which encodes the signature instead of the `disamb`, so the base method
# and its same-signature dispatcher want the identical clean C name. The
# clean-vs-unique tie-break used to depend on a per-MODULE set (`mangledPrcs`),
# which the per-module IC backend cannot share — the base mangled clean at this
# owner but `speak_u<n>` (an unstable `itemId.item`) at every demander, so it was
# defined once and referenced under names nobody defined. See
# ccgutils.makeUnique (disamb, not itemId) + ccgtypes.fillBackendName.
type Animal* = ref object of RootObj
method speak*(a: Animal): string {.base.} =
"generic-animal-sound"

14
tests/ic/mmethdog.nim Normal file
View File

@@ -0,0 +1,14 @@
# Helper for tmethitanium: an override in a DIFFERENT module than the base, plus
# a `procCall` super-reference to the base from this non-owner module. That
# cross-module reference to the base impl is what diverged from the base's
# definition name under the per-module Itanium mangling.
import mmethanimal
type Dog* = ref object of Animal
method speak*(a: Dog): string =
"woof"
proc speakBoth*(a: Dog): string =
procCall(speak(Animal(a))) & "/" & speak(a)

23
tests/ic/mmethupref.nim Normal file
View File

@@ -0,0 +1,23 @@
# Helper for tmethupref: a `{.base.}` method whose body contains a closure
# iterator that in turn contains a nested closure capturing a method-local.
# The capture forces an `up` reference chain (nested closure -> iterator env ->
# method env). The method also gets a dispatcher (a `copySym` clone that shares
# the method's body sub-tree, including the iterator). Under `nim ic` the
# dispatcher used to be treated as an owned runtime routine and lambda-lifted in
# the per-module lower stage, lifting the SHARED iterator a second time under a
# different owner identity -> "up references do not agree" / "could not determine
# closure type". See nifbackend.ownsRuntimeRoutine (sfDispatcher exclusion).
type Base* = ref object of RootObj
val*: int
method compute*(b: Base): int {.base.} =
var acc = b.val
iterator steps(): int {.closure.} =
proc bump() =
acc += 1
bump()
bump()
yield acc
for s in steps():
result = s

12
tests/ic/mmodsymadd.nim Normal file
View File

@@ -0,0 +1,12 @@
import mmodsymasm
# The dead `else` branch qualifies a re-exported module (`mmodsymarm`) whose
# `foo` is gated out on this host: the module sym binds at template-definition
# but the member never resolves, so a dangling module-symbol reference survives
# into the serialized template body. Before the `ModMarker` fix this failed
# under `nim ic` with `symbol has no offset` when the consumer loaded this NIF.
template satAdd*(a, b: uint64): uint64 =
when not defined(mmodSymFakeArch):
mmodsymasm.mmodsymx86.foo(a, b)
else:
mmodsymasm.mmodsymarm.foo(a, b)

5
tests/ic/mmodsymarm.nim Normal file
View File

@@ -0,0 +1,5 @@
# Gated out on every host (mmodSymFakeArch is never defined): `foo` does NOT
# exist here, so a qualified `…mmodsymarm.foo` cannot resolve to the proc and
# leaves the bare re-exported MODULE symbol dangling in the template body.
when defined(mmodSymFakeArch):
func foo*(a, b: uint64): uint64 = a + b

2
tests/ic/mmodsymasm.nim Normal file
View File

@@ -0,0 +1,2 @@
import mmodsymx86, mmodsymarm
export mmodsymx86, mmodsymarm

3
tests/ic/mmodsymx86.nim Normal file
View File

@@ -0,0 +1,3 @@
# Live on every real host: provides `foo` so the template's taken branch resolves.
when not defined(mmodSymFakeArch):
func foo*(a, b: uint64): uint64 = a + b

21
tests/ic/mpureenum.nim Normal file
View File

@@ -0,0 +1,21 @@
# Helper module for tpureenum.nim (not a test itself; no `discard`).
#
# Defines a `{.pure.}` enum whose field name (`Number`) collides with a distinct
# type of the same name. Under `nim ic` the pure enum is loaded from a NIF; its
# fields must NOT leak into the importer's unqualified scope (the source path
# keeps them out via `declarePureEnumField`). Before the fix, a loaded pure
# enum's fields were marked bare-importable and leaked into `interf`, so the
# field shadowed the distinct type and `uint64(x).Number` failed with
# "undeclared field 'Number' for type system.uint64" (the nim-json-serialization
# `JsonValueKind.Number` vs web3 `Number = distinct uint64` bug).
type
Number* = distinct uint64
JsonValueKind* {.pure.} = enum
String, Number, Object, Array, Bool, Null
proc val*(x: Number): uint64 = uint64(x)
proc toNumber*(x: uint64): Number =
x.Number # must bind to the distinct TYPE `Number`, not the pure enum field

View File

@@ -0,0 +1,68 @@
# Helper module for tsighashstable.nim (not a test itself; no `discard`).
#
# Models nim-serialization's auto-serialization registry: a flavor records which
# types it auto-serializes in a `std/macrocache` keyed by `signatureHash(T)`,
# computed through a generic `{.compileTime.}` func. The registration happens
# here (at this module's compile time); the lookup happens in the importer.
#
# Under `nim ic` the two modules are compiled separately, so the generic
# `getSig[T]` is instantiated independently on each side. `signatureHash` must
# therefore hash the *type* `T` denotes, not the generic parameter symbol — the
# latter mixes in a per-module `disamb` counter and diverges across the NIF
# boundary, making the lookup miss.
import std/[macrocache, macros, typetraits]
type DefaultFlavor* = object
macro calcSig*(T: typed): untyped =
doAssert(T.typeKind == ntyTypeDesc)
result = newLit(signatureHash(T))
func getSig*(F: type DefaultFlavor, T: distinct type): string {.compileTime.} =
calcSig(T)
func getTable*(F: type DefaultFlavor): CacheTable {.compileTime.} =
CacheTable("nsrzStableTable" & typetraits.name(F))
func setAuto*(F: type DefaultFlavor, T: distinct type) {.compileTime.} =
let sig = F.getSig(T)
let table = F.getTable()
if not table.hasKey(sig):
table[sig] = newLit(1)
func getAuto*(F: type DefaultFlavor, T: distinct type): bool {.compileTime.} =
let sig = F.getSig(T)
let table = F.getTable()
table.hasKey(sig)
func tcOrMember*(F: type DefaultFlavor, TC: distinct type, TM: distinct type): bool {.compileTime.} =
## Is `TM` registered, or its parent type class `TC`? Models
## `typeClassOrMemberAutoSerialize` — used to auto-serialize any `object`/`tuple`.
if F.getAuto(TM): return true
if F.getAuto(TC): return true
false
template autoCheck*(F: distinct type, T: distinct type, body) =
when not F.getAuto(T):
{.error: "auto serialization not enabled for `" & typetraits.name(T) & "`".}
else:
body
template autoCheckTC*(F: distinct type, TC: distinct type, M: distinct type, body) =
when not F.tcOrMember(TC, M):
{.error: "auto serialization not enabled for `" & typetraits.name(M) &
"` of typeclass `" & typetraits.name(TC) & "`".}
else:
body
static:
setAuto(DefaultFlavor, string)
setAuto(DefaultFlavor, SomeInteger)
setAuto(DefaultFlavor, seq)
# Builtin *type classes*: `object`/`tuple` are `tyBuiltInTypeClass`, whose
# `signatureHash` must not mix in the placeholder son's process-local type id
# or the lookup misses across the NIF boundary (the real nim-serialization bug
# surfaced by Nimbus: `MultiAddress`/`RequestId` "of typeclass `object`").
setAuto(DefaultFlavor, object)
setAuto(DefaultFlavor, tuple)

3
tests/ic/mtoffcodec.nim Normal file
View File

@@ -0,0 +1,3 @@
# Helper for ttypeoffer.nim: the extra overload that flips `compiles(toSszType(Gwei))`.
import mtoffgwei
template toSszType*(v: Gwei): uint64 = uint64(v)

2
tests/ic/mtoffgwei.nim Normal file
View File

@@ -0,0 +1,2 @@
# Helper for ttypeoffer.nim: a distinct basic type.
type Gwei* = distinct uint64

10
tests/ic/mtoffssz.nim Normal file
View File

@@ -0,0 +1,10 @@
# Helper for ttypeoffer.nim: a generic container whose hash-array bound depends
# on a `mixin toSszType` resolved at the instantiation site (cf. ssz dataPerChunk).
template perChunk*(T: type): int =
mixin toSszType
when compiles(toSszType(default(T))): 4 else: 1
type
HA*[N: static int; T] = object
data*: array[N, T]
hashes*: array[N div perChunk(T), uint64]

5
tests/ic/mtoffstate.nim Normal file
View File

@@ -0,0 +1,5 @@
# Helper for ttypeoffer.nim ("datatypes" analog): instantiates HA[64,Gwei] WITHOUT
# the codec in scope -> perChunk=1. This is the instance that must be shared.
import mtoffssz, mtoffgwei
type StateA* = object
field*: HA[64, Gwei]

10
tests/ic/mtoffuser.nim Normal file
View File

@@ -0,0 +1,10 @@
# Helper for ttypeoffer.nim ("db_immutable" analog): imports the codec (so
# `toSszType(Gwei)` is visible -> perChunk=4) and re-instantiates HA[64,Gwei].
# With the `(toffer …)` fix it reuses mtoffstate's instance instead.
import mtoffssz, mtoffgwei, mtoffcodec, mtoffstate
type StateB* = object
field*: HA[64, Gwei]
proc check*() =
static: doAssert sizeof(StateA) == sizeof(StateB)
echo "ok"

2
tests/ic/mtscopea.nim Normal file
View File

@@ -0,0 +1,2 @@
# Helper for ttransitiveoffer.nim: const that the generic body binds at definition.
const TScopeSize* = 65

2
tests/ic/mtscopeb.nim Normal file
View File

@@ -0,0 +1,2 @@
# Helper: a CONFLICTING const of the same name, visible only in the consumer.
const TScopeSize* = 33

7
tests/ic/mtscopegen.nim Normal file
View File

@@ -0,0 +1,7 @@
# Helper: defines a generic whose body uses TScopeSize via the strformat `&`
# macro (late-bound), resolved in THIS module's scope (mtscopea -> 65).
import mtscopea, std/strformat
export mtscopea
func fromRaw*[T](x: T): string =
const msg = &"size {TScopeSize - 1}"
result = msg & " " & $int(x)

3
tests/ic/mtscopemid.nim Normal file
View File

@@ -0,0 +1,3 @@
# Helper: makes mtscopewarm a TRANSITIVE import of the consumer.
import mtscopewarm
export mtscopewarm

4
tests/ic/mtscopewarm.nim Normal file
View File

@@ -0,0 +1,4 @@
# Helper: instantiates fromRaw[int] in a CLEAN scope (no mtscopeb) -> the
# correct instance that the consumer must reuse.
import mtscopegen
proc warm*(): string = fromRaw(5)

View File

@@ -0,0 +1,16 @@
discard """
output: '''42'''
"""
# Regression test: `{.compileTime.}` module-level globals of a NIF-loaded module
# must be initialized so macros/compile-time procs that splice them produce valid
# code. Before the eager-init fix this failed under `nim ic` with
# "attempt to access a nil address" / "illformed AST: break nil" /
# "undeclared identifier". `koch ic` only checks the compile succeeds; a nil
# global makes `defineCtgValue` emit `var <nil>: int` / `<nil> = 42` and the
# compile fails.
import mctglobal
defineCtgValue()
echo ctgValue

View File

@@ -0,0 +1,18 @@
discard """
output: '''105'''
"""
# Regression test: a converter imported into a module and re-exported from it
# (mconvbase's `toReal` re-exported by mconvmid) must survive NIF serialization
# in the re-exporting module's interface, so a consumer that reaches the
# converter only through that re-export chain can still apply it implicitly.
#
# Before the fix only converters DEFINED in a module were logged for IC
# (`addConverterDef`); converters merely imported/re-exported (`addConverter`
# from `importer.addUnnamedIt`) were not, so under `nim ic` the re-exported
# converter was invisible to importers and `init(Reader, Handle)` failed with a
# type mismatch (the real-world symptom: faststreams `InputStreamHandle ->
# InputStream` dropped, breaking `SSZ.decode`/`encode` in Nimbus).
import mconvmid
echo decode()

7
tests/ic/temit.nim Normal file
View File

@@ -0,0 +1,7 @@
# Regression: a module-scope `{.emit.}` in an imported module must survive the
# `nim ic` backend reload (see memit.nim). Before the fix the dropped `#define`
# made the generated C fail to compile, so `nim ic` exited non-zero.
import memit
doAssert emitAnswer() == 42
echo emitAnswer()

18
tests/ic/temptyowned.nim Normal file
View File

@@ -0,0 +1,18 @@
discard """
output: '''called false
done'''
"""
# Regression test for the per-module IC backend dropping an owned routine whose
# body folds to `nkEmpty`. `memptyowned.emptyOwned` is a real, concrete, owned
# proc with an empty body; `memptycaller.callEmpty` references it across a module
# boundary. The owning module's `ownsRuntimeRoutine` seeding used to reject any
# routine with an `nkEmpty` body, so nobody emitted `emptyOwned` -> undefined
# reference at link (mirrors Nimbus `ncli` linking `extras.incInternalErrors`).
# Whole-program cgen always emits it as `void emptyOwned(void){}`; the per-module
# backend must too.
import memptycaller
callEmpty(false)
echo "done"

View File

@@ -0,0 +1,12 @@
discard """
output: '''200'''
"""
# Regression test for the `(offer …)` generic-instance sharing across the NIF
# boundary. A generic loaded from a NIF whose body uses a type-bound op on a
# CONCRETE type (here getOrDefault on Table[MultiCodec,int]) must be reused, not
# re-instantiated in the consumer's scope which lacks the type's `==`. Before the
# fix this failed under `nim ic` with `hashcommon.nim type mismatch ... MultiCodec`.
import mgofpid
echo callDigest()

23
tests/ic/tinitorder.nim Normal file
View File

@@ -0,0 +1,23 @@
discard """
output: '''42 3'''
"""
# Regression test for per-module-backend module-init ORDERING.
#
# NimMain must call each module's init in DEPENDENCY (post-order) order: an
# imported module's init has to run before its importer's. The whole-program
# backend gets this for free (it iterates `modulesClosed`, built in module-finish
# order); the per-module backend reconstructs it in `nifbackend`. The earlier
# code iterated `bl.mods` by POSITION, which runs importers before their
# dependencies (an importer gets a lower file position than the modules it
# imports) — and the system module (which runs `initGC` in its init) was not
# ordered first at all.
#
# Module chain: tinitorder -> minitordera -> minitorderb. `minitorderb`'s init
# sets a global to 42; `minitordera`'s init reads it (and allocates a seq). With
# the buggy order `minitordera` runs first and reads 0 (or, under refc, crashes
# allocating before the GC is up). Correct order prints `42 3`.
import minitordera
echo getASawB(), " ", getACount()

46
tests/ic/tmeta_async.nim Normal file
View File

@@ -0,0 +1,46 @@
discard """
description: '''metamorphic IC: async/await across an edit (continuations + clean==incremental)'''
"""
# Async is the most cache-fragile area under `nim ic`: the `{.async.}` transform
# generates continuation closures and lifts environments, and those lowered
# bodies must survive incremental edits and converge to exactly what a clean
# build produces. This drives an edit to an async proc body and asserts the
# importer is NOT rebuilt (the lifted continuation stays local to its module),
# a no-op changes nothing, and the final state is byte-identical to a clean
# build. See doc/ic_ideas.md and [[ic-nimbus-test]] (async was the long pole).
#? metamorphic
#!FILE worker.nim
import std/asyncdispatch
proc compute*(x: int): Future[int] {.async.} =
await sleepAsync(0)
result = x * 2
#!FILE main.nim
import std/asyncdispatch, worker
echo waitFor compute(21)
#!STEP expect: 42
# --- edit the async proc body. The continuation env lives in `worker`, so only
# `worker` rebuilds; `main` (the caller) is left untouched -> modules: 1.
# (An async body edit does perturb `worker`'s interface cookie via the
# generated env type, so this is not asserted as a pure `body-edit`.)
#!FILE worker.nim
import std/asyncdispatch
proc compute*(x: int): Future[int] {.async.} =
await sleepAsync(0)
result = x * 3
#!STEP expect: 63; modules: 1
# --- re-emit identical content: nothing may change, lifted closures included.
#!FILE worker.nim
import std/asyncdispatch
proc compute*(x: int): Future[int] {.async.} =
await sleepAsync(0)
result = x * 3
#!STEP expect: 63; noop

View File

@@ -0,0 +1,64 @@
discard """
description: '''metamorphic IC: expression-based `let x = case ...` idiom'''
"""
# nimbus-eth2 leans on expression-style code (`let x = case ...` rather than
# statement assignment). This exercises that construct through the incremental
# path: a body edit that adds a `case` branch stays local to the module, while a
# return-type change (interface edit) propagates to the importer even though the
# importer's source is byte-identical. See doc/ic_ideas.md.
#? metamorphic
#!FILE classify.nim
proc classify*(n: int): string =
let kind = case n
of 0: "zero"
of 1, 2, 3: "small"
else: "big"
result = kind & "(" & $n & ")"
#!FILE main.nim
import classify
echo classify(0), " ", classify(2), " ", classify(99)
#!STEP expect: zero(0) small(2) big(99)
# --- body edit: add a `case` branch and tweak a label. The signature is
# unchanged, so no interface cookie changes and only `classify` rebuilds.
#!FILE classify.nim
proc classify*(n: int): string =
let kind = case n
of 0: "ZERO"
of 1, 2, 3: "small"
of 4, 5, 6: "medium"
else: "big"
result = kind & "(" & $n & ")"
#!STEP expect: ZERO(0) small(2) big(99); body-edit; modules: 1
# --- re-emit identical content: nothing may change.
#!FILE classify.nim
proc classify*(n: int): string =
let kind = case n
of 0: "ZERO"
of 1, 2, 3: "small"
of 4, 5, 6: "medium"
else: "big"
result = kind & "(" & $n & ")"
#!STEP expect: ZERO(0) small(2) big(99); noop
# --- interface edit: the expression-`case` now yields `int`, changing
# `classify`'s return type. `main`'s source is byte-identical (`echo` prints
# either) yet must re-sem & recodegen -> the cookie changes and 2 modules
# rebuild. The final step also runs the clean==incremental check.
#!FILE classify.nim
proc classify*(n: int): int =
result = case n
of 0: 0
of 1, 2, 3: 1
of 4, 5, 6: 5
else: 9
#!STEP expect: 0 1 9; iface-edit; modules: 2

View File

@@ -0,0 +1,33 @@
discard """
description: '''metamorphic IC: generic instantiation cache stability across edits'''
"""
# A generic lives in `gen` but is *instantiated* in `main` (the per-module
# backend emits instance bodies at the instantiation site). Editing the generic
# body must therefore rebuild both `gen` and `main`, and an incremental edit must
# still converge to exactly the same artifacts as a clean build. This exercises
# the static/generic-instance cache path that has historically been bug-prone.
#? metamorphic
#!FILE gen.nim
proc box*[T](x: T): seq[T] = @[x, x]
#!FILE main.nim
import gen
echo box(3).len, " ", box("hi")[0]
#!STEP expect: 2 hi
# --- edit the generic body: the importer holds the instantiations, so both the
# definer and the instantiation site rebuild (2 modules).
#!FILE gen.nim
proc box*[T](x: T): seq[T] = @[x, x, x]
#!STEP expect: 3 hi; modules: 2
# --- re-emit identical content: nothing may change.
#!FILE gen.nim
proc box*[T](x: T): seq[T] = @[x, x, x]
#!STEP expect: 3 hi; noop

61
tests/ic/tmeta_ortype.nim Normal file
View File

@@ -0,0 +1,61 @@
discard """
description: '''metamorphic IC: or-type (type-class union) idiom from nimbus forks.nim'''
"""
# nimbus-eth2 writes a lot of generic code over `A | B | C` type-class unions
# (consensus forks). This models that idiom: a `Fruit = Apple | Banana` union
# with a generic `describe[T: Fruit]` dispatched by `when T is ...`. The generic
# is instantiated in `main`, so editing its body rebuilds both modules, and the
# incremental result must match a clean build. See doc/ic_ideas.md.
#? metamorphic
#!FILE forks.nim
type
Apple* = object
weight*: int
Banana* = object
length*: int
Fruit* = Apple | Banana
proc describe*[T: Fruit](x: T): string =
when T is Apple: "apple " & $x.weight
else: "banana " & $x.length
#!FILE main.nim
import forks
echo describe(Apple(weight: 5)), " | ", describe(Banana(length: 9))
#!STEP expect: apple 5 | banana 9
# --- edit the generic body (no signature change). The union/constraint is
# untouched, so no interface cookie changes; but `main` holds the two
# instantiations, so both modules' codegen rebuilds.
#!FILE forks.nim
type
Apple* = object
weight*: int
Banana* = object
length*: int
Fruit* = Apple | Banana
proc describe*[T: Fruit](x: T): string =
when T is Apple: "APPLE " & $x.weight
else: "BANANA " & $x.length
#!STEP expect: APPLE 5 | BANANA 9; body-edit; modules: 2
# --- re-emit identical content: nothing may change.
#!FILE forks.nim
type
Apple* = object
weight*: int
Banana* = object
length*: int
Fruit* = Apple | Banana
proc describe*[T: Fruit](x: T): string =
when T is Apple: "APPLE " & $x.weight
else: "BANANA " & $x.length
#!STEP expect: APPLE 5 | BANANA 9; noop

93
tests/ic/tmeta_result.nim Normal file
View File

@@ -0,0 +1,93 @@
discard """
description: '''metamorphic IC: Result[T, E] (nim-results style) variant-object idiom'''
"""
# nimbus-eth2 threads errors through nim-results' `Result[T, E]`, a generic
# *variant* (case) object. This models a minimal hermetic version and exercises
# the generic-variant cache path across edits: a body edit of a generic accessor,
# adding a public overload (an interface edit), and a no-op — with a final
# clean==incremental check. See doc/ic_ideas.md.
#? metamorphic
#!FILE results.nim
type
ResultKind = enum rOk, rErr
Result*[T, E] = object
case kind: ResultKind
of rOk: v: T
of rErr: e: E
proc ok*[T, E](x: T): Result[T, E] = Result[T, E](kind: rOk, v: x)
proc err*[T, E](x: E): Result[T, E] = Result[T, E](kind: rErr, e: x)
proc isOk*[T, E](r: Result[T, E]): bool = r.kind == rOk
proc get*[T, E](r: Result[T, E]): T = r.v
proc error*[T, E](r: Result[T, E]): E = r.e
#!FILE main.nim
import results
proc parse(s: string): Result[int, string] =
if s == "42": ok[int, string](42)
else: err[int, string]("bad: " & s)
let a = parse("42")
let b = parse("x")
echo (if a.isOk: $a.get else: a.error), " ", (if b.isOk: $b.get else: b.error)
#!STEP expect: 42 bad: x
# --- body-only edit of a generic accessor (`error`): signature unchanged, so no
# interface cookie changes; the importer holds the instantiation, so both
# modules' codegen rebuilds.
#!FILE results.nim
type
ResultKind = enum rOk, rErr
Result*[T, E] = object
case kind: ResultKind
of rOk: v: T
of rErr: e: E
proc ok*[T, E](x: T): Result[T, E] = Result[T, E](kind: rOk, v: x)
proc err*[T, E](x: E): Result[T, E] = Result[T, E](kind: rErr, e: x)
proc isOk*[T, E](r: Result[T, E]): bool = r.kind == rOk
proc get*[T, E](r: Result[T, E]): T = r.v
proc error*[T, E](r: Result[T, E]): E = "ERR:" & r.e
#!STEP expect: 42 ERR:bad: x; body-edit; modules: 2
# --- re-emit identical content: nothing may change.
#!FILE results.nim
type
ResultKind = enum rOk, rErr
Result*[T, E] = object
case kind: ResultKind
of rOk: v: T
of rErr: e: E
proc ok*[T, E](x: T): Result[T, E] = Result[T, E](kind: rOk, v: x)
proc err*[T, E](x: E): Result[T, E] = Result[T, E](kind: rErr, e: x)
proc isOk*[T, E](r: Result[T, E]): bool = r.kind == rOk
proc get*[T, E](r: Result[T, E]): T = r.v
proc error*[T, E](r: Result[T, E]): E = "ERR:" & r.e
#!STEP expect: 42 ERR:bad: x; noop
# --- interface edit: add a public `get` overload (a new exported signature).
# `main` doesn't call it, yet importing `results` whose interface changed
# forces a re-sem; the cookie changes and >= 2 modules rebuild. The final
# step also runs the clean==incremental check.
#!FILE results.nim
type
ResultKind = enum rOk, rErr
Result*[T, E] = object
case kind: ResultKind
of rOk: v: T
of rErr: e: E
proc ok*[T, E](x: T): Result[T, E] = Result[T, E](kind: rOk, v: x)
proc err*[T, E](x: E): Result[T, E] = Result[T, E](kind: rErr, e: x)
proc isOk*[T, E](r: Result[T, E]): bool = r.kind == rOk
proc get*[T, E](r: Result[T, E], fallback: T): T = (if r.kind == rOk: r.v else: fallback)
proc get*[T, E](r: Result[T, E]): T = r.v
proc error*[T, E](r: Result[T, E]): E = "ERR:" & r.e
#!STEP expect: 42 ERR:bad: x; iface-edit; modules: 2

50
tests/ic/tmeta_smoke.nim Normal file
View File

@@ -0,0 +1,50 @@
discard """
description: '''metamorphic IC: clean==incremental, no-op stability, body vs interface boundary'''
"""
# This is a *metamorphic* IC test (see the `#? metamorphic` marker and the
# runner in testament/categories.nim). It drives a sequence of cross-module
# edits through `nim ic` in one fixed build directory and checks the invariants
# the incremental backend must uphold (doc/ic_ideas.md).
#? metamorphic
#!FILE a.nim
proc greet*(): string = "hi"
proc secret(): int = 41 # private, body-only churn target
proc value*(): int = secret() + 1
#!FILE main.nim
import a
echo greet(), " ", value()
#!STEP expect: hi 42
# --- body-only edit: a private body changes, no signature does.
# => no `*.iface.bif` cookie changes, exactly 1 module's codegen rebuilds,
# the importer is left untouched.
#!FILE a.nim
proc greet*(): string = "hi"
proc secret(): int = 999
proc value*(): int = secret() + 1
#!STEP expect: hi 1000; body-edit; modules: 1
# --- no-op edit: re-emit byte-identical content. Nothing downstream may change.
#!FILE a.nim
proc greet*(): string = "hi"
proc secret(): int = 999
proc value*(): int = secret() + 1
#!STEP expect: hi 1000; noop
# --- interface edit: `value`'s return type changes (a signature change) while
# main.nim's source stays byte-identical. The interface cookie must change
# and the importer must be re-sem'd & recodegen'd (>= 2 modules rebuilt).
# The final step also runs the clean==incremental check.
#!FILE a.nim
proc greet*(): string = "hi"
proc secret(): int = 999
proc value*(): int64 = secret().int64 + 1
#!STEP expect: hi 1000; iface-edit

View File

@@ -0,0 +1,39 @@
discard """
description: '''metamorphic IC: edit propagation across a 3-module import chain'''
"""
# Chain: main -> b -> a. Demonstrates that a body edit stays local to the edited
# module, while an interface edit propagates to its direct importer but stops
# where the next signature is unchanged. See doc/ic_ideas.md and the runner in
# testament/categories.nim.
#? metamorphic
#!FILE a.nim
proc base*(): int = 1
#!FILE b.nim
import a
proc mid*(): int = base() + 10
#!FILE main.nim
import b
echo mid()
#!STEP expect: 11
# --- body-only edit of `a.base`: no signature changes, so nothing re-sems;
# only module `a`'s own codegen rebuilds.
#!FILE a.nim
proc base*(): int = 7
#!STEP expect: 17; body-edit; modules: 1
# --- interface edit of `a.base` (return type int -> int64). `b` uses `base`, so
# `a`'s cookie change forces `b` to re-sem & recodegen; but `b.mid`'s own
# signature is unchanged, so `main` is NOT rebuilt -> exactly 2 modules.
# The final step also runs the clean==incremental check.
#!FILE a.nim
proc base*(): int64 = 7
#!STEP expect: 17; iface-edit; modules: 2

33
tests/ic/tmethitanium.nim Normal file
View File

@@ -0,0 +1,33 @@
discard """
output: '''woof
generic-animal-sound
generic-animal-sound/woof'''
"""
# NOTE: the `--debugger:native` that triggers the Itanium mangling lives in the
# sibling `tmethitanium_temp.nim.cfg` (the IC test harness compiles the generated
# `_temp.nim` and does not thread a `matrix`/`$options` switch into the cg
# children; a project cfg is read by the driver, which forwards it).
# Regression test: under `nim ic --debugger:native` the per-module backend uses
# the Itanium C name mangling, which encodes the signature and drops the
# `disamb`. A `{.base.}` method (owner module mmethanimal) and its whole-program
# dispatcher (synthesized into this main module) then share a signature, so the
# clean-name uniqueness probe (`m.g.mangledPrcs`) — which only sees the current
# module — gave the base a clean name at its owner but an unstable
# `itemId.item`-based `speak_u<n>` at each demander. Result: the base was defined
# once (clean) but referenced under names defined nowhere ("undefined reference
# to speak_u1") while the dispatcher collided with the clean base ("multiple
# definition of speak"). This was the bulk of nimbus-eth2's libp2p method link
# failures under `nim ic`. Fixed by making the Itanium scheme use the stable
# `disamb` (ccgutils.makeUnique) and always uniquify routine names under the
# per-module backend (ccgtypes.fillBackendName), plus forwarding
# `--debugger:native` to the cg children (deps.computeForwardedArgs).
import mmethanimal, mmethdog
let a: Animal = Dog()
echo speak(a) # dispatches -> override
let b: Animal = Animal()
echo speak(b) # dispatches -> base body
echo speakBoth(Dog()) # procCall to base from non-owner module

15
tests/ic/tmethupref.nim Normal file
View File

@@ -0,0 +1,15 @@
discard """
output: '''42'''
"""
# Regression test: a `{.base.}` method whose body holds a closure iterator with a
# nested capturing closure must not crash the IC backend. The method's dispatcher
# (a `copySym` clone sharing the iterator) must NOT be lambda-lifted per module;
# otherwise the shared iterator's `up` field is baked twice under divergent owner
# identities -> "up references do not agree" / "could not determine closure type"
# (the real-world symptom: ~all libp2p async `{.base.}` methods failed under
# `nim ic`). Fixed by excluding `sfDispatcher` from nifbackend.ownsRuntimeRoutine.
import mmethupref
let b = Base(val: 40)
echo b.compute()

12
tests/ic/tmodsymref.nim Normal file
View File

@@ -0,0 +1,12 @@
discard """
output: '''7'''
"""
# Regression test: a cross-module MODULE-symbol reference left as a dangling
# qualifier in a template body (here `mmodsymasm.mmodsymarm.foo` in a dead
# `when`-branch, reached via re-export) must load under `nim ic` instead of
# raising `symbol has no offset`. Mirrors nim-intops' `inlineasm.arm64.X` in
# nimbus-eth2. See compiler/ast2nif.nim `ModMarker`.
import mmodsymadd
echo satAdd(3'u64, 4'u64)

21
tests/ic/tpureenum.nim Normal file
View File

@@ -0,0 +1,21 @@
discard """
output: '''5
Number
Object'''
"""
# Regression test: a `{.pure.}` enum loaded from a NIF must keep its fields out
# of the importer's unqualified scope (no leak), while still being reachable
# qualified AND via the restricted unambiguous-bare pure-enum fallback
# (`importPureEnumFields`, fed by `ifaces[].pureEnums` which the loader rebuilds
# from `PureEnumEntry` log ops).
#
# Before the fix the pure fields leaked into `interf` under `nim ic`, so
# `JsonValueKind.Number` shadowed the distinct `Number` type and the conversion
# below failed: "undeclared field 'Number' for type system.uint64".
import mpureenum
echo toNumber(5'u64).val # distinct conversion: field must NOT shadow type
echo JsonValueKind.Number # qualified pure-enum access still works
echo Object # unambiguous bare pure-enum field still resolves

View File

@@ -0,0 +1,53 @@
discard """
output: '''ok string
ok int
ok seq
ok object
ok tuple'''
"""
# Regression test: `signatureHash(T)` must be stable across the NIF boundary so
# that a macrocache keyed by it (nim-serialization's auto-serialization registry)
# can be populated in one module and queried from another under `nim ic`.
#
# Before the fix, `signatureHash` hashed the generic *parameter symbol* (whose
# `disamb` is a per-module instantiation counter) instead of the type it denotes.
# The registering module (msighashstable) and this importer instantiated the
# generic `getSig[T]` separately, got different `disamb`s, and the lookups for
# `string`/`SomeInteger` missed -> `{.error: auto serialization not enabled.}`.
import msighashstable
proc writeStr(F: type DefaultFlavor, v: string) =
autoCheck(F, string):
echo "ok string"
proc writeInt[T: SomeInteger](F: type DefaultFlavor, v: T) =
autoCheck(F, SomeInteger):
echo "ok int"
proc writeSeq[T](F: type DefaultFlavor, v: seq[T]) =
autoCheck(F, seq):
echo "ok seq"
type
Msg = object
a: int
b: string
# `object`/`tuple` are `tyBuiltInTypeClass`: a concrete type is auto-serialized
# by looking up its type class. The hash of the bare class keyword must match
# between msighashstable's registration and this lookup.
proc writeObj[T: object](F: type DefaultFlavor, v: T) =
autoCheckTC(F, object, typeof(v)):
echo "ok object"
proc writeTup[T: tuple](F: type DefaultFlavor, v: T) =
autoCheckTC(F, tuple, typeof(v)):
echo "ok tuple"
writeStr(DefaultFlavor, "hi")
writeInt(DefaultFlavor, 42)
writeSeq(DefaultFlavor, @[1, 2, 3])
writeObj(DefaultFlavor, Msg(a: 1, b: "x"))
writeTup(DefaultFlavor, (1, "x"))

View File

@@ -0,0 +1,38 @@
discard """
output: '''9'''
"""
# Regression test for object-field serialization of static-generic instances
# under `nim ic`.
#
# A generic object's instances SHARE one field PSym (same itemId) while each
# instance carries a DISTINCT field type, e.g. `Digest[32].data: array[32,byte]`
# vs `Digest[48].data: array[48,byte]` (this is exactly nimcrypto's `MDigest`,
# which crashed compiling nimbus's `altair.nim`). The `.s.bif` writer DEFs each
# field once inside its owning type's reclist and references it as a bare SymUse
# elsewhere, deduping by a per-Writer `emittedFieldSyms` set. That set wrongly
# spanned DIFFERENT type reclists: after the first instance's `data` def, every
# other instance's reclist got a typeless SymUse stub instead of its own typed
# def. On load that field had a nil `typ`/`owner`, and `=destroy` lifting
# (`liftdestructors.fillBodyObj`) dereferenced it -> SIGSEGV. The fix scopes the
# dedup per-reclist so each instance reclist is a self-contained typed def.
#
# The `seq` field forces `=destroy` to be lifted for `Outer`, which walks the
# reclists of both `Digest` instances (the crash path).
type
Digest[n: static int] = object
data: array[n, byte]
Outer = object
a: Digest[32]
b: Digest[48]
s: seq[int]
proc use(o: Outer): int =
result = o.a.data[0].int + o.b.data[0].int + o.s.len
var o: Outer
o.a.data[0] = 4
o.b.data[0] = 2
o.s = @[1, 2, 3]
echo use(o)

View File

@@ -0,0 +1,14 @@
discard """
output: '''size 64 64'''
"""
# Regression test for TRANSITIVE generic-instance offers. `fromRaw[int]` is first
# instantiated in mtscopewarm (a clean scope where `TScopeSize` is unambiguously
# 65). The consumer here also imports mtscopeb (`TScopeSize` = 33), so a fresh
# re-instantiation of fromRaw's body would resolve `TScopeSize` ambiguously. The
# clean instance reaches here only TRANSITIVELY (via mtscopemid), so the offer
# rebuild must walk the whole import closure, not just direct imports. Mirrors
# nimbus-eth2 `keys.fromRaw` -> `SkRawPublicKeySize` (secp vs secp256k1).
import mtscopegen, mtscopeb, mtscopemid
echo fromRaw(64)

14
tests/ic/ttypeoffer.nim Normal file
View File

@@ -0,0 +1,14 @@
discard """
output: '''ok'''
"""
# Regression test for the `(toffer …)` generic-TYPE-instance sharing across the
# NIF boundary. A `tyGenericInst` whose structure (here an `array` bound) depends
# on a `mixin`/`compiles()` resolved at the instantiation site must be REUSED
# from the module that created it, not re-instantiated in a consumer whose import
# scope flips the `compiles()` and so bakes a different bound. Mirrors the SSZ
# `HashArray[8192, Gwei]` `sizeof` divergence in nimbus-eth2. Before the fix this
# failed under `nim ic` with `doAssert sizeof(StateA) == sizeof(StateB)`.
import mtoffuser
check()

View File

@@ -0,0 +1,52 @@
discard """
action: "run"
"""
# Test: {.cast(uncheckedAssign).} must suppress FieldDiscriminantCheck
# when a yield inside the pragma block causes the closure-iterator
# transform to split the body. The discriminant assignment lands in
# the post-yield state and must inherit the wrapper.
type
MyKind = enum mkOne, mkTwo
MyVariant = object
case kind: MyKind
of mkOne:
x: int
of mkTwo:
y: float
iterator iterUncheckedYield(dest: var MyVariant): int {.closure.} =
{.cast(uncheckedAssign).}:
yield 1
dest.kind = mkTwo
block:
var v = MyVariant(kind: mkOne, x: 42)
var count = 0
for x in iterUncheckedYield(v):
if count == 0:
doAssert x == 1
inc count
# don't break — continue to advance past the yield,
# which runs the discriminant assignment
else:
break
doAssert v.kind == mkTwo
iterator iterNestedPragma(dest: var MyVariant): int {.closure.} =
{.cast(uncheckedAssign).}:
{.cast(gcsafe).}:
yield 1
dest.kind = mkTwo
block:
var v = MyVariant(kind: mkOne, x: 42)
var count = 0
for x in iterNestedPragma(v):
if count == 0:
doAssert x == 1
inc count
else:
break
doAssert v.kind == mkTwo

View File

@@ -3,7 +3,7 @@ discard """
-1
8
'''
ccodecheck: "'console.log(-1); function fac__tcodegendeclproc_u1(n_p0)'"
ccodecheck: "'console.log(-1); function fac_u' \\d+ '__tcodegendeclproc(n_p0)'"
"""
proc fac(n: int): int {.codegenDecl: "console.log(-1); function $2($3)".} =
return n

31
tests/macros/t10902.nim Normal file
View File

@@ -0,0 +1,31 @@
discard """
output: '''done'''
"""
# Issue #10902: cannot instantiate T when generating AST from macro
# https://github.com/nim-lang/Nim/issues/10902
import macros
type
Base[T] = ref object
macro genCloneProc(typeWithGenArg: untyped): untyped =
result = newProc(
ident "clone", [
typeWithGenArg,
newIdentDefs(
ident "self",
typeWithGenArg,
)
],
newStmtList(
newNimNode(nnkDiscardStmt).add(newEmptyNode())
)
)
let genericParamIdent = typeWithGenArg[1]
result[2] = newNimNode(nnkGenericParams)
result[2].add(newIdentDefs(genericParamIdent, newEmptyNode()))
genCloneProc(Base[T])
echo "done"

21
tests/macros/t13296.nim Normal file
View File

@@ -0,0 +1,21 @@
discard """
output: '''done'''
"""
# Issue #13296: Error: not unused with a macro
# https://github.com/nim-lang/Nim/issues/13296
import macros
macro dType(body: untyped) =
if body.kind == nnkCall:
var typ = newNimNode(nnkStmtList)
typ.add quote do:
discard
elif body.kind == nnkTypeSection:
result = newStmtList(
body
)
dType:
echo "hi"
echo "done"

16
tests/macros/t9892.nim Normal file
View File

@@ -0,0 +1,16 @@
discard """
output: '''1'''
"""
# Issue #9892: Incorrect "Error: not unused" from else branch in macro
# https://github.com/nim-lang/Nim/issues/9892
import macros
macro foo(x: typed): untyped =
result = newNimNode(nnkStmtListExpr)
if x.kind == nnkStmtListExpr:
result.add x
else:
result = x
echo foo(1)

View File

@@ -0,0 +1,12 @@
discard """
errormsg: "'sizeof' cannot be used with '.incompleteStruct' types"
line: 10
"""
type
MyStruct {.incompleteStruct.} = object
field: int
const i = sizeof(MyStruct)
echo i

8
tests/pragmas/t12424.nim Normal file
View File

@@ -0,0 +1,8 @@
discard """
nimout: '''t12424.nim(8, 10) Warning: This is a test warning from user code [User]'''
"""
# Issue #12424: Warning and Hint Pragmas do not print to console when declared from a std lib module
# https://github.com/nim-lang/Nim/issues/12424
# This test verifies that warning pragmas in user code work correctly.
{.warning: "This is a test warning from user code".}

View File

@@ -1,4 +1,5 @@
discard """
matrix: "; -d:nimMemfileFallback"
disabled: "Windows"
output: '''Full read size: 20
Half read size: 10 Data: Hello'''

View File

@@ -179,7 +179,7 @@ block fileOperations:
# Symlink handling in `copyFile`, `copyFileWithPermissions`, `copyFileToDir`,
# `copyDir`, `copyDirWithPermissions`, `moveFile`, and `moveDir`.
block:
const symlinksAreHandled = not defined(windows)
const symlinkCopiesAreHandled = not defined(windows)
const dname = buildDir/"D20210116T140629"
const subDir = dname/"sub"
const subDir2 = dname/"sub2"
@@ -189,98 +189,131 @@ block fileOperations:
const brokenSymlinkCopy = brokenSymlink & "_COPY"
const brokenSymlinkInSubDir = subDir/brokenSymlinkName
const brokenSymlinkInSubDir2 = subDir2/brokenSymlinkName
const symlinkProbeTarget = dname/"symlink_probe_target"
const symlinkProbeLink = dname/"symlink_probe_link"
createDir(subDir)
createSymlink(brokenSymlinkSrc, brokenSymlink)
proc removePathIfExists(path: string) =
if fileExists(path):
removeFile(path)
elif dirExists(path):
removeDir(path)
# Test copyFile
when symlinksAreHandled:
proc canCreateSymlinks(): bool =
# We need this check for Windows if we want to permit the block to run
# when we have admin privileges
try:
removePathIfExists(dname)
createDir(dname)
writeFile(symlinkProbeTarget, "")
createSymlink(symlinkProbeTarget, symlinkProbeLink)
result = true
except OSError:
result = false
finally:
removePathIfExists(symlinkProbeLink)
removePathIfExists(symlinkProbeTarget)
removePathIfExists(dname)
proc doAssertExpandedSymlink(path, expected: string) =
let actual = expandSymlink(path)
doAssert actual == expected,
"expandSymlink(" & path & ") returned " & actual &
" instead of " & expected
removePathIfExists(dname)
let symlinksAreAvailable = not defined(windows) or canCreateSymlinks()
if symlinksAreAvailable:
defer:
removePathIfExists(dname)
createDir(subDir)
createSymlink(brokenSymlinkSrc, brokenSymlink)
doAssertExpandedSymlink(brokenSymlink, brokenSymlinkSrc)
doAssertRaises(OSError):
copyFile(brokenSymlink, brokenSymlinkCopy)
doAssertRaises(OSError):
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkFollow})
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkIgnore})
doAssert not fileExists(brokenSymlinkCopy)
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkAsIs})
when symlinksAreHandled:
doAssert expandSymlink(brokenSymlinkCopy) == brokenSymlinkSrc
removeFile(brokenSymlinkCopy)
else:
discard expandSymlink(dname)
# Test copyFile
when symlinkCopiesAreHandled:
doAssertRaises(OSError):
copyFile(brokenSymlink, brokenSymlinkCopy)
doAssertRaises(OSError):
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkFollow})
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkIgnore})
doAssert not fileExists(brokenSymlinkCopy)
doAssertRaises(AssertionDefect):
copyFile(brokenSymlink, brokenSymlinkCopy,
{cfSymlinkAsIs, cfSymlinkFollow})
copyFile(brokenSymlink, brokenSymlinkCopy, {cfSymlinkAsIs})
when symlinkCopiesAreHandled:
doAssertExpandedSymlink(brokenSymlinkCopy, brokenSymlinkSrc)
removeFile(brokenSymlinkCopy)
else:
doAssert not fileExists(brokenSymlinkCopy)
doAssertRaises(AssertionDefect):
copyFile(brokenSymlink, brokenSymlinkCopy,
{cfSymlinkAsIs, cfSymlinkFollow})
# Test copyFileWithPermissions
when symlinksAreHandled:
doAssertRaises(OSError):
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy)
doAssertRaises(OSError):
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy,
options = {cfSymlinkFollow})
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy,
options = {cfSymlinkIgnore})
doAssert not fileExists(brokenSymlinkCopy)
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy,
options = {cfSymlinkAsIs})
when symlinksAreHandled:
doAssert expandSymlink(brokenSymlinkCopy) == brokenSymlinkSrc
removeFile(brokenSymlinkCopy)
else:
doAssert not fileExists(brokenSymlinkCopy)
doAssertRaises(AssertionDefect):
# Test copyFileWithPermissions
when symlinkCopiesAreHandled:
doAssertRaises(OSError):
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy)
doAssertRaises(OSError):
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy,
options = {cfSymlinkFollow})
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy,
options = {cfSymlinkAsIs, cfSymlinkFollow})
options = {cfSymlinkIgnore})
doAssert not fileExists(brokenSymlinkCopy)
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy,
options = {cfSymlinkAsIs})
when symlinkCopiesAreHandled:
doAssertExpandedSymlink(brokenSymlinkCopy, brokenSymlinkSrc)
removeFile(brokenSymlinkCopy)
else:
doAssert not fileExists(brokenSymlinkCopy)
doAssertRaises(AssertionDefect):
copyFileWithPermissions(brokenSymlink, brokenSymlinkCopy,
options = {cfSymlinkAsIs, cfSymlinkFollow})
# Test copyFileToDir
when symlinksAreHandled:
doAssertRaises(OSError):
copyFileToDir(brokenSymlink, subDir)
doAssertRaises(OSError):
copyFileToDir(brokenSymlink, subDir, {cfSymlinkFollow})
copyFileToDir(brokenSymlink, subDir, {cfSymlinkIgnore})
doAssert not fileExists(brokenSymlinkInSubDir)
copyFileToDir(brokenSymlink, subDir, {cfSymlinkAsIs})
when symlinksAreHandled:
doAssert expandSymlink(brokenSymlinkInSubDir) == brokenSymlinkSrc
removeFile(brokenSymlinkInSubDir)
else:
# Test copyFileToDir
when symlinkCopiesAreHandled:
doAssertRaises(OSError):
copyFileToDir(brokenSymlink, subDir)
doAssertRaises(OSError):
copyFileToDir(brokenSymlink, subDir, {cfSymlinkFollow})
copyFileToDir(brokenSymlink, subDir, {cfSymlinkIgnore})
doAssert not fileExists(brokenSymlinkInSubDir)
copyFileToDir(brokenSymlink, subDir, {cfSymlinkAsIs})
when symlinkCopiesAreHandled:
doAssertExpandedSymlink(brokenSymlinkInSubDir, brokenSymlinkSrc)
removeFile(brokenSymlinkInSubDir)
else:
doAssert not fileExists(brokenSymlinkInSubDir)
createSymlink(brokenSymlinkSrc, brokenSymlinkInSubDir)
createSymlink(brokenSymlinkSrc, brokenSymlinkInSubDir)
# Test copyDir
copyDir(subDir, subDir2)
when symlinksAreHandled:
doAssert expandSymlink(brokenSymlinkInSubDir2) == brokenSymlinkSrc
# Test copyDir
copyDir(subDir, subDir2)
when symlinkCopiesAreHandled:
doAssertExpandedSymlink(brokenSymlinkInSubDir2, brokenSymlinkSrc)
else:
doAssert not fileExists(brokenSymlinkInSubDir2)
removeDir(subDir2)
# Test copyDirWithPermissions
copyDirWithPermissions(subDir, subDir2)
when symlinkCopiesAreHandled:
doAssertExpandedSymlink(brokenSymlinkInSubDir2, brokenSymlinkSrc)
else:
doAssert not fileExists(brokenSymlinkInSubDir2)
removeDir(subDir2)
# Test moveFile
moveFile(brokenSymlink, brokenSymlinkCopy)
doAssertExpandedSymlink(brokenSymlinkCopy, brokenSymlinkSrc)
removeFile(brokenSymlinkCopy)
# Test moveDir
moveDir(subDir, subDir2)
doAssertExpandedSymlink(brokenSymlinkInSubDir2, brokenSymlinkSrc)
else:
doAssert not fileExists(brokenSymlinkInSubDir2)
removeDir(subDir2)
# Test copyDirWithPermissions
copyDirWithPermissions(subDir, subDir2)
when symlinksAreHandled:
doAssert expandSymlink(brokenSymlinkInSubDir2) == brokenSymlinkSrc
else:
doAssert not fileExists(brokenSymlinkInSubDir2)
removeDir(subDir2)
# Test moveFile
moveFile(brokenSymlink, brokenSymlinkCopy)
when not defined(windows):
doAssert expandSymlink(brokenSymlinkCopy) == brokenSymlinkSrc
else:
doAssert symlinkExists(brokenSymlinkCopy)
removeFile(brokenSymlinkCopy)
# Test moveDir
moveDir(subDir, subDir2)
when not defined(windows):
doAssert expandSymlink(brokenSymlinkInSubDir2) == brokenSymlinkSrc
else:
doAssert symlinkExists(brokenSymlinkInSubDir2)
removeDir(dname)
discard "Skipping symlink tests: symlink creation is not permitted in this environment"
block: # moveFile
let tempDir = getTempDir() / "D20210609T151608"

View File

@@ -0,0 +1,94 @@
discard """
matrix: "--mm:refc; --mm:orc; --mm:orc --strings:sso; --backend:cpp --mm:orc; --backend:js --mm:orc"
output: "OK"
"""
# Tests for `readRawDataStable` and the SSO static-long-string promotion path.
# `readRawDataStable` is available under every string implementation (refc / v2 /
# v3-sso / js) with the same signature, so the code below compiles unchanged on
# all backends -- the point being that users can prepare for `--strings:sso`
# without `when declared` hacks.
import std/assertions
const hasNativeSso = defined(nimsso) and
(defined(gcArc) or defined(gcAtomicArc) or defined(gcOrc) or defined(gcYrc))
type
Reader = object
buf: string
p: ptr UncheckedArray[char]
proc openFromBuffer(buf: sink string): Reader =
# `result` (and thus `buf`) is moved into the caller on return. A plain
# `readRawData` pointer into a small SSO string would dangle after that move;
# `readRawDataStable` pins the buffer to a stable address first.
result = Reader(buf: buf)
result.p = readRawDataStable(result.buf)
proc testStable() =
when not defined(js): # raw pointers are a degenerate nil no-op on the JS backend
block: # short buffer (kept inline under SSO) survives the move
var r = openFromBuffer("hello")
doAssert r.buf == "hello"
doAssert r.p[0] == 'h'
doAssert r.p[4] == 'o'
# Stable pointer == the live buffer's raw data after the move.
doAssert cast[uint](r.p) == cast[uint](readRawData(r.buf))
block: # medium buffer (len 12: inline overlay under SSO)
var r = openFromBuffer("hello world!")
doAssert r.p[11] == '!'
block: # already-long buffer: returned as-is (already heap-resident)
var r = openFromBuffer("this is a fairly long string buffer")
doAssert r.p[0] == 't'
doAssert r.p[34] == 'r'
block: # empty string: API is callable (the data pointer is implementation-defined)
var e = ""
discard readRawDataStable(e)
else:
# On JS the API exists and is callable (returns nil) so call sites are portable.
var s = "hello"
discard readRawDataStable(s)
proc testStaticLongPromotion() =
# Regression for the static-long -> heap promotion: when a string literal
# longer than the inline payload (PayloadSize = 14 under SSO) is first
# mutated, the new heap block must be filled from the full static payload,
# not from the 7-byte inline hot-prefix cache. Reading from the cache copied
# 7 valid chars and then ran off into the `more` pointer bytes -- the bug that
# corrupted .nif index files on Windows bootstrap (see Nimony tstatic_long_add).
# The assertion holds on every backend; only SSO ever risked the corruption.
var content = "(.nif27)\n(index\n" # len 16
let expected = "(.nif27)\n(index\n"
content.add 'X' # triggers static-long -> heap promotion
doAssert content.len == 17
doAssert content == expected & "X"
for i in 0 ..< expected.len:
doAssert content[i] == expected[i]
when hasNativeSso:
# A few SSO-tier-boundary sanity checks (short / medium / long, COW, shrink).
proc testSsoTiers() =
var a = "(.nif27)\n(index\n" # static long
let b = "(.nif27)\n(index\n"
doAssert a == b
a.add 'Z'
doAssert a == "(.nif27)\n(index\nZ"
var c = "abcdefghijklmnop" # static long, len 16
var d = c # COW share
d[0] = 'X'
doAssert c == "abcdefghijklmnop" # original untouched
doAssert d == "Xbcdefghijklmnop"
var e = "abcdefghijklmnop"
e.setLen 3 # shrink below the inline cache size
doAssert e == "abc"
doAssert e.len == 3
else:
proc testSsoTiers() = discard
testStable()
testStaticLongPromotion()
testSsoTiers()
echo "OK"

90
tests/system/ttypeof.nim Normal file
View File

@@ -0,0 +1,90 @@
static: doAssert typeof(1) is int
func isVar[T](x: var T): bool = true
func isVar[T](x: T): bool = false
proc testVarParams1(a: var int;
b: typeof(a);
c: typeof(a, typeOfIter);
d: typeof(a, typeOfIter, CompatibleTypeModifiers);
e: typeof(a, typeOfIter, RemoveTypeModifiers);
f: typeof(a, typeOfIter, KeepTypeModifiers);
g: typeof(a, modifierMode = CompatibleTypeModifiers);
h: typeof(a, modifierMode = RemoveTypeModifiers);
i: typeof(a, modifierMode = KeepTypeModifiers);
) =
doAssert not isVar(b)
doAssert not isVar(c)
doAssert not isVar(d)
doAssert not isVar(e)
doAssert isVar(f)
doAssert not isVar(g)
doAssert not isVar(h)
doAssert isVar(i)
static: doAssert testVarParams1 is proc (a: var int; b: int; c: int; d: int; e: int; f: var int; g: int; h: int; i: var int) {.nimcall.}
block:
var a, f, i: int
testVarParams1(a, 0, 0, 0, 0, f, 0, 0, i)
# `CompatibleTypeModifiers` and `RemoveTypeModifiers` remove only top `var`, not `var` inside proc type
proc testVarParams2(a: var proc(x: var int): var int;
b: typeof(a);
c: typeof(a, modifierMode = CompatibleTypeModifiers);
d: typeof(a, modifierMode = RemoveTypeModifiers);
e: typeof(a, modifierMode = KeepTypeModifiers)) =
doAssert not isVar(b)
doAssert not isVar(c)
doAssert not isVar(d)
doAssert isVar(e)
static: doAssert testVarParams2 is proc (a: var proc(x: var int): var int;
b: proc(x: var int): var int;
c: proc(x: var int): var int;
d: proc(x: var int): var int;
e: var proc(x: var int): var int) {.nimcall.}
block:
var a, e: proc(x: var int): var int = nil
let b, c, d: proc(x: var int): var int = nil
testVarParams2(a, b, c, d, e)
proc testRet(a: var int): typeof(a) = 0
static: doAssert testRet is proc (a: var int): int {.nimcall.}
proc testRet2(a: var int): typeof(a, modifierMode = CompatibleTypeModifiers) = 0
static: doAssert testRet2 is proc (a: var int): int {.nimcall.}
proc testRet3(a: var int): typeof(a, modifierMode = RemoveTypeModifiers) = 0
static: doAssert testRet3 is proc (a: var int): int {.nimcall.}
proc fooSink1(a: sink string;
b: typeof(a);
c: typeof(a, modifierMode = CompatibleTypeModifiers);
d: typeof(a, modifierMode = RemoveTypeModifiers);
e: typeof(a, modifierMode = KeepTypeModifiers)) = discard
static: doAssert fooSink1 is proc (a: sink string; b: sink string; c: sink string; d: string; e: sink string) {.nimcall.}
proc fooLentRet(a: seq[string]): lent string = a[0]
proc testLentRetComp(a: seq[string]): typeof(fooLentRet(a), modifierMode = CompatibleTypeModifiers) = a[0]
proc testLentRetRemo(a: seq[string]): typeof(fooLentRet(a), modifierMode = RemoveTypeModifiers) = a[0]
proc testLentRetKeep(a: seq[string]): typeof(fooLentRet(a), modifierMode = KeepTypeModifiers) = a[0]
# workaround # issue 25830
proc dummyLentProc(a: seq[string]): lent string = a[0]
static:
doAssert testLentRetComp is proc (a: seq[string]): string {.nimcall.}
doAssert testLentRetRemo is proc (a: seq[string]): string {.nimcall.}
doAssert testLentRetKeep is typeof(dummyLentProc)
proc voidProc() = discard
static:
doAssert typeof(voidProc()) is void
type
Foo = typeof(Bar)
Bar = int
static:
doAssert Foo is int

11
tests/typerel/t22842.nim Normal file
View File

@@ -0,0 +1,11 @@
discard """
output: '''done'''
"""
# Issue #22842: internal error: getTypeDescAux(tyAnything) with auto in proc type
# https://github.com/nim-lang/Nim/issues/22842
proc register(cb: proc (e: auto): void) = discard
register(proc (e: int) = echo e)
echo "done"

13
tests/vm/tquote.nim Normal file
View File

@@ -0,0 +1,13 @@
discard """
joinable: false
"""
import std/macros
static:
discard quote:
a and b
var x {.compileTime.} : NimNode =
quote do:
echo "xxx"

View File

@@ -28,3 +28,17 @@ block:
fun[(int, string)]()
fun[ref Foo]()
fun[seq[int]]()
block: # shrinking an `@[...]` seq literal in the VM
# A `@[a, b, c]` seq value keeps the array-literal type in the VM; shrinking it
# to length 1 must not be misread as a broadcast default array (was: the whole
# thing collapsed to `len` copies of the first element).
proc shrink =
var s = @[10, 20, 30]
s.setLen(1)
doAssert s == @[10]
var t = @["foo", "bar"]
t.delete(1)
doAssert t == @["foo"]
static: shrink()
shrink()

View File

@@ -855,3 +855,17 @@ block:
var r = Obj(x: 10)
r.value = 42
doAssert r.x == 42
block: # bug #25949
template loadFile(filename: string): auto =
when nimvm:
staticRead(filename)
else:
"something"
proc roundTrip(): bool =
let content = loadFile("tests/tomls/case.toml")
content == "something"
doAssert roundTrip()