feat(parser): implicit class binding for standalone METHOD bodies

Classic Clipper/Harbour form writes method implementations as bare
`METHOD Name(params)` statements following a `CLASS X ... ENDCLASS`
declaration, with the binding inferred from the most recent class:

  CREATE CLASS Shape
     METHOD Area
  ENDCLASS

  METHOD Area             -- binds to Shape
     RETURN 0

Five was requiring `METHOD Area CLASS Shape` explicitly. Without it,
parseMethodDecl left MethodDecl.ClassName empty, gengo skipped the
body emission, and the link step failed with `undefined: HB_SHAPE_AREA`.
The class registration had AddMethod("AREA", HB_SHAPE_AREA) pointing
at the missing symbol.

Parser tracks p.lastClassName at parseClassDecl, and parseMethodDecl
falls back to that value when no CLASS clause is supplied. Each new
CLASS declaration updates the tracker, so multi-class files still
dispatch correctly — verified with /tmp/test_implicit_class.prg
(Shape + Box both resolve their own Name/Area methods).

Unblocks harbour-core/tests/clsscope.prg and other OOP compat
tests that use this form. 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 16:52:23 +09:00
parent 2d82541d3d
commit 0a5482b6aa

View File

@@ -32,6 +32,12 @@ type Parser struct {
errors []Error
file string
GoDumps []string // inline Go code blocks from PP
// lastClassName tracks the most recent `CLASS X` / `CREATE CLASS X`
// declaration at file scope. Standalone `METHOD foo(...)` decls
// without an explicit `CLASS Y` clause default to this name —
// matches classic Clipper/Harbour where method bodies follow the
// class declaration and bind implicitly.
lastClassName string
}
// Error represents a parse error with position.
@@ -522,6 +528,7 @@ func (p *Parser) parseVarDecl() *ast.VarDecl {
func (p *Parser) parseClassDecl() *ast.ClassDecl {
classPos := p.expect(token.CLASS).Pos
name := p.expect(token.IDENT).Literal
p.lastClassName = name
var parent string
if p.match(token.INHERIT) {
@@ -1020,6 +1027,12 @@ func (p *Parser) parseMethodDecl() *ast.MethodDecl {
var className string
if p.match(token.CLASS) {
className = p.expect(token.IDENT).Literal
} else if p.lastClassName != "" {
// Implicit binding to the most recent `CREATE CLASS` / `CLASS`
// declaration — classic Clipper/Harbour form. Real code uses
// `CREATE CLASS CLSX ... ENDCLASS` followed by a bare
// `METHOD TestX() ... RETURN Self` without restating the class.
className = p.lastClassName
}
p.expectEndOfStmt()