• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

forst-lang / forst / 16034193780

02 Jul 2025 07:37PM UTC coverage: 37.619% (+0.3%) from 37.308%
16034193780

Pull #17

github

haveyaseen
fix(typechecker): implement proper nested field path traversal for shape guards

The typechecker was failing to handle nested field access with dot notation (e.g., op.input.name) in shape guards and other contexts. The root cause was that field lookup functions were not properly handling multi-segment field names and were incorrectly recursing into nested shapes for single-segment lookups.

This commit adds new path-aware field lookup functions (lookupFieldPath, lookupFieldPathOnShape, lookupFieldPathOnMergedFields) that properly handle dot notation by splitting field names and recursively traversing the path. The existing field lookup functions have been updated to use these new functions where appropriate, ensuring consistent behavior across type definitions, assertions, and variable lookups.

The fix specifically addresses the bug where looking up a field like "input" in a shape containing {input: {name: String}} would incorrectly try to find "input" in the nested shape instead of returning the shape type. For multi-segment paths like "input.name", the new logic properly splits the path and recurses into the nested shape to find "name".

All typechecker tests now pass, confirming that nested field access works correctly for shape guards and other use cases. The shape guard feature can now properly handle expressions like op.input.name where op is a shape with nested fields.
Pull Request #17: feat: Shape guards

1757 of 4800 new or added lines in 83 files covered. (36.6%)

89 existing lines in 29 files now uncovered.

3081 of 8190 relevant lines covered (37.62%)

6.35 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

59.26
/forst/internal/parser/scope_stack.go
1
package parser
2

3
import (
4
        "github.com/sirupsen/logrus"
5
)
6

7
// ScopeStack manages a stack of scopes during type checking
8
type ScopeStack struct {
9
        scopes  []*Scope
10
        current *Scope
11
        log     *logrus.Logger
12
}
13

14
// NewScopeStack creates a new stack with a global scope
15
func NewScopeStack(log *logrus.Logger) *ScopeStack {
63✔
16
        globalScope := NewScope("", true, false, log)
63✔
17
        scopes := []*Scope{globalScope}
63✔
18
        return &ScopeStack{
63✔
19
                scopes:  scopes,
63✔
20
                current: globalScope,
63✔
21
                log:     log,
63✔
22
        }
63✔
23
}
63✔
24

25
// PushScope creates and pushes a new scope for the given AST node
26
func (ss *ScopeStack) PushScope(scope *Scope) {
31✔
27
        if scope.IsGlobal {
31✔
NEW
28
                ss.log.Fatalf("Cannot push global scope")
×
NEW
29
        }
×
30
        ss.scopes = append(ss.scopes, scope)
31✔
31
        ss.current = scope
31✔
32
}
33

NEW
34
func (ss *ScopeStack) PopScope() {
×
NEW
35
        if ss.current.IsGlobal {
×
NEW
36
                ss.log.Fatalf("Cannot pop global scope")
×
NEW
37
        }
×
NEW
38
        ss.scopes = ss.scopes[:len(ss.scopes)-1]
×
NEW
39
        ss.current = ss.scopes[len(ss.scopes)-1]
×
40
}
41

42
func (ss *ScopeStack) CurrentScope() *Scope {
30✔
43
        return ss.current
30✔
44
}
30✔
45

NEW
46
func (ss *ScopeStack) GlobalScope() *Scope {
×
NEW
47
        return ss.scopes[0]
×
NEW
48
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc