fix(gengo): compound assign for non-LOCAL LHS

Audit follow-up after Wave 1's pcode `+=` fix surfaced a parallel
class of silent miscompiles in the *gengo* (native-Go) emit path.
Three real bugs hiding behind happy-path test coverage:

  * `arr[i] += x` was ASSIGN-only — the IndexExpr branch returned
    after emitting `arr[i] := x`, dropping the original element.
    Now: PushArray + Push index, ArrayPush to read, fold with RHS,
    re-do PushArray + index, ArrayPop to store.

  * `alias->field += x` (and the M-> / MEMVAR-> namespace variants)
    were ASSIGN-only too. Same shape of bug — `x->v += 7` compiled
    as `x->v := 7`. Compound branch reads via PushAliasField (or
    PushMemvar for M->), folds, stores via SetAliasField (or
    PopMemvar).

  * PRIVATE / PUBLIC mid-function declarations were treated as
    extra LOCAL slots. emitMidVarDecl extended `locals` past the
    function's declared count and emitted `PopLocalFast(idx)` for
    the init. The slot didn't exist at runtime, so the init either
    silently scribbled past the frame (small N) or panicked with
    "local variable index out of range" once exercised. New logic:
    PRIVATE/PUBLIC declarations bypass the locals table and emit
    `PopMemvar(name)` for the init expression. The runtime auto-
    creates the memvar.

  * Memvar assignment fallback. After the LOCAL/STATIC checks miss
    in emitAssign, the bottom path used to be a one-line WARN that
    emitted RHS + `Pop()` — silently discarding the value. PRIVATE
    pSum stayed at its initial value forever. Now: ASSIGN goes
    through PopMemvar; compound forms read via PushMemvar, fold,
    write back via PopMemvar.

Test fixture (tests/std_ch/test_compound_lhs.prg) covers all four
shapes. The std.ch runner picks it up so the regression suite now
stands at 15/15.

Other gates green:
  go test ./...      : PASS
  FiveSql2 SQL:1999  : 43/43
  Harbour compat     : 56/56
  std.ch suite       : 15/15
  FRB suite          : 5/5

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 05:14:28 +09:00
parent efb615bed9
commit 6a30c4e50e
3 changed files with 108 additions and 2 deletions

View File

@@ -30,6 +30,7 @@ TESTS=(
test_set_deleted
test_unsupported
test_block_comma
test_compound_lhs
)
work="$(mktemp -d)"

View File

@@ -0,0 +1,36 @@
PROCEDURE Main()
LOCAL aArr := { 10, 20, 30 }, oObj, n
/* 1. array index += */
aArr[2] += 5
? "1. aArr[2] += 5 =>", aArr[2], "(expect 25)"
/* 2. alias->field += (use a real workarea) */
FErase("c.dbf")
dbCreate("c.dbf", { {"V", "N", 6, 0} })
USE c.dbf NEW EXCLUSIVE ALIAS x
dbAppend(); FieldPut(1, 100)
dbCommit()
x->v += 7
? "2. x->v += 7 =>", x->v, "(expect 107)"
x->(dbCloseArea())
FErase("c.dbf")
/* 3. memvar / private += */
PRIVATE pSum := 50
pSum += 25
? "3. PRIVATE pSum += 25 =>", pSum, "(expect 75)"
/* 4. STATIC += (already worked per gengo source, sanity) */
StaticTest()
? "DONE"
RETURN
STATIC FUNCTION StaticTest()
STATIC s_n := 100
s_n += 5
? "4. STATIC s_n += 5 =>", s_n, "(expect 105)"
RETURN NIL