Skip to content

Commit fa7ebdd

Browse files
authored
feat: add support for hidden bindings, transitive capture propagation, and UDF metadata improvements (#975)
1 parent 0419fe9 commit fa7ebdd

22 files changed

Lines changed: 719 additions & 96 deletions

pkg/compiler/debug_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,48 @@ RETURN (
129129
}
130130
}
131131

132+
func TestDebugInfoHidesForwardingOnlyCaptures(t *testing.T) {
133+
program, err := New(WithDebugInfo()).Compile(source.New("hidden_capture.fql", `
134+
LET base = 10
135+
FUNC target(value) => base + value
136+
FUNC forward(value) => target(value)
137+
RETURN forward(1)
138+
`))
139+
if err != nil {
140+
t.Fatal(err)
141+
}
142+
143+
forwardID := -1
144+
for id, fn := range program.Functions.UserDefined {
145+
if fn.Name == "forward" {
146+
forwardID = id
147+
break
148+
}
149+
}
150+
151+
if forwardID < 0 {
152+
t.Fatal("expected forward UDF metadata")
153+
}
154+
155+
found := false
156+
for _, point := range program.Metadata.DebugPoints {
157+
if point.FunctionID != forwardID {
158+
continue
159+
}
160+
161+
found = true
162+
for _, binding := range point.Bindings {
163+
if binding.Name == "base" {
164+
t.Fatalf("forwarding-only capture leaked into debugger bindings: %#v", point.Bindings)
165+
}
166+
}
167+
}
168+
169+
if !found {
170+
t.Fatal("expected forward UDF debug point")
171+
}
172+
}
173+
132174
func TestDebugInfoPreservesLexicalShadowing(t *testing.T) {
133175
program, err := New(WithDebugInfo()).Compile(source.New("shadow.fql", `LET x = 1
134176
RETURN (
Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package internal
22

3-
import "github.com/antlr4-go/antlr/v4"
3+
import (
4+
"github.com/antlr4-go/antlr/v4"
5+
6+
"github.com/MontFerret/ferret/v2/pkg/compiler/internal/core"
7+
)
48

59
type captureBindingInfo struct {
610
Decl antlr.ParserRuleContext
711
Name string
12+
ID core.BindingID
813
Mutable bool
914
}

pkg/compiler/internal/binding_compiler.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ func (c *BindingCompiler) CompileVariableDeclaration(ctx fql.IVariableDeclaratio
7979
}
8080

8181
opts := core.BindingOptions{
82+
ID: bindingIDFromRule(decl),
8283
Mutable: mutable,
8384
Storage: storage,
8485
}
@@ -228,6 +229,7 @@ func (c *BindingCompiler) captureBindingForDeclaration(ctx fql.IVariableDeclarat
228229
Name: c.declarationName(ctx),
229230
Mutable: c.isMutableDeclaration(ctx),
230231
Decl: ctx.(antlr.ParserRuleContext),
232+
ID: bindingIDFromRule(ctx.(antlr.ParserRuleContext)),
231233
}
232234
}
233235

pkg/compiler/internal/call_resolver.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,21 @@ func (r *CallResolver) ResolveUDF(ctx fql.IFunctionCallContext) (*core.UDFInfo,
8888
return nil, false
8989
}
9090

91+
return r.ResolveUDFInScope(ctx, r.session.Function.UDFScope)
92+
}
93+
94+
// ResolveUDFInScope resolves an unqualified source call against an explicit lexical UDF scope.
95+
func (r *CallResolver) ResolveUDFInScope(ctx fql.IFunctionCallContext, scope *core.UDFScope) (*core.UDFInfo, bool) {
96+
if r == nil || r.session == nil || r.session.Program.UDFs == nil || scope == nil {
97+
return nil, false
98+
}
99+
91100
name, ok := r.ResolveLocalFunctionName(ctx)
92101
if !ok {
93102
return nil, false
94103
}
95104

96-
return r.session.Program.UDFs.Resolve(name, r.session.Function.UDFScope)
105+
return r.session.Program.UDFs.Resolve(name, scope)
97106
}
98107

99108
func (r *CallResolver) applyNamespaceAlias(ns string) string {

0 commit comments

Comments
 (0)