feat(analyzer): walk CLASS method bodies for undeclared-var warnings
Phase 2 of the analyzer originally only called analyzeFunc on *ast.FuncDecl. Class methods parse as *ast.MethodDecl and were silently skipped — meaning anything inside `METHOD Foo() CLASS TBar` got zero static checking, including the undeclared-variable scan. This is what let FindExclusive's DBI_FULLPATH / DBI_SHARED references ship: the gengo fallback (now PushMemvar, previously PushLocal(0)) turned them into runtime NIL / crash, but the analyzer never flagged them at build time because it never descended into the method body. Fix: add analyzeMethod — same scope setup as analyzeFunc (module statics, parameters, LOCAL/STATIC decls) — and route MethodDecl to it from the Phase 2 dispatch. Also register PCCOMPILE / PCEVAL / SQLSCAN in the RTL allow-list so FiveSql2's new pcode hot-path RTL doesn't trip the warning. Expected side effect: the FiveSql2 build now emits 17 real warnings from TSqlIndex.prg — undefined DBOI_* order-info constants and unregistered RTL functions (FieldType, FieldLen, ordCreate, dbCreateIndex, dbClearIndex). These are real tech debt hiding behind PushMemvar's silent NIL fallback; left as-is to surface them rather than suppress. Validation: - FiveSql2 43/43 - Harbour compat 51/51 - go test ./compiler/analyzer/... PASS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -111,11 +111,13 @@ func Analyze(file *ast.File, externalFuncs ...map[string]bool) []Diagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 2: Analyze each function
|
||||
// Phase 2: Analyze each function and class method body
|
||||
for _, d := range file.Decls {
|
||||
switch decl := d.(type) {
|
||||
case *ast.FuncDecl:
|
||||
a.analyzeFunc(decl)
|
||||
case *ast.MethodDecl:
|
||||
a.analyzeMethod(decl)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,6 +177,63 @@ func (a *Analyzer) analyzeFunc(fn *ast.FuncDecl) {
|
||||
}
|
||||
}
|
||||
|
||||
// analyzeMethod walks a class-method body (`METHOD Foo() CLASS TBar`)
|
||||
// applying the same undeclared-variable and unused-variable checks
|
||||
// that analyzeFunc performs on standalone functions. Without this,
|
||||
// unresolved identifiers inside CLASS methods silently fell through
|
||||
// to gengo's memvar fallback (NIL at runtime) — e.g. a missing
|
||||
// `#include "dbinfo.ch"` leaving DBI_FULLPATH undefined in a method.
|
||||
func (a *Analyzer) analyzeMethod(m *ast.MethodDecl) {
|
||||
a.scope = &Scope{
|
||||
Name: m.Name,
|
||||
Declared: make(map[string]VarInfo),
|
||||
Used: make(map[string]bool),
|
||||
}
|
||||
|
||||
// Module-level STATICs are visible to class methods too
|
||||
for name, info := range a.moduleStatics {
|
||||
a.scope.Declared[name] = info
|
||||
}
|
||||
|
||||
// Parameters
|
||||
for _, p := range m.Params {
|
||||
a.scope.Declared[strings.ToUpper(p.Name)] = VarInfo{
|
||||
Name: p.Name,
|
||||
Pos: p.NamePos,
|
||||
IsParam: true,
|
||||
}
|
||||
}
|
||||
|
||||
// LOCAL / STATIC declarations inside the method
|
||||
for _, d := range m.Decls {
|
||||
if vd, ok := d.(*ast.VarDecl); ok {
|
||||
for _, v := range vd.Vars {
|
||||
a.scope.Declared[strings.ToUpper(v.Name)] = VarInfo{
|
||||
Name: v.Name,
|
||||
Pos: v.NamePos,
|
||||
Kind: vd.Scope,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, stmt := range m.Body {
|
||||
a.analyzeStmt(stmt)
|
||||
}
|
||||
|
||||
// Unused-variable hints (same exclusions as analyzeFunc)
|
||||
for name, info := range a.scope.Declared {
|
||||
if !a.scope.Used[name] && !info.IsParam {
|
||||
lower := strings.ToLower(info.Name)
|
||||
if lower == "i" || lower == "j" || lower == "k" || lower == "n" ||
|
||||
lower == "err" || lower == "_" {
|
||||
continue
|
||||
}
|
||||
a.hint(info.Pos, "unused variable '%s'", info.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Analyzer) analyzeStmt(stmt ast.Stmt) {
|
||||
if stmt == nil {
|
||||
return
|
||||
@@ -487,6 +546,8 @@ var rtlFunctions = map[string]bool{
|
||||
"DBRECALL": true, "DBCOMMIT": true, "DBRLOCK": true, "DBRUNLOCK": true,
|
||||
"DBSEEK": true, "DBSELECTAREA": true, "DBPACK": true, "DBZAP": true,
|
||||
"DBCREATE": true, "DBINFO": true, "DBORDERINFO": true, "DBSETINDEX": true,
|
||||
// FiveSql2 hybrid hot-path RTL (pcode + Go-native scan)
|
||||
"PCCOMPILE": true, "PCEVAL": true, "SQLSCAN": true,
|
||||
"RECALL": true, "PACK": true, "ZAP": true,
|
||||
"FLOCK": true, "DBUNLOCK": true,
|
||||
"__DBPACK": true, "__DBZAP": true,
|
||||
|
||||
Reference in New Issue
Block a user