mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-04 14:38:38 +00:00
Merge branch 'devel' into pr_field
This commit is contained in:
19
tests/arc/t19312.nim
Normal file
19
tests/arc/t19312.nim
Normal 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[]
|
||||
12
tests/arc/tisolated_primitive.nim
Normal file
12
tests/arc/tisolated_primitive.nim
Normal 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()
|
||||
28
tests/arc/tmitems_loopvar.nim
Normal file
28
tests/arc/tmitems_loopvar.nim
Normal 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
|
||||
@@ -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
17
tests/async/t16416.nim
Normal 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"
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
28
tests/concepts/conceptv2negative/tconsistentparams.nim
Normal file
28
tests/concepts/conceptv2negative/tconsistentparams.nim
Normal 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
23
tests/concepts/t14913.nim
Normal 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"
|
||||
@@ -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")
|
||||
|
||||
30
tests/cpp/tcpp_default_ctor_assignment.h
Normal file
30
tests/cpp/tcpp_default_ctor_assignment.h
Normal 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
|
||||
14
tests/cpp/tcpp_default_ctor_assignment.nim
Normal file
14
tests/cpp/tcpp_default_ctor_assignment.nim
Normal 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()
|
||||
20
tests/destructor/t9617.nim
Normal file
20
tests/destructor/t9617.nim
Normal 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"
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
11
tests/errmsgs/t16956.nim
Normal 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]
|
||||
18
tests/generics/mopensymdot.nim
Normal file
18
tests/generics/mopensymdot.nim
Normal 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
35
tests/generics/t21252.nim
Normal 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"
|
||||
@@ -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
|
||||
|
||||
12
tests/generics/topensymdot.nim
Normal file
12
tests/generics/topensymdot.nim
Normal 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
16
tests/ic/mconvbase.nim
Normal 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
13
tests/ic/mconvmid.nim
Normal 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
28
tests/ic/mctglobal.nim
Normal 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
|
||||
2
tests/ic/mdefconverter.nim
Normal file
2
tests/ic/mdefconverter.nim
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
converter toBool*(x: int): bool = x != 0
|
||||
14
tests/ic/memit.nim
Normal file
14
tests/ic/memit.nim
Normal 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)
|
||||
9
tests/ic/memptycaller.nim
Normal file
9
tests/ic/memptycaller.nim
Normal 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
12
tests/ic/memptyowned.nim
Normal 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
3
tests/ic/mgofmc.nim
Normal 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
10
tests/ic/mgofmh.nim
Normal 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
6
tests/ic/mgofpid.nim
Normal 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
29
tests/ic/minitordera.nim
Normal 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
15
tests/ic/minitorderb.nim
Normal 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
15
tests/ic/mmethanimal.nim
Normal 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
14
tests/ic/mmethdog.nim
Normal 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
23
tests/ic/mmethupref.nim
Normal 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
12
tests/ic/mmodsymadd.nim
Normal 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
5
tests/ic/mmodsymarm.nim
Normal 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
2
tests/ic/mmodsymasm.nim
Normal file
@@ -0,0 +1,2 @@
|
||||
import mmodsymx86, mmodsymarm
|
||||
export mmodsymx86, mmodsymarm
|
||||
3
tests/ic/mmodsymx86.nim
Normal file
3
tests/ic/mmodsymx86.nim
Normal 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
21
tests/ic/mpureenum.nim
Normal 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
|
||||
68
tests/ic/msighashstable.nim
Normal file
68
tests/ic/msighashstable.nim
Normal 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
3
tests/ic/mtoffcodec.nim
Normal 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
2
tests/ic/mtoffgwei.nim
Normal file
@@ -0,0 +1,2 @@
|
||||
# Helper for ttypeoffer.nim: a distinct basic type.
|
||||
type Gwei* = distinct uint64
|
||||
10
tests/ic/mtoffssz.nim
Normal file
10
tests/ic/mtoffssz.nim
Normal 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
5
tests/ic/mtoffstate.nim
Normal 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
10
tests/ic/mtoffuser.nim
Normal 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
2
tests/ic/mtscopea.nim
Normal 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
2
tests/ic/mtscopeb.nim
Normal 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
7
tests/ic/mtscopegen.nim
Normal 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
3
tests/ic/mtscopemid.nim
Normal 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
4
tests/ic/mtscopewarm.nim
Normal 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)
|
||||
16
tests/ic/tcompiletimeglobal.nim
Normal file
16
tests/ic/tcompiletimeglobal.nim
Normal 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
|
||||
18
tests/ic/tconverterreexport.nim
Normal file
18
tests/ic/tconverterreexport.nim
Normal 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
7
tests/ic/temit.nim
Normal 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
18
tests/ic/temptyowned.nim
Normal 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"
|
||||
12
tests/ic/tgenericoffer.nim
Normal file
12
tests/ic/tgenericoffer.nim
Normal 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
23
tests/ic/tinitorder.nim
Normal 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
46
tests/ic/tmeta_async.nim
Normal 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
|
||||
64
tests/ic/tmeta_exprcase.nim
Normal file
64
tests/ic/tmeta_exprcase.nim
Normal 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
|
||||
33
tests/ic/tmeta_generic.nim
Normal file
33
tests/ic/tmeta_generic.nim
Normal 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
61
tests/ic/tmeta_ortype.nim
Normal 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
93
tests/ic/tmeta_result.nim
Normal 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
50
tests/ic/tmeta_smoke.nim
Normal 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
|
||||
39
tests/ic/tmeta_transitive.nim
Normal file
39
tests/ic/tmeta_transitive.nim
Normal 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
33
tests/ic/tmethitanium.nim
Normal 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
15
tests/ic/tmethupref.nim
Normal 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
12
tests/ic/tmodsymref.nim
Normal 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
21
tests/ic/tpureenum.nim
Normal 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
|
||||
53
tests/ic/tsighashstable.nim
Normal file
53
tests/ic/tsighashstable.nim
Normal 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"))
|
||||
38
tests/ic/tstaticgenfield.nim
Normal file
38
tests/ic/tstaticgenfield.nim
Normal 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)
|
||||
14
tests/ic/ttransitiveoffer.nim
Normal file
14
tests/ic/ttransitiveoffer.nim
Normal 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
14
tests/ic/ttypeoffer.nim
Normal 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()
|
||||
52
tests/iter/tlowerpragmablock.nim
Normal file
52
tests/iter/tlowerpragmablock.nim
Normal 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
|
||||
@@ -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
31
tests/macros/t10902.nim
Normal 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
21
tests/macros/t13296.nim
Normal 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
16
tests/macros/t9892.nim
Normal 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)
|
||||
12
tests/misc/tsizeof_incompleteStruct.nim
Normal file
12
tests/misc/tsizeof_incompleteStruct.nim
Normal 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
8
tests/pragmas/t12424.nim
Normal 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".}
|
||||
@@ -1,4 +1,5 @@
|
||||
discard """
|
||||
matrix: "; -d:nimMemfileFallback"
|
||||
disabled: "Windows"
|
||||
output: '''Full read size: 20
|
||||
Half read size: 10 Data: Hello'''
|
||||
|
||||
@@ -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"
|
||||
|
||||
94
tests/system/treadrawdatastable.nim
Normal file
94
tests/system/treadrawdatastable.nim
Normal 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
90
tests/system/ttypeof.nim
Normal 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
11
tests/typerel/t22842.nim
Normal 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
13
tests/vm/tquote.nim
Normal 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"
|
||||
@@ -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()
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user