From 9d4970cf7144e597aca47d421679aff4c9d088f3 Mon Sep 17 00:00:00 2001 From: ringabout <43030857+ringabout@users.noreply.github.com> Date: Mon, 20 Jul 2026 18:59:18 +0800 Subject: [PATCH] =?UTF-8?q?fixes=20#26000;=20Cannot=20add=20members=20to?= =?UTF-8?q?=20enum-indexed=20array=20of=20seqs=20at=20com=E2=80=A6=20(#260?= =?UTF-8?q?13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …pile time fixes #26000 vm: preserve lvalues for mutations of broadcast array elements Load in-place mutation targets through their address so mutations don't operate on detached copies of broadcast defaults. This also preserves nested lvalues and evaluates indexed destinations only once. Cover sequence, string, and set mutations through direct, nested, field, enum-indexed, and range-indexed array elements. (cherry picked from commit 2915691515f7b9a264e82494abeaa5ce6caf5d8e) --- compiler/vmgen.nim | 18 ++++- tests/vm/tbroadcastarraymutation.nim | 101 +++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 tests/vm/tbroadcastarraymutation.nim diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim index 00fbe5cb22..2459eca236 100644 --- a/compiler/vmgen.nim +++ b/compiler/vmgen.nim @@ -851,14 +851,26 @@ proc genBinaryStmt(c: PCtx; n: PNode; opc: TOpcode) = c.freeTemp(tmp) c.freeTemp(dest) +proc genMutatingValue(c: PCtx; n: PNode): TRegister = + ## Loads the value of an in-place mutation target while keeping it attached to + ## its original storage. Compound lvalues must be resolved through their + ## address: a normal value load can return a detached copy (for example, when + ## indexing a broadcast default array). + if needsAsgnPatch(n): + let address = c.genx(n, {gfNodeAddr}) + result = c.getTemp(n.typ) + c.gABC(n, opcLdDeref, result, address) + c.freeTemp(address) + else: + result = c.genx(n) + proc genBinaryStmtVar(c: PCtx; n: PNode; opc: TOpcode) = var x = n[1] if x.kind in {nkAddr, nkHiddenAddr}: x = x[0] let - dest = c.genx(x) + dest = c.genMutatingValue(x) tmp = c.genx(n[2]) c.gABC(n, opc, dest, tmp, 0) - #c.genAsgnPatch(n[1], dest) c.freeTemp(tmp) c.freeTemp(dest) @@ -1162,7 +1174,7 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; flags: TGenFlags = {}, m: TMag of mIncl, mExcl: unused(c, n, dest) - var d = c.genx(n[1]) + var d = c.genMutatingValue(n[1]) var tmp = c.genx(n[2]) c.genSetType(n[1], d) c.gABC(n, if m == mIncl: opcIncl else: opcExcl, d, tmp) diff --git a/tests/vm/tbroadcastarraymutation.nim b/tests/vm/tbroadcastarraymutation.nim new file mode 100644 index 0000000000..c67bbfd7e1 --- /dev/null +++ b/tests/vm/tbroadcastarraymutation.nim @@ -0,0 +1,101 @@ +type + Container = object + numbers: seq[int] + text: string + chars: set[char] + + Variant = object + case enabled: bool + of false: + numbers: seq[int] + else: + discard + + Index = enum + index0, index1, index2, index3, index4, index5, index6, index7, + index8, index9, index10, index11, index12, index13, index14, index15, + index16, index17, index18, index19, index20, index21, index22, index23, + index24, index25, index26, index27, index28, index29, index30, index31, + index32 + + Outer = object + values: array[33, seq[int]] + +proc directSeq(): array[33, seq[int]] = + result[32].add 1 + +proc directStringChar(): array[33, string] = + result[32].add 'a' + +proc directStringString(): array[33, string] = + result[32].add "ab" + +proc directSet(): array[33, set[char]] = + result[32].incl 'a' + +proc fieldSeq(): array[33, Container] = + result[32].numbers.add 1 + +proc fieldStringChar(): array[33, Container] = + result[32].text.add 'a' + +proc fieldStringString(): array[33, Container] = + result[32].text.add "ab" + +proc fieldSet(): array[33, Container] = + result[32].chars.incl 'a' + +proc nestedSeq(): array[33, array[33, seq[int]]] = + result[32][32].add 1 + +proc checkedFieldSeq(): array[33, Variant] = + result[32].numbers.add 1 + +proc enumIndexSeq(): array[Index, seq[int]] = + result[index32].add 1 + +proc rangeIndexSeq(): array[10..42, seq[int]] = + result[42].add 1 + +proc firstIndexSeq(): array[33, seq[int]] = + result[0].add 1 + +proc middleIndexSeq(): array[33, seq[int]] = + result[16].add 1 + +proc objectArraySeq(): Outer = + result.values[32].add 1 + +proc singleEvaluation(): tuple[values: array[33, seq[int]], evaluations: int] = + var evaluations = 0 + proc index(): int = + inc evaluations + 32 + result.values[index()].add 1 + result.evaluations = evaluations + +proc test = + let direct = directSeq() + doAssert direct[0].len == 0 + doAssert direct[31].len == 0 + doAssert direct[32] == @[1] + doAssert directStringChar()[32] == "a" + doAssert directStringString()[32] == "ab" + doAssert 'a' in directSet()[32] + doAssert fieldSeq()[32].numbers == @[1] + doAssert fieldStringChar()[32].text == "a" + doAssert fieldStringString()[32].text == "ab" + doAssert 'a' in fieldSet()[32].chars + doAssert nestedSeq()[32][32] == @[1] + doAssert checkedFieldSeq()[32].numbers == @[1] + doAssert enumIndexSeq()[index32] == @[1] + doAssert rangeIndexSeq()[42] == @[1] + doAssert firstIndexSeq()[0] == @[1] + doAssert middleIndexSeq()[16] == @[1] + doAssert objectArraySeq().values[32] == @[1] + let evaluated = singleEvaluation() + doAssert evaluated.values[32] == @[1] + doAssert evaluated.evaluations == 1 + +static: test() +test()