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

forst-lang / forst / 28759162038

05 Jul 2026 11:46PM UTC coverage: 79.096% (-0.09%) from 79.183%
28759162038

push

github

haveyaseen
fix: infer nested inline shapes and emit typed array literals

Register concrete hash types when Match constraints merge nested `{ ... }`
fields and `Array({ ... })` element shapes, including cases where nested
shapes only lived on Type assertions. Accept array literals in Value
constraints when a contextual field type is available, and persist nested
shape structure on parsed shape-type fields.

During Go codegen, pass expected field types into shape and array literal
lowering so composite literals use the inferred element type (including
arrays of inline shapes). Emit qualified sibling Forst types as
importLocal.TypeName in generated Go. Re-enable anonymous_objects.ft in the
examples pipeline and align tests with ArrayLiteralNode elements as
[]ExpressionNode.

fix(ast)!: change ArrayLiteralNode.Value to []ExpressionNode

BREAKING CHANGE: Manual AST builders and walkers must use
[]ExpressionNode for array literal elements instead of []LiteralNode.

feat(parser)!: parse Ok and Err as dedicated expression nodes

BREAKING CHANGE: Ok(...) and Err(...) become OkExprNode and ErrExprNode rather
than FunctionCallNode. They remain invalid as ordinary value expressions
outside supported lowering contexts.

feat(parser)!: add postfix method calls and reject expression field access

BREAKING CHANGE: recv.method(args) is supported; dotted field access on
expression receivers is rejected in favor of methods only.

feat(parser)!: add package-level var declarations

BREAKING CHANGE: Top-level var is now parsed and typechecked.

feat(parser)!: add typed composite array literals and slice conversions

BREAKING CHANGE: Go-style []T{...} array literals, Int(x) conversion calls,
and []T(expr) slice conversions such as []byte(s) are now part of the
grammar.

fix(parser)!: treat pkg.Type parameters as qualified types, not assertions

BREAKING CHANGE: Function parameters written as pkg.Type are typed references,
not shape-guard assertion chains.

113 of 141 new or added lines in 9 files covered. (80.14%)

349 existing lines in 13 files now uncovered.

27221 of 34415 relevant lines covered (79.1%)

134.23 hits per line

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

95.0
/forst/internal/typechecker/infer_function_node.go
1
package typechecker
2

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

6
        logrus "github.com/sirupsen/logrus"
7
)
8

9
func (tc *TypeChecker) inferFunctionNode(functionNode ast.FunctionNode) ([]ast.TypeNode, error) {
1,018✔
10
        prevFn := tc.currentFunction
1,018✔
11
        tc.currentFunction = &functionNode
1,018✔
12
        prevErrBranchDepth := tc.resultErrIfBranchDepth
1,018✔
13
        tc.resultErrIfBranchDepth = 0
1,018✔
14
        defer func() {
2,036✔
15
                tc.currentFunction = prevFn
1,018✔
16
                tc.resultErrIfBranchDepth = prevErrBranchDepth
1,018✔
17
        }()
1,018✔
18

19
        tc.log.WithFields(logrus.Fields{
1,018✔
20
                "function": "inferNodeType",
1,018✔
21
                "fn":       functionNode.Ident.ID,
1,018✔
22
                "phase":    "ENTER",
1,018✔
23
        }).Debug("Function node type inference")
1,018✔
24

1,018✔
25
        if err := tc.RestoreScope(functionNode); err != nil {
1,018✔
26
                return nil, err
×
27
        }
×
28
        tc.log.WithFields(logrus.Fields{
1,018✔
29
                "function": "inferNodeType",
1,018✔
30
                "fn":       functionNode.Ident.ID,
1,018✔
31
        }).Debug("Restored function scope")
1,018✔
32

1,018✔
33
        for _, param := range functionNode.Params {
1,187✔
34
                switch typedParam := param.(type) {
169✔
35
                case ast.SimpleParamNode:
168✔
36
                        tc.scopeStack.currentScope().RegisterSymbol(
168✔
37
                                typedParam.Ident.ID,
168✔
38
                                []ast.TypeNode{typedParam.Type},
168✔
39
                                SymbolVariable)
168✔
40
                        tc.bindVariableGoTypeFromParamType(typedParam.Ident.ID, typedParam.Type)
168✔
41
                case ast.DestructuredParamNode:
1✔
42
                        tc.registerDestructuredParamSymbols(typedParam.Fields, typedParam.Type, SymbolVariable)
1✔
43
                }
44
        }
45
        tc.DebugPrintCurrentScope()
1,018✔
46

1,018✔
47
        params := make([]ast.Node, len(functionNode.Params))
1,018✔
48
        for index, param := range functionNode.Params {
1,187✔
49
                params[index] = param
169✔
50
        }
169✔
51

52
        paramTypes, err := tc.inferNodeTypes(params, functionNode)
1,018✔
53
        if err != nil {
1,018✔
54
                return nil, err
×
UNCOV
55
        }
×
56

57
        for index, inferredParamTypes := range paramTypes {
1,187✔
58
                param := functionNode.Params[index]
169✔
59
                tc.log.WithFields(logrus.Fields{
169✔
60
                        "paramTypes": inferredParamTypes,
169✔
61
                        "param":      param.GetIdent(),
169✔
62
                        "function":   "inferNodeType",
169✔
63
                }).Trace("Storing param variable type")
169✔
64

169✔
65
                switch p := param.(type) {
169✔
66
                case ast.SimpleParamNode:
168✔
67
                        tc.scopeStack.currentScope().RegisterSymbol(
168✔
68
                                p.Ident.ID,
168✔
69
                                inferredParamTypes,
168✔
70
                                SymbolVariable)
168✔
71
                        if len(inferredParamTypes) > 0 {
336✔
72
                                tc.VariableTypes[p.Ident.ID] = append([]ast.TypeNode(nil), inferredParamTypes...)
168✔
73
                        }
168✔
74
                case ast.DestructuredParamNode:
1✔
75
                        if shapeFields, ok := tc.ShapeFieldsFromParamType(p.Type); ok {
2✔
76
                                for _, fieldName := range p.Fields {
3✔
77
                                        sf, ok := shapeFields[fieldName]
2✔
78
                                        if !ok {
2✔
UNCOV
79
                                                continue
×
80
                                        }
81
                                        if tn, ok := ShapeFieldTypeNode(sf); ok {
4✔
82
                                                fieldTypes := []ast.TypeNode{tn}
2✔
83
                                                tc.scopeStack.currentScope().RegisterSymbol(
2✔
84
                                                        ast.Identifier(fieldName),
2✔
85
                                                        fieldTypes,
2✔
86
                                                        SymbolVariable)
2✔
87
                                                tc.VariableTypes[ast.Identifier(fieldName)] = append([]ast.TypeNode(nil), fieldTypes...)
2✔
88
                                        }
2✔
89
                                }
90
                        }
91
                }
92
        }
93

94
        if signature, ok := tc.Functions[functionNode.Ident.ID]; ok {
2,003✔
95
                for index := range signature.Parameters {
1,131✔
96
                        if index < len(paramTypes) && len(paramTypes[index]) >= 1 {
292✔
97
                                signature.Parameters[index].Type = paramTypes[index][0]
146✔
98
                        }
146✔
99
                }
100
        }
101

102
        for _, bodyNode := range functionNode.Body {
2,845✔
103
                if _, err := tc.inferNodeType(bodyNode); err != nil {
1,864✔
104
                        return nil, err
37✔
105
                }
37✔
106
        }
107

108
        inferredType, err := tc.inferFunctionReturnType(functionNode)
981✔
109
        if err != nil {
982✔
110
                return nil, err
1✔
111
        }
1✔
112
        tc.storeInferredFunctionReturnType(&functionNode, inferredType)
980✔
113
        if err := tc.validateInferredReceiverMethodReturn(functionNode, inferredType); err != nil {
981✔
114
                return nil, err
1✔
115
        }
1✔
116
        tc.popScope()
979✔
117

979✔
118
        tc.log.WithFields(logrus.Fields{
979✔
119
                "function": "inferNodeType",
979✔
120
                "fn":       functionNode.Ident.ID,
979✔
121
                "phase":    "EXIT",
979✔
122
        }).Debug("Function node type inference")
979✔
123

979✔
124
        return inferredType, nil
979✔
125
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc