Introduce typed semantic analysis pipeline

This commit is contained in:
pavel 2026-08-27 19:28:24 +02:00
commit f4cd4f4458
30 changed files with 2079 additions and 2381 deletions

231
internal/lang/types.go Normal file
View file

@ -0,0 +1,231 @@
package lang
import (
"fmt"
"strings"
)
type Type interface {
typeNode()
String() string
}
type UnknownType struct{}
func (UnknownType) typeNode() {}
func (UnknownType) String() string { return "<unknown>" }
type NamedType struct{ Name string }
func (NamedType) typeNode() {}
func (t NamedType) String() string { return t.Name }
type ClassType struct{ Class *ClassSymbol }
func (ClassType) typeNode() {}
func (t ClassType) String() string { return t.Class.Name }
type NullableType struct{ Element Type }
func (NullableType) typeNode() {}
func (t NullableType) String() string { return t.Element.String() + "?" }
type GoPointerType struct{ Element Type }
func (GoPointerType) typeNode() {}
func (t GoPointerType) String() string { return "*" + t.Element.String() }
type FunctionType struct {
Params []Type
Result Type
}
func (FunctionType) typeNode() {}
func (t FunctionType) String() string {
params := make([]string, 0, len(t.Params))
for _, param := range t.Params {
params = append(params, param.String())
}
return "(" + strings.Join(params, ", ") + ") -> " + t.Result.String()
}
type GenericType struct {
Base Type
Args []Type
}
func (GenericType) typeNode() {}
func (t GenericType) String() string {
args := make([]string, 0, len(t.Args))
for _, arg := range t.Args {
args = append(args, arg.String())
}
return t.Base.String() + "<" + strings.Join(args, ", ") + ">"
}
func ParseType(text string) (Type, error) {
text = strings.TrimSpace(text)
if text == "" {
return UnknownType{}, nil
}
if strings.HasSuffix(text, "?") {
element, err := ParseType(strings.TrimSpace(strings.TrimSuffix(text, "?")))
if err != nil {
return nil, err
}
return NullableType{Element: element}, nil
}
if strings.HasPrefix(text, "*") {
element, err := ParseType(strings.TrimSpace(strings.TrimPrefix(text, "*")))
if err != nil {
return nil, err
}
return GoPointerType{Element: element}, nil
}
if params, result, ok := parseFunctionType(text); ok {
resolvedParams := make([]Type, 0, len(params))
for _, param := range params {
resolved, err := ParseType(param)
if err != nil {
return nil, err
}
resolvedParams = append(resolvedParams, resolved)
}
resolvedResult, err := ParseType(result)
if err != nil {
return nil, err
}
return FunctionType{Params: resolvedParams, Result: resolvedResult}, nil
}
if base, args, ok := parseGenericType(text); ok {
resolvedBase, err := ParseType(base)
if err != nil {
return nil, err
}
resolvedArgs := make([]Type, 0, len(args))
for _, arg := range args {
resolved, err := ParseType(arg)
if err != nil {
return nil, err
}
resolvedArgs = append(resolvedArgs, resolved)
}
return GenericType{Base: resolvedBase, Args: resolvedArgs}, nil
}
if strings.ContainsAny(text, "<>?()") {
return nil, fmt.Errorf("invalid type %q", text)
}
return NamedType{Name: text}, nil
}
func resolveClassTypes(typ Type, classes map[string]*ClassSymbol) Type {
switch value := typ.(type) {
case NamedType:
if class, ok := classes[value.Name]; ok {
return ClassType{Class: class}
}
return value
case NullableType:
return NullableType{Element: resolveClassTypes(value.Element, classes)}
case GoPointerType:
return GoPointerType{Element: resolveClassTypes(value.Element, classes)}
case FunctionType:
params := make([]Type, len(value.Params))
for i, param := range value.Params {
params[i] = resolveClassTypes(param, classes)
}
return FunctionType{Params: params, Result: resolveClassTypes(value.Result, classes)}
case GenericType:
args := make([]Type, len(value.Args))
for i, arg := range value.Args {
args[i] = resolveClassTypes(arg, classes)
}
return GenericType{Base: resolveClassTypes(value.Base, classes), Args: args}
default:
return typ
}
}
func typeEqual(left, right Type) bool { return left.String() == right.String() }
func renderGoType(typ Type) string {
switch value := typ.(type) {
case UnknownType:
return ""
case ClassType:
return "*" + value.Class.Name
case NullableType:
element := renderGoType(value.Element)
switch value.Element.(type) {
case ClassType, GoPointerType:
return element
}
if element == "error" || element == "any" {
return element
}
return "*" + element
case GoPointerType:
return "*" + renderGoType(value.Element)
case FunctionType:
params := make([]string, len(value.Params))
for i, param := range value.Params {
params[i] = renderGoType(param)
}
result := renderGoType(value.Result)
if result == "" {
return "func(" + strings.Join(params, ", ") + ")"
}
return "func(" + strings.Join(params, ", ") + ") " + result
case GenericType:
base := value.Base.String()
args := make([]string, len(value.Args))
for i, arg := range value.Args {
args[i] = renderGoType(arg)
}
switch base {
case "List", "MutableList":
return "[]" + args[0]
case "Map", "MutableMap":
return "map[" + args[0] + "]" + args[1]
case "Channel":
return "chan " + args[0]
case "Result":
if args[0] == "" {
args[0] = "struct{}"
}
return "GotlinResult[" + args[0] + "]"
case "GotlinSQLIterator":
argument := args[0]
if class, ok := value.Args[0].(ClassType); ok {
argument = class.Class.Name
}
return "*GotlinSQLIterator[" + argument + "]"
}
return base + "[" + strings.Join(args, ", ") + "]"
case NamedType:
switch value.Name {
case "Int":
return "int"
case "Long":
return "int64"
case "Float", "Double":
return "float64"
case "String":
return "string"
case "Any":
return "any"
case "ByteSlice":
return "[]byte"
case "Boolean":
return "bool"
case "Unit":
return ""
case "Error":
return "error"
default:
return value.Name
}
default:
return ""
}
}