Compare commits

..

13 Commits

Author SHA1 Message Date
ringabout
c2c7e1788e test #25848 2026-06-08 20:48:22 +08:00
Andreas Rumpf
d9e28aac8e parser: concept of (#25878)
Co-authored-by: Gerke Max Preussner <gmpreussner@headcrash.industries>
2026-06-08 11:32:04 +02:00
Andreas Rumpf
c84764a097 emit modern NIF-27 (#25877) 2026-06-08 09:13:26 +02:00
ringabout
1d7510dff0 fixes #22936; Generic inheritance matching gives type mismatch when object has members (#25836)
fixes #22936

This pull request improves the compiler's handling of generic type
constraints, specifically for subtypes of generics, and adds a test to
cover this behavior. The main changes are an enhancement to the type
relationship logic in the compiler and a new test case for generic
subtyping with `Future`.

### Compiler improvements for generic subtyping

* Updated `typeRel` in `compiler/sigmatch.nim` to allow generic
constraints (like `F: Future`) to accept not just direct instantiations
but also descendants of the generic family, ensuring more flexible and
correct overload resolution. Inheritance depth is now considered for
overload ranking, making deeper descendants slightly less preferred,
consistent with other inheritance-based matches.

### New test coverage

* Added a test in `tests/typerel/t8905.nim` to verify that generic
constraints correctly accept subtypes of `Future`, including a custom
`B[T, E] = ref object of Future[T]` type, and that overloads like
`take`, `takeMany`, and the macro `checkFutures` work as expected with
these types.
2026-06-08 09:12:00 +02:00
Tomohiro
9b80b2e868 fixes-25655; defining >= operator generates compile error (#25787)
Fixes https://github.com/nim-lang/Nim/issues/25655

---------

Co-authored-by: Andreas Rumpf <araq4k@proton.me>
2026-06-08 09:00:00 +02:00
ringabout
f5930d0bb3 fixes #20811; Nested proc with inner being generic cannot access parameters of outer proc (#25837)
fixes  #20811

This pull request addresses issues with parameter capture in nested
generic procedures and templates, ensuring that outer parameters are
correctly visible and accessible within nested scopes. The main changes
include a fix in the semantic analysis logic and the addition of
targeted regression tests.

### Semantic analysis improvements:
* Updated `semGenericStmtSymbol` in `compiler/semgnrc.nim` to ensure
that parameters from outer scopes are preserved and accessible in nested
generic procedures, fixing visibility issues with captured parameters.

### Added regression tests:
* Added `tests/generics/t20811.nim` to verify that both generic and
plain inner procedures can access parameters from their enclosing
procedure.
* Extended `tests/template/topensym.nim` with a new block for issue
#20811 to test that template-injected parameters are correctly captured
and visible in nested generic procedures.
2026-06-08 08:55:37 +02:00
ringabout
4497d89267 fixes #18238; Nested object construction can zero same memory multiple times for --mm:refc (#25834)
fixes #18238

This pull request makes a targeted change to the object construction
logic in the `genObjConstr` procedure. The main update refines the
conditions under which memory zeroing is required during object
construction, making the behavior more accurate for different garbage
collection and destructor options.

Key logic update:

- Improved the `needsZeroMem` condition in `genObjConstr` to check for
the presence of garbage-collected references and the `optSeqDestructors`
option, instead of relying solely on the selected garbage collector and
field flags. This ensures memory is zeroed only when necessary,
potentially improving performance and correctness.


```c
T1_ = NIM_NIL;
T1_ = ((tyObject_E__uEKympBdEK4SY9anUbpNaLQ*) newObj((&NTIrefe__bJ9cSuxv8xHYxmdolQqFkUw_), sizeof(tyObject_E__uEKympBdEK4SY9anUbpNaLQ)));
nimZeroMem(((void*) ((&(*T1_).z.z.z.z))), sizeof(tyObject_A__G2lWlL9cFqoiWWwZmWqfJ9bA));
(*T1_).z.z.z.z.y = ((NI) 5);
asgnRef(((void**) ((&z1__test8_u12))), T1_);
asgnRef(((void**) ((&z2__test8_u55))), new__test8_u13());
(*z2__test8_u55).z.z.z.z.y = ((NI) 5);
T2_ = NIM_NIL;
T2_ = ((tyObject_E__uEKympBdEK4SY9anUbpNaLQ*) newObj((&NTIrefe__bJ9cSuxv8xHYxmdolQqFkUw_), sizeof(tyObject_E__uEKympBdEK4SY9anUbpNaLQ)));
asgnRef(((void**) ((&z3__test8_u56))), T2_);
(*z3__test8_u56).z.z.z.z.y = ((NI) 5);
```


The original test case has already been fixed for `ORC`, now extends it
to `refc`: if a constructor is fully initialized, it does not need a
zero-fill step
2026-06-08 08:54:15 +02:00
ringabout
f959a02037 fixes #25725; environment misses: s with iterator (#25828)
fixes #25725

This pull request makes significant improvements to symbol handling
during transformation passes in the compiler, particularly for routines
(procedures, iterators) and their parameters. The changes ensure that
when routines are copied (for inlining, closure generation, etc.), all
relevant symbols and type headers are also freshly copied and correctly
owned, preventing subtle bugs from symbol reuse. Additionally, new
regression tests are added to cover previously problematic iterator
cases.

**Improvements to symbol copying and ownership:**

* Introduced `freshOwnedSym` to create a fresh copy of a symbol with a
specified owner, ensuring that transformed routines and their parameters
do not share symbols with the originals, which prevents accidental
aliasing and ownership issues.
* Refactored `freshVar` to use `freshOwnedSym`, centralizing fresh
symbol creation logic.
* Added `introduceNewRoutineHeaderSyms` and `copyRoutineTypeHeader` to
ensure that when routines are copied, all parameter/result symbols and
their types are also freshly copied and mapped, avoiding shared state
between original and transformed routines.
* Updated `introduceNewLocalVars` to use `freshOwnedSym` for routine
symbols and to invoke the new header/type copying procedures, ensuring
correctness in routine transformation.

**Testing and regression coverage:**

* Added new blocks to `tests/iter/titer_issues.nim` to test iterator
transformation edge cases, including scenarios that previously led to
symbol reuse bugs (e.g., bugs #25724 and #25725).
2026-06-08 08:53:10 +02:00
Andreas Rumpf
3c6449dbdd fixes #25850 (#25875) 2026-06-07 19:55:56 +02:00
ringabout
f1ff8b6d9e fixes #25849; fixes #25872; Iteration on elements of array (#25860)
fixes #25849
fixes https://github.com/nim-lang/Nim/issues/25872
2026-06-06 07:58:19 +02:00
Ryan McConnell
46259cd0b8 fix sortVTableDispatchers KeyError on re-entrant method registration via when isMainModule (#25856)
Encountered in realistic scenario. Didn't really look at this one. AI
one shot it lol

When a module defines method-bearing types and a when isMainModule
block imports additional modules that also define methods on the same
type hierarchy, sortVTableDispatchers crashes with:

Error: unhandled exception: key not found: (module: N, item: M)
[KeyError]

Root cause: the itemTable built during vtable sorting is populated
from g.objectTree[baseType], which only contains types from the
current compilation pass. When when isMainModule triggers re-import
of method-bearing modules, the method bucket contains types from both
passes. Types from the first pass have ItemIds not present in the
second pass's object tree, so itemTable[obj.itemId] raises KeyError
at line 155.

Fix: if obj.itemId is missing from itemTable, create an empty slot
array of the correct length. The entry is a local temporary — the
second loop in sortVTableDispatchers only calls setMethodsPerType
for types in the current object tree, so types from the prior pass
retain their already-established dispatch. The entry exists solely to
prevent the KeyError during the assignment loop.

The methodIndexLen used for the new entry is the bucket's slot count,
which is correct for any type in the hierarchy.

Added test tests/method/tvtable_reentry.nim that defines methods
across three types in two compilation passes and verifies dispatch
correctness for all three.
2026-06-05 16:37:00 +02:00
ringabout
4b374eb0a6 stop a temp register from being freed if addressed for lent (#25861)
ref https://github.com/nim-lang/Nim/issues/25849

The important part is in compiler/vmgen.nim:1838: when the VM lowers
a[i] or a.b as an address-producing operation, it emits opcLdArrAddr /
opcLdObjAddr. That returns an alias into the storage owned by the source
register. Before the patch, that source register could still betreated
as a normal temporary and later reclaimed or reused by the allocator.
Once that happened, the address result was still live, but the backing
temp was no longer guaranteed to exist, which is what led to the
nil/illegal-storage crash.

The fix is to pin that source temp by changing its slot kind to
slotTempPerm right after emitting the address load. You can see the same
lifetime rule already existed for the generic addr(...) path around
compiler/vmgen.nim:1551: if the source is a temporary and we take its
address, the compiler marks it permanent so freeTemp won’t recycle it.
The patch extends that exact rule to array and object address loads:

- compiler/vmgen.nim:1843
- compiler/vmgen.nim:1861

slotTempPerm is outside the normal freeTemp range in
compiler/vmgen.nim:248, so once a temp is upgraded to permanent, the VM
allocator stops treating it as reusable. That is the actual root-cause
fix: it preserves the backing storage for the address result until the
surrounding evaluation is done.

The regression test in tests/vm/t25849.nim:8 forces exactly that path
with a local lent iterator over an array and a static VM evaluation.
2026-06-04 13:29:48 +02:00
Corey Leavitt
c8e805a2fa fixes #25595; cursor inference: a recorded mutation extends the variable's liveness (#25864)
fixes #25595

## Bug

A `let` bound to a field of a value-type **case object** with a `ref`
field is inferred as a non-owning cursor, but the cursor's source can be
mutated through the cursor's own ref during a call, freeing the ref
while the borrow still reads it. Use-after-free under arc/orc (refc is
unaffected, it has no cursor inference):

```nim
var destroyed = false
type
  O = ref object
    value: int
    home: H
  W = object
    case k: bool
    of true: r: O
    of false: discard
  H = ref object
    w: W
proc `=destroy`(o: var typeof(O()[])) =
  destroyed = true
proc clear(o: O): int =
  o.home.w = W()             # overwrites h.w via the back-reference -> frees the ref
  doAssert not destroyed     # fails: the element was destroyed during the call
  result = o.value
proc go(h: H): int =
  let c = h.w                # inferred cursor (borrow of h.w)
  result = clear(c.r)
proc main =
  let h = H()
  let o = O(value: 42)
  o.home = h
  h.w = W(k: true, r: o)
  doAssert go(h) == 42
main()
```

The `not destroyed` assert fails: the element is destroyed during the
call, so the following `o.value` read is a use-after-free. The same code
with the `=destroy` guard removed (so the freed `o.value` is actually
read) is reported as `heap-use-after-free` by ASan under `-d:useMalloc
-fsanitize=address`. Longstanding (reproduces back to 2.2.0).
`--cursorInference:off` is a workaround.

## Root cause

Cursor inference (`varpartitions.computeCursors`) cursors `let c = h.w`
unless `dangerousMutation` finds a mutation of `c`'s graph within `c`'s
alive range `aliveStart..aliveEnd`. Here the mutation (the `clear(c.r)`
call) *is* connected to `c`'s graph and *is* recorded with `isMutated`,
but it is recorded at an `abstractTime` just past `c.aliveEnd`, so the
range check misses it.

The gap is timing. `aliveEnd` is set from the last `nkSym` use of `c`. A
call records its argument's mutation *after* traversing the whole
argument subtree (`potentialMutationViaArg`). When the argument is `c.r`
on a case object it is an `nkCheckedFieldExpr` (the discriminant check),
whose extra nodes advance `abstractTime` past `c`'s last `nkSym`. A
plain `nkDotExpr` has no such gap, so the bug needs a case object.

## Fix

In `potentialMutation`, extend the mutated variable's liveness to the
mutation time:

```nim
v.s[id].aliveEnd = max(v.s[id].aliveEnd, v.abstractTime)
```

A variable mutated at time T is provably alive at T, so this only
completes the liveness computation that `dangerousMutation` relies on.
The worst case is an extra copy, never an unsound cursor.

## Note on the locus

The fix is conservative by mechanism (it runs at every recorded
mutation) but perf-neutral in practice: it only suppresses a cursor
where the corrected liveness proves the borrow unsafe (cursor counts are
unchanged on the suites). I can scope it to call arguments if you'd
prefer it narrower.

## Test

`tests/arc/t25595.nim`, matrix `--mm:orc; --mm:arc; --mm:refc`: the
repro above as a `doAssert`. Fails (UAF) on arc/orc before the fix and
passes after. refc passes throughout.

## Checks

- repro passes on orc/arc after the fix. The guard-removed variant
(which reads the freed value) is ASan-clean after the fix and was
heap-use-after-free before. refc unaffected.
- testament `destructor` 90/90, `arc` 120/120. `views` 5/6, same as
stock (the one failure is environmental and pre-exists this change).
- perf-neutral: inferred-cursor count is identical stock vs fix across
the `arc` and `destructor` test files under `--mm:orc` (322 vs 322).
2026-06-03 07:25:33 +02:00
29 changed files with 414 additions and 69 deletions

View File

@@ -1904,7 +1904,9 @@ proc genObjConstr(p: BProc, e: PNode, d: var TLoc) =
var tmp: TLoc = default(TLoc)
var r: Rope
let needsZeroMem = p.config.selectedGC notin {gcArc, gcAtomicArc, gcOrc, gcYrc} or nfAllFieldsSet notin e.flags
let needsZeroMem =
nfAllFieldsSet notin e.flags or
(optSeqDestructors notin p.config.globalOptions and containsGarbageCollectedRef(t))
if useTemp:
tmp = getTemp(p, t)
r = rdLoc(tmp)

View File

@@ -803,6 +803,23 @@ proc hasCustomDestructor(c: Con, t: PType): bool =
obj = skipTypes(obj.baseClass, abstractPtrs)
result = result or isCustomDestructor(c, obj)
const
exprBranchKinds = {nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt,
nkTryStmt, nkPragmaBlock}
proc distributeAsgn(asgnKind: TNodeKind; dest, ri: PNode; c: var Con; s: var Scope): PNode =
## Distributes an assignment ``dest = ri`` into the leaf expressions of
## ``ri`` when ``ri`` is an expression-based control flow construct. This
## avoids creating pointless intermediate temporaries (bug #25850). The
## descent is recursive so that nestings like ``block: ...; if c: a else: b``
## assign directly to ``dest`` instead of going through a temp per branch.
if ri.kind in exprBranchKinds:
template process(child, s): untyped =
distributeAsgn(asgnKind, dest, child, c, s)
handleNestedTempl(ri, process, willProduceStmt = true)
else:
result = newTree(asgnKind, dest, p(ri, c, s, consumed))
proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSingleUsedTemp}; inReturn = false): PNode =
if n.kind in {nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkIfStmt,
nkIfExpr, nkCaseStmt, nkWhen, nkWhileStmt, nkParForStmt, nkTryStmt, nkPragmaBlock}:
@@ -1004,13 +1021,11 @@ proc p(n: PNode; c: var Con; s: var Scope; mode: ProcessMode; tmpFlags = {sfSing
result = moveOrCopy(p(n[0], c, s, mode), n[1], c, s, flags)
elif isDiscriminantField(n[0]):
result = c.genDiscriminantAsgn(s, n)
elif n[1].kind in {nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt, nkTryStmt, nkPragmaBlock}:
elif n[1].kind in exprBranchKinds:
# Distribute the assignment into each branch to avoid
# creating pointless temporaries for expression-based control flow.
let dest = p(n[0], c, s, mode)
template process(child, s): untyped =
newTree(n.kind, dest, p(child, c, s, consumed))
handleNestedTempl(n[1], process, willProduceStmt = true)
result = distributeAsgn(n.kind, dest, n[1], c, s)
else:
result = copyNode(n)
result.add p(n[0], c, s, mode)

View File

@@ -100,6 +100,7 @@ type
warnGlobalVarConstructorTemporary = "GlobalVarConstructorTemporary",
warnImplicitRangeConversion = "ImplicitRangeConversion",
warnSystemRangeConversion = "SystemRangeConversion",
warnInvalidCmpOp = "InvalidCmpOp",
# hints
hintSuccess = "Success", hintSuccessX = "SuccessX",
hintCC = "CC",
@@ -210,6 +211,7 @@ const
warnGlobalVarConstructorTemporary: "global variable '$1' initialization requires a temporary variable",
warnImplicitRangeConversion: "implicit range conversion $1",
warnSystemRangeConversion: "implicit range conversion $1",
warnInvalidCmpOp: "$1",
hintSuccess: "operation successful: $#",
# keep in sync with `testament.isSuccess`
hintSuccessX: "$build\n$loc lines; ${sec}s; $mem; proj: $project; out: $output",

View File

@@ -183,12 +183,6 @@ func `<`*(a: ExprIndex, b: ExprIndex): bool =
func `<=`*(a: ExprIndex, b: ExprIndex): bool =
a.int16 <= b.int16
func `>`*(a: ExprIndex, b: ExprIndex): bool =
a.int16 > b.int16
func `>=`*(a: ExprIndex, b: ExprIndex): bool =
a.int16 >= b.int16
func `==`*(a: ExprIndex, b: ExprIndex): bool =
a.int16 == b.int16

View File

@@ -2241,14 +2241,17 @@ proc parseTypeClassParam(p: var Parser): PNode =
proc parseTypeClass(p: var Parser): PNode =
#| conceptParam = ('var' | 'out' | 'ptr' | 'ref' | 'static' | 'type')? symbol
#| conceptDecl = 'concept' conceptParam ^* ',' (pragma)? ('of' typeDesc ^* ',')?
#| conceptDecl = 'concept' (conceptParam ^* ',' (pragma)?)? ('of' typeDesc ^* ',')?
#| &IND{>} stmt
result = newNodeP(nkTypeClassTy, p)
getTok(p)
if p.tok.tokType == tkComment:
skipComment(p, result)
if p.tok.indent < 0:
if p.tok.tokType == tkOf and p.tok.indent < 0:
# new-styled `concept of A, B` on the same line as `concept`
result.add(p.emptyNode)
elif p.tok.indent < 0:
var args = newNodeP(nkArgList, p)
result.add(args)
args.add(p.parseTypeClassParam)
@@ -2274,9 +2277,10 @@ proc parseTypeClass(p: var Parser): PNode =
result.add(p.emptyNode)
if p.tok.tokType == tkComment:
skipComment(p, result)
# an initial IND{>} HAS to follow:
# an initial IND{>} HAS to follow, unless this concept inherits requirements:
if not realInd(p):
if result.isNewStyleConcept:
let hasParents = result[2].kind != nkEmpty
if result.isNewStyleConcept and not hasParents:
parMessage(p, "routine expected, but found '$1' (empty new-styled concepts are not allowed)", p.tok)
result.add(p.emptyNode)
else:

View File

@@ -129,7 +129,14 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym,
result.typ = nil
onUse(n.info, s)
of skParam:
result = n
if s.owner == c.p.owner:
# Parameters of the routine currently being semchecked stay as local
# identifiers
result = n
else:
# Preserve captured outer parameters so nested generic procs can still
# see them after the generic pre-pass.
result = newSymNode(s, n.info)
onUse(n.info, s)
of skType:
if (s.typ != nil) and

View File

@@ -1559,10 +1559,9 @@ proc track(tracked: PEffects, n: PNode) =
message(tracked.config, n.info, warnPtrToCstringConv,
$n[1].typ)
# Check for implicit range conversions. Compile-time constants are already
# fully known here, so only non-constant values need the downsizing warning.
# Check for implicit range conversions
if n.kind == nkHiddenStdConv and (not tracked.isArrayIndexing) and
getConstExpr(tracked.ownerModule, n[1], tracked.c.idgen, tracked.graph) == nil and
n[1].kind notin {nkCharLit..nkUInt64Lit, nkFloatLit..nkFloat128Lit} and
shouldWarnRangeConversion(tracked.config, n.info, n.typ, n[1].typ):
message(tracked.config, n.info, warnImplicitRangeConversion,
typeToString(n[1].typ) & " -> " & typeToString(n.typ))

View File

@@ -2642,6 +2642,11 @@ proc semProcAux(c: PContext, n: PNode, kind: TSymKind,
elif s.name.s == "()" and callOperator notin c.features:
localError(c.config, n.info, "the overloaded " & s.name.s &
" operator has to be enabled with {.experimental: \"callOperator\".}")
elif sfImportc notin s.flags and (s.name.s == ">" or s.name.s == ">=" or s.name.s == "!="):
# ignore imported procs as these operators in backend language might have different semantics
let op1 = if s.name.s == "!=": "==" elif s.name.s == ">": "<" else: "<="
message(c.config, n.info, warnInvalidCmpOp, "define `" & op1 & "` instead of `" & s.name.s & "` to implement user defined comparison operator. " &
"it allows you to use `" & s.name.s & "` automatically.")
if sfBorrow in s.flags and c.config.cmd notin cmdDocLike:
result[bodyPos] = c.graph.emptyNode

View File

@@ -1759,6 +1759,21 @@ proc typeRel(c: var TCandidate, f, aOrig: PType,
let ff = last(f)
if ff != nil:
result = typeRel(c, ff, a, flags)
if result == isNone and a.kind == tyGenericInst and trBindGenericParam in flags:
var depth = -1
# Generic-parameter constraints like `F: Future` can miss in `last(f)`
# when the actual type inherits from a concrete generic instantiation.
# Keep this fallback scoped to generic-parameter matching so typedesc
# overloads such as `type Future[T]` still prefer more specific
# descendants like `InternalRaisesFuture[T, E]`.
if isGenericSubtype(c, a, f, depth, f) and depth > 0:
var askip = skippedNone
let aobj = a.skipToObject(askip)
if aobj != nil and tfFinal notin aobj.flags:
# Keep overload ranking consistent with other inheritance-based
# matches: deeper descendants are slightly worse candidates.
inc c.inheritancePenalty, depth + int(c.inheritancePenalty < 0)
result = isGeneric
of tyGenericInvocation:
var x = a.skipGenericAlias
if x.kind == tyGenericParam and x.len > 0:

View File

@@ -22,7 +22,7 @@ import std / tables
import
options, ast, astalgo, trees, msgs,
idents, renderer, types, semfold, magicsys, cgmeth,
idents, renderer, types, semfold, magicsys, cgmeth, parampatterns,
lowerings, liftlocals,
modulegraphs, lineinfos
@@ -90,11 +90,21 @@ proc getCurrOwner(c: PTransf): PSym =
if c.transCon != nil: result = c.transCon.owner
else: result = c.module
proc freshOwnedSym(c: PTransf; s, owner: PSym): PNode =
# We need to copy the symbol here because we might need to change its owner and
# we don't want to mess with the original symbol which might be used in other places.
# This can happen for example for iterators which are transformed multiple times when
# they are used in different contexts.
var fresh = copySym(s, c.idgen)
if fresh.kind notin routineKinds:
incl(fresh.flagsImpl, sfFromGeneric)
setOwner(fresh, owner)
result = newSymNode(fresh)
proc newTemp(c: PTransf, typ: PType, info: TLineInfo): PNode =
let r = newSym(skTemp, getIdent(c.graph.cache, genPrefix), c.idgen, getCurrOwner(c), info)
r.typ = typ #skipTypes(typ, {tyGenericInst, tyAlias, tySink})
incl(r.flagsImpl, sfFromGeneric)
let owner = getCurrOwner(c)
result = newSymNode(r)
proc transform(c: PTransf, n: PNode, noConstFold = false): PNode
@@ -185,11 +195,39 @@ proc transformSym(c: PTransf, n: PNode): PNode =
result = transformSymAux(c, n)
proc freshVar(c: PTransf; v: PSym): PNode =
let owner = getCurrOwner(c)
var newVar = copySym(v, c.idgen)
incl(newVar.flagsImpl, sfFromGeneric)
setOwner(newVar, owner)
result = newSymNode(newVar)
result = freshOwnedSym(c, v, getCurrOwner(c))
proc introduceNewRoutineHeaderSyms(c: PTransf; n: PNode; oldOwner, newOwner: PSym) =
# We need to introduce new symbols for the parameters and result of a routine when
# we copy it for inlining or closure generation.
# Otherwise, we would have multiple nodes referring to the same parameter symbols which
# can lead to problems when we need to change the owner of these symbols.
case n.kind
of nkSym:
if n.sym.owner == oldOwner:
c.transCon.mapping[n.sym.itemId] = freshOwnedSym(c, n.sym, newOwner)
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
discard
else:
for i in 0..<n.len:
introduceNewRoutineHeaderSyms(c, n[i], oldOwner, newOwner)
proc copyRoutineTypeHeader(c: PTransf; oldProc, newProc: PSym) =
# We need to copy the routine type header to ensure that
# modifications to the newProc do not affect the oldProc.
if oldProc.typ != nil and oldProc.typ.kind == tyProc and oldProc.typ.n != nil:
newProc.typ = copyType(oldProc.typ, c.idgen, newProc)
newProc.typ.n = newNodeI(oldProc.typ.n.kind, oldProc.typ.n.info)
if oldProc.typ.n.len > 0:
newProc.typ.n.add copyTree(oldProc.typ.n[0])
for i in 1..<oldProc.typ.n.len:
let oldParam = oldProc.typ.n[i].sym
var newParam = getOrDefault(c.transCon.mapping, oldParam.itemId)
if newParam == nil:
newParam = freshOwnedSym(c, oldParam, newProc)
c.transCon.mapping[oldParam.itemId] = newParam
doAssert newParam.kind == nkSym
newProc.typ.addParam newParam.sym
proc transformVarSection(c: PTransf, v: PNode): PNode =
result = newTransNode(v)
@@ -338,11 +376,18 @@ proc introduceNewLocalVars(c: PTransf, n: PNode): PNode =
return n
of nkLambdaKinds, nkProcDef, nkFuncDef, nkMethodDef, nkConverterDef: # todo optimize nosideeffects?
result = newTransNode(n)
let x = newSymNode(copySym(n[namePos].sym, c.idgen))
c.transCon.mapping[n[namePos].sym.itemId] = x
let oldProc = n[namePos].sym
let x = freshOwnedSym(c, oldProc, oldProc.owner)
c.transCon.mapping[oldProc.itemId] = x
introduceNewRoutineHeaderSyms(c, n[paramsPos], oldProc, x.sym)
if resultPos < n.len and n[resultPos] != nil:
introduceNewRoutineHeaderSyms(c, n[resultPos], oldProc, x.sym)
copyRoutineTypeHeader(c, oldProc, x.sym)
result[namePos] = x # we have to copy proc definitions for iters
for i in 1..<n.len:
result[i] = introduceNewLocalVars(c, n[i])
if x.sym.typ != nil and x.sym.typ.kind == tyProc:
result[paramsPos] = x.sym.typ.n
result[namePos].sym.ast = result
else:
result = newTransNode(n)
@@ -675,7 +720,7 @@ type
paDirectMapping, paFastAsgn, paFastAsgnTakeTypeFromArg
paVarAsgn, paComplexOpenarray, paViaIndirection
proc putArgInto(arg: PNode, formal: PType): TPutArgInto =
proc putArgInto(arg: PNode, formal: PType; borrowedFirstArg = false): TPutArgInto =
# This analyses how to treat the mapping "formal <-> arg" in an
# inline context.
if formal.kind == tyTypeDesc: return paDirectMapping
@@ -726,6 +771,13 @@ proc putArgInto(arg: PNode, formal: PType): TPutArgInto =
if skipTypes(formal, abstractInst).kind in {tyVar, tyLent}: result = paVarAsgn
else: result = paFastAsgn
if borrowedFirstArg and result == paDirectMapping and parampatterns.exprRoot(arg) == nil and
parampatterns.isAssignable(nil, arg) == arNone:
# Inline iterators like `items(array)` borrow from the first argument.
# If that argument is just a transient expression, materialize it so the
# lifted closure keeps the backing storage alive across yields.
result = paFastAsgnTakeTypeFromArg
proc findWrongOwners(c: PTransf, n: PNode) =
if n.kind == nkVarSection:
let x = n[0][0]
@@ -824,13 +876,16 @@ proc transformFor(c: PTransf, n: PNode): PNode =
if iter.kind != skIterator: return result
# generate access statements for the parameters (unless they are constant)
pushTransCon(c, newC)
let borrowedIterResult =
iter.typ != nil and iter.typ.returnType != nil and
skipTypes(iter.typ.returnType, abstractInst).kind in {tyLent, tyVar}
for i in 1..<call.len:
var arg = transform(c, call[i])
let ff = skipTypes(iter.typ, abstractInst)
# can happen for 'nim check':
if i >= ff.n.len: return result
var formal = ff.n[i].sym
let pa = putArgInto(arg, formal.typ)
let pa = putArgInto(arg, formal.typ, borrowedIterResult and i == 1)
case pa
of paDirectMapping:
newC.mapping[formal.itemId] = arg

View File

@@ -185,6 +185,9 @@ proc root(v: var Partitions; start: int): int =
proc potentialMutation(v: var Partitions; s: PSym; level: int; info: TLineInfo) =
let id = variableId(v, s)
if id >= 0:
# mutated here => alive here: keep aliveEnd in sync so dangerousMutation catches
# mutations recorded after the var's last use (e.g. via a call arg). See #25595.
v.s[id].aliveEnd = max(v.s[id].aliveEnd, v.abstractTime)
let r = root(v, id)
let flags = if s.kind == skParam:
if isConstParam(s):

View File

@@ -152,6 +152,8 @@ proc sortVTableDispatchers*(g: ModuleGraph) =
rootItemIdCount.inc(baseType.itemId)
for idx in 0..<g.methods[bucket].methods.len:
let obj = g.methods[bucket].methods[idx].typ.firstParamType.skipTypes(skipPtrs)
if obj.itemId notin itemTable:
itemTable[obj.itemId] = newSeq[PSym](methodIndexLen)
itemTable[obj.itemId][mIndex] = g.methods[bucket].methods[idx]
for baseType in rootTypeSeq:

View File

@@ -188,7 +188,7 @@ objectPart = IND{>} objectPart^+IND{=} DED
/ objectWhen / objectCase / 'nil' / 'discard' / declColonEquals
objectDecl = 'object' ('of' typeDesc)? COMMENT? objectPart
conceptParam = ('var' | 'out' | 'ptr' | 'ref' | 'static' | 'type')? symbol
conceptDecl = 'concept' conceptParam ^* ',' (pragma)? ('of' typeDesc ^* ',')?
conceptDecl = 'concept' (conceptParam ^* ',' (pragma)?)? ('of' typeDesc ^* ',')?
&IND{>} stmt
typeDef = identVisDot genericParamList? pragma '=' optInd typeDefValue
indAndComment?

View File

@@ -16,11 +16,11 @@ const
ChecksumsStableCommit = "0b8e46379c5bc1bf73d8b3011908389c60fb9b98" # 2.0.1
SatStableCommit = "e63eaea8baf00bed8bcd5a29ffd8823abb265b39"
NimonyStableCommit = "750aa47f2139fe5ad69f04b44428b752011fe873" # unversioned \
NimonyStableCommit = "fca0e938b04695a3aa4e85abcc976571189f2bd2" # unversioned \
# Note that Nimony uses Nim as a git submodule but we don't want to install
# Nimony's dependency to Nim as we are Nim. So a `git clone` without --recursive
# is **required** here.
# Commit from 2026-05-05
# Commit from 2026-06-08
# examples of possible values for fusion: #head, #ea82b54, 1.2.3
FusionStableHash = "#562467452b32cb7a97410ea177f083e6d8405734"

View File

@@ -3,7 +3,7 @@
when defined(nimPreviewSlimSystem):
import std/assertions
when not defined(nimNoLentIterators):
when (not defined(nimNoLentIterators)) and not defined(js) and not defined(nimscript):
template lent2(T): untyped = lent T
else:
template lent2(T): untyped = T
@@ -37,7 +37,7 @@ iterator mitems*[T](a: var openArray[T]): var T {.inline.} =
yield a[i]
unCheckedInc(i)
iterator items*[IX, T](a: array[IX, T]): T {.inline.} =
iterator items*[IX, T](a: array[IX, T]): lent2 T {.inline.} =
## Iterates over each item of `a`.
when a.len > 0:
var i = low(IX)
@@ -143,7 +143,7 @@ iterator items*[T: Ordinal](s: Slice[T]): T =
for x in s.a .. s.b:
yield x
iterator pairs*[T](a: openArray[T]): tuple[key: int, val: T] {.inline.} =
iterator pairs*[T](a: openArray[T]): tuple[key: int, val: lent T] {.inline.} =
## Iterates over each item of `a`. Yields `(index, a[index])` pairs.
var i = 0
while i < len(a):
@@ -158,7 +158,7 @@ iterator mpairs*[T](a: var openArray[T]): tuple[key: int, val: var T]{.inline.}
yield (i, a[i])
unCheckedInc(i)
iterator pairs*[IX, T](a: array[IX, T]): tuple[key: IX, val: T] {.inline.} =
iterator pairs*[IX, T](a: array[IX, T]): tuple[key: IX, val: lent T] {.inline.} =
## Iterates over each item of `a`. Yields `(index, a[index])` pairs.
when a.len > 0:
var i = low(IX)
@@ -177,7 +177,7 @@ iterator mpairs*[IX, T](a: var array[IX, T]): tuple[key: IX, val: var T] {.inlin
if i >= high(IX): break
unCheckedInc(i)
iterator pairs*[T](a: seq[T]): tuple[key: int, val: T] {.inline.} =
iterator pairs*[T](a: seq[T]): tuple[key: int, val: lent T] {.inline.} =
## Iterates over each item of `a`. Yields `(index, a[index])` pairs.
var i = 0
let L = len(a)

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,30 +0,0 @@
discard """
cmd: "nim check $options --hints:off --warning:ImplicitRangeConversion --warningaserror:ImplicitRangeConversion $file"
action: "compile"
"""
type
E = enum
ea, eb
R = range[eb..eb]
I = range[0..3]
proc accept(r: R) = discard
proc accept(i: I) = discard
var r: R
var i: I
const enumOk = eb
const enumAlias = enumOk
const intOk = 1 + 2
r = eb
r = enumOk
r = enumAlias
accept(eb)
accept(enumOk)
accept(enumAlias)
i = intOk
accept(intOk)

View File

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

View File

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