fix(gengo): M-> and MEMVAR-> route to memvar table, not workarea

Harbour reserves the aliases `M` and `MEMVAR` for the memvar
namespace — `M->cVar` reads a PUBLIC/PRIVATE memvar, not a DBF
field in a workarea named M. Five's emitAliasExpr and emitAssign
treated all aliases identically, emitting:

  t.PushAliasField("M", "cVar")              // read
  _wa := t.WA.(*hbrdd.WorkAreaManager); _wa.SetAliasField("M", ...) // write

which triggered a spurious hbrdd import on programs using memvars
and attempted a workarea lookup that couldn't find a "M" area at
runtime.

Detect the reserved aliases (case-insensitive) at the three
AliasExpr call sites — the read path (emitAliasExpr) and both
assign paths (emitAssign for statements, emitAssignExpr for
expression context) — and route to t.PushMemvar / t.PopMemvar
instead. The existing Thread helpers hash into the MemvarTable
populated by PUBLIC/PRIVATE declarations.

Unblocks harbour-core/tests/macro.prg build (runtime still needs
the TVALUE test helper, unrelated). FiveSql2 43/43, Harbour compat
56/56, Go test ALL PASS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-18 17:14:18 +09:00
parent 65b2edc906
commit 385a4ec6a2

View File

@@ -1486,6 +1486,14 @@ func (g *Generator) emitAssign(a *ast.AssignExpr, locals localMap) {
if aliasExpr, ok := a.Left.(*ast.AliasExpr); ok {
if aliasIdent, ok2 := aliasExpr.Alias.(*ast.IdentExpr); ok2 {
if fieldIdent, ok3 := aliasExpr.Field.(*ast.IdentExpr); ok3 {
upper := strings.ToUpper(aliasIdent.Name)
// `M->name := v` / `MEMVAR->name := v` are memvar writes,
// not workarea field writes.
if upper == "M" || upper == "MEMVAR" {
g.emitExpr(a.Right)
g.writeln(fmt.Sprintf(`t.PopMemvar(%q)`, fieldIdent.Name))
return
}
g.emitExpr(a.Right)
g.writeln(fmt.Sprintf(`{ _wa := t.WA.(*hbrdd.WorkAreaManager); _wa.SetAliasField(%q, %q, t.Pop2()) }`, aliasIdent.Name, fieldIdent.Name))
return
@@ -2716,6 +2724,16 @@ func (g *Generator) emitAssignExpr(e *ast.AssignExpr) {
if aliasExpr, ok := e.Left.(*ast.AliasExpr); ok {
if aliasIdent, ok2 := aliasExpr.Alias.(*ast.IdentExpr); ok2 {
if fieldIdent, ok3 := aliasExpr.Field.(*ast.IdentExpr); ok3 {
upper := strings.ToUpper(aliasIdent.Name)
if upper == "M" || upper == "MEMVAR" {
// Memvar write — leaves a copy on the stack for the
// enclosing expression to pick up (matches Dup+SetAlias
// path).
g.emitExpr(e.Right)
g.writeln("t.Dup()")
g.writeln(fmt.Sprintf(`t.PopMemvar(%q)`, fieldIdent.Name))
return
}
g.emitExpr(e.Right)
g.writeln("t.Dup()")
g.writeln(fmt.Sprintf(`{ _wa := t.WA.(*hbrdd.WorkAreaManager); _wa.SetAliasField(%q, %q, t.Pop2()) }`, aliasIdent.Name, fieldIdent.Name))
@@ -3082,6 +3100,13 @@ func (g *Generator) emitAliasExpr(e *ast.AliasExpr) {
// Case 1: alias->field (static alias, simple field name)
if ident, ok := e.Alias.(*ast.IdentExpr); ok && isFieldIdent {
upper := strings.ToUpper(ident.Name)
// `M->name` / `MEMVAR->name` access the memvar namespace, not
// a database workarea. Harbour reserves both aliases for this.
if upper == "M" || upper == "MEMVAR" {
g.writeln(fmt.Sprintf(`t.PushMemvar(%q)`, fieldIdent.Name))
return
}
g.writeln(fmt.Sprintf(`t.PushAliasField(%q, %q)`, ident.Name, fieldIdent.Name))
return
}