Move LSP semantics into typed analysis
This commit is contained in:
parent
f4cd4f4458
commit
bac1183593
26 changed files with 1232 additions and 637 deletions
22
README.md
22
README.md
|
|
@ -19,6 +19,7 @@ the semantic Type-to-Go mapping turns a class such as `User` into `*User`.
|
||||||
- `val` and `var`
|
- `val` and `var`
|
||||||
- `Int`, `Long`, `String`, `Boolean`, `Unit`
|
- `Int`, `Long`, `String`, `Boolean`, `Unit`
|
||||||
- function types like `(String) -> Unit`
|
- function types like `(String) -> Unit`
|
||||||
|
- user generic functions and classes with inferred or explicit type arguments
|
||||||
- `if`, `else`, `while`, and `for (item in items)`
|
- `if`, `else`, `while`, and `for (item in items)`
|
||||||
- function calls
|
- function calls
|
||||||
- lambdas like `{ x: Int -> println(x) }` and `{ println(it) }`
|
- lambdas like `{ x: Int -> println(x) }` and `{ println(it) }`
|
||||||
|
|
@ -126,6 +127,27 @@ checked during Gotlin compilation. A match used as an expression also requires
|
||||||
every arm to return the same type. Block-style statement matches remain
|
every arm to return the same type. Block-style statement matches remain
|
||||||
available for side effects.
|
available for side effects.
|
||||||
|
|
||||||
|
## User generics
|
||||||
|
|
||||||
|
Functions and classes may declare type parameters. Calls infer straightforward
|
||||||
|
type bindings from arguments or accept explicit type arguments:
|
||||||
|
|
||||||
|
```kotlin
|
||||||
|
data class Box<T>(var value: T) {
|
||||||
|
fun get(): T { return value }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun identity<T>(value: T): T { return value }
|
||||||
|
|
||||||
|
val number = identity(42)
|
||||||
|
val text = identity<String>("value")
|
||||||
|
val box = Box("boxed")
|
||||||
|
```
|
||||||
|
|
||||||
|
Type parameters currently use an implicit `Any` constraint. Generic methods
|
||||||
|
with their own type parameters are intentionally deferred; place parameters on
|
||||||
|
the enclosing class or a top-level function.
|
||||||
|
|
||||||
## Null safety
|
## Null safety
|
||||||
|
|
||||||
Types are non-nullable by default. Add `?` explicitly when `null` is valid:
|
Types are non-nullable by default. Add `?` explicitly when `null` is valid:
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ Current support:
|
||||||
- publish diagnostics from the existing Gotlin parser/code generator
|
- publish diagnostics from the existing Gotlin parser/code generator
|
||||||
- `textDocument/hover` for packages, imports, and top-level functions
|
- `textDocument/hover` for packages, imports, and top-level functions
|
||||||
- `textDocument/definition` for imported namespaces and top-level functions
|
- `textDocument/definition` for imported namespaces and top-level functions
|
||||||
- simple semantic diagnostics for duplicate imports/functions and undefined names
|
- structured resolver/type diagnostics from `lang.Analyze`, including package-context symbols
|
||||||
- optional fallback to `gopls` for hover/definition on Go-imported symbols
|
- optional fallback to `gopls` for hover/definition on Go-imported symbols
|
||||||
|
|
||||||
Build:
|
Build:
|
||||||
|
|
|
||||||
|
|
@ -160,6 +160,7 @@ type diagnostic struct {
|
||||||
Range rng `json:"range"`
|
Range rng `json:"range"`
|
||||||
Severity int `json:"severity,omitempty"`
|
Severity int `json:"severity,omitempty"`
|
||||||
Source string `json:"source,omitempty"`
|
Source string `json:"source,omitempty"`
|
||||||
|
Code string `json:"code,omitempty"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -306,14 +307,14 @@ func buildDocumentState(text string) documentState {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
state.program = program
|
state.program = program
|
||||||
if _, err := lang.Analyze(program); err != nil {
|
_, semanticDiagnostics := lang.AnalyzeWithContext(program, nil)
|
||||||
state.diagnostics = append(state.diagnostics, diagnosticFromError(text, err))
|
|
||||||
}
|
|
||||||
state.symbols = indexSymbols(text, program)
|
state.symbols = indexSymbols(text, program)
|
||||||
state.diagnostics = semanticDiagnostics(text, program, state.symbols)
|
state.diagnostics = diagnosticsFromSemantic(text, semanticDiagnostics)
|
||||||
|
if len(semanticDiagnostics) == 0 {
|
||||||
if _, err := lang.GenerateGo(program); err != nil {
|
if _, err := lang.GenerateGo(program); err != nil {
|
||||||
state.diagnostics = append(state.diagnostics, diagnosticFromError(text, err))
|
state.diagnostics = append(state.diagnostics, diagnosticFromError(text, err))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,10 +326,11 @@ func (s *server) buildDocumentState(uri, text string) documentState {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
state.program = program
|
state.program = program
|
||||||
state.symbols = indexSymbols(text, program)
|
|
||||||
programs, symbols := s.packageContext(uri, program.PackagePath, text)
|
programs, symbols := s.packageContext(uri, program.PackagePath, text)
|
||||||
|
_, semanticDiagnostics := lang.AnalyzeWithContext(program, programs)
|
||||||
|
state.symbols = indexSymbols(text, program)
|
||||||
state.packageSymbols = symbols
|
state.packageSymbols = symbols
|
||||||
state.diagnostics = semanticDiagnosticsWithPackage(text, program, state.symbols, programs)
|
state.diagnostics = diagnosticsFromSemantic(text, semanticDiagnostics)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -363,7 +365,9 @@ func (s *server) packageContext(currentURI, packagePath, currentText string) ([]
|
||||||
if parseErr != nil || program.PackagePath != packagePath {
|
if parseErr != nil || program.PackagePath != packagePath {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if candidateURI != currentURI {
|
||||||
programs = append(programs, program)
|
programs = append(programs, program)
|
||||||
|
}
|
||||||
for _, sym := range indexSymbols(text, program) {
|
for _, sym := range indexSymbols(text, program) {
|
||||||
sym.URI = candidateURI
|
sym.URI = candidateURI
|
||||||
symbols = append(symbols, sym)
|
symbols = append(symbols, sym)
|
||||||
|
|
@ -729,227 +733,22 @@ func indexSymbols(text string, program *lang.Program) []symbol {
|
||||||
return symbols
|
return symbols
|
||||||
}
|
}
|
||||||
|
|
||||||
func semanticDiagnostics(text string, program *lang.Program, symbols []symbol) []diagnostic {
|
func diagnosticsFromSemantic(text string, semantic []lang.SemanticDiagnostic) []diagnostic {
|
||||||
return semanticDiagnosticsWithPackage(text, program, symbols, nil)
|
diagnostics := make([]diagnostic, 0, len(semantic))
|
||||||
}
|
for _, item := range semantic {
|
||||||
|
start := offsetToPosition(text, item.Span.Start)
|
||||||
func semanticDiagnosticsWithPackage(text string, program *lang.Program, symbols []symbol, packagePrograms []*lang.Program) []diagnostic {
|
endOffset := item.Span.End
|
||||||
var diagnostics []diagnostic
|
if endOffset <= item.Span.Start {
|
||||||
|
endOffset = item.Span.Start + 1
|
||||||
funcSymbols := map[string]symbol{}
|
|
||||||
importSymbols := map[string]symbol{}
|
|
||||||
typeSymbols := map[string]symbol{}
|
|
||||||
for _, sym := range symbols {
|
|
||||||
switch sym.Kind {
|
|
||||||
case symbolKindFunction:
|
|
||||||
if prev, ok := funcSymbols[sym.Name]; ok {
|
|
||||||
diagnostics = append(diagnostics, duplicateDiagnostic(sym.Range, "duplicate function "+sym.Name))
|
|
||||||
diagnostics = append(diagnostics, duplicateDiagnostic(prev.Range, "duplicate function "+sym.Name))
|
|
||||||
} else {
|
|
||||||
funcSymbols[sym.Name] = sym
|
|
||||||
}
|
}
|
||||||
case symbolKindModule:
|
diagnostics = append(diagnostics, diagnostic{
|
||||||
if prev, ok := importSymbols[sym.Name]; ok {
|
Range: rng{Start: start, End: offsetToPosition(text, endOffset)},
|
||||||
diagnostics = append(diagnostics, duplicateDiagnostic(sym.Range, "duplicate import alias "+sym.Name))
|
Severity: int(item.Severity),
|
||||||
diagnostics = append(diagnostics, duplicateDiagnostic(prev.Range, "duplicate import alias "+sym.Name))
|
Source: "gotlin",
|
||||||
} else {
|
Code: item.Code,
|
||||||
importSymbols[sym.Name] = sym
|
Message: item.Message,
|
||||||
}
|
|
||||||
case symbolKindClass, symbolKindInterface, symbolKindEnum:
|
|
||||||
if prev, ok := typeSymbols[sym.Name]; ok {
|
|
||||||
diagnostics = append(diagnostics, duplicateDiagnostic(sym.Range, "duplicate type "+sym.Name))
|
|
||||||
diagnostics = append(diagnostics, duplicateDiagnostic(prev.Range, "duplicate type "+sym.Name))
|
|
||||||
} else {
|
|
||||||
typeSymbols[sym.Name] = sym
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imports := map[string]bool{}
|
|
||||||
for _, imp := range program.Imports {
|
|
||||||
imports[importAlias(imp)] = true
|
|
||||||
}
|
|
||||||
functions := map[string]lang.FunctionDecl{}
|
|
||||||
for _, fn := range program.Functions {
|
|
||||||
functions[fn.Name] = fn
|
|
||||||
}
|
|
||||||
types := map[string]bool{}
|
|
||||||
for _, decl := range program.Interfaces {
|
|
||||||
types[decl.Name] = true
|
|
||||||
}
|
|
||||||
for _, decl := range program.Classes {
|
|
||||||
types[decl.Name] = true
|
|
||||||
}
|
|
||||||
for _, decl := range program.Enums {
|
|
||||||
types[decl.Name] = true
|
|
||||||
}
|
|
||||||
for _, sibling := range packagePrograms {
|
|
||||||
for _, fn := range sibling.Functions {
|
|
||||||
functions[fn.Name] = fn
|
|
||||||
}
|
|
||||||
for _, decl := range sibling.Interfaces {
|
|
||||||
types[decl.Name] = true
|
|
||||||
}
|
|
||||||
for _, decl := range sibling.Classes {
|
|
||||||
types[decl.Name] = true
|
|
||||||
}
|
|
||||||
for _, decl := range sibling.Enums {
|
|
||||||
types[decl.Name] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, fn := range program.Functions {
|
|
||||||
diagnostics = append(diagnostics, functionSemanticDiagnostics(text, fn, functions, imports, types, nil)...)
|
|
||||||
}
|
|
||||||
for _, class := range program.Classes {
|
|
||||||
fields := map[string]bool{
|
|
||||||
"this": true,
|
|
||||||
}
|
|
||||||
for _, field := range class.Fields {
|
|
||||||
fields[field.Name] = true
|
|
||||||
}
|
|
||||||
for _, method := range class.Methods {
|
|
||||||
diagnostics = append(diagnostics, functionSemanticDiagnostics(text, method, functions, imports, types, fields)...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.SliceStable(diagnostics, func(i, j int) bool {
|
|
||||||
if diagnostics[i].Range.Start.Line != diagnostics[j].Range.Start.Line {
|
|
||||||
return diagnostics[i].Range.Start.Line < diagnostics[j].Range.Start.Line
|
|
||||||
}
|
|
||||||
return diagnostics[i].Range.Start.Character < diagnostics[j].Range.Start.Character
|
|
||||||
})
|
})
|
||||||
return uniqueDiagnostics(diagnostics)
|
|
||||||
}
|
|
||||||
|
|
||||||
func functionSemanticDiagnostics(text string, fn lang.FunctionDecl, functions map[string]lang.FunctionDecl, imports map[string]bool, types map[string]bool, fields map[string]bool) []diagnostic {
|
|
||||||
scope := map[string]bool{}
|
|
||||||
for _, param := range fn.Params {
|
|
||||||
scope[param.Name] = true
|
|
||||||
}
|
}
|
||||||
for name := range fields {
|
|
||||||
scope[name] = true
|
|
||||||
}
|
|
||||||
var diagnostics []diagnostic
|
|
||||||
var walkExpr func(expr lang.Expr, scope map[string]bool)
|
|
||||||
var walkStmts func(stmts []lang.Stmt, scope map[string]bool)
|
|
||||||
|
|
||||||
walkStmts = func(stmts []lang.Stmt, scope map[string]bool) {
|
|
||||||
for _, stmt := range stmts {
|
|
||||||
switch s := stmt.(type) {
|
|
||||||
case lang.VarDecl:
|
|
||||||
walkExpr(s.Value, scope)
|
|
||||||
scope[s.Name] = true
|
|
||||||
case lang.MultiVarDecl:
|
|
||||||
walkExpr(s.Value, scope)
|
|
||||||
for _, name := range s.Names {
|
|
||||||
scope[name] = true
|
|
||||||
}
|
|
||||||
case lang.AssignStmt:
|
|
||||||
if !scope[s.Name] {
|
|
||||||
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, s.Name, "undefined variable "+s.Name))
|
|
||||||
}
|
|
||||||
walkExpr(s.Value, scope)
|
|
||||||
case lang.AddAssignStmt:
|
|
||||||
if !scope[s.Name] {
|
|
||||||
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, s.Name, "undefined variable "+s.Name))
|
|
||||||
}
|
|
||||||
walkExpr(s.Value, scope)
|
|
||||||
case lang.MultiAssignStmt:
|
|
||||||
for _, name := range s.Names {
|
|
||||||
if !scope[name] {
|
|
||||||
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, name, "undefined variable "+name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
walkExpr(s.Value, scope)
|
|
||||||
case lang.ReturnStmt:
|
|
||||||
if s.Value != nil {
|
|
||||||
walkExpr(s.Value, scope)
|
|
||||||
}
|
|
||||||
case lang.ThrowStmt:
|
|
||||||
walkExpr(s.Value, scope)
|
|
||||||
case lang.DeferStmt:
|
|
||||||
walkExpr(s.Value, scope)
|
|
||||||
case lang.ExprStmt:
|
|
||||||
walkExpr(s.Value, scope)
|
|
||||||
case lang.IfStmt:
|
|
||||||
walkExpr(s.Cond, scope)
|
|
||||||
thenScope := copyScope(scope)
|
|
||||||
elseScope := copyScope(scope)
|
|
||||||
walkStmts(s.Then, thenScope)
|
|
||||||
walkStmts(s.Else, elseScope)
|
|
||||||
case lang.WhileStmt:
|
|
||||||
walkExpr(s.Cond, scope)
|
|
||||||
bodyScope := copyScope(scope)
|
|
||||||
walkStmts(s.Body, bodyScope)
|
|
||||||
case lang.ForEachStmt:
|
|
||||||
walkExpr(s.Source, scope)
|
|
||||||
bodyScope := copyScope(scope)
|
|
||||||
bodyScope[s.Name] = true
|
|
||||||
walkStmts(s.Body, bodyScope)
|
|
||||||
case lang.MatchStmt:
|
|
||||||
walkExpr(s.Value, scope)
|
|
||||||
for _, matchCase := range s.Cases {
|
|
||||||
caseScope := copyScope(scope)
|
|
||||||
for _, binding := range matchCase.Bindings {
|
|
||||||
caseScope[binding] = true
|
|
||||||
}
|
|
||||||
walkStmts(matchCase.Body, caseScope)
|
|
||||||
}
|
|
||||||
case lang.TryCatchStmt:
|
|
||||||
tryScope := copyScope(scope)
|
|
||||||
walkStmts(s.TryBody, tryScope)
|
|
||||||
catchScope := copyScope(scope)
|
|
||||||
catchScope[s.CatchName] = true
|
|
||||||
walkStmts(s.CatchBody, catchScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
walkExpr = func(expr lang.Expr, scope map[string]bool) {
|
|
||||||
switch e := expr.(type) {
|
|
||||||
case lang.IdentExpr:
|
|
||||||
if isBuiltin(e.Name) || scope[e.Name] || functions[e.Name].Name != "" || imports[e.Name] || types[e.Name] {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, e.Name, "undefined identifier "+e.Name))
|
|
||||||
case lang.UnaryExpr:
|
|
||||||
walkExpr(e.Value, scope)
|
|
||||||
case lang.BinaryExpr:
|
|
||||||
walkExpr(e.Left, scope)
|
|
||||||
walkExpr(e.Right, scope)
|
|
||||||
case lang.CallExpr:
|
|
||||||
walkExpr(e.Callee, scope)
|
|
||||||
for _, arg := range e.Args {
|
|
||||||
walkExpr(arg, scope)
|
|
||||||
}
|
|
||||||
case lang.SelectorExpr:
|
|
||||||
walkExpr(e.Receiver, scope)
|
|
||||||
case lang.SafeSelectorExpr:
|
|
||||||
walkExpr(e.Receiver, scope)
|
|
||||||
case lang.NonNullExpr:
|
|
||||||
walkExpr(e.Value, scope)
|
|
||||||
case lang.IndexExpr:
|
|
||||||
walkExpr(e.Receiver, scope)
|
|
||||||
walkExpr(e.Index, scope)
|
|
||||||
case lang.EnumVariantExpr:
|
|
||||||
for _, value := range e.Values {
|
|
||||||
walkExpr(value, scope)
|
|
||||||
}
|
|
||||||
case lang.LambdaExpr:
|
|
||||||
lambdaScope := copyScope(scope)
|
|
||||||
if e.ImplicitIt {
|
|
||||||
lambdaScope["it"] = true
|
|
||||||
}
|
|
||||||
for _, param := range e.Params {
|
|
||||||
lambdaScope[param.Name] = true
|
|
||||||
}
|
|
||||||
walkStmts(e.Body, lambdaScope)
|
|
||||||
case lang.NullExpr:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
walkStmts(fn.Body, scope)
|
|
||||||
return diagnostics
|
return diagnostics
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -973,25 +772,6 @@ func diagnosticFromError(text string, err error) diagnostic {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func duplicateDiagnostic(r rng, message string) diagnostic {
|
|
||||||
return diagnostic{
|
|
||||||
Range: r,
|
|
||||||
Severity: diagnosticSeverityWarning,
|
|
||||||
Source: "gotlin",
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func undefinedNameDiagnostic(text string, name string, message string) diagnostic {
|
|
||||||
r := findWordRange(text, name)
|
|
||||||
return diagnostic{
|
|
||||||
Range: r,
|
|
||||||
Severity: diagnosticSeverityWarning,
|
|
||||||
Source: "gotlin",
|
|
||||||
Message: message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func offsetToPosition(text string, offset int) position {
|
func offsetToPosition(text string, offset int) position {
|
||||||
runes := []rune(text)
|
runes := []rune(text)
|
||||||
if offset < 0 {
|
if offset < 0 {
|
||||||
|
|
@ -1221,7 +1001,11 @@ func renderImportDetail(imp lang.ImportDecl) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderFunctionSignature(fn lang.FunctionDecl) string {
|
func renderFunctionSignature(fn lang.FunctionDecl) string {
|
||||||
return renderFunctionSignatureFromParts(fn.Name, fn.Params, fn.ReturnType)
|
name := fn.Name
|
||||||
|
if len(fn.TypeParams) > 0 {
|
||||||
|
name += "<" + strings.Join(fn.TypeParams, ", ") + ">"
|
||||||
|
}
|
||||||
|
return renderFunctionSignatureFromParts(name, fn.Params, fn.ReturnType)
|
||||||
}
|
}
|
||||||
|
|
||||||
func renderFunctionSignatureFromParts(name string, fnParams []lang.Param, returnType string) string {
|
func renderFunctionSignatureFromParts(name string, fnParams []lang.Param, returnType string) string {
|
||||||
|
|
@ -1244,6 +1028,11 @@ func renderClassSignature(decl lang.ClassDecl) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString("class ")
|
b.WriteString("class ")
|
||||||
b.WriteString(decl.Name)
|
b.WriteString(decl.Name)
|
||||||
|
if len(decl.TypeParams) > 0 {
|
||||||
|
b.WriteString("<")
|
||||||
|
b.WriteString(strings.Join(decl.TypeParams, ", "))
|
||||||
|
b.WriteString(">")
|
||||||
|
}
|
||||||
|
|
||||||
var fields []string
|
var fields []string
|
||||||
for _, field := range decl.Fields {
|
for _, field := range decl.Fields {
|
||||||
|
|
@ -1289,289 +1078,59 @@ func renderVariableSignature(name string, mutable bool, typ string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectVariableDeclInfos(program *lang.Program) []variableDeclInfo {
|
func collectVariableDeclInfos(program *lang.Program) []variableDeclInfo {
|
||||||
functions := map[string]lang.FunctionDecl{}
|
var result []variableDeclInfo
|
||||||
for _, fn := range program.Functions {
|
var walk func([]lang.Stmt)
|
||||||
functions[fn.Name] = fn
|
walk = func(statements []lang.Stmt) {
|
||||||
}
|
for _, statement := range statements {
|
||||||
classes := map[string]lang.ClassDecl{}
|
switch value := statement.(type) {
|
||||||
for _, class := range program.Classes {
|
|
||||||
classes[class.Name] = class
|
|
||||||
}
|
|
||||||
|
|
||||||
var out []variableDeclInfo
|
|
||||||
var walkStmts func(stmts []lang.Stmt, scope map[string]string)
|
|
||||||
var inferExprType func(expr lang.Expr, scope map[string]string) string
|
|
||||||
var inferCallReturnTypes func(call lang.CallExpr, scope map[string]string) []string
|
|
||||||
|
|
||||||
inferCallReturnTypes = func(call lang.CallExpr, scope map[string]string) []string {
|
|
||||||
switch callee := call.Callee.(type) {
|
|
||||||
case lang.IdentExpr:
|
|
||||||
if fn, ok := functions[callee.Name]; ok {
|
|
||||||
return []string{fn.ReturnType}
|
|
||||||
}
|
|
||||||
if _, ok := classes[callee.Name]; ok {
|
|
||||||
return []string{callee.Name}
|
|
||||||
}
|
|
||||||
switch callee.Name {
|
|
||||||
case "println":
|
|
||||||
return []string{"Unit"}
|
|
||||||
case "Channel":
|
|
||||||
if len(call.TypeArgs) == 1 {
|
|
||||||
return []string{"Channel<" + call.TypeArgs[0] + ">"}
|
|
||||||
}
|
|
||||||
return []string{"Channel<any>"}
|
|
||||||
case "listOf", "mutableListOf":
|
|
||||||
if len(call.TypeArgs) == 1 {
|
|
||||||
return []string{"List<" + call.TypeArgs[0] + ">"}
|
|
||||||
}
|
|
||||||
if len(call.Args) > 0 {
|
|
||||||
elemType := inferExprType(call.Args[0], scope)
|
|
||||||
for i := 1; i < len(call.Args); i++ {
|
|
||||||
argType := inferExprType(call.Args[i], scope)
|
|
||||||
if argType == "" || elemType == "" || argType != elemType {
|
|
||||||
elemType = ""
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if elemType != "" {
|
|
||||||
return []string{"List<" + elemType + ">"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []string{"List<any>"}
|
|
||||||
case "mapOf", "mutableMapOf":
|
|
||||||
if len(call.TypeArgs) == 2 {
|
|
||||||
return []string{"Map<" + call.TypeArgs[0] + ", " + call.TypeArgs[1] + ">"}
|
|
||||||
}
|
|
||||||
if len(call.Args) > 0 && len(call.Args)%2 == 0 {
|
|
||||||
keyType := inferExprType(call.Args[0], scope)
|
|
||||||
valType := inferExprType(call.Args[1], scope)
|
|
||||||
for i := 2; i < len(call.Args); i += 2 {
|
|
||||||
nextKeyType := inferExprType(call.Args[i], scope)
|
|
||||||
nextValType := inferExprType(call.Args[i+1], scope)
|
|
||||||
if keyType == "" || nextKeyType == "" || keyType != nextKeyType {
|
|
||||||
keyType = ""
|
|
||||||
}
|
|
||||||
if valType == "" || nextValType == "" || valType != nextValType {
|
|
||||||
valType = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if keyType != "" && valType != "" {
|
|
||||||
return []string{"Map<" + keyType + ", " + valType + ">"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []string{"Map<any, any>"}
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
case lang.SelectorExpr:
|
|
||||||
receiverType := inferExprType(callee.Receiver, scope)
|
|
||||||
if callee.Name == "read" {
|
|
||||||
if elemType, ok := channelElementType(receiverType); ok {
|
|
||||||
return []string{elemType}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if callee.Name == "send" {
|
|
||||||
return []string{"Unit"}
|
|
||||||
}
|
|
||||||
switch receiverType {
|
|
||||||
case "*sql.DB", "sql.DB":
|
|
||||||
switch callee.Name {
|
|
||||||
case "Ping":
|
|
||||||
return []string{"error"}
|
|
||||||
}
|
|
||||||
case "*bun.DB", "bun.DB":
|
|
||||||
switch callee.Name {
|
|
||||||
case "NewSelect":
|
|
||||||
return []string{"*bun.SelectQuery"}
|
|
||||||
case "NewCreateTable":
|
|
||||||
return []string{"*bun.CreateTableQuery"}
|
|
||||||
case "NewInsert":
|
|
||||||
return []string{"*bun.InsertQuery"}
|
|
||||||
}
|
|
||||||
case "*bun.SelectQuery", "bun.SelectQuery":
|
|
||||||
switch callee.Name {
|
|
||||||
case "ColumnExpr", "Model":
|
|
||||||
return []string{"*bun.SelectQuery"}
|
|
||||||
case "Scan":
|
|
||||||
return []string{"error"}
|
|
||||||
case "Count":
|
|
||||||
return []string{"Int", "error"}
|
|
||||||
}
|
|
||||||
case "*bun.CreateTableQuery", "bun.CreateTableQuery":
|
|
||||||
switch callee.Name {
|
|
||||||
case "Model", "IfNotExists":
|
|
||||||
return []string{"*bun.CreateTableQuery"}
|
|
||||||
case "Exec":
|
|
||||||
return []string{"sql.Result", "error"}
|
|
||||||
}
|
|
||||||
case "*bun.InsertQuery", "bun.InsertQuery":
|
|
||||||
switch callee.Name {
|
|
||||||
case "Model":
|
|
||||||
return []string{"*bun.InsertQuery"}
|
|
||||||
case "Exec":
|
|
||||||
return []string{"sql.Result", "error"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if path, ok := selectorPathLang(call.Callee); ok {
|
|
||||||
switch path {
|
|
||||||
case "context.Background":
|
|
||||||
return []string{"context.Context"}
|
|
||||||
case "sql.OpenDB":
|
|
||||||
return []string{"*sql.DB"}
|
|
||||||
case "bun.NewDB":
|
|
||||||
return []string{"*bun.DB"}
|
|
||||||
case "pgdriver.NewConnector":
|
|
||||||
return []string{"pgdriver.Connector"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
inferExprType = func(expr lang.Expr, scope map[string]string) string {
|
|
||||||
switch e := expr.(type) {
|
|
||||||
case lang.IntExpr:
|
|
||||||
return "Int"
|
|
||||||
case lang.StringExpr:
|
|
||||||
return "String"
|
|
||||||
case lang.BoolExpr:
|
|
||||||
return "Boolean"
|
|
||||||
case lang.NullExpr:
|
|
||||||
return "null"
|
|
||||||
case lang.IdentExpr:
|
|
||||||
return scope[e.Name]
|
|
||||||
case lang.CallExpr:
|
|
||||||
types := inferCallReturnTypes(e, scope)
|
|
||||||
if len(types) > 0 {
|
|
||||||
return types[0]
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
case lang.SelectorExpr:
|
|
||||||
receiverType := inferExprType(e.Receiver, scope)
|
|
||||||
if receiverType == "" {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
trimmed := strings.TrimPrefix(receiverType, "*")
|
|
||||||
if class, ok := classes[trimmed]; ok {
|
|
||||||
for _, field := range class.Fields {
|
|
||||||
if field.Name == e.Name {
|
|
||||||
return field.Type
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
walkStmts = func(stmts []lang.Stmt, scope map[string]string) {
|
|
||||||
for _, stmt := range stmts {
|
|
||||||
switch s := stmt.(type) {
|
|
||||||
case lang.VarDecl:
|
case lang.VarDecl:
|
||||||
typ := s.Type
|
typ := value.Type
|
||||||
if typ == "" {
|
if typ == "" {
|
||||||
resolved := lang.ResolvedType(s.Value)
|
resolved := lang.ResolvedType(value.Value)
|
||||||
if resolved.String() != "<unknown>" {
|
if resolved.String() != "<unknown>" {
|
||||||
typ = resolved.String()
|
typ = resolved.String()
|
||||||
} else {
|
|
||||||
typ = inferExprType(s.Value, scope)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
out = append(out, variableDeclInfo{Name: s.Name, Mutable: s.Mutable, Type: typ})
|
result = append(result, variableDeclInfo{Name: value.Name, Mutable: value.Mutable, Type: typ})
|
||||||
scope[s.Name] = typ
|
|
||||||
case lang.MultiVarDecl:
|
case lang.MultiVarDecl:
|
||||||
types := []string(nil)
|
types := make([]string, len(value.Names))
|
||||||
if call, ok := s.Value.(lang.CallExpr); ok {
|
if fallible, ok := lang.ResolvedType(value.Value).(lang.GenericType); ok && fallible.Base.String() == "Result" && len(fallible.Args) == 2 && len(types) == 2 {
|
||||||
types = inferCallReturnTypes(call, scope)
|
types[0] = fallible.Args[0].String()
|
||||||
|
types[1] = fallible.Args[1].String() + "?"
|
||||||
|
} else if tuple, ok := lang.ResolvedType(value.Value).(lang.TupleType); ok && len(tuple.Elements) == len(types) {
|
||||||
|
for index, element := range tuple.Elements {
|
||||||
|
types[index] = element.String()
|
||||||
}
|
}
|
||||||
for i, name := range s.Names {
|
|
||||||
typ := ""
|
|
||||||
if i < len(types) {
|
|
||||||
typ = types[i]
|
|
||||||
}
|
}
|
||||||
out = append(out, variableDeclInfo{Name: name, Mutable: s.Mutable, Type: typ})
|
for index, name := range value.Names {
|
||||||
scope[name] = typ
|
result = append(result, variableDeclInfo{Name: name, Mutable: value.Mutable, Type: types[index]})
|
||||||
}
|
|
||||||
case lang.AddAssignStmt:
|
|
||||||
if _, ok := scope[s.Name]; !ok {
|
|
||||||
scope[s.Name] = ""
|
|
||||||
}
|
}
|
||||||
case lang.IfStmt:
|
case lang.IfStmt:
|
||||||
thenScope := copyTypeScope(scope)
|
walk(value.Then)
|
||||||
elseScope := copyTypeScope(scope)
|
walk(value.Else)
|
||||||
walkStmts(s.Then, thenScope)
|
|
||||||
walkStmts(s.Else, elseScope)
|
|
||||||
case lang.WhileStmt:
|
case lang.WhileStmt:
|
||||||
bodyScope := copyTypeScope(scope)
|
walk(value.Body)
|
||||||
walkStmts(s.Body, bodyScope)
|
case lang.ForEachStmt:
|
||||||
|
walk(value.Body)
|
||||||
|
case lang.MatchStmt:
|
||||||
|
for _, matchCase := range value.Cases {
|
||||||
|
walk(matchCase.Body)
|
||||||
|
}
|
||||||
case lang.TryCatchStmt:
|
case lang.TryCatchStmt:
|
||||||
tryScope := copyTypeScope(scope)
|
walk(value.TryBody)
|
||||||
walkStmts(s.TryBody, tryScope)
|
walk(value.CatchBody)
|
||||||
catchScope := copyTypeScope(scope)
|
|
||||||
catchScope[s.CatchName] = s.CatchType
|
|
||||||
walkStmts(s.CatchBody, catchScope)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, function := range program.Functions {
|
||||||
for _, fn := range program.Functions {
|
walk(function.Body)
|
||||||
scope := map[string]string{}
|
|
||||||
for _, param := range fn.Params {
|
|
||||||
scope[param.Name] = param.Type
|
|
||||||
}
|
|
||||||
walkStmts(fn.Body, scope)
|
|
||||||
}
|
}
|
||||||
for _, class := range program.Classes {
|
for _, class := range program.Classes {
|
||||||
fieldScope := map[string]string{}
|
|
||||||
for _, field := range class.Fields {
|
|
||||||
fieldScope[field.Name] = field.Type
|
|
||||||
}
|
|
||||||
for _, method := range class.Methods {
|
for _, method := range class.Methods {
|
||||||
scope := copyTypeScope(fieldScope)
|
walk(method.Body)
|
||||||
for _, param := range method.Params {
|
|
||||||
scope[param.Name] = param.Type
|
|
||||||
}
|
|
||||||
walkStmts(method.Body, scope)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out
|
return result
|
||||||
}
|
|
||||||
|
|
||||||
func copyTypeScope(scope map[string]string) map[string]string {
|
|
||||||
dup := make(map[string]string, len(scope))
|
|
||||||
for k, v := range scope {
|
|
||||||
dup[k] = v
|
|
||||||
}
|
|
||||||
return dup
|
|
||||||
}
|
|
||||||
|
|
||||||
func channelElementType(typ string) (string, bool) {
|
|
||||||
typ = strings.TrimSpace(typ)
|
|
||||||
if !strings.HasPrefix(typ, "Channel<") || !strings.HasSuffix(typ, ">") {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
inner := strings.TrimSpace(strings.TrimSuffix(strings.TrimPrefix(typ, "Channel<"), ">"))
|
|
||||||
if inner == "" {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return inner, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func selectorPathLang(expr lang.Expr) (string, bool) {
|
|
||||||
switch e := expr.(type) {
|
|
||||||
case lang.IdentExpr:
|
|
||||||
return e.Name, true
|
|
||||||
case lang.SelectorExpr:
|
|
||||||
left, ok := selectorPathLang(e.Receiver)
|
|
||||||
if !ok {
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
return left + "." + e.Name, true
|
|
||||||
default:
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func resolveStdlibTarget(state documentState, pos position) (stdlibTarget, bool) {
|
func resolveStdlibTarget(state documentState, pos position) (stdlibTarget, bool) {
|
||||||
|
|
|
||||||
|
|
@ -450,6 +450,21 @@ fun main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuildDocumentStateUsesStructuredSemanticDiagnostics(t *testing.T) {
|
||||||
|
text := "package demo\nfun main() { println(missing) }"
|
||||||
|
state := buildDocumentState(text)
|
||||||
|
if len(state.diagnostics) != 1 {
|
||||||
|
t.Fatalf("diagnostics = %#v", state.diagnostics)
|
||||||
|
}
|
||||||
|
diagnostic := state.diagnostics[0]
|
||||||
|
if diagnostic.Code != "undefined-symbol" || diagnostic.Severity != diagnosticSeverityError || diagnostic.Message != "undefined identifier missing" {
|
||||||
|
t.Fatalf("unexpected diagnostic: %#v", diagnostic)
|
||||||
|
}
|
||||||
|
if diagnostic.Range.Start.Line != 1 || diagnostic.Range.Start.Character != 21 {
|
||||||
|
t.Fatalf("unexpected diagnostic range: %#v", diagnostic.Range)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuildDocumentStateInfersHttpServerVariableTypes(t *testing.T) {
|
func TestBuildDocumentStateInfersHttpServerVariableTypes(t *testing.T) {
|
||||||
text := strings.TrimSpace(`
|
text := strings.TrimSpace(`
|
||||||
package demo
|
package demo
|
||||||
|
|
@ -460,7 +475,7 @@ import github.com.uptrace.bun
|
||||||
class C(val db: *bun.DB) {
|
class C(val db: *bun.DB) {
|
||||||
fun run() {
|
fun run() {
|
||||||
val ctx = context.Background()
|
val ctx = context.Background()
|
||||||
val total = db.NewSelect().ColumnExpr("1").Count(ctx)
|
val total = db.NewSelect().ColumnExpr("1").Count(ctx).unwrap()
|
||||||
println(total)
|
println(total)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
examples/generics.gt
Normal file
15
examples/generics.gt
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
data class Box<T>(var value: T) {
|
||||||
|
fun get(): T { return value }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun identity<T>(value: T): T { return value }
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val number = identity(42)
|
||||||
|
val text = identity<String>("value")
|
||||||
|
val box = Box(text)
|
||||||
|
println(number)
|
||||||
|
println(box.get())
|
||||||
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ class EpicControllerImpl(val db: *bun.DB) {
|
||||||
db.NewCreateTable().Model(model).IfNotExists().Exec(ctx)
|
db.NewCreateTable().Model(model).IfNotExists().Exec(ctx)
|
||||||
val user = BunUser("user-from-gotlin")
|
val user = BunUser("user-from-gotlin")
|
||||||
db.NewInsert().Model(user).Exec(ctx)
|
db.NewInsert().Model(user).Exec(ctx)
|
||||||
val total = db.NewSelect().Model(model).Count(ctx)
|
val total = db.NewSelect().Model(model).Count(ctx).unwrap()
|
||||||
|
|
||||||
fmt.Fprintln(w, "bun users total:", total)
|
fmt.Fprintln(w, "bun users total:", total)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
go.mod
10
go.mod
|
|
@ -2,7 +2,10 @@ module gotlin
|
||||||
|
|
||||||
go 1.25.6
|
go 1.25.6
|
||||||
|
|
||||||
require github.com/jackc/pgx/v5 v5.7.6
|
require (
|
||||||
|
github.com/jackc/pgx/v5 v5.7.6
|
||||||
|
golang.org/x/tools v0.49.0
|
||||||
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
|
@ -21,8 +24,9 @@ require (
|
||||||
go.opentelemetry.io/otel v1.40.0 // indirect
|
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||||
golang.org/x/crypto v0.48.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/mod v0.39.0 // indirect
|
||||||
golang.org/x/sys v0.41.0 // indirect
|
golang.org/x/sync v0.22.0 // indirect
|
||||||
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
golang.org/x/text v0.34.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
mellium.im/sasl v0.3.2 // indirect
|
mellium.im/sasl v0.3.2 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
7
go.sum
7
go.sum
|
|
@ -37,12 +37,19 @@ go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZY
|
||||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
|
golang.org/x/mod v0.39.0 h1:UF5zwQdCRRUpHfyPwr7d4UrGiVeldIsogtzWVnczL74=
|
||||||
|
golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||||
|
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
|
golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
|
||||||
|
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=
|
mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,10 @@ type Program struct {
|
||||||
Embeds []EmbedDecl
|
Embeds []EmbedDecl
|
||||||
}
|
}
|
||||||
|
|
||||||
type EmbedDecl struct{ Path, Name, Type string }
|
type EmbedDecl struct {
|
||||||
|
Path, Name, Type string
|
||||||
|
TypeRef TypeRef
|
||||||
|
}
|
||||||
|
|
||||||
type EnumDecl struct {
|
type EnumDecl struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
@ -20,6 +23,7 @@ type EnumDecl struct {
|
||||||
type EnumVariant struct {
|
type EnumVariant struct {
|
||||||
Name string
|
Name string
|
||||||
PayloadTypes []string
|
PayloadTypes []string
|
||||||
|
PayloadRefs []TypeRef
|
||||||
StringValue string
|
StringValue string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,6 +39,7 @@ type InterfaceDecl struct {
|
||||||
|
|
||||||
type ClassDecl struct {
|
type ClassDecl struct {
|
||||||
Name string
|
Name string
|
||||||
|
TypeParams []string
|
||||||
Data bool
|
Data bool
|
||||||
JSONNaming string
|
JSONNaming string
|
||||||
Table string
|
Table string
|
||||||
|
|
@ -48,6 +53,7 @@ type FieldDecl struct {
|
||||||
Private bool
|
Private bool
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
|
TypeRef TypeRef
|
||||||
Column string
|
Column string
|
||||||
ID bool
|
ID bool
|
||||||
Generated bool
|
Generated bool
|
||||||
|
|
@ -55,15 +61,19 @@ type FieldDecl struct {
|
||||||
|
|
||||||
type FunctionSignature struct {
|
type FunctionSignature struct {
|
||||||
Name string
|
Name string
|
||||||
|
TypeParams []string
|
||||||
Params []Param
|
Params []Param
|
||||||
ReturnType string
|
ReturnType string
|
||||||
|
ReturnRef TypeRef
|
||||||
Suspend bool
|
Suspend bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type FunctionDecl struct {
|
type FunctionDecl struct {
|
||||||
Name string
|
Name string
|
||||||
|
TypeParams []string
|
||||||
Params []Param
|
Params []Param
|
||||||
ReturnType string
|
ReturnType string
|
||||||
|
ReturnRef TypeRef
|
||||||
Body []Stmt
|
Body []Stmt
|
||||||
Suspend bool
|
Suspend bool
|
||||||
}
|
}
|
||||||
|
|
@ -71,6 +81,7 @@ type FunctionDecl struct {
|
||||||
type Param struct {
|
type Param struct {
|
||||||
Name string
|
Name string
|
||||||
Type string
|
Type string
|
||||||
|
TypeRef TypeRef
|
||||||
}
|
}
|
||||||
|
|
||||||
type Stmt interface {
|
type Stmt interface {
|
||||||
|
|
@ -84,7 +95,9 @@ type Expr interface {
|
||||||
type VarDecl struct {
|
type VarDecl struct {
|
||||||
Mutable bool
|
Mutable bool
|
||||||
Name string
|
Name string
|
||||||
|
Pos int
|
||||||
Type string
|
Type string
|
||||||
|
TypeRef TypeRef
|
||||||
Value Expr
|
Value Expr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -173,6 +186,7 @@ type TryCatchStmt struct {
|
||||||
TryBody []Stmt
|
TryBody []Stmt
|
||||||
CatchName string
|
CatchName string
|
||||||
CatchType string
|
CatchType string
|
||||||
|
CatchRef TypeRef
|
||||||
CatchBody []Stmt
|
CatchBody []Stmt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -208,6 +222,7 @@ type MatchExprCase struct {
|
||||||
type IdentExpr struct {
|
type IdentExpr struct {
|
||||||
Meta ExprMeta
|
Meta ExprMeta
|
||||||
Name string
|
Name string
|
||||||
|
Pos int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (IdentExpr) exprNode() {}
|
func (IdentExpr) exprNode() {}
|
||||||
|
|
@ -280,6 +295,7 @@ type SelectorExpr struct {
|
||||||
Meta ExprMeta
|
Meta ExprMeta
|
||||||
Receiver Expr
|
Receiver Expr
|
||||||
Name string
|
Name string
|
||||||
|
NamePos int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (SelectorExpr) exprNode() {}
|
func (SelectorExpr) exprNode() {}
|
||||||
|
|
@ -288,6 +304,7 @@ type SafeSelectorExpr struct {
|
||||||
Meta ExprMeta
|
Meta ExprMeta
|
||||||
Receiver Expr
|
Receiver Expr
|
||||||
Name string
|
Name string
|
||||||
|
NamePos int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (SafeSelectorExpr) exprNode() {}
|
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("func ")
|
||||||
g.write(fn.Name)
|
g.write(fn.Name)
|
||||||
|
g.write(renderGoTypeParameters(fn.TypeParams))
|
||||||
g.write(g.renderGoFunctionParams(fn))
|
g.write(g.renderGoFunctionParams(fn))
|
||||||
if ret := g.goReturnType(fn.ReturnType); ret != "" {
|
if ret := g.goReturnType(fn.ReturnType); ret != "" {
|
||||||
g.write(" ")
|
g.write(" ")
|
||||||
|
|
@ -346,7 +347,7 @@ func enumVariant(decl EnumDecl, name string) *EnumVariant {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *goGenerator) classDecl(class ClassDecl) error {
|
func (g *goGenerator) classDecl(class ClassDecl) error {
|
||||||
g.line("type " + class.Name + " struct {")
|
g.line("type " + class.Name + renderGoTypeParameters(class.TypeParams) + " struct {")
|
||||||
g.indentLevel++
|
g.indentLevel++
|
||||||
for _, field := range class.Fields {
|
for _, field := range class.Fields {
|
||||||
name := field.Name
|
name := field.Name
|
||||||
|
|
@ -366,6 +367,7 @@ func (g *goGenerator) classDecl(class ClassDecl) error {
|
||||||
g.line("")
|
g.line("")
|
||||||
g.write("func New")
|
g.write("func New")
|
||||||
g.write(class.Name)
|
g.write(class.Name)
|
||||||
|
g.write(renderGoTypeParameters(class.TypeParams))
|
||||||
g.write("(")
|
g.write("(")
|
||||||
for i, field := range class.Fields {
|
for i, field := range class.Fields {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
|
|
@ -377,11 +379,13 @@ func (g *goGenerator) classDecl(class ClassDecl) error {
|
||||||
}
|
}
|
||||||
g.write(") *")
|
g.write(") *")
|
||||||
g.write(class.Name)
|
g.write(class.Name)
|
||||||
|
g.write(renderGoTypeArguments(class.TypeParams))
|
||||||
g.write(" {\n")
|
g.write(" {\n")
|
||||||
g.indentLevel++
|
g.indentLevel++
|
||||||
g.writeIndent()
|
g.writeIndent()
|
||||||
g.write("return &")
|
g.write("return &")
|
||||||
g.write(class.Name)
|
g.write(class.Name)
|
||||||
|
g.write(renderGoTypeArguments(class.TypeParams))
|
||||||
g.write("{")
|
g.write("{")
|
||||||
for i, field := range class.Fields {
|
for i, field := range class.Fields {
|
||||||
if i > 0 {
|
if i > 0 {
|
||||||
|
|
@ -427,6 +431,7 @@ func (g *goGenerator) method(class ClassDecl, fn FunctionDecl) error {
|
||||||
|
|
||||||
g.write("func (self *")
|
g.write("func (self *")
|
||||||
g.write(class.Name)
|
g.write(class.Name)
|
||||||
|
g.write(renderGoTypeArguments(class.TypeParams))
|
||||||
g.write(") ")
|
g.write(") ")
|
||||||
g.write(fn.Name)
|
g.write(fn.Name)
|
||||||
g.write(g.renderGoFunctionParams(fn))
|
g.write(g.renderGoFunctionParams(fn))
|
||||||
|
|
@ -792,8 +797,8 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
|
||||||
if e.Name == "this" {
|
if e.Name == "this" {
|
||||||
return "self", nil
|
return "self", nil
|
||||||
}
|
}
|
||||||
if g.classField(*g.currentClass, e.Name) && !g.isDefined(e.Name) {
|
if field, found := classFieldByName(*g.currentClass, e.Name); found && !g.isDefined(e.Name) {
|
||||||
return "self." + e.Name, nil
|
return "self." + mappingFieldName(*g.currentClass, field), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return e.Name, 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 ident, ok := e.Callee.(IdentExpr); ok {
|
||||||
if _, ok := g.semantic.Classes[ident.Name]; ok && (resolvedCall == nil || resolvedCall.Meaning == ClassConstructionExpr) {
|
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 {
|
if ident, ok := e.Callee.(IdentExpr); ok {
|
||||||
|
|
@ -1217,6 +1222,9 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
if len(e.TypeArgs) > 0 {
|
||||||
|
callee += g.renderCallTypeArguments(e.TypeArgs)
|
||||||
|
}
|
||||||
call := fmt.Sprintf("%s(%s)", callee, strings.Join(args, ", "))
|
call := fmt.Sprintf("%s(%s)", callee, strings.Join(args, ", "))
|
||||||
if result, ok := g.goErrorResult(e, call, expectedType); ok {
|
if result, ok := g.goErrorResult(e, call, expectedType); ok {
|
||||||
return result, nil
|
return result, nil
|
||||||
|
|
@ -2460,18 +2468,11 @@ func (g *goGenerator) isDefined(name string) bool {
|
||||||
return false
|
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 {
|
func (g *goGenerator) assignTarget(name string) string {
|
||||||
if g.currentClass != nil && g.classField(*g.currentClass, name) && !g.isDefined(name) {
|
if g.currentClass != nil && !g.isDefined(name) {
|
||||||
return "self." + name
|
if field, found := classFieldByName(*g.currentClass, name); found {
|
||||||
|
return "self." + mappingFieldName(*g.currentClass, field)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
@ -2496,6 +2497,35 @@ func (g *goGenerator) renderGoFunctionParams(function FunctionDecl) string {
|
||||||
return g.renderGoParamsWithPrefix(function.Params, prefix)
|
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 {
|
func (g *goGenerator) renderGoParamsWithPrefix(params []Param, prefix string) string {
|
||||||
var b strings.Builder
|
var b strings.Builder
|
||||||
b.WriteString("(")
|
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 http net.http
|
||||||
import json encoding.json
|
import json encoding.json
|
||||||
|
|
||||||
fun main() {
|
fun main(handler: (http.ResponseWriter, *http.Request) -> Unit, body: ByteSlice, target: Any) {
|
||||||
http.handleFunc("/health", handler)
|
http.handleFunc("/health", handler)
|
||||||
json.unmarshal(body, &target)
|
json.unmarshal(body, &target)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,32 @@ import (
|
||||||
"go/importer"
|
"go/importer"
|
||||||
gotypes "go/types"
|
gotypes "go/types"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
golangpackages "golang.org/x/tools/go/packages"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var importedGoPackages sync.Map
|
||||||
|
|
||||||
func importGoPackage(decl ImportDecl) (*gotypes.Package, error) {
|
func importGoPackage(decl ImportDecl) (*gotypes.Package, error) {
|
||||||
path := strings.Trim(decl.Path, `"`)
|
path := strings.Trim(decl.Path, `"`)
|
||||||
if !strings.Contains(path, "/") {
|
if !strings.Contains(path, "/") {
|
||||||
path = importPathToGoPath(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 {
|
func (semantic *SemanticProgram) goSelectorType(alias, name string) Type {
|
||||||
|
|
@ -119,7 +137,11 @@ func semanticTypeFromGoResults(results *gotypes.Tuple) Type {
|
||||||
if results.Len() == 1 {
|
if results.Len() == 1 {
|
||||||
return last
|
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 {
|
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 ExprMeta struct{ Semantic *HIRExpr }
|
||||||
|
|
||||||
type HIRExpr struct {
|
type HIRExpr struct {
|
||||||
|
ID int
|
||||||
Type Type
|
Type Type
|
||||||
Meaning ExprMeaning
|
Meaning ExprMeaning
|
||||||
Symbol *Symbol
|
Symbol *Symbol
|
||||||
|
|
@ -113,6 +114,7 @@ type HIRFunction struct {
|
||||||
type HIRProgram struct {
|
type HIRProgram struct {
|
||||||
Functions []*HIRFunction
|
Functions []*HIRFunction
|
||||||
Methods []*HIRFunction
|
Methods []*HIRFunction
|
||||||
|
Expressions []*HIRExpr
|
||||||
}
|
}
|
||||||
|
|
||||||
func exprMeta(expr Expr) *HIRExpr {
|
func exprMeta(expr Expr) *HIRExpr {
|
||||||
|
|
@ -240,10 +242,10 @@ func (resolver *semanticResolver) resolve() error {
|
||||||
symbol, _ := resolver.program.Global.Lookup(decl.Name)
|
symbol, _ := resolver.program.Global.Lookup(decl.Name)
|
||||||
scope := NewScope(resolver.program.Global)
|
scope := NewScope(resolver.program.Global)
|
||||||
for _, param := range decl.Params {
|
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})
|
_ = 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.resolveStmts(decl.Body, scope, nil, result)
|
||||||
resolver.program.HIR.Functions = append(resolver.program.HIR.Functions, &HIRFunction{Symbol: symbol, Decl: decl, Scope: scope})
|
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 := NewScope(resolver.program.Global)
|
||||||
_ = scope.Define(&Symbol{Name: "this", Kind: VariableSymbol, Type: ClassType{Class: class}})
|
_ = scope.Define(&Symbol{Name: "this", Kind: VariableSymbol, Type: ClassType{Class: class}})
|
||||||
for _, param := range method.Params {
|
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})
|
_ = 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.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})
|
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 {
|
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[0] = result.Args[0]
|
||||||
valueTypes[1] = NullableType{Element: result.Args[1]}
|
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 {
|
for index, name := range value.Names {
|
||||||
_ = scope.Define(&Symbol{Name: name, Kind: VariableSymbol, Type: valueTypes[index], Mutable: value.Mutable})
|
_ = 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{})
|
expected := Type(UnknownType{})
|
||||||
if symbol, ok := scope.Lookup(value.Name); ok {
|
if symbol, ok := scope.Lookup(value.Name); ok {
|
||||||
expected = symbol.Type
|
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)
|
value.Value, _ = resolver.resolveExpr(value.Value, scope, class, expected)
|
||||||
stmts[index] = value
|
stmts[index] = value
|
||||||
case AddAssignStmt:
|
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{})
|
value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{})
|
||||||
stmts[index] = value
|
stmts[index] = value
|
||||||
case MultiAssignStmt:
|
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{})
|
value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{})
|
||||||
stmts[index] = value
|
stmts[index] = value
|
||||||
case ReturnStmt:
|
case ReturnStmt:
|
||||||
|
|
@ -421,8 +448,11 @@ func (resolver *semanticResolver) resolveExpr(expr Expr, scope *Scope, class *Cl
|
||||||
expr = value
|
expr = value
|
||||||
case LambdaExpr:
|
case LambdaExpr:
|
||||||
lambdaScope := NewScope(scope)
|
lambdaScope := NewScope(scope)
|
||||||
|
if value.ImplicitIt {
|
||||||
|
_ = lambdaScope.Define(&Symbol{Name: "it", Kind: VariableSymbol, Type: UnknownType{}})
|
||||||
|
}
|
||||||
for _, param := range value.Params {
|
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})
|
_ = lambdaScope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
|
||||||
}
|
}
|
||||||
resolver.resolveStmts(value.Body, lambdaScope, class, functionResult(expected))
|
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)
|
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.validateEnumExpression(expr, meaning)
|
||||||
|
resolver.validateGenericCall(expr)
|
||||||
if meaning == GoCallExpr && isResultType(expected) {
|
if meaning == GoCallExpr && isResultType(expected) {
|
||||||
typ = expected
|
typ = expected
|
||||||
}
|
}
|
||||||
if nullable, ok := typ.(NullableType); ok && typeEqual(nullable.Element, expected) {
|
if nullable, ok := typ.(NullableType); ok && typeEqual(nullable.Element, expected) {
|
||||||
typ = 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)
|
semantic.Node = resolver.hirNode(expr, semantic)
|
||||||
|
resolver.program.HIR.Expressions = append(resolver.program.HIR.Expressions, semantic)
|
||||||
return withExprMeta(expr, semantic), typ
|
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) {
|
func (resolver *semanticResolver) validateEnumExpression(expr Expr, meaning ExprMeaning) {
|
||||||
if meaning != EnumConstructionExpr {
|
if meaning != EnumConstructionExpr {
|
||||||
return
|
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 {
|
func collectionElement(typ Type) Type {
|
||||||
if generic, ok := typ.(GenericType); ok && len(generic.Args) > 0 {
|
if generic, ok := typ.(GenericType); ok && len(generic.Args) > 0 {
|
||||||
return generic.Args[len(generic.Args)-1]
|
return generic.Args[len(generic.Args)-1]
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,14 @@ func Parse(input string) (*Program, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
p := &parser{tokens: tokens}
|
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) {
|
func (p *parser) parseProgram() (*Program, error) {
|
||||||
|
|
@ -292,6 +299,10 @@ func (p *parser) parseClass() (ClassDecl, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ClassDecl{}, err
|
return ClassDecl{}, err
|
||||||
}
|
}
|
||||||
|
typeParams, err := p.parseTypeParameters()
|
||||||
|
if err != nil {
|
||||||
|
return ClassDecl{}, err
|
||||||
|
}
|
||||||
var fields []FieldDecl
|
var fields []FieldDecl
|
||||||
if p.match(tokenLParen) {
|
if p.match(tokenLParen) {
|
||||||
fields, err = p.parseClassFields()
|
fields, err = p.parseClassFields()
|
||||||
|
|
@ -308,7 +319,7 @@ func (p *parser) parseClass() (ClassDecl, error) {
|
||||||
return ClassDecl{}, err
|
return ClassDecl{}, err
|
||||||
}
|
}
|
||||||
if !p.match(tokenLBrace) {
|
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
|
var methods []FunctionDecl
|
||||||
|
|
@ -329,7 +340,7 @@ func (p *parser) parseClass() (ClassDecl, error) {
|
||||||
return ClassDecl{}, err
|
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) {
|
func (p *parser) parseClassParents() ([]string, error) {
|
||||||
|
|
@ -467,6 +478,7 @@ func (p *parser) parseFunction() (FunctionDecl, error) {
|
||||||
|
|
||||||
return FunctionDecl{
|
return FunctionDecl{
|
||||||
Name: signature.Name,
|
Name: signature.Name,
|
||||||
|
TypeParams: signature.TypeParams,
|
||||||
Params: signature.Params,
|
Params: signature.Params,
|
||||||
ReturnType: signature.ReturnType,
|
ReturnType: signature.ReturnType,
|
||||||
Body: body,
|
Body: body,
|
||||||
|
|
@ -483,6 +495,10 @@ func (p *parser) parseFunctionSignature() (FunctionSignature, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return FunctionSignature{}, err
|
return FunctionSignature{}, err
|
||||||
}
|
}
|
||||||
|
typeParams, err := p.parseTypeParameters()
|
||||||
|
if err != nil {
|
||||||
|
return FunctionSignature{}, err
|
||||||
|
}
|
||||||
if _, err := p.expect(tokenLParen, "expected '('"); err != nil {
|
if _, err := p.expect(tokenLParen, "expected '('"); err != nil {
|
||||||
return FunctionSignature{}, err
|
return FunctionSignature{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -505,12 +521,39 @@ func (p *parser) parseFunctionSignature() (FunctionSignature, error) {
|
||||||
|
|
||||||
return FunctionSignature{
|
return FunctionSignature{
|
||||||
Name: name.lexeme,
|
Name: name.lexeme,
|
||||||
|
TypeParams: typeParams,
|
||||||
Params: params,
|
Params: params,
|
||||||
ReturnType: returnType,
|
ReturnType: returnType,
|
||||||
Suspend: suspend,
|
Suspend: suspend,
|
||||||
}, nil
|
}, 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) {
|
func (p *parser) parseParams() ([]Param, error) {
|
||||||
var params []Param
|
var params []Param
|
||||||
if p.check(tokenRParen) {
|
if p.check(tokenRParen) {
|
||||||
|
|
@ -528,7 +571,11 @@ func (p *parser) parseParams() ([]Param, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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) {
|
if !p.match(tokenComma) {
|
||||||
return params, nil
|
return params, nil
|
||||||
}
|
}
|
||||||
|
|
@ -832,7 +879,7 @@ func (p *parser) parseVarDecl(mutable bool) (Stmt, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(names) == 1 {
|
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}
|
decl := MultiVarDecl{Mutable: mutable, Names: make([]string, 0, len(names)), Value: value}
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
|
|
@ -908,7 +955,7 @@ func (p *parser) parsePrefix() (Expr, error) {
|
||||||
tok := p.advance()
|
tok := p.advance()
|
||||||
switch tok.kind {
|
switch tok.kind {
|
||||||
case tokenIdent:
|
case tokenIdent:
|
||||||
return p.parsePostfix(IdentExpr{Name: tok.lexeme})
|
return p.parsePostfix(IdentExpr{Name: tok.lexeme, Pos: tok.pos})
|
||||||
case tokenInt:
|
case tokenInt:
|
||||||
return IntExpr{Value: tok.lexeme}, nil
|
return IntExpr{Value: tok.lexeme}, nil
|
||||||
case tokenFloat:
|
case tokenFloat:
|
||||||
|
|
@ -999,6 +1046,10 @@ func (p *parser) parseLambdaParams() ([]Param, bool, error) {
|
||||||
return nil, false, err
|
return nil, false, err
|
||||||
}
|
}
|
||||||
param.Type = typ
|
param.Type = typ
|
||||||
|
param.TypeRef, err = ParseTypeRef(typ)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
params = append(params, param)
|
params = append(params, param)
|
||||||
if !p.match(tokenComma) {
|
if !p.match(tokenComma) {
|
||||||
|
|
@ -1028,13 +1079,13 @@ func (p *parser) parsePostfix(expr Expr) (Expr, error) {
|
||||||
if !selectorName(name.lexeme) {
|
if !selectorName(name.lexeme) {
|
||||||
return nil, fmt.Errorf("expected selector name at %d, found %q", name.pos, 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):
|
case p.match(tokenSafeDot):
|
||||||
name := p.advance()
|
name := p.advance()
|
||||||
if !selectorName(name.lexeme) {
|
if !selectorName(name.lexeme) {
|
||||||
return nil, fmt.Errorf("expected selector name at %d, found %q", name.pos, 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):
|
case p.match(tokenDoubleBang):
|
||||||
expr = NonNullExpr{Value: expr}
|
expr = NonNullExpr{Value: expr}
|
||||||
case p.match(tokenQuestion):
|
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}})
|
_ = checker.scope.Define(&Symbol{Name: "this", Kind: VariableSymbol, Type: ClassType{Class: class}})
|
||||||
}
|
}
|
||||||
for _, param := range function.Params {
|
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})
|
_ = checker.scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
|
||||||
}
|
}
|
||||||
return checker.checkStmts(function.Body)
|
return checker.checkStmts(function.Body)
|
||||||
|
|
@ -46,7 +50,7 @@ func (checker *mutabilityChecker) checkStmts(statements []Stmt) error {
|
||||||
if err := checker.checkExpr(value.Value); err != nil {
|
if err := checker.checkExpr(value.Value); err != nil {
|
||||||
return err
|
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})
|
_ = checker.scope.Define(&Symbol{Name: value.Name, Kind: VariableSymbol, Type: typ, Mutable: value.Mutable})
|
||||||
case MultiVarDecl:
|
case MultiVarDecl:
|
||||||
if err := checker.checkExpr(value.Value); err != nil {
|
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{}})
|
_ = checker.scope.Define(&Symbol{Name: "it", Kind: VariableSymbol, Type: UnknownType{}})
|
||||||
}
|
}
|
||||||
for _, param := range value.Params {
|
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})
|
_ = checker.scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
|
||||||
}
|
}
|
||||||
return checker.checkStmts(value.Body)
|
return checker.checkStmts(value.Body)
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ func (scope *Scope) Lookup(name string) (*Symbol, bool) {
|
||||||
|
|
||||||
type ClassSymbol struct {
|
type ClassSymbol struct {
|
||||||
Name string
|
Name string
|
||||||
|
TypeParams []string
|
||||||
Decl *ClassDecl
|
Decl *ClassDecl
|
||||||
Fields map[string]*Symbol
|
Fields map[string]*Symbol
|
||||||
Methods map[string]*Symbol
|
Methods map[string]*Symbol
|
||||||
|
|
@ -65,9 +66,30 @@ type SemanticProgram struct {
|
||||||
GoPackages map[string]*gotypes.Package
|
GoPackages map[string]*gotypes.Package
|
||||||
HIR *HIRProgram
|
HIR *HIRProgram
|
||||||
Mappings *mappingState
|
Mappings *mappingState
|
||||||
|
Diagnostics []SemanticDiagnostic
|
||||||
}
|
}
|
||||||
|
|
||||||
func Analyze(program *Program) (*SemanticProgram, error) {
|
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{
|
semantic := &SemanticProgram{
|
||||||
Syntax: program,
|
Syntax: program,
|
||||||
Global: NewScope(nil),
|
Global: NewScope(nil),
|
||||||
|
|
@ -79,23 +101,29 @@ func Analyze(program *Program) (*SemanticProgram, error) {
|
||||||
GoPackages: map[string]*gotypes.Package{},
|
GoPackages: map[string]*gotypes.Package{},
|
||||||
Mappings: &mappingState{functions: map[string]string{}},
|
Mappings: &mappingState{functions: map[string]string{}},
|
||||||
}
|
}
|
||||||
for index := range program.Classes {
|
programs := append([]*Program{program}, additional...)
|
||||||
decl := &program.Classes[index]
|
for _, source := range programs {
|
||||||
class := &ClassSymbol{Name: decl.Name, Decl: decl, Fields: map[string]*Symbol{}, Methods: map[string]*Symbol{}}
|
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.Classes[decl.Name] = *decl
|
||||||
semantic.ClassInfo[decl.Name] = class
|
semantic.ClassInfo[decl.Name] = class
|
||||||
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: ClassSymbolKind, Type: ClassType{Class: class}, Decl: decl}); err != nil {
|
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: ClassSymbolKind, Type: ClassType{Class: class}, Decl: decl}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index := range program.Enums {
|
}
|
||||||
decl := &program.Enums[index]
|
for _, source := range programs {
|
||||||
|
for index := range source.Enums {
|
||||||
|
decl := &source.Enums[index]
|
||||||
semantic.Enums[decl.Name] = *decl
|
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 {
|
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: EnumSymbolKind, Type: NamedType{Name: decl.Name}, Decl: decl}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, imported := range program.Imports {
|
}
|
||||||
|
for _, source := range programs {
|
||||||
|
for _, imported := range source.Imports {
|
||||||
name := imported.Alias
|
name := imported.Alias
|
||||||
if name == "" {
|
if name == "" {
|
||||||
name = defaultImportAlias(imported)
|
name = defaultImportAlias(imported)
|
||||||
|
|
@ -111,10 +139,24 @@ func Analyze(program *Program) (*SemanticProgram, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index := range program.Functions {
|
}
|
||||||
decl := &program.Functions[index]
|
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
|
semantic.Functions[decl.Name] = *decl
|
||||||
typ, err := semantic.functionType(decl.Params, decl.ReturnType)
|
typ, err := semantic.functionType(decl.TypeParams, decl.Params, decl.ReturnType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("function %s: %w", decl.Name, err)
|
return nil, fmt.Errorf("function %s: %w", decl.Name, err)
|
||||||
}
|
}
|
||||||
|
|
@ -122,12 +164,14 @@ func Analyze(program *Program) (*SemanticProgram, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for index := range program.Classes {
|
}
|
||||||
decl := &program.Classes[index]
|
for _, source := range programs {
|
||||||
|
for index := range source.Classes {
|
||||||
|
decl := &source.Classes[index]
|
||||||
class := semantic.ClassInfo[decl.Name]
|
class := semantic.ClassInfo[decl.Name]
|
||||||
for fieldIndex := range decl.Fields {
|
for fieldIndex := range decl.Fields {
|
||||||
field := &decl.Fields[fieldIndex]
|
field := &decl.Fields[fieldIndex]
|
||||||
typ, err := semantic.ResolveType(field.Type)
|
typ, err := semantic.ResolveTypeRefWithParams(field.TypeRef, decl.TypeParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("field %s.%s: %w", decl.Name, field.Name, err)
|
return nil, fmt.Errorf("field %s.%s: %w", decl.Name, field.Name, err)
|
||||||
}
|
}
|
||||||
|
|
@ -135,13 +179,18 @@ func Analyze(program *Program) (*SemanticProgram, error) {
|
||||||
}
|
}
|
||||||
for methodIndex := range decl.Methods {
|
for methodIndex := range decl.Methods {
|
||||||
method := &decl.Methods[methodIndex]
|
method := &decl.Methods[methodIndex]
|
||||||
typ, err := semantic.functionType(method.Params, method.ReturnType)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("method %s.%s: %w", decl.Name, method.Name, err)
|
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 {
|
if err := validateMutability(semantic); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -164,6 +213,30 @@ func (semantic *SemanticProgram) ResolveType(text string) (Type, error) {
|
||||||
return resolved, nil
|
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 {
|
func validateNoClassPointer(typ Type) error {
|
||||||
switch value := typ.(type) {
|
switch value := typ.(type) {
|
||||||
case GoPointerType:
|
case GoPointerType:
|
||||||
|
|
@ -190,20 +263,24 @@ func validateNoClassPointer(typ Type) error {
|
||||||
return nil
|
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))
|
paramTypes := make([]Type, len(params))
|
||||||
for index, param := range params {
|
for index, param := range params {
|
||||||
typ, err := semantic.ResolveType(param.Type)
|
typ, err := semantic.ResolveTypeRefWithParams(param.TypeRef, typeParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
paramTypes[index] = typ
|
paramTypes[index] = typ
|
||||||
}
|
}
|
||||||
resultType, err := semantic.ResolveType(result)
|
resultRef, err := ParseTypeRef(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
func (semantic *SemanticProgram) GoType(text string) string {
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,14 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
||||||
return resolve(value.TypeArgs[0])
|
return resolve(value.TypeArgs[0])
|
||||||
}
|
}
|
||||||
receiverType := semantic.TypeOf(selector.Receiver, environment)
|
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 {
|
if iterator, ok := receiverType.(GenericType); ok && iterator.Base.String() == "GotlinSQLIterator" && len(iterator.Args) == 1 {
|
||||||
switch selector.Name {
|
switch selector.Name {
|
||||||
case "next":
|
case "next":
|
||||||
|
|
@ -78,10 +86,10 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
||||||
return NamedType{Name: "Unit"}
|
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 method, ok := class.Methods[selector.Name]; ok {
|
||||||
if function, ok := method.Type.(FunctionType); ok {
|
if function, ok := method.Type.(FunctionType); ok {
|
||||||
return function.Result
|
return substituteType(function.Result, bindings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,17 +98,63 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ident, ok := value.Callee.(IdentExpr); ok {
|
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 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 {
|
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]}}
|
return GenericType{Base: NamedType{Name: "List"}, Args: []Type{mapping.Args[0]}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if function, ok := semantic.Functions[ident.Name]; ok {
|
if symbol, ok := semantic.Global.Lookup(ident.Name); ok && symbol.Kind == FunctionSymbolKind {
|
||||||
return resolve(function.ReturnType)
|
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 {
|
if class, ok := semantic.ClassInfo[ident.Name]; ok {
|
||||||
|
if len(class.TypeParams) == 0 {
|
||||||
return ClassType{Class: class}
|
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 {
|
if sqlType, ok := sqlChainResultType(value); ok {
|
||||||
return resolve(sqlType)
|
return resolve(sqlType)
|
||||||
|
|
@ -121,9 +175,9 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
||||||
return NamedType{Name: receiver.Name}
|
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 {
|
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) {
|
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 {
|
func classTypeOf(typ Type) *ClassSymbol {
|
||||||
|
class, _ := classInstance(typ)
|
||||||
|
return class
|
||||||
|
}
|
||||||
|
|
||||||
|
func classInstance(typ Type) (*ClassSymbol, map[string]Type) {
|
||||||
switch value := typ.(type) {
|
switch value := typ.(type) {
|
||||||
case ClassType:
|
case ClassType:
|
||||||
return value.Class
|
return value.Class, map[string]Type{}
|
||||||
case NullableType:
|
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 nil
|
}
|
||||||
|
return class.Class, bindings
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 {
|
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
|
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{}
|
type UnknownType struct{}
|
||||||
|
|
||||||
func (UnknownType) typeNode() {}
|
func (UnknownType) typeNode() {}
|
||||||
|
|
@ -20,6 +33,11 @@ type NamedType struct{ Name string }
|
||||||
func (NamedType) typeNode() {}
|
func (NamedType) typeNode() {}
|
||||||
func (t NamedType) String() string { return t.Name }
|
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 }
|
type ClassType struct{ Class *ClassSymbol }
|
||||||
|
|
||||||
func (ClassType) typeNode() {}
|
func (ClassType) typeNode() {}
|
||||||
|
|
@ -36,6 +54,7 @@ func (GoPointerType) typeNode() {}
|
||||||
func (t GoPointerType) String() string { return "*" + t.Element.String() }
|
func (t GoPointerType) String() string { return "*" + t.Element.String() }
|
||||||
|
|
||||||
type FunctionType struct {
|
type FunctionType struct {
|
||||||
|
TypeParams []string
|
||||||
Params []Type
|
Params []Type
|
||||||
Result Type
|
Result Type
|
||||||
}
|
}
|
||||||
|
|
@ -54,6 +73,17 @@ type GenericType struct {
|
||||||
Args []Type
|
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 (GenericType) typeNode() {}
|
||||||
func (t GenericType) String() string {
|
func (t GenericType) String() string {
|
||||||
args := make([]string, 0, len(t.Args))
|
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 ClassType{Class: class}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
|
case TypeParameterType:
|
||||||
|
return value
|
||||||
case NullableType:
|
case NullableType:
|
||||||
return NullableType{Element: resolveClassTypes(value.Element, classes)}
|
return NullableType{Element: resolveClassTypes(value.Element, classes)}
|
||||||
case GoPointerType:
|
case GoPointerType:
|
||||||
|
|
@ -141,6 +173,46 @@ func resolveClassTypes(typ Type, classes map[string]*ClassSymbol) Type {
|
||||||
args[i] = resolveClassTypes(arg, classes)
|
args[i] = resolveClassTypes(arg, classes)
|
||||||
}
|
}
|
||||||
return GenericType{Base: resolveClassTypes(value.Base, classes), Args: args}
|
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:
|
default:
|
||||||
return typ
|
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 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 {
|
func renderGoType(typ Type) string {
|
||||||
switch value := typ.(type) {
|
switch value := typ.(type) {
|
||||||
case UnknownType:
|
case UnknownType:
|
||||||
|
|
@ -201,7 +330,12 @@ func renderGoType(typ Type) string {
|
||||||
}
|
}
|
||||||
return "*GotlinSQLIterator[" + argument + "]"
|
return "*GotlinSQLIterator[" + argument + "]"
|
||||||
}
|
}
|
||||||
|
if class, ok := value.Base.(ClassType); ok {
|
||||||
|
return "*" + class.Class.Name + "[" + strings.Join(args, ", ") + "]"
|
||||||
|
}
|
||||||
return base + "[" + strings.Join(args, ", ") + "]"
|
return base + "[" + strings.Join(args, ", ") + "]"
|
||||||
|
case TypeParameterType:
|
||||||
|
return value.Name
|
||||||
case NamedType:
|
case NamedType:
|
||||||
switch value.Name {
|
switch value.Name {
|
||||||
case "Int":
|
case "Int":
|
||||||
|
|
@ -225,6 +359,12 @@ func renderGoType(typ Type) string {
|
||||||
default:
|
default:
|
||||||
return value.Name
|
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:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,9 @@ fun create(): Service { return Service(Repository()) }`)
|
||||||
if program.Classes[1].Fields[0].Type != "Repository" || program.Functions[0].ReturnType != "Service" {
|
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])
|
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 {
|
if _, err := GenerateGo(program); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
@ -67,6 +70,9 @@ fun parse(value: String): Result<Int, Error> {
|
||||||
if semantic.HIR == nil || len(semantic.HIR.Functions) != 1 {
|
if semantic.HIR == nil || len(semantic.HIR.Functions) != 1 {
|
||||||
t.Fatal("typed HIR function was not created")
|
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)
|
decl := program.Functions[0].Body[0].(VarDecl)
|
||||||
attempt := decl.Value.(TryExpr)
|
attempt := decl.Value.(TryExpr)
|
||||||
if attempt.Meta.Semantic == nil || attempt.Meta.Semantic.Meaning != PropagateResultExpr || attempt.Meta.Semantic.Type.String() != "Int" {
|
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 {
|
if _, ok := attempt.Meta.Semantic.Node.(HIRPropagateResult); !ok {
|
||||||
t.Fatalf("propagation did not lower to HIRPropagateResult: %#v", attempt.Meta.Semantic.Node)
|
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)
|
call := attempt.Value.(CallExpr)
|
||||||
if call.Meta.Semantic == nil || call.Meta.Semantic.Meaning != GoCallExpr || call.Meta.Semantic.Type.String() != "Result<Int, Error>" {
|
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)
|
t.Fatalf("external call was not resolved: %#v", call.Meta.Semantic)
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ for (const declaration of [
|
||||||
|
|
||||||
const prefixes = new Set(Object.values(snippets).map((snippet) => snippet.prefix));
|
const prefixes = new Set(Object.values(snippets).map((snippet) => snippet.prefix));
|
||||||
for (const prefix of [
|
for (const prefix of [
|
||||||
"dataclass", "tablerow", "embed", "coroutinescope", "launch", "async", "enum", "match", "matchvalue", "mapto", "safe", "nonnull", "resultfun", "resultmatch", "defer", "foreach", "sqlfetch",
|
"dataclass", "genericfun", "genericclass", "tablerow", "embed", "coroutinescope", "launch", "async", "enum", "match", "matchvalue", "mapto", "safe", "nonnull", "resultfun", "resultmatch", "defer", "foreach", "sqlfetch",
|
||||||
"sqlsingle", "sqliterator", "sqlinsertnothing", "sqlinsertupdate", "sqlupdatereturning"
|
"sqlsingle", "sqliterator", "sqlinsertnothing", "sqlinsertupdate", "sqlupdatereturning"
|
||||||
]) {
|
]) {
|
||||||
assert(prefixes.has(prefix), `snippets are missing prefix ${prefix}`);
|
assert(prefixes.has(prefix), `snippets are missing prefix ${prefix}`);
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,22 @@
|
||||||
],
|
],
|
||||||
"description": "Gotlin data class"
|
"description": "Gotlin data class"
|
||||||
},
|
},
|
||||||
|
"Generic Function": {
|
||||||
|
"prefix": "genericfun",
|
||||||
|
"body": [
|
||||||
|
"fun ${1:identity}<${2:T}>(${3:value}: ${2:T}): ${2:T} {",
|
||||||
|
" return ${3:value}",
|
||||||
|
"}"
|
||||||
|
],
|
||||||
|
"description": "Gotlin generic function"
|
||||||
|
},
|
||||||
|
"Generic Data Class": {
|
||||||
|
"prefix": "genericclass",
|
||||||
|
"body": [
|
||||||
|
"data class ${1:Box}<${2:T}>(var ${3:value}: ${2:T})"
|
||||||
|
],
|
||||||
|
"description": "Gotlin generic data class"
|
||||||
|
},
|
||||||
"SQL Table Row": {
|
"SQL Table Row": {
|
||||||
"prefix": "tablerow",
|
"prefix": "tablerow",
|
||||||
"body": [
|
"body": [
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue