feat(static): file-local scoping for STATIC FUNCTION / PROCEDURE

Harbour STATIC FUNCTION / PROCEDURE is scoped to its source file —
multiple .prg files can each declare a `STATIC FUNCTION fn_HGet()`
without colliding. Five previously dropped them into the global VM
symbol table by their plain name, so multi-file builds (e.g. labdb's
22 .prg files where seven each defined their own STATIC fn_HGet)
either failed with redeclaration or silently linked every caller to
whichever definition won. fivenode_go's `sed` rename workaround can
now go away.

Mechanism

* ast.FuncDecl gains IsStatic. parser.go sets it whenever the
  top-level STATIC keyword precedes FUNCTION / PROCEDURE.

* gengo records every same-file STATIC FUNCTION name in
  g.staticFuncs. The symbol-table entry and the Go function name
  for those declarations are mangled to

      __STATIC__<fileKey>__<NAME>

  so two files declaring `helper()` register two distinct symbols.

* emitPushSymbol rewrites call sites that match a name in
  g.staticFuncs to the same mangled form, so same-file references
  still resolve while cross-file references would look for a
  symbol that doesn't exist.

* cmd/five/main.go's buildMultiPRG excludes STATIC FUNCTIONs from
  the cross-file analyzer table — a foreign file calling another
  file's STATIC now triggers a clean "undeclared variable" warning
  instead of a runtime "function not found" deep inside vm.Run.

Verified

  /tmp/a.prg + /tmp/b.prg each define `STATIC FUNCTION helper()`
  returning their own string. Building both into one binary shows
  each file calling its own helper:

    a says: alpha (from a.prg)
    (call B): beta (from b.prg)

  Misuse (file X calling file Y's STATIC) now warns at compile
  time. Full regression: go test ./compiler/... ./hbrt/...
  ./hbrtl/..., Compat 56/56, std.ch 17/17, FRB 7/7, FiveSql2 43/43.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 17:27:06 +09:00
parent 7629f95235
commit f3e0ffe10c
4 changed files with 69 additions and 7 deletions

View File

@@ -236,7 +236,11 @@ func (p *Parser) parseFile() *ast.File {
// STATIC FUNCTION/PROCEDURE → function declaration with static scope
if p.peekAt(1) == token.FUNCTION_KW || p.peekAt(1) == token.PROCEDURE {
p.advance() // skip STATIC
file.Decls = append(file.Decls, p.parseFuncDecl())
fd := p.parseFuncDecl()
if fd != nil {
fd.IsStatic = true
}
file.Decls = append(file.Decls, fd)
} else {
// Top-level STATIC declaration (module-level variable)
file.Decls = append(file.Decls, p.parseVarDecl())