Move LSP semantics into typed analysis

This commit is contained in:
pavel 2026-08-27 21:28:11 +02:00
commit bac1183593
26 changed files with 1232 additions and 637 deletions

View file

@ -10,7 +10,10 @@ type Program struct {
Embeds []EmbedDecl
}
type EmbedDecl struct{ Path, Name, Type string }
type EmbedDecl struct {
Path, Name, Type string
TypeRef TypeRef
}
type EnumDecl struct {
Name string
@ -20,6 +23,7 @@ type EnumDecl struct {
type EnumVariant struct {
Name string
PayloadTypes []string
PayloadRefs []TypeRef
StringValue string
}
@ -35,6 +39,7 @@ type InterfaceDecl struct {
type ClassDecl struct {
Name string
TypeParams []string
Data bool
JSONNaming string
Table string
@ -48,6 +53,7 @@ type FieldDecl struct {
Private bool
Name string
Type string
TypeRef TypeRef
Column string
ID bool
Generated bool
@ -55,22 +61,27 @@ type FieldDecl struct {
type FunctionSignature struct {
Name string
TypeParams []string
Params []Param
ReturnType string
ReturnRef TypeRef
Suspend bool
}
type FunctionDecl struct {
Name string
TypeParams []string
Params []Param
ReturnType string
ReturnRef TypeRef
Body []Stmt
Suspend bool
}
type Param struct {
Name string
Type string
Name string
Type string
TypeRef TypeRef
}
type Stmt interface {
@ -84,7 +95,9 @@ type Expr interface {
type VarDecl struct {
Mutable bool
Name string
Pos int
Type string
TypeRef TypeRef
Value Expr
}
@ -173,6 +186,7 @@ type TryCatchStmt struct {
TryBody []Stmt
CatchName string
CatchType string
CatchRef TypeRef
CatchBody []Stmt
}
@ -208,6 +222,7 @@ type MatchExprCase struct {
type IdentExpr struct {
Meta ExprMeta
Name string
Pos int
}
func (IdentExpr) exprNode() {}
@ -280,6 +295,7 @@ type SelectorExpr struct {
Meta ExprMeta
Receiver Expr
Name string
NamePos int
}
func (SelectorExpr) exprNode() {}
@ -288,6 +304,7 @@ type SafeSelectorExpr struct {
Meta ExprMeta
Receiver Expr
Name string
NamePos int
}
func (SafeSelectorExpr) exprNode() {}

View file

@ -0,0 +1,50 @@
package lang
import (
"fmt"
"regexp"
"strconv"
)
type DiagnosticSeverity int
const (
DiagnosticError DiagnosticSeverity = 1
DiagnosticWarning DiagnosticSeverity = 2
)
type SourceSpan struct {
Start int
End int
}
type SemanticDiagnostic struct {
Code string
Message string
Severity DiagnosticSeverity
Span SourceSpan
}
func (diagnostic SemanticDiagnostic) Error() string { return diagnostic.Message }
var semanticOffsetPattern = regexp.MustCompile(`\sat\s(\d+)$`)
func diagnosticForError(code string, err error) SemanticDiagnostic {
span := SourceSpan{}
if match := semanticOffsetPattern.FindStringSubmatch(err.Error()); len(match) == 2 {
if offset, parseErr := strconv.Atoi(match[1]); parseErr == nil {
span = SourceSpan{Start: offset, End: offset + 1}
}
}
return SemanticDiagnostic{Code: code, Message: err.Error(), Severity: DiagnosticError, Span: span}
}
func undefinedDiagnostic(name string, position int) SemanticDiagnostic {
end := position + len(name)
return SemanticDiagnostic{
Code: "undefined-symbol",
Message: fmt.Sprintf("undefined identifier %s", name),
Severity: DiagnosticError,
Span: SourceSpan{Start: position, End: end},
}
}

View file

@ -0,0 +1,37 @@
package lang
import "testing"
func TestAnalyzeProducesStructuredUndefinedDiagnostic(t *testing.T) {
source := `package demo fun main() { println(missing) }`
program, err := Parse(source)
if err != nil {
t.Fatal(err)
}
_, diagnostics := AnalyzeWithContext(program, nil)
if len(diagnostics) != 1 {
t.Fatalf("diagnostics = %#v", diagnostics)
}
diagnostic := diagnostics[0]
if diagnostic.Code != "undefined-symbol" || diagnostic.Severity != DiagnosticError || diagnostic.Message != "undefined identifier missing" {
t.Fatalf("unexpected diagnostic: %#v", diagnostic)
}
if got := source[diagnostic.Span.Start:diagnostic.Span.End]; got != "missing" {
t.Fatalf("diagnostic span = %q", got)
}
}
func TestAnalyzeUsesAdditionalPackagePrograms(t *testing.T) {
current, err := Parse(`package demo fun main() { helper() }`)
if err != nil {
t.Fatal(err)
}
sibling, err := Parse(`package demo fun helper() { println("ok") }`)
if err != nil {
t.Fatal(err)
}
_, diagnostics := AnalyzeWithContext(current, []*Program{sibling})
if len(diagnostics) != 0 {
t.Fatalf("package symbol was not resolved: %#v", diagnostics)
}
}

View file

@ -263,6 +263,7 @@ func (g *goGenerator) function(fn FunctionDecl) error {
}
g.write("func ")
g.write(fn.Name)
g.write(renderGoTypeParameters(fn.TypeParams))
g.write(g.renderGoFunctionParams(fn))
if ret := g.goReturnType(fn.ReturnType); ret != "" {
g.write(" ")
@ -346,7 +347,7 @@ func enumVariant(decl EnumDecl, name string) *EnumVariant {
}
func (g *goGenerator) classDecl(class ClassDecl) error {
g.line("type " + class.Name + " struct {")
g.line("type " + class.Name + renderGoTypeParameters(class.TypeParams) + " struct {")
g.indentLevel++
for _, field := range class.Fields {
name := field.Name
@ -366,6 +367,7 @@ func (g *goGenerator) classDecl(class ClassDecl) error {
g.line("")
g.write("func New")
g.write(class.Name)
g.write(renderGoTypeParameters(class.TypeParams))
g.write("(")
for i, field := range class.Fields {
if i > 0 {
@ -377,11 +379,13 @@ func (g *goGenerator) classDecl(class ClassDecl) error {
}
g.write(") *")
g.write(class.Name)
g.write(renderGoTypeArguments(class.TypeParams))
g.write(" {\n")
g.indentLevel++
g.writeIndent()
g.write("return &")
g.write(class.Name)
g.write(renderGoTypeArguments(class.TypeParams))
g.write("{")
for i, field := range class.Fields {
if i > 0 {
@ -427,6 +431,7 @@ func (g *goGenerator) method(class ClassDecl, fn FunctionDecl) error {
g.write("func (self *")
g.write(class.Name)
g.write(renderGoTypeArguments(class.TypeParams))
g.write(") ")
g.write(fn.Name)
g.write(g.renderGoFunctionParams(fn))
@ -792,8 +797,8 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
if e.Name == "this" {
return "self", nil
}
if g.classField(*g.currentClass, e.Name) && !g.isDefined(e.Name) {
return "self." + e.Name, nil
if field, found := classFieldByName(*g.currentClass, e.Name); found && !g.isDefined(e.Name) {
return "self." + mappingFieldName(*g.currentClass, field), nil
}
}
return e.Name, nil
@ -1189,7 +1194,7 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
}
if ident, ok := e.Callee.(IdentExpr); ok {
if _, ok := g.semantic.Classes[ident.Name]; ok && (resolvedCall == nil || resolvedCall.Meaning == ClassConstructionExpr) {
return fmt.Sprintf("New%s(%s)", ident.Name, strings.Join(args, ", ")), nil
return fmt.Sprintf("New%s%s(%s)", ident.Name, g.renderCallTypeArguments(e.TypeArgs), strings.Join(args, ", ")), nil
}
}
if ident, ok := e.Callee.(IdentExpr); ok {
@ -1217,6 +1222,9 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
if err != nil {
return "", err
}
if len(e.TypeArgs) > 0 {
callee += g.renderCallTypeArguments(e.TypeArgs)
}
call := fmt.Sprintf("%s(%s)", callee, strings.Join(args, ", "))
if result, ok := g.goErrorResult(e, call, expectedType); ok {
return result, nil
@ -2460,18 +2468,11 @@ func (g *goGenerator) isDefined(name string) bool {
return false
}
func (g *goGenerator) classField(class ClassDecl, name string) bool {
for _, field := range class.Fields {
if field.Name == name {
return true
}
}
return false
}
func (g *goGenerator) assignTarget(name string) string {
if g.currentClass != nil && g.classField(*g.currentClass, name) && !g.isDefined(name) {
return "self." + name
if g.currentClass != nil && !g.isDefined(name) {
if field, found := classFieldByName(*g.currentClass, name); found {
return "self." + mappingFieldName(*g.currentClass, field)
}
}
return name
}
@ -2496,6 +2497,35 @@ func (g *goGenerator) renderGoFunctionParams(function FunctionDecl) string {
return g.renderGoParamsWithPrefix(function.Params, prefix)
}
func renderGoTypeParameters(params []string) string {
if len(params) == 0 {
return ""
}
values := make([]string, len(params))
for index, param := range params {
values[index] = param + " any"
}
return "[" + strings.Join(values, ", ") + "]"
}
func renderGoTypeArguments(params []string) string {
if len(params) == 0 {
return ""
}
return "[" + strings.Join(params, ", ") + "]"
}
func (g *goGenerator) renderCallTypeArguments(args []string) string {
if len(args) == 0 {
return ""
}
values := make([]string, len(args))
for index, argument := range args {
values[index] = g.goType(argument)
}
return "[" + strings.Join(values, ", ") + "]"
}
func (g *goGenerator) renderGoParamsWithPrefix(params []Param, prefix string) string {
var b strings.Builder
b.WriteString("(")

View file

@ -0,0 +1,72 @@
package lang
import (
"strings"
"testing"
)
func TestGenerateGenericFunctionsAndClasses(t *testing.T) {
program, err := Parse(`package demo
data class Box<T>(var value: T) {
fun get(): T { return value }
}
fun identity<T>(value: T): T { return value }
fun wrap<T>(value: T): Box<T> { return Box(value) }
fun main() {
val inferred = identity(42)
val explicit = identity<String>("value")
val box = wrap("boxed")
println(box.get())
}`)
if err != nil {
t.Fatal(err)
}
output, err := GenerateGo(program)
if err != nil {
t.Fatal(err)
}
code := string(output)
for _, expected := range []string{
"type Box[T any] struct",
"func NewBox[T any](value T) *Box[T]",
"func (self *Box[T]) get() T",
"func identity[T any](value T) T",
"func wrap[T any](value T) *Box[T]",
"identity(42)",
`identity[string]("value")`,
`wrap("boxed")`,
} {
if !strings.Contains(code, expected) {
t.Fatalf("missing %q:\n%s", expected, code)
}
}
main := program.Functions[2]
inferred := main.Body[0].(VarDecl)
if got := ResolvedType(inferred.Value).String(); got != "Int" {
t.Fatalf("inferred generic type = %s", got)
}
box := main.Body[2].(VarDecl)
if got := ResolvedType(box.Value).String(); got != "Box<String>" {
t.Fatalf("generic class type = %s", got)
}
}
func TestRejectInvalidGenericDeclarationsAndCalls(t *testing.T) {
for _, test := range []struct{ source, message string }{
{`package demo fun identity<T, T>(value: T): T { return value }`, "duplicate type parameter T"},
{`package demo fun identity<T>(value: T): T { return value } fun main() { identity<String, Int>("x") }`, "expects 1 type arguments, got 2"},
{`package demo class Box<T>(val value: T) { fun convert<R>(): T { return value } }`, "generic methods are not supported"},
} {
program, err := Parse(test.source)
if err != nil {
if strings.Contains(err.Error(), test.message) {
continue
}
t.Fatalf("unexpected parse error: %v", err)
}
_, err = GenerateGo(program)
if err == nil || !strings.Contains(err.Error(), test.message) {
t.Fatalf("error = %v, want %q", err, test.message)
}
}
}

View file

@ -12,7 +12,7 @@ package demo
import http net.http
import json encoding.json
fun main() {
fun main(handler: (http.ResponseWriter, *http.Request) -> Unit, body: ByteSlice, target: Any) {
http.handleFunc("/health", handler)
json.unmarshal(body, &target)
}

View file

@ -4,14 +4,32 @@ import (
"go/importer"
gotypes "go/types"
"strings"
"sync"
golangpackages "golang.org/x/tools/go/packages"
)
var importedGoPackages sync.Map
func importGoPackage(decl ImportDecl) (*gotypes.Package, error) {
path := strings.Trim(decl.Path, `"`)
if !strings.Contains(path, "/") {
path = importPathToGoPath(path)
}
return importer.Default().Import(path)
if cached, ok := importedGoPackages.Load(path); ok {
return cached.(*gotypes.Package), nil
}
pkg, err := importer.Default().Import(path)
if err == nil {
importedGoPackages.Store(path, pkg)
return pkg, nil
}
loaded, loadErr := golangpackages.Load(&golangpackages.Config{Mode: golangpackages.NeedName | golangpackages.NeedTypes | golangpackages.NeedImports | golangpackages.NeedDeps}, path)
if loadErr != nil || len(loaded) == 0 || loaded[0].Types == nil {
return nil, err
}
importedGoPackages.Store(path, loaded[0].Types)
return loaded[0].Types, nil
}
func (semantic *SemanticProgram) goSelectorType(alias, name string) Type {
@ -119,7 +137,11 @@ func semanticTypeFromGoResults(results *gotypes.Tuple) Type {
if results.Len() == 1 {
return last
}
return UnknownType{}
elements := make([]Type, results.Len())
for index := range elements {
elements[index] = semanticTypeFromGo(results.At(index).Type())
}
return TupleType{Elements: elements}
}
func semanticTypeFromGo(typ gotypes.Type) Type {

View file

@ -0,0 +1,32 @@
package lang
import "testing"
func TestGoTypesResolvesFallibleCallsMethodsAndTuples(t *testing.T) {
program, err := Parse(`package demo
import os
import strconv
fun main() {
val parsed = strconv.atoi("42")
val value, found = os.lookupEnv("HOME")
println(parsed)
println(value)
println(found)
}`)
if err != nil {
t.Fatal(err)
}
_, diagnostics := AnalyzeWithContext(program, nil)
if len(diagnostics) != 0 {
t.Fatalf("diagnostics: %#v", diagnostics)
}
body := program.Functions[0].Body
parsed := body[0].(VarDecl)
if got := ResolvedType(parsed.Value).String(); got != "Result<Int, Error>" {
t.Fatalf("Atoi type = %s", got)
}
lookup := body[1].(MultiVarDecl)
if got := ResolvedType(lookup.Value).String(); got != "(String, Boolean)" {
t.Fatalf("LookupEnv type = %s", got)
}
}

View file

@ -28,6 +28,7 @@ const (
type ExprMeta struct{ Semantic *HIRExpr }
type HIRExpr struct {
ID int
Type Type
Meaning ExprMeaning
Symbol *Symbol
@ -111,8 +112,9 @@ type HIRFunction struct {
}
type HIRProgram struct {
Functions []*HIRFunction
Methods []*HIRFunction
Functions []*HIRFunction
Methods []*HIRFunction
Expressions []*HIRExpr
}
func exprMeta(expr Expr) *HIRExpr {
@ -240,10 +242,10 @@ func (resolver *semanticResolver) resolve() error {
symbol, _ := resolver.program.Global.Lookup(decl.Name)
scope := NewScope(resolver.program.Global)
for _, param := range decl.Params {
typ, _ := resolver.program.ResolveType(param.Type)
typ, _ := resolver.program.ResolveTypeRefWithParams(param.TypeRef, decl.TypeParams)
_ = scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
}
result, _ := resolver.program.ResolveType(decl.ReturnType)
result, _ := resolver.program.ResolveTypeRefWithParams(decl.ReturnRef, decl.TypeParams)
resolver.resolveStmts(decl.Body, scope, nil, result)
resolver.program.HIR.Functions = append(resolver.program.HIR.Functions, &HIRFunction{Symbol: symbol, Decl: decl, Scope: scope})
}
@ -255,10 +257,12 @@ func (resolver *semanticResolver) resolve() error {
scope := NewScope(resolver.program.Global)
_ = scope.Define(&Symbol{Name: "this", Kind: VariableSymbol, Type: ClassType{Class: class}})
for _, param := range method.Params {
typ, _ := resolver.program.ResolveType(param.Type)
params := append(append([]string{}, decl.TypeParams...), method.TypeParams...)
typ, _ := resolver.program.ResolveTypeRefWithParams(param.TypeRef, params)
_ = scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
}
result, _ := resolver.program.ResolveType(method.ReturnType)
params := append(append([]string{}, decl.TypeParams...), method.TypeParams...)
result, _ := resolver.program.ResolveTypeRefWithParams(method.ReturnRef, params)
resolver.resolveStmts(method.Body, scope, class, result)
resolver.program.HIR.Methods = append(resolver.program.HIR.Methods, &HIRFunction{Symbol: class.Methods[method.Name], Decl: method, Scope: scope})
}
@ -289,6 +293,8 @@ func (resolver *semanticResolver) resolveStmts(stmts []Stmt, scope *Scope, class
if result, ok := ResolvedType(value.Value).(GenericType); ok && result.Base.String() == "Result" && len(result.Args) == 2 && len(valueTypes) == 2 {
valueTypes[0] = result.Args[0]
valueTypes[1] = NullableType{Element: result.Args[1]}
} else if tuple, ok := ResolvedType(value.Value).(TupleType); ok && len(tuple.Elements) == len(valueTypes) {
copy(valueTypes, tuple.Elements)
}
for index, name := range value.Names {
_ = scope.Define(&Symbol{Name: name, Kind: VariableSymbol, Type: valueTypes[index], Mutable: value.Mutable})
@ -298,13 +304,34 @@ func (resolver *semanticResolver) resolveStmts(stmts []Stmt, scope *Scope, class
expected := Type(UnknownType{})
if symbol, ok := scope.Lookup(value.Name); ok {
expected = symbol.Type
} else if class != nil && class.Fields[value.Name] != nil {
expected = class.Fields[value.Name].Type
} else {
resolver.addDiagnostic(SemanticDiagnostic{Code: "undefined-variable", Message: "undefined variable " + value.Name, Severity: DiagnosticError, Span: SourceSpan{Start: value.Pos, End: value.Pos + len(value.Name)}})
}
value.Value, _ = resolver.resolveExpr(value.Value, scope, class, expected)
stmts[index] = value
case AddAssignStmt:
_, local := scope.Lookup(value.Name)
field := false
if class != nil {
_, field = class.Fields[value.Name]
}
if !local && !field {
resolver.addDiagnostic(SemanticDiagnostic{Code: "undefined-variable", Message: "undefined variable " + value.Name, Severity: DiagnosticError, Span: SourceSpan{Start: value.Pos, End: value.Pos + len(value.Name)}})
}
value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{})
stmts[index] = value
case MultiAssignStmt:
for index, name := range value.Names {
if _, ok := scope.Lookup(name); !ok {
position := 0
if index < len(value.Positions) {
position = value.Positions[index]
}
resolver.addDiagnostic(SemanticDiagnostic{Code: "undefined-variable", Message: "undefined variable " + name, Severity: DiagnosticError, Span: SourceSpan{Start: position, End: position + len(name)}})
}
}
value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{})
stmts[index] = value
case ReturnStmt:
@ -421,8 +448,11 @@ func (resolver *semanticResolver) resolveExpr(expr Expr, scope *Scope, class *Cl
expr = value
case LambdaExpr:
lambdaScope := NewScope(scope)
if value.ImplicitIt {
_ = lambdaScope.Define(&Symbol{Name: "it", Kind: VariableSymbol, Type: UnknownType{}})
}
for _, param := range value.Params {
typ, _ := resolver.program.ResolveType(param.Type)
typ, _ := resolver.program.ResolveTypeRef(param.TypeRef)
_ = lambdaScope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
}
resolver.resolveStmts(value.Body, lambdaScope, class, functionResult(expected))
@ -443,15 +473,20 @@ func (resolver *semanticResolver) resolveExpr(expr Expr, scope *Scope, class *Cl
}
}
meaning, symbol := resolver.meaning(expr, scope)
if ident, ok := expr.(IdentExpr); ok && meaning == UnresolvedExpr && isUnknownType(typ) && !isSemanticBuiltin(ident.Name) {
resolver.addDiagnostic(undefinedDiagnostic(ident.Name, ident.Pos))
}
resolver.validateEnumExpression(expr, meaning)
resolver.validateGenericCall(expr)
if meaning == GoCallExpr && isResultType(expected) {
typ = expected
}
if nullable, ok := typ.(NullableType); ok && typeEqual(nullable.Element, expected) {
typ = expected
}
semantic := &HIRExpr{Type: typ, Meaning: meaning, Symbol: symbol}
semantic := &HIRExpr{ID: len(resolver.program.HIR.Expressions) + 1, Type: typ, Meaning: meaning, Symbol: symbol}
semantic.Node = resolver.hirNode(expr, semantic)
resolver.program.HIR.Expressions = append(resolver.program.HIR.Expressions, semantic)
return withExprMeta(expr, semantic), typ
}
@ -699,6 +734,31 @@ func (resolver *semanticResolver) fail(err error) {
}
}
func (resolver *semanticResolver) addDiagnostic(diagnostic SemanticDiagnostic) {
for _, existing := range resolver.program.Diagnostics {
if existing.Code == diagnostic.Code && existing.Span.Start == diagnostic.Span.Start && existing.Message == diagnostic.Message {
return
}
}
resolver.program.Diagnostics = append(resolver.program.Diagnostics, diagnostic)
}
func isSemanticBuiltin(name string) bool {
return semanticBuiltins[name]
}
var semanticBuiltins = map[string]bool{
"println": true, "runCatching": true, "Channel": true,
"listOf": true, "mutableListOf": true, "mapOf": true, "mutableMapOf": true,
"append": true, "keys": true, "goAssert": true, "len": true, "cap": true,
"make": true, "new": true, "copy": true, "delete": true, "close": true,
"panic": true, "recover": true, "string": true, "int": true, "float64": true, "bool": true,
"sql": true, "set": true, "now": true, "Result": true, "ByteSlice": true,
"runBlocking": true, "coroutineScope": true, "launch": true, "async": true,
"delay": true, "withTimeout": true, "isActive": true,
"continue": true, "break": true,
}
func (resolver *semanticResolver) validateEnumExpression(expr Expr, meaning ExprMeaning) {
if meaning != EnumConstructionExpr {
return
@ -745,6 +805,30 @@ func (resolver *semanticResolver) validateEnumExpression(expr Expr, meaning Expr
}
}
func (resolver *semanticResolver) validateGenericCall(expr Expr) {
call, ok := expr.(CallExpr)
if !ok {
return
}
ident, ok := call.Callee.(IdentExpr)
if !ok {
return
}
var params []string
found := false
if function, ok := resolver.program.Functions[ident.Name]; ok {
params = function.TypeParams
found = true
}
if class, ok := resolver.program.ClassInfo[ident.Name]; ok {
params = class.TypeParams
found = true
}
if found && len(call.TypeArgs) > 0 && len(call.TypeArgs) != len(params) {
resolver.fail(fmt.Errorf("%s expects %d type arguments, got %d", ident.Name, len(params), len(call.TypeArgs)))
}
}
func collectionElement(typ Type) Type {
if generic, ok := typ.(GenericType); ok && len(generic.Args) > 0 {
return generic.Args[len(generic.Args)-1]

View file

@ -17,7 +17,14 @@ func Parse(input string) (*Program, error) {
return nil, err
}
p := &parser{tokens: tokens}
return p.parseProgram()
program, err := p.parseProgram()
if err != nil {
return nil, err
}
if err := hydrateTypeRefs(program); err != nil {
return nil, err
}
return program, nil
}
func (p *parser) parseProgram() (*Program, error) {
@ -292,6 +299,10 @@ func (p *parser) parseClass() (ClassDecl, error) {
if err != nil {
return ClassDecl{}, err
}
typeParams, err := p.parseTypeParameters()
if err != nil {
return ClassDecl{}, err
}
var fields []FieldDecl
if p.match(tokenLParen) {
fields, err = p.parseClassFields()
@ -308,7 +319,7 @@ func (p *parser) parseClass() (ClassDecl, error) {
return ClassDecl{}, err
}
if !p.match(tokenLBrace) {
return ClassDecl{Name: name.lexeme, Data: data, Fields: fields, Parents: parents, Methods: nil}, nil
return ClassDecl{Name: name.lexeme, TypeParams: typeParams, Data: data, Fields: fields, Parents: parents, Methods: nil}, nil
}
var methods []FunctionDecl
@ -329,7 +340,7 @@ func (p *parser) parseClass() (ClassDecl, error) {
return ClassDecl{}, err
}
return ClassDecl{Name: name.lexeme, Data: data, Fields: fields, Parents: parents, Methods: methods}, nil
return ClassDecl{Name: name.lexeme, TypeParams: typeParams, Data: data, Fields: fields, Parents: parents, Methods: methods}, nil
}
func (p *parser) parseClassParents() ([]string, error) {
@ -467,6 +478,7 @@ func (p *parser) parseFunction() (FunctionDecl, error) {
return FunctionDecl{
Name: signature.Name,
TypeParams: signature.TypeParams,
Params: signature.Params,
ReturnType: signature.ReturnType,
Body: body,
@ -483,6 +495,10 @@ func (p *parser) parseFunctionSignature() (FunctionSignature, error) {
if err != nil {
return FunctionSignature{}, err
}
typeParams, err := p.parseTypeParameters()
if err != nil {
return FunctionSignature{}, err
}
if _, err := p.expect(tokenLParen, "expected '('"); err != nil {
return FunctionSignature{}, err
}
@ -505,12 +521,39 @@ func (p *parser) parseFunctionSignature() (FunctionSignature, error) {
return FunctionSignature{
Name: name.lexeme,
TypeParams: typeParams,
Params: params,
ReturnType: returnType,
Suspend: suspend,
}, nil
}
func (p *parser) parseTypeParameters() ([]string, error) {
if !p.match(tokenLt) {
return nil, nil
}
var params []string
seen := map[string]bool{}
for {
name, err := p.expect(tokenIdent, "expected type parameter")
if err != nil {
return nil, err
}
if seen[name.lexeme] {
return nil, fmt.Errorf("duplicate type parameter %s", name.lexeme)
}
seen[name.lexeme] = true
params = append(params, name.lexeme)
if !p.match(tokenComma) {
break
}
}
if _, err := p.expect(tokenGt, "expected '>' after type parameters"); err != nil {
return nil, err
}
return params, nil
}
func (p *parser) parseParams() ([]Param, error) {
var params []Param
if p.check(tokenRParen) {
@ -528,7 +571,11 @@ func (p *parser) parseParams() ([]Param, error) {
if err != nil {
return nil, err
}
params = append(params, Param{Name: name.lexeme, Type: typ})
ref, err := ParseTypeRef(typ)
if err != nil {
return nil, err
}
params = append(params, Param{Name: name.lexeme, Type: typ, TypeRef: ref})
if !p.match(tokenComma) {
return params, nil
}
@ -832,7 +879,7 @@ func (p *parser) parseVarDecl(mutable bool) (Stmt, error) {
return nil, err
}
if len(names) == 1 {
return VarDecl{Mutable: mutable, Name: name, Type: typ, Value: value}, nil
return VarDecl{Mutable: mutable, Name: name, Pos: names[0].pos, Type: typ, Value: value}, nil
}
decl := MultiVarDecl{Mutable: mutable, Names: make([]string, 0, len(names)), Value: value}
for _, name := range names {
@ -908,7 +955,7 @@ func (p *parser) parsePrefix() (Expr, error) {
tok := p.advance()
switch tok.kind {
case tokenIdent:
return p.parsePostfix(IdentExpr{Name: tok.lexeme})
return p.parsePostfix(IdentExpr{Name: tok.lexeme, Pos: tok.pos})
case tokenInt:
return IntExpr{Value: tok.lexeme}, nil
case tokenFloat:
@ -999,6 +1046,10 @@ func (p *parser) parseLambdaParams() ([]Param, bool, error) {
return nil, false, err
}
param.Type = typ
param.TypeRef, err = ParseTypeRef(typ)
if err != nil {
return nil, false, err
}
}
params = append(params, param)
if !p.match(tokenComma) {
@ -1028,13 +1079,13 @@ func (p *parser) parsePostfix(expr Expr) (Expr, error) {
if !selectorName(name.lexeme) {
return nil, fmt.Errorf("expected selector name at %d, found %q", name.pos, name.lexeme)
}
expr = SelectorExpr{Receiver: expr, Name: name.lexeme}
expr = SelectorExpr{Receiver: expr, Name: name.lexeme, NamePos: name.pos}
case p.match(tokenSafeDot):
name := p.advance()
if !selectorName(name.lexeme) {
return nil, fmt.Errorf("expected selector name at %d, found %q", name.pos, name.lexeme)
}
expr = SafeSelectorExpr{Receiver: expr, Name: name.lexeme}
expr = SafeSelectorExpr{Receiver: expr, Name: name.lexeme, NamePos: name.pos}
case p.match(tokenDoubleBang):
expr = NonNullExpr{Value: expr}
case p.match(tokenQuestion):

View file

@ -33,7 +33,11 @@ func (checker *mutabilityChecker) checkFunction(function *FunctionDecl, class *C
_ = checker.scope.Define(&Symbol{Name: "this", Kind: VariableSymbol, Type: ClassType{Class: class}})
}
for _, param := range function.Params {
typ, _ := checker.semantic.ResolveType(param.Type)
typeParams := append([]string{}, function.TypeParams...)
if class != nil {
typeParams = append(class.TypeParams, typeParams...)
}
typ, _ := checker.semantic.ResolveTypeRefWithParams(param.TypeRef, typeParams)
_ = checker.scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
}
return checker.checkStmts(function.Body)
@ -46,7 +50,7 @@ func (checker *mutabilityChecker) checkStmts(statements []Stmt) error {
if err := checker.checkExpr(value.Value); err != nil {
return err
}
typ, _ := checker.semantic.ResolveType(value.Type)
typ, _ := checker.semantic.ResolveTypeRef(value.TypeRef)
_ = checker.scope.Define(&Symbol{Name: value.Name, Kind: VariableSymbol, Type: typ, Mutable: value.Mutable})
case MultiVarDecl:
if err := checker.checkExpr(value.Value); err != nil {
@ -214,7 +218,7 @@ func (checker *mutabilityChecker) checkExpr(expr Expr) error {
_ = checker.scope.Define(&Symbol{Name: "it", Kind: VariableSymbol, Type: UnknownType{}})
}
for _, param := range value.Params {
typ, _ := checker.semantic.ResolveType(param.Type)
typ, _ := checker.semantic.ResolveTypeRef(param.TypeRef)
_ = checker.scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
}
return checker.checkStmts(value.Body)

View file

@ -48,26 +48,48 @@ func (scope *Scope) Lookup(name string) (*Symbol, bool) {
}
type ClassSymbol struct {
Name string
Decl *ClassDecl
Fields map[string]*Symbol
Methods map[string]*Symbol
Name string
TypeParams []string
Decl *ClassDecl
Fields map[string]*Symbol
Methods map[string]*Symbol
}
type SemanticProgram struct {
Syntax *Program
Global *Scope
Classes map[string]ClassDecl
ClassInfo map[string]*ClassSymbol
Functions map[string]FunctionDecl
Enums map[string]EnumDecl
Imports map[string]bool
GoPackages map[string]*gotypes.Package
HIR *HIRProgram
Mappings *mappingState
Syntax *Program
Global *Scope
Classes map[string]ClassDecl
ClassInfo map[string]*ClassSymbol
Functions map[string]FunctionDecl
Enums map[string]EnumDecl
Imports map[string]bool
GoPackages map[string]*gotypes.Package
HIR *HIRProgram
Mappings *mappingState
Diagnostics []SemanticDiagnostic
}
func Analyze(program *Program) (*SemanticProgram, error) {
semantic, diagnostics := AnalyzeWithContext(program, nil)
if len(diagnostics) > 0 {
return nil, diagnostics[0]
}
return semantic, nil
}
func AnalyzeWithContext(program *Program, additional []*Program) (*SemanticProgram, []SemanticDiagnostic) {
semantic, err := analyzeProgram(program, additional)
if err != nil {
return semantic, []SemanticDiagnostic{diagnosticForError("semantic-error", err)}
}
diagnostics := semantic.Diagnostics
if len(diagnostics) > 0 {
return semantic, diagnostics
}
return semantic, nil
}
func analyzeProgram(program *Program, additional []*Program) (*SemanticProgram, error) {
semantic := &SemanticProgram{
Syntax: program,
Global: NewScope(nil),
@ -79,67 +101,94 @@ func Analyze(program *Program) (*SemanticProgram, error) {
GoPackages: map[string]*gotypes.Package{},
Mappings: &mappingState{functions: map[string]string{}},
}
for index := range program.Classes {
decl := &program.Classes[index]
class := &ClassSymbol{Name: decl.Name, Decl: decl, Fields: map[string]*Symbol{}, Methods: map[string]*Symbol{}}
semantic.Classes[decl.Name] = *decl
semantic.ClassInfo[decl.Name] = class
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: ClassSymbolKind, Type: ClassType{Class: class}, Decl: decl}); err != nil {
return nil, err
}
}
for index := range program.Enums {
decl := &program.Enums[index]
semantic.Enums[decl.Name] = *decl
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: EnumSymbolKind, Type: NamedType{Name: decl.Name}, Decl: decl}); err != nil {
return nil, err
}
}
for _, imported := range program.Imports {
name := imported.Alias
if name == "" {
name = defaultImportAlias(imported)
}
semantic.Imports[name] = true
if importedPackage, err := importGoPackage(imported); err == nil {
semantic.GoPackages[name] = importedPackage
}
if existing, ok := semantic.Global.Lookup(name); ok && existing.Kind == ImportSymbolKind {
continue
}
if err := semantic.Global.Define(&Symbol{Name: name, Kind: ImportSymbolKind, Type: NamedType{Name: name}, Decl: imported}); err != nil {
return nil, err
}
}
for index := range program.Functions {
decl := &program.Functions[index]
semantic.Functions[decl.Name] = *decl
typ, err := semantic.functionType(decl.Params, decl.ReturnType)
if err != nil {
return nil, fmt.Errorf("function %s: %w", decl.Name, err)
}
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: FunctionSymbolKind, Type: typ, Decl: decl}); err != nil {
return nil, err
}
}
for index := range program.Classes {
decl := &program.Classes[index]
class := semantic.ClassInfo[decl.Name]
for fieldIndex := range decl.Fields {
field := &decl.Fields[fieldIndex]
typ, err := semantic.ResolveType(field.Type)
if err != nil {
return nil, fmt.Errorf("field %s.%s: %w", decl.Name, field.Name, err)
programs := append([]*Program{program}, additional...)
for _, source := range programs {
for index := range source.Classes {
decl := &source.Classes[index]
class := &ClassSymbol{Name: decl.Name, TypeParams: decl.TypeParams, Decl: decl, Fields: map[string]*Symbol{}, Methods: map[string]*Symbol{}}
semantic.Classes[decl.Name] = *decl
semantic.ClassInfo[decl.Name] = class
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: ClassSymbolKind, Type: ClassType{Class: class}, Decl: decl}); err != nil {
return nil, err
}
class.Fields[field.Name] = &Symbol{Name: field.Name, Kind: VariableSymbol, Type: typ, Mutable: field.Mutable, Decl: field}
}
for methodIndex := range decl.Methods {
method := &decl.Methods[methodIndex]
typ, err := semantic.functionType(method.Params, method.ReturnType)
if err != nil {
return nil, fmt.Errorf("method %s.%s: %w", decl.Name, method.Name, err)
}
for _, source := range programs {
for index := range source.Enums {
decl := &source.Enums[index]
semantic.Enums[decl.Name] = *decl
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: EnumSymbolKind, Type: NamedType{Name: decl.Name}, Decl: decl}); err != nil {
return nil, err
}
}
}
for _, source := range programs {
for _, imported := range source.Imports {
name := imported.Alias
if name == "" {
name = defaultImportAlias(imported)
}
semantic.Imports[name] = true
if importedPackage, err := importGoPackage(imported); err == nil {
semantic.GoPackages[name] = importedPackage
}
if existing, ok := semantic.Global.Lookup(name); ok && existing.Kind == ImportSymbolKind {
continue
}
if err := semantic.Global.Define(&Symbol{Name: name, Kind: ImportSymbolKind, Type: NamedType{Name: name}, Decl: imported}); err != nil {
return nil, err
}
}
}
for _, source := range programs {
for index := range source.Embeds {
embedded := &source.Embeds[index]
typ, err := semantic.ResolveType(embedded.Type)
if err != nil {
return nil, err
}
if _, exists := semantic.Global.Lookup(embedded.Name); !exists {
_ = semantic.Global.Define(&Symbol{Name: embedded.Name, Kind: VariableSymbol, Type: typ, Decl: embedded})
}
}
}
for _, source := range programs {
for index := range source.Functions {
decl := &source.Functions[index]
semantic.Functions[decl.Name] = *decl
typ, err := semantic.functionType(decl.TypeParams, decl.Params, decl.ReturnType)
if err != nil {
return nil, fmt.Errorf("function %s: %w", decl.Name, err)
}
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: FunctionSymbolKind, Type: typ, Decl: decl}); err != nil {
return nil, err
}
}
}
for _, source := range programs {
for index := range source.Classes {
decl := &source.Classes[index]
class := semantic.ClassInfo[decl.Name]
for fieldIndex := range decl.Fields {
field := &decl.Fields[fieldIndex]
typ, err := semantic.ResolveTypeRefWithParams(field.TypeRef, decl.TypeParams)
if err != nil {
return nil, fmt.Errorf("field %s.%s: %w", decl.Name, field.Name, err)
}
class.Fields[field.Name] = &Symbol{Name: field.Name, Kind: VariableSymbol, Type: typ, Mutable: field.Mutable, Decl: field}
}
for methodIndex := range decl.Methods {
method := &decl.Methods[methodIndex]
if len(method.TypeParams) > 0 {
return nil, fmt.Errorf("generic methods are not supported; declare type parameters on class %s", decl.Name)
}
methodParams := append(append([]string{}, decl.TypeParams...), method.TypeParams...)
typ, err := semantic.functionType(methodParams, method.Params, method.ReturnType)
if err != nil {
return nil, fmt.Errorf("method %s.%s: %w", decl.Name, method.Name, err)
}
class.Methods[method.Name] = &Symbol{Name: method.Name, Kind: FunctionSymbolKind, Type: typ, Decl: method}
}
class.Methods[method.Name] = &Symbol{Name: method.Name, Kind: FunctionSymbolKind, Type: typ, Decl: method}
}
}
if err := validateMutability(semantic); err != nil {
@ -164,6 +213,30 @@ func (semantic *SemanticProgram) ResolveType(text string) (Type, error) {
return resolved, nil
}
func (semantic *SemanticProgram) ResolveTypeRef(ref TypeRef) (Type, error) {
return semantic.ResolveTypeRefWithParams(ref, nil)
}
func (semantic *SemanticProgram) ResolveTypeRefWithParams(ref TypeRef, params []string) (Type, error) {
typ := ref.Syntax
if typ == nil {
parsed, err := ParseType(ref.Source)
if err != nil {
return nil, err
}
typ = parsed
}
paramSet := map[string]bool{}
for _, param := range params {
paramSet[param] = true
}
resolved := resolveClassTypes(resolveTypeParameters(typ, paramSet), semantic.ClassInfo)
if err := validateNoClassPointer(resolved); err != nil {
return nil, err
}
return resolved, nil
}
func validateNoClassPointer(typ Type) error {
switch value := typ.(type) {
case GoPointerType:
@ -190,20 +263,24 @@ func validateNoClassPointer(typ Type) error {
return nil
}
func (semantic *SemanticProgram) functionType(params []Param, result string) (Type, error) {
func (semantic *SemanticProgram) functionType(typeParams []string, params []Param, result string) (Type, error) {
paramTypes := make([]Type, len(params))
for index, param := range params {
typ, err := semantic.ResolveType(param.Type)
typ, err := semantic.ResolveTypeRefWithParams(param.TypeRef, typeParams)
if err != nil {
return nil, err
}
paramTypes[index] = typ
}
resultType, err := semantic.ResolveType(result)
resultRef, err := ParseTypeRef(result)
if err != nil {
return nil, err
}
return FunctionType{Params: paramTypes, Result: resultType}, nil
resultType, err := semantic.ResolveTypeRefWithParams(resultRef, typeParams)
if err != nil {
return nil, err
}
return FunctionType{TypeParams: typeParams, Params: paramTypes, Result: resultType}, nil
}
func (semantic *SemanticProgram) GoType(text string) string {

View file

@ -66,6 +66,14 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
return resolve(value.TypeArgs[0])
}
receiverType := semantic.TypeOf(selector.Receiver, environment)
if channel, ok := receiverType.(GenericType); ok && channel.Base.String() == "Channel" && len(channel.Args) == 1 {
if selector.Name == "read" {
return channel.Args[0]
}
if selector.Name == "send" {
return NamedType{Name: "Unit"}
}
}
if iterator, ok := receiverType.(GenericType); ok && iterator.Base.String() == "GotlinSQLIterator" && len(iterator.Args) == 1 {
switch selector.Name {
case "next":
@ -78,10 +86,10 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
return NamedType{Name: "Unit"}
}
}
if class := classTypeOf(receiverType); class != nil {
if class, bindings := classInstance(receiverType); class != nil {
if method, ok := class.Methods[selector.Name]; ok {
if function, ok := method.Type.(FunctionType); ok {
return function.Result
return substituteType(function.Result, bindings)
}
}
}
@ -90,16 +98,62 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
}
}
if ident, ok := value.Callee.(IdentExpr); ok {
switch ident.Name {
case "Channel":
if len(value.TypeArgs) == 1 {
return GenericType{Base: NamedType{Name: "Channel"}, Args: []Type{resolve(value.TypeArgs[0])}}
}
case "listOf", "mutableListOf":
element := Type(UnknownType{})
if len(value.TypeArgs) == 1 {
element = resolve(value.TypeArgs[0])
} else if len(value.Args) > 0 {
element = semantic.TypeOf(value.Args[0], environment)
}
return GenericType{Base: NamedType{Name: "List"}, Args: []Type{element}}
case "mapOf", "mutableMapOf":
key, item := Type(UnknownType{}), Type(UnknownType{})
if len(value.TypeArgs) == 2 {
key, item = resolve(value.TypeArgs[0]), resolve(value.TypeArgs[1])
} else if len(value.Args) >= 2 {
key, item = semantic.TypeOf(value.Args[0], environment), semantic.TypeOf(value.Args[1], environment)
}
return GenericType{Base: NamedType{Name: "Map"}, Args: []Type{key, item}}
case "ByteSlice":
return NamedType{Name: "ByteSlice"}
case "async":
if len(value.TypeArgs) == 1 {
return GenericType{Base: NamedType{Name: "Deferred"}, Args: []Type{resolve(value.TypeArgs[0])}}
}
}
if ident.Name == "keys" && len(value.Args) == 1 {
if mapping, ok := semantic.TypeOf(value.Args[0], environment).(GenericType); ok && (mapping.Base.String() == "Map" || mapping.Base.String() == "MutableMap") && len(mapping.Args) == 2 {
return GenericType{Base: NamedType{Name: "List"}, Args: []Type{mapping.Args[0]}}
}
}
if function, ok := semantic.Functions[ident.Name]; ok {
return resolve(function.ReturnType)
if symbol, ok := semantic.Global.Lookup(ident.Name); ok && symbol.Kind == FunctionSymbolKind {
if function, ok := symbol.Type.(FunctionType); ok {
bindings := semantic.callTypeBindings(function.TypeParams, function.Params, value.TypeArgs, value.Args, environment)
return substituteType(function.Result, bindings)
}
}
if class, ok := semantic.ClassInfo[ident.Name]; ok {
return ClassType{Class: class}
if len(class.TypeParams) == 0 {
return ClassType{Class: class}
}
params := make([]Type, 0, len(class.Fields))
for _, field := range class.Decl.Fields {
params = append(params, class.Fields[field.Name].Type)
}
bindings := semantic.callTypeBindings(class.TypeParams, params, value.TypeArgs, value.Args, environment)
args := make([]Type, len(class.TypeParams))
for index, name := range class.TypeParams {
args[index] = bindings[name]
if args[index] == nil {
args[index] = TypeParameterType{Name: name}
}
}
return GenericType{Base: ClassType{Class: class}, Args: args}
}
}
if sqlType, ok := sqlChainResultType(value); ok {
@ -121,9 +175,9 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
return NamedType{Name: receiver.Name}
}
}
if class := classTypeOf(semantic.TypeOf(value.Receiver, environment)); class != nil {
if class, bindings := classInstance(semantic.TypeOf(value.Receiver, environment)); class != nil {
if field, ok := class.Fields[value.Name]; ok {
return field.Type
return substituteType(field.Type, bindings)
}
}
if field := semantic.goFieldType(semantic.TypeOf(value.Receiver, environment), value.Name); !isUnknownType(field) {
@ -169,13 +223,43 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
}
func classTypeOf(typ Type) *ClassSymbol {
class, _ := classInstance(typ)
return class
}
func classInstance(typ Type) (*ClassSymbol, map[string]Type) {
switch value := typ.(type) {
case ClassType:
return value.Class
return value.Class, map[string]Type{}
case NullableType:
return classTypeOf(value.Element)
return classInstance(value.Element)
case GenericType:
if class, ok := value.Base.(ClassType); ok {
bindings := map[string]Type{}
for index, name := range class.Class.TypeParams {
if index < len(value.Args) {
bindings[name] = value.Args[index]
}
}
return class.Class, bindings
}
}
return nil
return nil, nil
}
func (semantic *SemanticProgram) callTypeBindings(typeParams []string, params []Type, explicit []string, args []Expr, environment TypeEnvironment) map[string]Type {
bindings := map[string]Type{}
for index, argument := range explicit {
if index < len(typeParams) {
bindings[typeParams[index]] = func() Type { typ, _ := semantic.ResolveType(argument); return typ }()
}
}
for index, argument := range args {
if index < len(params) {
inferTypeBindings(params[index], semantic.TypeOf(argument, environment), bindings)
}
}
return bindings
}
func nullableSemanticType(typ Type) Type {

248
internal/lang/type_refs.go Normal file
View file

@ -0,0 +1,248 @@
package lang
import "fmt"
func hydrateTypeRefs(program *Program) error {
for index := range program.Embeds {
ref, err := ParseTypeRef(program.Embeds[index].Type)
if err != nil {
return err
}
program.Embeds[index].TypeRef = ref
}
for enumIndex := range program.Enums {
for variantIndex := range program.Enums[enumIndex].Variants {
variant := &program.Enums[enumIndex].Variants[variantIndex]
variant.PayloadRefs = make([]TypeRef, len(variant.PayloadTypes))
for index, text := range variant.PayloadTypes {
ref, err := ParseTypeRef(text)
if err != nil {
return fmt.Errorf("enum %s.%s: %w", program.Enums[enumIndex].Name, variant.Name, err)
}
variant.PayloadRefs[index] = ref
}
}
}
for interfaceIndex := range program.Interfaces {
for methodIndex := range program.Interfaces[interfaceIndex].Methods {
if err := hydrateSignatureRefs(&program.Interfaces[interfaceIndex].Methods[methodIndex]); err != nil {
return err
}
}
}
for classIndex := range program.Classes {
class := &program.Classes[classIndex]
for fieldIndex := range class.Fields {
ref, err := ParseTypeRef(class.Fields[fieldIndex].Type)
if err != nil {
return fmt.Errorf("field %s.%s: %w", class.Name, class.Fields[fieldIndex].Name, err)
}
class.Fields[fieldIndex].TypeRef = ref
}
for methodIndex := range class.Methods {
if err := hydrateFunctionRefs(&class.Methods[methodIndex]); err != nil {
return err
}
}
}
for index := range program.Functions {
if err := hydrateFunctionRefs(&program.Functions[index]); err != nil {
return err
}
}
return nil
}
func hydrateSignatureRefs(signature *FunctionSignature) error {
for index := range signature.Params {
ref, err := ParseTypeRef(signature.Params[index].Type)
if err != nil {
return err
}
signature.Params[index].TypeRef = ref
}
ref, err := ParseTypeRef(signature.ReturnType)
if err != nil {
return err
}
signature.ReturnRef = ref
return nil
}
func hydrateFunctionRefs(function *FunctionDecl) error {
for index := range function.Params {
ref, err := ParseTypeRef(function.Params[index].Type)
if err != nil {
return err
}
function.Params[index].TypeRef = ref
}
ref, err := ParseTypeRef(function.ReturnType)
if err != nil {
return err
}
function.ReturnRef = ref
return hydrateStmtTypeRefs(function.Body)
}
func hydrateStmtTypeRefs(statements []Stmt) error {
for index, statement := range statements {
switch value := statement.(type) {
case VarDecl:
ref, err := ParseTypeRef(value.Type)
if err != nil {
return err
}
value.TypeRef = ref
if err := hydrateExprTypeRefs(value.Value); err != nil {
return err
}
statements[index] = value
case MultiVarDecl:
if err := hydrateExprTypeRefs(value.Value); err != nil {
return err
}
case AssignStmt:
if err := hydrateExprTypeRefs(value.Value); err != nil {
return err
}
case AddAssignStmt:
if err := hydrateExprTypeRefs(value.Value); err != nil {
return err
}
case MultiAssignStmt:
if err := hydrateExprTypeRefs(value.Value); err != nil {
return err
}
case ReturnStmt:
if value.Value != nil {
if err := hydrateExprTypeRefs(value.Value); err != nil {
return err
}
}
case ThrowStmt:
if err := hydrateExprTypeRefs(value.Value); err != nil {
return err
}
case DeferStmt:
if err := hydrateExprTypeRefs(value.Value); err != nil {
return err
}
case ExprStmt:
if err := hydrateExprTypeRefs(value.Value); err != nil {
return err
}
case IfStmt:
if err := hydrateExprTypeRefs(value.Cond); err != nil {
return err
}
if err := hydrateStmtTypeRefs(value.Then); err != nil {
return err
}
if err := hydrateStmtTypeRefs(value.Else); err != nil {
return err
}
case WhileStmt:
if err := hydrateExprTypeRefs(value.Cond); err != nil {
return err
}
if err := hydrateStmtTypeRefs(value.Body); err != nil {
return err
}
case ForEachStmt:
if err := hydrateExprTypeRefs(value.Source); err != nil {
return err
}
if err := hydrateStmtTypeRefs(value.Body); err != nil {
return err
}
case MatchStmt:
if err := hydrateExprTypeRefs(value.Value); err != nil {
return err
}
for _, matchCase := range value.Cases {
if err := hydrateStmtTypeRefs(matchCase.Body); err != nil {
return err
}
}
case TryCatchStmt:
ref, err := ParseTypeRef(value.CatchType)
if err != nil {
return err
}
value.CatchRef = ref
statements[index] = value
if err := hydrateStmtTypeRefs(value.TryBody); err != nil {
return err
}
if err := hydrateStmtTypeRefs(value.CatchBody); err != nil {
return err
}
}
}
return nil
}
func hydrateExprTypeRefs(expression Expr) error {
switch value := expression.(type) {
case UnaryExpr:
return hydrateExprTypeRefs(value.Value)
case NonNullExpr:
return hydrateExprTypeRefs(value.Value)
case TryExpr:
return hydrateExprTypeRefs(value.Value)
case BinaryExpr:
if err := hydrateExprTypeRefs(value.Left); err != nil {
return err
}
return hydrateExprTypeRefs(value.Right)
case CallExpr:
if err := hydrateExprTypeRefs(value.Callee); err != nil {
return err
}
for _, argument := range value.Args {
if err := hydrateExprTypeRefs(argument); err != nil {
return err
}
}
for _, argument := range value.NamedArgs {
if err := hydrateExprTypeRefs(argument.Value); err != nil {
return err
}
}
case SelectorExpr:
return hydrateExprTypeRefs(value.Receiver)
case SafeSelectorExpr:
return hydrateExprTypeRefs(value.Receiver)
case IndexExpr:
if err := hydrateExprTypeRefs(value.Receiver); err != nil {
return err
}
return hydrateExprTypeRefs(value.Index)
case EnumVariantExpr:
for _, item := range value.Values {
if err := hydrateExprTypeRefs(item); err != nil {
return err
}
}
case MatchExpr:
if err := hydrateExprTypeRefs(value.Value); err != nil {
return err
}
for _, matchCase := range value.Cases {
if err := hydrateExprTypeRefs(matchCase.Value); err != nil {
return err
}
}
case LambdaExpr:
for index := range value.Params {
ref, err := ParseTypeRef(value.Params[index].Type)
if err != nil {
return err
}
value.Params[index].TypeRef = ref
}
return hydrateStmtTypeRefs(value.Body)
}
return nil
}

View file

@ -10,6 +10,19 @@ type Type interface {
String() string
}
type TypeRef struct {
Source string
Syntax Type
}
func ParseTypeRef(source string) (TypeRef, error) {
typ, err := ParseType(source)
if err != nil {
return TypeRef{}, err
}
return TypeRef{Source: source, Syntax: typ}, nil
}
type UnknownType struct{}
func (UnknownType) typeNode() {}
@ -20,6 +33,11 @@ type NamedType struct{ Name string }
func (NamedType) typeNode() {}
func (t NamedType) String() string { return t.Name }
type TypeParameterType struct{ Name string }
func (TypeParameterType) typeNode() {}
func (t TypeParameterType) String() string { return t.Name }
type ClassType struct{ Class *ClassSymbol }
func (ClassType) typeNode() {}
@ -36,8 +54,9 @@ func (GoPointerType) typeNode() {}
func (t GoPointerType) String() string { return "*" + t.Element.String() }
type FunctionType struct {
Params []Type
Result Type
TypeParams []string
Params []Type
Result Type
}
func (FunctionType) typeNode() {}
@ -54,6 +73,17 @@ type GenericType struct {
Args []Type
}
type TupleType struct{ Elements []Type }
func (TupleType) typeNode() {}
func (t TupleType) String() string {
elements := make([]string, len(t.Elements))
for index, element := range t.Elements {
elements[index] = element.String()
}
return "(" + strings.Join(elements, ", ") + ")"
}
func (GenericType) typeNode() {}
func (t GenericType) String() string {
args := make([]string, 0, len(t.Args))
@ -125,6 +155,8 @@ func resolveClassTypes(typ Type, classes map[string]*ClassSymbol) Type {
return ClassType{Class: class}
}
return value
case TypeParameterType:
return value
case NullableType:
return NullableType{Element: resolveClassTypes(value.Element, classes)}
case GoPointerType:
@ -141,6 +173,46 @@ func resolveClassTypes(typ Type, classes map[string]*ClassSymbol) Type {
args[i] = resolveClassTypes(arg, classes)
}
return GenericType{Base: resolveClassTypes(value.Base, classes), Args: args}
case TupleType:
elements := make([]Type, len(value.Elements))
for index, element := range value.Elements {
elements[index] = resolveClassTypes(element, classes)
}
return TupleType{Elements: elements}
default:
return typ
}
}
func resolveTypeParameters(typ Type, params map[string]bool) Type {
switch value := typ.(type) {
case NamedType:
if params[value.Name] {
return TypeParameterType{Name: value.Name}
}
return value
case NullableType:
return NullableType{Element: resolveTypeParameters(value.Element, params)}
case GoPointerType:
return GoPointerType{Element: resolveTypeParameters(value.Element, params)}
case FunctionType:
resolved := make([]Type, len(value.Params))
for index, param := range value.Params {
resolved[index] = resolveTypeParameters(param, params)
}
return FunctionType{TypeParams: value.TypeParams, Params: resolved, Result: resolveTypeParameters(value.Result, params)}
case GenericType:
args := make([]Type, len(value.Args))
for index, arg := range value.Args {
args[index] = resolveTypeParameters(arg, params)
}
return GenericType{Base: resolveTypeParameters(value.Base, params), Args: args}
case TupleType:
elements := make([]Type, len(value.Elements))
for index, element := range value.Elements {
elements[index] = resolveTypeParameters(element, params)
}
return TupleType{Elements: elements}
default:
return typ
}
@ -148,6 +220,63 @@ func resolveClassTypes(typ Type, classes map[string]*ClassSymbol) Type {
func typeEqual(left, right Type) bool { return left.String() == right.String() }
func substituteType(typ Type, bindings map[string]Type) Type {
switch value := typ.(type) {
case TypeParameterType:
if bound, ok := bindings[value.Name]; ok {
return bound
}
return value
case NullableType:
return NullableType{Element: substituteType(value.Element, bindings)}
case GoPointerType:
return GoPointerType{Element: substituteType(value.Element, bindings)}
case GenericType:
args := make([]Type, len(value.Args))
for index, arg := range value.Args {
args[index] = substituteType(arg, bindings)
}
return GenericType{Base: substituteType(value.Base, bindings), Args: args}
case TupleType:
elements := make([]Type, len(value.Elements))
for index, element := range value.Elements {
elements[index] = substituteType(element, bindings)
}
return TupleType{Elements: elements}
case FunctionType:
params := make([]Type, len(value.Params))
for index, param := range value.Params {
params[index] = substituteType(param, bindings)
}
return FunctionType{TypeParams: value.TypeParams, Params: params, Result: substituteType(value.Result, bindings)}
default:
return typ
}
}
func inferTypeBindings(parameter, argument Type, bindings map[string]Type) {
switch expected := parameter.(type) {
case TypeParameterType:
if _, exists := bindings[expected.Name]; !exists && !isUnknownType(argument) {
bindings[expected.Name] = argument
}
case NullableType:
if actual, ok := argument.(NullableType); ok {
inferTypeBindings(expected.Element, actual.Element, bindings)
}
case GenericType:
actual, ok := argument.(GenericType)
if !ok || expected.Base.String() != actual.Base.String() {
return
}
for index := range expected.Args {
if index < len(actual.Args) {
inferTypeBindings(expected.Args[index], actual.Args[index], bindings)
}
}
}
}
func renderGoType(typ Type) string {
switch value := typ.(type) {
case UnknownType:
@ -201,7 +330,12 @@ func renderGoType(typ Type) string {
}
return "*GotlinSQLIterator[" + argument + "]"
}
if class, ok := value.Base.(ClassType); ok {
return "*" + class.Class.Name + "[" + strings.Join(args, ", ") + "]"
}
return base + "[" + strings.Join(args, ", ") + "]"
case TypeParameterType:
return value.Name
case NamedType:
switch value.Name {
case "Int":
@ -225,6 +359,12 @@ func renderGoType(typ Type) string {
default:
return value.Name
}
case TupleType:
elements := make([]string, len(value.Elements))
for index, element := range value.Elements {
elements[index] = renderGoType(element)
}
return "(" + strings.Join(elements, ", ") + ")"
default:
return ""
}

View file

@ -35,6 +35,9 @@ fun create(): Service { return Service(Repository()) }`)
if program.Classes[1].Fields[0].Type != "Repository" || program.Functions[0].ReturnType != "Service" {
t.Fatalf("syntax types were rewritten: %#v %#v", program.Classes[1].Fields[0], program.Functions[0])
}
if _, ok := program.Classes[1].Fields[0].TypeRef.Syntax.(NamedType); !ok {
t.Fatalf("field syntax TypeRef was not parsed: %#v", program.Classes[1].Fields[0].TypeRef)
}
if _, err := GenerateGo(program); err != nil {
t.Fatal(err)
}
@ -67,6 +70,9 @@ fun parse(value: String): Result<Int, Error> {
if semantic.HIR == nil || len(semantic.HIR.Functions) != 1 {
t.Fatal("typed HIR function was not created")
}
if len(semantic.HIR.Expressions) == 0 {
t.Fatal("standalone HIR expression arena is empty")
}
decl := program.Functions[0].Body[0].(VarDecl)
attempt := decl.Value.(TryExpr)
if attempt.Meta.Semantic == nil || attempt.Meta.Semantic.Meaning != PropagateResultExpr || attempt.Meta.Semantic.Type.String() != "Int" {
@ -75,6 +81,9 @@ fun parse(value: String): Result<Int, Error> {
if _, ok := attempt.Meta.Semantic.Node.(HIRPropagateResult); !ok {
t.Fatalf("propagation did not lower to HIRPropagateResult: %#v", attempt.Meta.Semantic.Node)
}
if semantic.HIR.Expressions[attempt.Meta.Semantic.ID-1] != attempt.Meta.Semantic {
t.Fatal("syntax annotation does not reference the HIR-owned expression")
}
call := attempt.Value.(CallExpr)
if call.Meta.Semantic == nil || call.Meta.Semantic.Meaning != GoCallExpr || call.Meta.Semantic.Type.String() != "Result<Int, Error>" {
t.Fatalf("external call was not resolved: %#v", call.Meta.Semantic)