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

forst-lang / forst / 16012315797

01 Jul 2025 11:25PM UTC coverage: 37.792% (+0.5%) from 37.308%
16012315797

Pull #17

github

haveyaseen
tests: Increase parser test coverage
Pull Request #17: feat: Shape guards

1666 of 4519 new or added lines in 83 files covered. (36.87%)

89 existing lines in 29 files now uncovered.

2989 of 7909 relevant lines covered (37.79%)

6.56 hits per line

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

44.26
/forst/internal/parser/function.go
1
package parser
2

3
import (
4
        "forst/internal/ast"
5
)
6

UNCOV
7
func (p *Parser) parseParameterType() ast.TypeNode {
×
UNCOV
8
        next := p.peek()
×
UNCOV
9
        // TODO: Even if the next token is a dot this could be a type without any constraints
×
UNCOV
10
        if next.Type == ast.TokenDot || next.Type == ast.TokenLParen {
×
11
                assertion := p.parseAssertionChain(true)
×
12
                return ast.TypeNode{
×
13
                        Ident:     ast.TypeAssertion,
×
14
                        Assertion: &assertion,
×
15
                }
×
16
        }
×
17
        // Disallow Shape({...}) wrapper for shape types
NEW
18
        if p.current().Type == ast.TokenIdentifier && p.current().Value == "Shape" {
×
NEW
19
                ident := p.expect(ast.TokenIdentifier)
×
NEW
20
                if p.current().Type == ast.TokenLParen {
×
NEW
21
                        p.FailWithParseError(p.current(), "Shape({...}) wrapper is not allowed. Use {...} directly for shape types.")
×
NEW
22
                }
×
NEW
23
                return ast.TypeNode{
×
NEW
24
                        Ident: ast.TypeIdent(ident.Value),
×
NEW
25
                }
×
26
        }
27
        // Allow direct {...} for shape types
NEW
28
        if p.current().Type == ast.TokenLBrace {
×
NEW
29
                shape := p.parseShape(nil)
×
NEW
30
                baseType := ast.TypeIdent(ast.TypeShape)
×
NEW
31
                return ast.TypeNode{
×
NEW
32
                        Ident: ast.TypeShape,
×
NEW
33
                        Assertion: &ast.AssertionNode{
×
NEW
34
                                BaseType: &baseType,
×
NEW
35
                                Constraints: []ast.ConstraintNode{{
×
NEW
36
                                        Name: "Match",
×
NEW
37
                                        Args: []ast.ConstraintArgumentNode{{
×
NEW
38
                                                Shape: &shape,
×
NEW
39
                                        }},
×
NEW
40
                                }},
×
NEW
41
                        },
×
NEW
42
                }
×
NEW
43
        }
×
UNCOV
44
        return p.parseType(TypeIdentOpts{AllowLowercaseTypes: false})
×
45
}
46

47
func (p *Parser) parseDestructuredParameter() ast.ParamNode {
×
48
        p.expect(ast.TokenLBrace)
×
49
        fields := []string{}
×
50

×
51
        // Parse fields until we hit closing brace
×
52
        for p.current().Type != ast.TokenRBrace {
×
53
                name := p.expect(ast.TokenIdentifier)
×
54
                fields = append(fields, name.Value)
×
55

×
56
                // Handle comma between fields
×
57
                if p.current().Type == ast.TokenComma {
×
58
                        p.advance()
×
59
                }
×
60
        }
61

62
        p.expect(ast.TokenRBrace)
×
63
        p.expect(ast.TokenColon)
×
64

×
65
        paramType := p.parseParameterType()
×
66

×
67
        return ast.DestructuredParamNode{
×
68
                Fields: fields,
×
69
                Type:   paramType,
×
70
        }
×
71
}
72

73
func (p *Parser) parseSimpleParameter() ast.ParamNode {
4✔
74
        name := p.expect(ast.TokenIdentifier).Value
4✔
75
        // Remove colon requirement - use Go-style parameter declarations
4✔
76

4✔
77
        tok := p.current()
4✔
78
        if tok.Type == ast.TokenIdentifier && (p.peek().Type == ast.TokenDot || p.peek().Type == ast.TokenLParen) {
4✔
NEW
79
                assertion := p.parseAssertionChain(true)
×
NEW
80
                return ast.SimpleParamNode{
×
NEW
81
                        Ident: ast.Ident{ID: ast.Identifier(name)},
×
NEW
82
                        Type: ast.TypeNode{
×
NEW
83
                                Ident:     ast.TypeAssertion,
×
NEW
84
                                Assertion: &assertion,
×
NEW
85
                        },
×
NEW
86
                }
×
NEW
87
        }
×
88

89
        if tok.Type == ast.TokenIdentifier && tok.Value == "Shape" {
4✔
NEW
90
                // Check if this is Shape({...})
×
NEW
91
                if p.peek().Type == ast.TokenLParen {
×
NEW
92
                        p.FailWithParseError(tok, "Direct usage of Shape({...}) is not allowed. Use a shape type directly, e.g. { field: Type }.")
×
NEW
93
                }
×
94
                // Allow direct usage of Shape as a type name
UNCOV
95
                p.advance()
×
NEW
96
                typeIdent := ast.TypeIdent("Shape")
×
NEW
97
                return ast.SimpleParamNode{
×
NEW
98
                        Ident: ast.Ident{ID: ast.Identifier(name)},
×
NEW
99
                        Type:  ast.TypeNode{Ident: typeIdent},
×
NEW
100
                }
×
101
        }
102
        if tok.Type == ast.TokenLBrace {
4✔
NEW
103
                shape := p.parseShape(nil)
×
NEW
104
                baseType := ast.TypeIdent(ast.TypeShape)
×
NEW
105
                return ast.SimpleParamNode{
×
NEW
106
                        Ident: ast.Ident{ID: ast.Identifier(name)},
×
NEW
107
                        Type: ast.TypeNode{
×
NEW
108
                                Ident: ast.TypeShape,
×
NEW
109
                                Assertion: &ast.AssertionNode{
×
NEW
110
                                        BaseType: &baseType,
×
NEW
111
                                        Constraints: []ast.ConstraintNode{{
×
NEW
112
                                                Name: "Match",
×
NEW
113
                                                Args: []ast.ConstraintArgumentNode{{
×
NEW
114
                                                        Shape: &shape,
×
NEW
115
                                                }},
×
NEW
116
                                        }},
×
NEW
117
                                },
×
NEW
118
                        },
×
NEW
119
                }
×
UNCOV
120
        }
×
121
        // Parse the type, which may include dots (e.g. AppMutation.Input)
122
        typ := p.parseType(TypeIdentOpts{AllowLowercaseTypes: false})
4✔
123
        p.logParsedNodeWithMessage(typ, "Parsed parameter type, next token: "+p.current().Type.String()+" ("+p.current().Value+")")
4✔
124
        return ast.SimpleParamNode{
4✔
125
                Ident: ast.Ident{ID: ast.Identifier(name)},
4✔
126
                Type:  typ,
4✔
127
        }
4✔
128
}
129

130
func (p *Parser) parseParameter() ast.ParamNode {
4✔
131
        switch p.current().Type {
4✔
132
        case ast.TokenIdentifier:
4✔
133
                return p.parseSimpleParameter()
4✔
134
        case ast.TokenLBrace:
×
135
                return p.parseDestructuredParameter()
×
136
        default:
×
NEW
137
                p.FailWithParseError(p.current(), "Expected parameter")
×
NEW
138
                panic("Reached unreachable path")
×
139
        }
140
}
141

142
// Parse function parameters
143
func (p *Parser) parseFunctionSignature() []ast.ParamNode {
30✔
144
        p.expect(ast.TokenLParen)
30✔
145
        params := []ast.ParamNode{}
30✔
146

30✔
147
        // Handle empty parameter list
30✔
148
        if p.current().Type == ast.TokenRParen {
57✔
149
                p.advance()
27✔
150
                return params
27✔
151
        }
27✔
152

153
        // Parse parameters
154
        for {
6✔
155
                param := p.parseParameter()
3✔
156
                switch param.(type) {
3✔
157
                case ast.DestructuredParamNode:
×
NEW
158
                        p.logParsedNodeWithMessage(param, "Parsed destructured function param")
×
159
                default:
3✔
160
                        p.logParsedNodeWithMessage(param, "Parsed function param")
3✔
161
                }
162
                params = append(params, param)
3✔
163

3✔
164
                // Check if there are more parameters
3✔
165
                if p.current().Type == ast.TokenComma {
3✔
166
                        p.advance()
×
167
                } else {
3✔
168
                        break
3✔
169
                }
170
        }
171

172
        p.expect(ast.TokenRParen)
3✔
173
        return params
3✔
174
}
175

176
func (p *Parser) parseReturnType() []ast.TypeNode {
30✔
177
        returnType := []ast.TypeNode{}
30✔
178
        if p.current().Type == ast.TokenColon {
37✔
179
                p.advance() // Consume the colon
7✔
180
                returnType = append(returnType, p.parseType(TypeIdentOpts{AllowLowercaseTypes: false}))
7✔
181
        }
7✔
182
        return returnType
30✔
183
}
184

185
func (p *Parser) parseReturnStatement() ast.ReturnNode {
21✔
186
        p.advance() // Move past `return`
21✔
187

21✔
188
        returnExpression := p.parseExpression()
21✔
189

21✔
190
        return ast.ReturnNode{
21✔
191
                Value: returnExpression,
21✔
192
                Type:  ast.TypeNode{Ident: ast.TypeImplicit},
21✔
193
        }
21✔
194
}
21✔
195

196
func (p *Parser) parseFunctionBody() []ast.Node {
30✔
197
        return p.parseBlock()
30✔
198
}
30✔
199

200
// Parse a function definition
201
func (p *Parser) parseFunctionDefinition() ast.FunctionNode {
30✔
202
        p.expect(ast.TokenFunc)               // Expect `fn`
30✔
203
        name := p.expect(ast.TokenIdentifier) // Function name
30✔
204

30✔
205
        p.context.ScopeStack.CurrentScope().FunctionName = name.Value
30✔
206

30✔
207
        params := p.parseFunctionSignature() // Parse function parameters
30✔
208

30✔
209
        returnType := p.parseReturnType()
30✔
210

30✔
211
        body := p.parseFunctionBody()
30✔
212

30✔
213
        node := ast.FunctionNode{
30✔
214
                Ident:       ast.Ident{ID: ast.Identifier(name.Value)},
30✔
215
                ReturnTypes: returnType,
30✔
216
                Params:      params,
30✔
217
                Body:        body,
30✔
218
        }
30✔
219

30✔
220
        return node
30✔
221
}
30✔
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