From c48f487780ad382821c19180486021c9e243ca5c Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:49:06 +0800 Subject: [PATCH 1/9] fixes: replace ensureMutable with backendEnsureMutable in ccgtypes (#25640) --- compiler/ccgtypes.nim | 8 ++++---- tests/ic/tmiscs.nim | 26 +++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 67b7469cb0..236f0f86e5 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -309,7 +309,7 @@ proc addAbiCheck(m: BModule; t: PType, name: Rope) = proc fillResult(conf: ConfigRef; param: PNode, proctype: PType) = - ensureMutable param.sym + backendEnsureMutable param.sym fillLoc(param.sym.locImpl, locParam, param, "Result", OnStack) let t = param.sym.typ @@ -542,7 +542,7 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params var types, names, args: seq[string] = @[] if not isCtor: var this = t.n[1].sym - ensureMutable this + backendEnsureMutable this fillParamName(m, this) fillLoc(this.locImpl, locParam, t.n[1], this.paramStorageLoc) @@ -564,7 +564,7 @@ proc genMemberProcParams(m: BModule; prc: PSym, superCall, rettype, name, params else: descKind = dkRefParam var typ, name: string - ensureMutable param + backendEnsureMutable param fillParamName(m, param) fillLoc(param.locImpl, locParam, t.n[i], param.paramStorageLoc) @@ -1183,7 +1183,7 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool let isCtor = sfConstructor in prc.flags var check = initIntSet() fillBackendName(m, prc) - ensureMutable prc + backendEnsureMutable prc fillLoc(prc.locImpl, locProc, prc.ast[namePos], OnUnknown) var memberOp = "#." #only virtual var typ: PType diff --git a/tests/ic/tmiscs.nim b/tests/ic/tmiscs.nim index 72407afcc4..34cd79fe99 100644 --- a/tests/ic/tmiscs.nim +++ b/tests/ic/tmiscs.nim @@ -4,6 +4,8 @@ discard """ 5 3 2 +1.0 +2.0 ''' """ @@ -42,4 +44,26 @@ proc divmod(a, b: int): (int, int) = let (q, r) = divmod(17, 5) echo q -echo r \ No newline at end of file +echo r + + +# Shallow object with seq (trigger GC interaction) +type + Matrix = object + rows, cols: int + data: seq[float] + +proc newMatrix(r, c: int): Matrix = + Matrix(rows: r, cols: c, data: newSeq[float](r * c)) + +proc `[]`(m: Matrix, r, c: int): float = + m.data[r * m.cols + c] + +proc `[]=`(m: var Matrix, r, c: int, v: float) = + m.data[r * m.cols + c] = v + +var m = newMatrix(2, 2) +m[0, 0] = 1.0 +m[1, 1] = 2.0 +echo m[0, 0] +echo m[1, 1] From 6f85d348f41fc380d22d1701aaabd6da1df129be Mon Sep 17 00:00:00 2001 From: metagn Date: Tue, 24 Mar 2026 10:27:28 +0300 Subject: [PATCH 2/9] fix `@` for openarray on nimscript [backport:2.2] (#25641) Even on nimscript, the `else` branch of the `when nimvm` below compiles and gives an "undeclared identifier: copyMem" error. Regression since #25064. --- lib/system.nim | 2 +- tests/test_nimscript.nims | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/system.nim b/lib/system.nim index 306818ffa0..03164b4f32 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -1469,7 +1469,7 @@ when defined(nimHasTopDownInference): ## This is not as efficient as turning a fixed length array into a sequence ## as it always copies every element of `a`. let sz = a.len - when supportsCopyMem(T) and not defined(js): + when supportsCopyMem(T) and not defined(js) and not defined(nimscript): result = newSeqUninit[T](sz) when nimvm: for i in 0..sz-1: result[i] = a[i] diff --git a/tests/test_nimscript.nims b/tests/test_nimscript.nims index 15e9d878d8..02572cb5ac 100644 --- a/tests/test_nimscript.nims +++ b/tests/test_nimscript.nims @@ -143,3 +143,7 @@ proc discardableCall(cmd: string): int {.discardable.} = result = 123 discardableCall "echo hi" + +block: + let a = "abc" + doAssert @a == @['a', 'b', 'c'] From 158d59ce4866ffbf1530ebc9e227c4dcf7cd846f Mon Sep 17 00:00:00 2001 From: Tomohiro Date: Tue, 24 Mar 2026 22:34:46 +0900 Subject: [PATCH 3/9] fixes #25635; registers module suffix correctly (#25645) `toNifFilename` proc doesn't return correct Nif file path because module suffix is registered with wrong proc. So `moduleFromNifFile` doesn't load the Nif file. --- compiler/nifbackend.nim | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/compiler/nifbackend.nim b/compiler/nifbackend.nim index fa293bbfe3..181a3dc040 100644 --- a/compiler/nifbackend.nim +++ b/compiler/nifbackend.nim @@ -44,14 +44,16 @@ proc loadModuleDependencies(g: ModuleGraph; mainFileIdx: FileIndex): seq[Precomp let suffix = stack.pop() if not visited.containsOrIncl(suffix.string): - let nifFile = toGeneratedFile(g.config, AbsoluteFile(suffix.string), ".nif") - let fileIdx = msgs.fileInfoIdx(g.config, nifFile) + var isKnownFile = false + let fileIdx = g.config.registerNifSuffix(suffix.string, isKnownFile) let precomp = moduleFromNifFile(g, fileIdx, {LoadFullAst}) if precomp.module != nil: result.add precomp for dep in precomp.deps: if not visited.contains(dep.string): stack.add dep + else: + assert false, "Recompiling module is not implemented." if mainModule.module != nil: result.add mainModule From e25820cf523b31b16fca53704d9003a6de1bf099 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 27 Mar 2026 03:38:54 +0800 Subject: [PATCH 4/9] fixes #25642; Add support for static type in semTypeNode (#25646) fixes #25642 --- compiler/semtypes.nim | 3 +++ tests/generics/tgeneric0.nim | 3 +++ 2 files changed, 6 insertions(+) diff --git a/compiler/semtypes.nim b/compiler/semtypes.nim index da7576ca4a..e2f91587ff 100644 --- a/compiler/semtypes.nim +++ b/compiler/semtypes.nim @@ -2234,6 +2234,9 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType = result = semAnyRef(c, n, tyPtr, prev) elif op.id == ord(wRef): result = semAnyRef(c, n, tyRef, prev) + elif op.id == ord(wStatic): + checkSonsLen(n, 2, c.config) + result = semStaticType(c, n[1], prev) elif op.id == ord(wType): checkSonsLen(n, 2, c.config) result = semTypeOf(c, n[1], prev) diff --git a/tests/generics/tgeneric0.nim b/tests/generics/tgeneric0.nim index 76e9cd8d51..db749a38d3 100644 --- a/tests/generics/tgeneric0.nim +++ b/tests/generics/tgeneric0.nim @@ -219,3 +219,6 @@ block: # bug #19531 x.cb() y.cb() + +block: + proc r(_: typedesc, _: static uint | static int) = discard; r(uint, 0) From 2fc9c8084c36c19395cbfb16a118f05e2677f3b2 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Fri, 27 Mar 2026 09:00:14 +0800 Subject: [PATCH 5/9] fixes #25658; two overflowed *= causes program deadloop sysFatal on --exceptions:goto (#25660) fixes #25658 --- lib/system/arithmetics.nim | 16 +++-- tests/stdlib/tmisc_issues.nim | 117 ++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 7 deletions(-) diff --git a/lib/system/arithmetics.nim b/lib/system/arithmetics.nim index 5711004822..b2e4e41720 100644 --- a/lib/system/arithmetics.nim +++ b/lib/system/arithmetics.nim @@ -306,15 +306,15 @@ proc `mod`*(x, y: uint32): uint32 {.magic: "ModU", noSideEffect.} proc `mod`*(x, y: uint64): uint64 {.magic: "ModU", noSideEffect.} proc `+=`*[T: SomeInteger](x: var T, y: T) {. - magic: "Inc", noSideEffect.} + magic: "Inc", noSideEffect, systemRaisesDefect.} ## Increments an integer. proc `-=`*[T: SomeInteger](x: var T, y: T) {. - magic: "Dec", noSideEffect.} + magic: "Dec", noSideEffect, systemRaisesDefect.} ## Decrements an integer. proc `*=`*[T: SomeInteger](x: var T, y: T) {. - inline, noSideEffect.} = + inline, noSideEffect, systemRaisesDefect.} = ## Binary `*=` operator for integers. x = x * y @@ -339,20 +339,22 @@ proc `+=`*[T: float|float32|float64] (x: var T, y: T) {. x = x + y proc `-=`*[T: float|float32|float64] (x: var T, y: T) {. - inline, noSideEffect.} = + inline, noSideEffect, systemRaisesDefect.} = ## Decrements in place a floating point number. x = x - y proc `*=`*[T: float|float32|float64] (x: var T, y: T) {. - inline, noSideEffect.} = + inline, noSideEffect, systemRaisesDefect.} = ## Multiplies in place a floating point number. x = x * y -proc `/=`*(x: var float64, y: float64) {.inline, noSideEffect.} = +proc `/=`*(x: var float64, y: float64) {. + inline, noSideEffect, systemRaisesDefect.} = ## Divides in place a floating point number. x = x / y -proc `/=`*[T: float|float32](x: var T, y: T) {.inline, noSideEffect.} = +proc `/=`*[T: float|float32](x: var T, y: T) {. + inline, noSideEffect, systemRaisesDefect.} = ## Divides in place a floating point number. x = x / y diff --git a/tests/stdlib/tmisc_issues.nim b/tests/stdlib/tmisc_issues.nim index 4f7707d976..18dea064cf 100644 --- a/tests/stdlib/tmisc_issues.nim +++ b/tests/stdlib/tmisc_issues.nim @@ -64,3 +64,120 @@ block: # bug #24683 cast[ptr int](addr x)[] = 10 doAssert x == @[1, 2, 3, 4, 45, 56, 67, 999, 88, 777] + +when not defined(js): + block: + var x = high int + var result = x + + # assert that multiplying highest int by highest int overflows + + doAssertRaises(OverflowDefect): + x *= x + + doAssertRaises(OverflowDefect): + result *= x + + # overflow via compound assignment on int + var a = high(int) + doAssertRaises(OverflowDefect): + a += 1 + + var b = low(int) + doAssertRaises(OverflowDefect): + b -= 1 + + var c = high(int) + doAssertRaises(OverflowDefect): + c *= 2 + + # add smaller signed types too + var a8 = high(int8) + doAssertRaises(OverflowDefect): + a8 += 1 + + var b8 = low(int8) + doAssertRaises(OverflowDefect): + b8 -= 1 + + var c8 = high(int8) + doAssertRaises(OverflowDefect): + c8 *= 2 + + var a16 = high(int16) + doAssertRaises(OverflowDefect): + a16 += 1 + + var b16 = low(int16) + doAssertRaises(OverflowDefect): + b16 -= 1 + + # arithmetic operations that can overflow (non-compound direct ops) + doAssertRaises(OverflowDefect): + discard high(int) + 1 + + doAssertRaises(OverflowDefect): + discard low(int) - 1 + + doAssertRaises(OverflowDefect): + discard high(int) * 2 + + doAssertRaises(OverflowDefect): + discard low(int) div -1 + + # int8 overflow for signed operations + doAssertRaises(OverflowDefect): + discard high(int8) + 1'i8 + + doAssertRaises(OverflowDefect): + discard low(int8) - 1'i8 + + doAssertRaises(OverflowDefect): + discard high(int8) * 2'i8 + + # enum overflow, from arithmetics.succ/pred + type E = enum eA, eB + doAssertRaises(OverflowDefect): + discard eB.succ + doAssertRaises(OverflowDefect): + discard eA.pred + + # floating-point compound divide should produce inf (not raise by defect) + var f = 1.0 + f /= 0.0 + # 1.0/0.0 is inf, check not finite + #doAssert not f.isFinite # `isFinite` not in this context, but avoid crash + # simple check ensures it mutated to a very large value + # (in Nim, `inf` is represented as 1e300*1e300; this compares as true) + doAssert f == 1.0 / 0.0 + + # Additional overflow cases across various integer widths + doAssertRaises(OverflowDefect): + discard high(int32) + 1'i32 + + doAssertRaises(OverflowDefect): + discard low(int32) - 1'i32 + + doAssertRaises(OverflowDefect): + discard high(int64) + 1'i64 + + doAssertRaises(OverflowDefect): + discard low(int64) - 1'i64 + + doAssertRaises(OverflowDefect): + discard -low(int64) + + doAssertRaises(OverflowDefect): + discard abs(low(int8)) + + doAssertRaises(OverflowDefect): + discard high(int32) * 2'i32 + + doAssertRaises(OverflowDefect): + discard high(int64) * 2'i64 + + doAssertRaises(OverflowDefect): + discard low(int32) div -1'i32 + + doAssertRaises(OverflowDefect): + discard low(int64) div -1'i64 From 7f6b76b34c9c4ab873802b7ee19ec417ef60dc46 Mon Sep 17 00:00:00 2001 From: cui Date: Fri, 27 Mar 2026 17:19:35 +0800 Subject: [PATCH 6/9] fixes #25671; commands: fix --maxLoopIterationsVM positive check (#25672) Fixes bug #25671. The previous condition `not value > 0` was parsed as `(not value) > 0`, not `not (value > 0)`, so the check did not reliably enforce a positive `--maxLoopIterationsvm` limit. Align with `--maxcalldepthvm` by using `value <= 0`. --- compiler/commands.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/commands.nim b/compiler/commands.nim index 3d2aabdc03..be5a8abd27 100644 --- a/compiler/commands.nim +++ b/compiler/commands.nim @@ -951,7 +951,7 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo; expectArg(conf, switch, arg, pass, info) var value: int = 10_000_000 discard parseSaturatedNatural(arg, value) - if not value > 0: localError(conf, info, "maxLoopIterationsVM must be a positive integer greater than zero") + if value <= 0: localError(conf, info, "maxLoopIterationsVM must be a positive integer greater than zero") conf.maxLoopIterationsVM = value of "maxcalldepthvm": expectArg(conf, switch, arg, pass, info) From 5c86c1eda9cd75e0fa85b132ca4c8306e9fc81d5 Mon Sep 17 00:00:00 2001 From: cui Date: Fri, 27 Mar 2026 17:20:10 +0800 Subject: [PATCH 7/9] fixes #25670; docgen: cmpDecimalsIgnoreCase max() used wrong index for b (#25669) Fixes bug #25670. The second argument to `max` in `cmpDecimalsIgnoreCase` used `limitB - iA` instead of `limitB - iB`, which could mis-order numeric segments when sorting doc index entries. --- compiler/docgen.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/docgen.nim b/compiler/docgen.nim index 307761409c..71af95a5f2 100644 --- a/compiler/docgen.nim +++ b/compiler/docgen.nim @@ -148,7 +148,7 @@ proc cmpDecimalsIgnoreCase(a, b: string): int = limitB = iB while limitA < aLen and isDigit(a[limitA]): inc limitA while limitB < bLen and isDigit(b[limitB]): inc limitB - var pos = max(limitA-iA, limitB-iA) + var pos = max(limitA-iA, limitB-iB) while pos > 0: if limitA-pos < iA: # digit in `a` is 0 effectively result = ord('0') - ord(b[limitB-pos]) From 78282b241f16c66e40782775ee1d4c16b8af8d6f Mon Sep 17 00:00:00 2001 From: cui Date: Sat, 28 Mar 2026 16:22:54 +0800 Subject: [PATCH 8/9] fixes #25674; parsecfg: bound-check CR/LF pair in replace() (#25675) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes bug #25674. `replace` read `s[i+1]` for a CRLF pair without ensuring `i+1 < s.len()`, so a value ending in a lone `\\c` (quoted in `writeConfig`) raised `IndexDefect`. - Fix: only treat `\\c\\l` when the following character exists. - Test: `tests/stdlib/tparsecfg.nim` block bug #25674 — fails before fix, passes after. --- lib/pure/parsecfg.nim | 2 +- tests/stdlib/tparsecfg.nim | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pure/parsecfg.nim b/lib/pure/parsecfg.nim index c5e71c0179..af5a661cf6 100644 --- a/lib/pure/parsecfg.nim +++ b/lib/pure/parsecfg.nim @@ -556,7 +556,7 @@ proc replace(s: string): string = while i < s.len(): if s[i] == '\\': d.add(r"\\") - elif s[i] == '\c' and s[i+1] == '\l': + elif s[i] == '\c' and i+1 < s.len() and s[i+1] == '\l': d.add(r"\c\l") inc(i) elif s[i] == '\c': diff --git a/tests/stdlib/tparsecfg.nim b/tests/stdlib/tparsecfg.nim index 2600d6f663..693ea5c86a 100644 --- a/tests/stdlib/tparsecfg.nim +++ b/tests/stdlib/tparsecfg.nim @@ -130,3 +130,9 @@ block: doAssert dict.getSectionValue(section4, "can_values_be_as_well") == "True" doAssert dict.getSectionValue(section4, "does_that_mean_anything_special") == "False" doAssert dict.getSectionValue(section4, "purpose") == "formatting for readability" + +block: # bug #25674 + var dict = newConfig() + dict.setSectionKey("", "key", "value\c") + var s = newStringStream() + dict.writeConfig(s) From 7a82c5920c46fa7a3393ebdecc54716cb1015366 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Mon, 30 Mar 2026 21:09:11 +0800 Subject: [PATCH 9/9] fixes #25677; fixes #25678; typeAllowedAux to improve flag handling (#25684) fixes #25677; fixes #25678 This pull request introduces both a bug fix to the type checking logic in the compiler and new test cases for lent types involving procedures and tables. The most significant change is a refinement in how type flags are handled for procedure and function types in the compiler, which improves correctness in type allowance checks. Additionally, the test suite is expanded to cover more complex scenarios with lent types and table lookups. **Compiler improvements:** * Refined the handling of type flags in `typeAllowedAux` for procedure and function types by introducing `innerFlags`, which removes certain flags (`taObjField`, `taTupField`, `taIsOpenArray`) before recursing into parameter and return types. This ensures more accurate type checking and prevents inappropriate flag propagation. **Testing enhancements:** * Added new test blocks in `tests/lent/tlents.nim` to cover lent procedure types stored in objects and used as table values, including a function that retrieves such procedures from a table by key. * Introduced a test case for an object containing a lent procedure field, ensuring correct behavior when accessing and using these fields. --- compiler/typeallowed.nim | 7 ++++--- tests/lent/tlents.nim | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/compiler/typeallowed.nim b/compiler/typeallowed.nim index 80b532371c..05584341b6 100644 --- a/compiler/typeallowed.nim +++ b/compiler/typeallowed.nim @@ -99,12 +99,13 @@ proc typeAllowedAux(marker: var IntSet, typ: PType, kind: TSymKind, if isInlineIterator(typ) and kind in {skVar, skLet, skConst, skParam, skResult}: # only closure iterators may be assigned to anything. result = t - let f = if kind in {skProc, skFunc}: flags+{taNoUntyped} else: flags + let innerFlags = flags - {taObjField, taTupField, taIsOpenArray} + let f = if kind in {skProc, skFunc}: innerFlags+{taNoUntyped} else: innerFlags for _, a in t.paramTypes: if result != nil: break - result = typeAllowedAux(marker, a, skParam, c, f-{taIsOpenArray}) + result = typeAllowedAux(marker, a, skParam, c, f) if result.isNil and t.returnType != nil: - result = typeAllowedAux(marker, t.returnType, skResult, c, flags) + result = typeAllowedAux(marker, t.returnType, skResult, c, innerFlags) of tyTypeDesc: if kind in {skVar, skLet, skConst} and taProcContextIsNotMacro in flags: result = t diff --git a/tests/lent/tlents.nim b/tests/lent/tlents.nim index 28fe0602ed..1b14972239 100644 --- a/tests/lent/tlents.nim +++ b/tests/lent/tlents.nim @@ -23,3 +23,25 @@ block: doAssert x(a) == 1 doAssert y(a) == 1 + +import std/tables + +block: + type + R = proc(): lent O {.nimcall.} + F = object + schema: R + O = object + fields: Table[string, F] + + func f(o: O, key: string): R = + if key in o.fields: o.fields[key].schema + else: nil + +block: + type + R = proc(): lent O + O = object + r: R + + func f(o: O): int = 42