Move LSP semantics into typed analysis
This commit is contained in:
parent
f4cd4f4458
commit
bac1183593
26 changed files with 1232 additions and 637 deletions
|
|
@ -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() {}
|
||||
|
|
|
|||
50
internal/lang/diagnostics.go
Normal file
50
internal/lang/diagnostics.go
Normal 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},
|
||||
}
|
||||
}
|
||||
37
internal/lang/diagnostics_test.go
Normal file
37
internal/lang/diagnostics_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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("(")
|
||||
|
|
|
|||
72
internal/lang/generics_test.go
Normal file
72
internal/lang/generics_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
32
internal/lang/go_types_test.go
Normal file
32
internal/lang/go_types_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
248
internal/lang/type_refs.go
Normal 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
|
||||
}
|
||||
|
|
@ -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 ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue