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`
|
||||
- `Int`, `Long`, `String`, `Boolean`, `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)`
|
||||
- function calls
|
||||
- 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
|
||||
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
|
||||
|
||||
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
|
||||
- `textDocument/hover` for packages, imports, 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
|
||||
|
||||
Build:
|
||||
|
|
|
|||
|
|
@ -160,6 +160,7 @@ type diagnostic struct {
|
|||
Range rng `json:"range"`
|
||||
Severity int `json:"severity,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
Code string `json:"code,omitempty"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
|
|
@ -306,13 +307,13 @@ func buildDocumentState(text string) documentState {
|
|||
return state
|
||||
}
|
||||
state.program = program
|
||||
if _, err := lang.Analyze(program); err != nil {
|
||||
state.diagnostics = append(state.diagnostics, diagnosticFromError(text, err))
|
||||
}
|
||||
_, semanticDiagnostics := lang.AnalyzeWithContext(program, nil)
|
||||
state.symbols = indexSymbols(text, program)
|
||||
state.diagnostics = semanticDiagnostics(text, program, state.symbols)
|
||||
if _, err := lang.GenerateGo(program); err != nil {
|
||||
state.diagnostics = append(state.diagnostics, diagnosticFromError(text, err))
|
||||
state.diagnostics = diagnosticsFromSemantic(text, semanticDiagnostics)
|
||||
if len(semanticDiagnostics) == 0 {
|
||||
if _, err := lang.GenerateGo(program); err != nil {
|
||||
state.diagnostics = append(state.diagnostics, diagnosticFromError(text, err))
|
||||
}
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
|
@ -325,10 +326,11 @@ func (s *server) buildDocumentState(uri, text string) documentState {
|
|||
return state
|
||||
}
|
||||
state.program = program
|
||||
state.symbols = indexSymbols(text, program)
|
||||
programs, symbols := s.packageContext(uri, program.PackagePath, text)
|
||||
_, semanticDiagnostics := lang.AnalyzeWithContext(program, programs)
|
||||
state.symbols = indexSymbols(text, program)
|
||||
state.packageSymbols = symbols
|
||||
state.diagnostics = semanticDiagnosticsWithPackage(text, program, state.symbols, programs)
|
||||
state.diagnostics = diagnosticsFromSemantic(text, semanticDiagnostics)
|
||||
return state
|
||||
}
|
||||
|
||||
|
|
@ -363,7 +365,9 @@ func (s *server) packageContext(currentURI, packagePath, currentText string) ([]
|
|||
if parseErr != nil || program.PackagePath != packagePath {
|
||||
return nil
|
||||
}
|
||||
programs = append(programs, program)
|
||||
if candidateURI != currentURI {
|
||||
programs = append(programs, program)
|
||||
}
|
||||
for _, sym := range indexSymbols(text, program) {
|
||||
sym.URI = candidateURI
|
||||
symbols = append(symbols, sym)
|
||||
|
|
@ -729,227 +733,22 @@ func indexSymbols(text string, program *lang.Program) []symbol {
|
|||
return symbols
|
||||
}
|
||||
|
||||
func semanticDiagnostics(text string, program *lang.Program, symbols []symbol) []diagnostic {
|
||||
return semanticDiagnosticsWithPackage(text, program, symbols, nil)
|
||||
}
|
||||
|
||||
func semanticDiagnosticsWithPackage(text string, program *lang.Program, symbols []symbol, packagePrograms []*lang.Program) []diagnostic {
|
||||
var diagnostics []diagnostic
|
||||
|
||||
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:
|
||||
if prev, ok := importSymbols[sym.Name]; ok {
|
||||
diagnostics = append(diagnostics, duplicateDiagnostic(sym.Range, "duplicate import alias "+sym.Name))
|
||||
diagnostics = append(diagnostics, duplicateDiagnostic(prev.Range, "duplicate import alias "+sym.Name))
|
||||
} else {
|
||||
importSymbols[sym.Name] = sym
|
||||
}
|
||||
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
|
||||
}
|
||||
func diagnosticsFromSemantic(text string, semantic []lang.SemanticDiagnostic) []diagnostic {
|
||||
diagnostics := make([]diagnostic, 0, len(semantic))
|
||||
for _, item := range semantic {
|
||||
start := offsetToPosition(text, item.Span.Start)
|
||||
endOffset := item.Span.End
|
||||
if endOffset <= item.Span.Start {
|
||||
endOffset = item.Span.Start + 1
|
||||
}
|
||||
diagnostics = append(diagnostics, diagnostic{
|
||||
Range: rng{Start: start, End: offsetToPosition(text, endOffset)},
|
||||
Severity: int(item.Severity),
|
||||
Source: "gotlin",
|
||||
Code: item.Code,
|
||||
Message: item.Message,
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -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 {
|
||||
runes := []rune(text)
|
||||
if offset < 0 {
|
||||
|
|
@ -1221,7 +1001,11 @@ func renderImportDetail(imp lang.ImportDecl) 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 {
|
||||
|
|
@ -1244,6 +1028,11 @@ func renderClassSignature(decl lang.ClassDecl) string {
|
|||
var b strings.Builder
|
||||
b.WriteString("class ")
|
||||
b.WriteString(decl.Name)
|
||||
if len(decl.TypeParams) > 0 {
|
||||
b.WriteString("<")
|
||||
b.WriteString(strings.Join(decl.TypeParams, ", "))
|
||||
b.WriteString(">")
|
||||
}
|
||||
|
||||
var fields []string
|
||||
for _, field := range decl.Fields {
|
||||
|
|
@ -1289,289 +1078,59 @@ func renderVariableSignature(name string, mutable bool, typ string) string {
|
|||
}
|
||||
|
||||
func collectVariableDeclInfos(program *lang.Program) []variableDeclInfo {
|
||||
functions := map[string]lang.FunctionDecl{}
|
||||
for _, fn := range program.Functions {
|
||||
functions[fn.Name] = fn
|
||||
}
|
||||
classes := map[string]lang.ClassDecl{}
|
||||
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) {
|
||||
var result []variableDeclInfo
|
||||
var walk func([]lang.Stmt)
|
||||
walk = func(statements []lang.Stmt) {
|
||||
for _, statement := range statements {
|
||||
switch value := statement.(type) {
|
||||
case lang.VarDecl:
|
||||
typ := s.Type
|
||||
typ := value.Type
|
||||
if typ == "" {
|
||||
resolved := lang.ResolvedType(s.Value)
|
||||
resolved := lang.ResolvedType(value.Value)
|
||||
if resolved.String() != "<unknown>" {
|
||||
typ = resolved.String()
|
||||
} else {
|
||||
typ = inferExprType(s.Value, scope)
|
||||
}
|
||||
}
|
||||
out = append(out, variableDeclInfo{Name: s.Name, Mutable: s.Mutable, Type: typ})
|
||||
scope[s.Name] = typ
|
||||
result = append(result, variableDeclInfo{Name: value.Name, Mutable: value.Mutable, Type: typ})
|
||||
case lang.MultiVarDecl:
|
||||
types := []string(nil)
|
||||
if call, ok := s.Value.(lang.CallExpr); ok {
|
||||
types = inferCallReturnTypes(call, scope)
|
||||
}
|
||||
for i, name := range s.Names {
|
||||
typ := ""
|
||||
if i < len(types) {
|
||||
typ = types[i]
|
||||
types := make([]string, len(value.Names))
|
||||
if fallible, ok := lang.ResolvedType(value.Value).(lang.GenericType); ok && fallible.Base.String() == "Result" && len(fallible.Args) == 2 && len(types) == 2 {
|
||||
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()
|
||||
}
|
||||
out = append(out, variableDeclInfo{Name: name, Mutable: s.Mutable, Type: typ})
|
||||
scope[name] = typ
|
||||
}
|
||||
case lang.AddAssignStmt:
|
||||
if _, ok := scope[s.Name]; !ok {
|
||||
scope[s.Name] = ""
|
||||
for index, name := range value.Names {
|
||||
result = append(result, variableDeclInfo{Name: name, Mutable: value.Mutable, Type: types[index]})
|
||||
}
|
||||
case lang.IfStmt:
|
||||
thenScope := copyTypeScope(scope)
|
||||
elseScope := copyTypeScope(scope)
|
||||
walkStmts(s.Then, thenScope)
|
||||
walkStmts(s.Else, elseScope)
|
||||
walk(value.Then)
|
||||
walk(value.Else)
|
||||
case lang.WhileStmt:
|
||||
bodyScope := copyTypeScope(scope)
|
||||
walkStmts(s.Body, bodyScope)
|
||||
walk(value.Body)
|
||||
case lang.ForEachStmt:
|
||||
walk(value.Body)
|
||||
case lang.MatchStmt:
|
||||
for _, matchCase := range value.Cases {
|
||||
walk(matchCase.Body)
|
||||
}
|
||||
case lang.TryCatchStmt:
|
||||
tryScope := copyTypeScope(scope)
|
||||
walkStmts(s.TryBody, tryScope)
|
||||
catchScope := copyTypeScope(scope)
|
||||
catchScope[s.CatchName] = s.CatchType
|
||||
walkStmts(s.CatchBody, catchScope)
|
||||
walk(value.TryBody)
|
||||
walk(value.CatchBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, fn := range program.Functions {
|
||||
scope := map[string]string{}
|
||||
for _, param := range fn.Params {
|
||||
scope[param.Name] = param.Type
|
||||
}
|
||||
walkStmts(fn.Body, scope)
|
||||
for _, function := range program.Functions {
|
||||
walk(function.Body)
|
||||
}
|
||||
for _, class := range program.Classes {
|
||||
fieldScope := map[string]string{}
|
||||
for _, field := range class.Fields {
|
||||
fieldScope[field.Name] = field.Type
|
||||
}
|
||||
for _, method := range class.Methods {
|
||||
scope := copyTypeScope(fieldScope)
|
||||
for _, param := range method.Params {
|
||||
scope[param.Name] = param.Type
|
||||
}
|
||||
walkStmts(method.Body, scope)
|
||||
walk(method.Body)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
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) {
|
||||
text := strings.TrimSpace(`
|
||||
package demo
|
||||
|
|
@ -460,7 +475,7 @@ import github.com.uptrace.bun
|
|||
class C(val db: *bun.DB) {
|
||||
fun run() {
|
||||
val ctx = context.Background()
|
||||
val total = db.NewSelect().ColumnExpr("1").Count(ctx)
|
||||
val total = db.NewSelect().ColumnExpr("1").Count(ctx).unwrap()
|
||||
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)
|
||||
val user = BunUser("user-from-gotlin")
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
10
go.mod
10
go.mod
|
|
@ -2,7 +2,10 @@ module gotlin
|
|||
|
||||
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 (
|
||||
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/trace v1.40.0 // indirect
|
||||
golang.org/x/crypto v0.48.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/mod v0.39.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
|
||||
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=
|
||||
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||
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/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/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/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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=
|
||||
|
|
|
|||
|
|
@ -10,7 +10,10 @@ type Program struct {
|
|||
Embeds []EmbedDecl
|
||||
}
|
||||
|
||||
type EmbedDecl struct{ Path, Name, Type string }
|
||||
type EmbedDecl struct {
|
||||
Path, Name, Type string
|
||||
TypeRef TypeRef
|
||||
}
|
||||
|
||||
type EnumDecl struct {
|
||||
Name string
|
||||
|
|
@ -20,6 +23,7 @@ type EnumDecl struct {
|
|||
type EnumVariant struct {
|
||||
Name string
|
||||
PayloadTypes []string
|
||||
PayloadRefs []TypeRef
|
||||
StringValue string
|
||||
}
|
||||
|
||||
|
|
@ -35,6 +39,7 @@ type InterfaceDecl struct {
|
|||
|
||||
type ClassDecl struct {
|
||||
Name string
|
||||
TypeParams []string
|
||||
Data bool
|
||||
JSONNaming string
|
||||
Table string
|
||||
|
|
@ -48,6 +53,7 @@ type FieldDecl struct {
|
|||
Private bool
|
||||
Name string
|
||||
Type string
|
||||
TypeRef TypeRef
|
||||
Column string
|
||||
ID bool
|
||||
Generated bool
|
||||
|
|
@ -55,22 +61,27 @@ type FieldDecl struct {
|
|||
|
||||
type FunctionSignature struct {
|
||||
Name string
|
||||
TypeParams []string
|
||||
Params []Param
|
||||
ReturnType string
|
||||
ReturnRef TypeRef
|
||||
Suspend bool
|
||||
}
|
||||
|
||||
type FunctionDecl struct {
|
||||
Name string
|
||||
TypeParams []string
|
||||
Params []Param
|
||||
ReturnType string
|
||||
ReturnRef TypeRef
|
||||
Body []Stmt
|
||||
Suspend bool
|
||||
}
|
||||
|
||||
type Param struct {
|
||||
Name string
|
||||
Type string
|
||||
Name string
|
||||
Type string
|
||||
TypeRef TypeRef
|
||||
}
|
||||
|
||||
type Stmt interface {
|
||||
|
|
@ -84,7 +95,9 @@ type Expr interface {
|
|||
type VarDecl struct {
|
||||
Mutable bool
|
||||
Name string
|
||||
Pos int
|
||||
Type string
|
||||
TypeRef TypeRef
|
||||
Value Expr
|
||||
}
|
||||
|
||||
|
|
@ -173,6 +186,7 @@ type TryCatchStmt struct {
|
|||
TryBody []Stmt
|
||||
CatchName string
|
||||
CatchType string
|
||||
CatchRef TypeRef
|
||||
CatchBody []Stmt
|
||||
}
|
||||
|
||||
|
|
@ -208,6 +222,7 @@ type MatchExprCase struct {
|
|||
type IdentExpr struct {
|
||||
Meta ExprMeta
|
||||
Name string
|
||||
Pos int
|
||||
}
|
||||
|
||||
func (IdentExpr) exprNode() {}
|
||||
|
|
@ -280,6 +295,7 @@ type SelectorExpr struct {
|
|||
Meta ExprMeta
|
||||
Receiver Expr
|
||||
Name string
|
||||
NamePos int
|
||||
}
|
||||
|
||||
func (SelectorExpr) exprNode() {}
|
||||
|
|
@ -288,6 +304,7 @@ type SafeSelectorExpr struct {
|
|||
Meta ExprMeta
|
||||
Receiver Expr
|
||||
Name string
|
||||
NamePos int
|
||||
}
|
||||
|
||||
func (SafeSelectorExpr) exprNode() {}
|
||||
|
|
|
|||
50
internal/lang/diagnostics.go
Normal file
50
internal/lang/diagnostics.go
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package lang
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
type DiagnosticSeverity int
|
||||
|
||||
const (
|
||||
DiagnosticError DiagnosticSeverity = 1
|
||||
DiagnosticWarning DiagnosticSeverity = 2
|
||||
)
|
||||
|
||||
type SourceSpan struct {
|
||||
Start int
|
||||
End int
|
||||
}
|
||||
|
||||
type SemanticDiagnostic struct {
|
||||
Code string
|
||||
Message string
|
||||
Severity DiagnosticSeverity
|
||||
Span SourceSpan
|
||||
}
|
||||
|
||||
func (diagnostic SemanticDiagnostic) Error() string { return diagnostic.Message }
|
||||
|
||||
var semanticOffsetPattern = regexp.MustCompile(`\sat\s(\d+)$`)
|
||||
|
||||
func diagnosticForError(code string, err error) SemanticDiagnostic {
|
||||
span := SourceSpan{}
|
||||
if match := semanticOffsetPattern.FindStringSubmatch(err.Error()); len(match) == 2 {
|
||||
if offset, parseErr := strconv.Atoi(match[1]); parseErr == nil {
|
||||
span = SourceSpan{Start: offset, End: offset + 1}
|
||||
}
|
||||
}
|
||||
return SemanticDiagnostic{Code: code, Message: err.Error(), Severity: DiagnosticError, Span: span}
|
||||
}
|
||||
|
||||
func undefinedDiagnostic(name string, position int) SemanticDiagnostic {
|
||||
end := position + len(name)
|
||||
return SemanticDiagnostic{
|
||||
Code: "undefined-symbol",
|
||||
Message: fmt.Sprintf("undefined identifier %s", name),
|
||||
Severity: DiagnosticError,
|
||||
Span: SourceSpan{Start: position, End: end},
|
||||
}
|
||||
}
|
||||
37
internal/lang/diagnostics_test.go
Normal file
37
internal/lang/diagnostics_test.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package lang
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestAnalyzeProducesStructuredUndefinedDiagnostic(t *testing.T) {
|
||||
source := `package demo fun main() { println(missing) }`
|
||||
program, err := Parse(source)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, diagnostics := AnalyzeWithContext(program, nil)
|
||||
if len(diagnostics) != 1 {
|
||||
t.Fatalf("diagnostics = %#v", diagnostics)
|
||||
}
|
||||
diagnostic := diagnostics[0]
|
||||
if diagnostic.Code != "undefined-symbol" || diagnostic.Severity != DiagnosticError || diagnostic.Message != "undefined identifier missing" {
|
||||
t.Fatalf("unexpected diagnostic: %#v", diagnostic)
|
||||
}
|
||||
if got := source[diagnostic.Span.Start:diagnostic.Span.End]; got != "missing" {
|
||||
t.Fatalf("diagnostic span = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnalyzeUsesAdditionalPackagePrograms(t *testing.T) {
|
||||
current, err := Parse(`package demo fun main() { helper() }`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sibling, err := Parse(`package demo fun helper() { println("ok") }`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, diagnostics := AnalyzeWithContext(current, []*Program{sibling})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("package symbol was not resolved: %#v", diagnostics)
|
||||
}
|
||||
}
|
||||
|
|
@ -263,6 +263,7 @@ func (g *goGenerator) function(fn FunctionDecl) error {
|
|||
}
|
||||
g.write("func ")
|
||||
g.write(fn.Name)
|
||||
g.write(renderGoTypeParameters(fn.TypeParams))
|
||||
g.write(g.renderGoFunctionParams(fn))
|
||||
if ret := g.goReturnType(fn.ReturnType); ret != "" {
|
||||
g.write(" ")
|
||||
|
|
@ -346,7 +347,7 @@ func enumVariant(decl EnumDecl, name string) *EnumVariant {
|
|||
}
|
||||
|
||||
func (g *goGenerator) classDecl(class ClassDecl) error {
|
||||
g.line("type " + class.Name + " struct {")
|
||||
g.line("type " + class.Name + renderGoTypeParameters(class.TypeParams) + " struct {")
|
||||
g.indentLevel++
|
||||
for _, field := range class.Fields {
|
||||
name := field.Name
|
||||
|
|
@ -366,6 +367,7 @@ func (g *goGenerator) classDecl(class ClassDecl) error {
|
|||
g.line("")
|
||||
g.write("func New")
|
||||
g.write(class.Name)
|
||||
g.write(renderGoTypeParameters(class.TypeParams))
|
||||
g.write("(")
|
||||
for i, field := range class.Fields {
|
||||
if i > 0 {
|
||||
|
|
@ -377,11 +379,13 @@ func (g *goGenerator) classDecl(class ClassDecl) error {
|
|||
}
|
||||
g.write(") *")
|
||||
g.write(class.Name)
|
||||
g.write(renderGoTypeArguments(class.TypeParams))
|
||||
g.write(" {\n")
|
||||
g.indentLevel++
|
||||
g.writeIndent()
|
||||
g.write("return &")
|
||||
g.write(class.Name)
|
||||
g.write(renderGoTypeArguments(class.TypeParams))
|
||||
g.write("{")
|
||||
for i, field := range class.Fields {
|
||||
if i > 0 {
|
||||
|
|
@ -427,6 +431,7 @@ func (g *goGenerator) method(class ClassDecl, fn FunctionDecl) error {
|
|||
|
||||
g.write("func (self *")
|
||||
g.write(class.Name)
|
||||
g.write(renderGoTypeArguments(class.TypeParams))
|
||||
g.write(") ")
|
||||
g.write(fn.Name)
|
||||
g.write(g.renderGoFunctionParams(fn))
|
||||
|
|
@ -792,8 +797,8 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
|
|||
if e.Name == "this" {
|
||||
return "self", nil
|
||||
}
|
||||
if g.classField(*g.currentClass, e.Name) && !g.isDefined(e.Name) {
|
||||
return "self." + e.Name, nil
|
||||
if field, found := classFieldByName(*g.currentClass, e.Name); found && !g.isDefined(e.Name) {
|
||||
return "self." + mappingFieldName(*g.currentClass, field), nil
|
||||
}
|
||||
}
|
||||
return e.Name, nil
|
||||
|
|
@ -1189,7 +1194,7 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
|
|||
}
|
||||
if ident, ok := e.Callee.(IdentExpr); ok {
|
||||
if _, ok := g.semantic.Classes[ident.Name]; ok && (resolvedCall == nil || resolvedCall.Meaning == ClassConstructionExpr) {
|
||||
return fmt.Sprintf("New%s(%s)", ident.Name, strings.Join(args, ", ")), nil
|
||||
return fmt.Sprintf("New%s%s(%s)", ident.Name, g.renderCallTypeArguments(e.TypeArgs), strings.Join(args, ", ")), nil
|
||||
}
|
||||
}
|
||||
if ident, ok := e.Callee.(IdentExpr); ok {
|
||||
|
|
@ -1217,6 +1222,9 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
|
|||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(e.TypeArgs) > 0 {
|
||||
callee += g.renderCallTypeArguments(e.TypeArgs)
|
||||
}
|
||||
call := fmt.Sprintf("%s(%s)", callee, strings.Join(args, ", "))
|
||||
if result, ok := g.goErrorResult(e, call, expectedType); ok {
|
||||
return result, nil
|
||||
|
|
@ -2460,18 +2468,11 @@ func (g *goGenerator) isDefined(name string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (g *goGenerator) classField(class ClassDecl, name string) bool {
|
||||
for _, field := range class.Fields {
|
||||
if field.Name == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *goGenerator) assignTarget(name string) string {
|
||||
if g.currentClass != nil && g.classField(*g.currentClass, name) && !g.isDefined(name) {
|
||||
return "self." + name
|
||||
if g.currentClass != nil && !g.isDefined(name) {
|
||||
if field, found := classFieldByName(*g.currentClass, name); found {
|
||||
return "self." + mappingFieldName(*g.currentClass, field)
|
||||
}
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
|
@ -2496,6 +2497,35 @@ func (g *goGenerator) renderGoFunctionParams(function FunctionDecl) string {
|
|||
return g.renderGoParamsWithPrefix(function.Params, prefix)
|
||||
}
|
||||
|
||||
func renderGoTypeParameters(params []string) string {
|
||||
if len(params) == 0 {
|
||||
return ""
|
||||
}
|
||||
values := make([]string, len(params))
|
||||
for index, param := range params {
|
||||
values[index] = param + " any"
|
||||
}
|
||||
return "[" + strings.Join(values, ", ") + "]"
|
||||
}
|
||||
|
||||
func renderGoTypeArguments(params []string) string {
|
||||
if len(params) == 0 {
|
||||
return ""
|
||||
}
|
||||
return "[" + strings.Join(params, ", ") + "]"
|
||||
}
|
||||
|
||||
func (g *goGenerator) renderCallTypeArguments(args []string) string {
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
values := make([]string, len(args))
|
||||
for index, argument := range args {
|
||||
values[index] = g.goType(argument)
|
||||
}
|
||||
return "[" + strings.Join(values, ", ") + "]"
|
||||
}
|
||||
|
||||
func (g *goGenerator) renderGoParamsWithPrefix(params []Param, prefix string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("(")
|
||||
|
|
|
|||
72
internal/lang/generics_test.go
Normal file
72
internal/lang/generics_test.go
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
package lang
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGenerateGenericFunctionsAndClasses(t *testing.T) {
|
||||
program, err := Parse(`package demo
|
||||
data class Box<T>(var value: T) {
|
||||
fun get(): T { return value }
|
||||
}
|
||||
fun identity<T>(value: T): T { return value }
|
||||
fun wrap<T>(value: T): Box<T> { return Box(value) }
|
||||
fun main() {
|
||||
val inferred = identity(42)
|
||||
val explicit = identity<String>("value")
|
||||
val box = wrap("boxed")
|
||||
println(box.get())
|
||||
}`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
output, err := GenerateGo(program)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
code := string(output)
|
||||
for _, expected := range []string{
|
||||
"type Box[T any] struct",
|
||||
"func NewBox[T any](value T) *Box[T]",
|
||||
"func (self *Box[T]) get() T",
|
||||
"func identity[T any](value T) T",
|
||||
"func wrap[T any](value T) *Box[T]",
|
||||
"identity(42)",
|
||||
`identity[string]("value")`,
|
||||
`wrap("boxed")`,
|
||||
} {
|
||||
if !strings.Contains(code, expected) {
|
||||
t.Fatalf("missing %q:\n%s", expected, code)
|
||||
}
|
||||
}
|
||||
main := program.Functions[2]
|
||||
inferred := main.Body[0].(VarDecl)
|
||||
if got := ResolvedType(inferred.Value).String(); got != "Int" {
|
||||
t.Fatalf("inferred generic type = %s", got)
|
||||
}
|
||||
box := main.Body[2].(VarDecl)
|
||||
if got := ResolvedType(box.Value).String(); got != "Box<String>" {
|
||||
t.Fatalf("generic class type = %s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectInvalidGenericDeclarationsAndCalls(t *testing.T) {
|
||||
for _, test := range []struct{ source, message string }{
|
||||
{`package demo fun identity<T, T>(value: T): T { return value }`, "duplicate type parameter T"},
|
||||
{`package demo fun identity<T>(value: T): T { return value } fun main() { identity<String, Int>("x") }`, "expects 1 type arguments, got 2"},
|
||||
{`package demo class Box<T>(val value: T) { fun convert<R>(): T { return value } }`, "generic methods are not supported"},
|
||||
} {
|
||||
program, err := Parse(test.source)
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), test.message) {
|
||||
continue
|
||||
}
|
||||
t.Fatalf("unexpected parse error: %v", err)
|
||||
}
|
||||
_, err = GenerateGo(program)
|
||||
if err == nil || !strings.Contains(err.Error(), test.message) {
|
||||
t.Fatalf("error = %v, want %q", err, test.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ package demo
|
|||
import http net.http
|
||||
import json encoding.json
|
||||
|
||||
fun main() {
|
||||
fun main(handler: (http.ResponseWriter, *http.Request) -> Unit, body: ByteSlice, target: Any) {
|
||||
http.handleFunc("/health", handler)
|
||||
json.unmarshal(body, &target)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,32 @@ import (
|
|||
"go/importer"
|
||||
gotypes "go/types"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
golangpackages "golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
var importedGoPackages sync.Map
|
||||
|
||||
func importGoPackage(decl ImportDecl) (*gotypes.Package, error) {
|
||||
path := strings.Trim(decl.Path, `"`)
|
||||
if !strings.Contains(path, "/") {
|
||||
path = importPathToGoPath(path)
|
||||
}
|
||||
return importer.Default().Import(path)
|
||||
if cached, ok := importedGoPackages.Load(path); ok {
|
||||
return cached.(*gotypes.Package), nil
|
||||
}
|
||||
pkg, err := importer.Default().Import(path)
|
||||
if err == nil {
|
||||
importedGoPackages.Store(path, pkg)
|
||||
return pkg, nil
|
||||
}
|
||||
loaded, loadErr := golangpackages.Load(&golangpackages.Config{Mode: golangpackages.NeedName | golangpackages.NeedTypes | golangpackages.NeedImports | golangpackages.NeedDeps}, path)
|
||||
if loadErr != nil || len(loaded) == 0 || loaded[0].Types == nil {
|
||||
return nil, err
|
||||
}
|
||||
importedGoPackages.Store(path, loaded[0].Types)
|
||||
return loaded[0].Types, nil
|
||||
}
|
||||
|
||||
func (semantic *SemanticProgram) goSelectorType(alias, name string) Type {
|
||||
|
|
@ -119,7 +137,11 @@ func semanticTypeFromGoResults(results *gotypes.Tuple) Type {
|
|||
if results.Len() == 1 {
|
||||
return last
|
||||
}
|
||||
return UnknownType{}
|
||||
elements := make([]Type, results.Len())
|
||||
for index := range elements {
|
||||
elements[index] = semanticTypeFromGo(results.At(index).Type())
|
||||
}
|
||||
return TupleType{Elements: elements}
|
||||
}
|
||||
|
||||
func semanticTypeFromGo(typ gotypes.Type) Type {
|
||||
|
|
|
|||
32
internal/lang/go_types_test.go
Normal file
32
internal/lang/go_types_test.go
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package lang
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestGoTypesResolvesFallibleCallsMethodsAndTuples(t *testing.T) {
|
||||
program, err := Parse(`package demo
|
||||
import os
|
||||
import strconv
|
||||
fun main() {
|
||||
val parsed = strconv.atoi("42")
|
||||
val value, found = os.lookupEnv("HOME")
|
||||
println(parsed)
|
||||
println(value)
|
||||
println(found)
|
||||
}`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, diagnostics := AnalyzeWithContext(program, nil)
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("diagnostics: %#v", diagnostics)
|
||||
}
|
||||
body := program.Functions[0].Body
|
||||
parsed := body[0].(VarDecl)
|
||||
if got := ResolvedType(parsed.Value).String(); got != "Result<Int, Error>" {
|
||||
t.Fatalf("Atoi type = %s", got)
|
||||
}
|
||||
lookup := body[1].(MultiVarDecl)
|
||||
if got := ResolvedType(lookup.Value).String(); got != "(String, Boolean)" {
|
||||
t.Fatalf("LookupEnv type = %s", got)
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,7 @@ const (
|
|||
type ExprMeta struct{ Semantic *HIRExpr }
|
||||
|
||||
type HIRExpr struct {
|
||||
ID int
|
||||
Type Type
|
||||
Meaning ExprMeaning
|
||||
Symbol *Symbol
|
||||
|
|
@ -111,8 +112,9 @@ type HIRFunction struct {
|
|||
}
|
||||
|
||||
type HIRProgram struct {
|
||||
Functions []*HIRFunction
|
||||
Methods []*HIRFunction
|
||||
Functions []*HIRFunction
|
||||
Methods []*HIRFunction
|
||||
Expressions []*HIRExpr
|
||||
}
|
||||
|
||||
func exprMeta(expr Expr) *HIRExpr {
|
||||
|
|
@ -240,10 +242,10 @@ func (resolver *semanticResolver) resolve() error {
|
|||
symbol, _ := resolver.program.Global.Lookup(decl.Name)
|
||||
scope := NewScope(resolver.program.Global)
|
||||
for _, param := range decl.Params {
|
||||
typ, _ := resolver.program.ResolveType(param.Type)
|
||||
typ, _ := resolver.program.ResolveTypeRefWithParams(param.TypeRef, decl.TypeParams)
|
||||
_ = scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
|
||||
}
|
||||
result, _ := resolver.program.ResolveType(decl.ReturnType)
|
||||
result, _ := resolver.program.ResolveTypeRefWithParams(decl.ReturnRef, decl.TypeParams)
|
||||
resolver.resolveStmts(decl.Body, scope, nil, result)
|
||||
resolver.program.HIR.Functions = append(resolver.program.HIR.Functions, &HIRFunction{Symbol: symbol, Decl: decl, Scope: scope})
|
||||
}
|
||||
|
|
@ -255,10 +257,12 @@ func (resolver *semanticResolver) resolve() error {
|
|||
scope := NewScope(resolver.program.Global)
|
||||
_ = scope.Define(&Symbol{Name: "this", Kind: VariableSymbol, Type: ClassType{Class: class}})
|
||||
for _, param := range method.Params {
|
||||
typ, _ := resolver.program.ResolveType(param.Type)
|
||||
params := append(append([]string{}, decl.TypeParams...), method.TypeParams...)
|
||||
typ, _ := resolver.program.ResolveTypeRefWithParams(param.TypeRef, params)
|
||||
_ = scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
|
||||
}
|
||||
result, _ := resolver.program.ResolveType(method.ReturnType)
|
||||
params := append(append([]string{}, decl.TypeParams...), method.TypeParams...)
|
||||
result, _ := resolver.program.ResolveTypeRefWithParams(method.ReturnRef, params)
|
||||
resolver.resolveStmts(method.Body, scope, class, result)
|
||||
resolver.program.HIR.Methods = append(resolver.program.HIR.Methods, &HIRFunction{Symbol: class.Methods[method.Name], Decl: method, Scope: scope})
|
||||
}
|
||||
|
|
@ -289,6 +293,8 @@ func (resolver *semanticResolver) resolveStmts(stmts []Stmt, scope *Scope, class
|
|||
if result, ok := ResolvedType(value.Value).(GenericType); ok && result.Base.String() == "Result" && len(result.Args) == 2 && len(valueTypes) == 2 {
|
||||
valueTypes[0] = result.Args[0]
|
||||
valueTypes[1] = NullableType{Element: result.Args[1]}
|
||||
} else if tuple, ok := ResolvedType(value.Value).(TupleType); ok && len(tuple.Elements) == len(valueTypes) {
|
||||
copy(valueTypes, tuple.Elements)
|
||||
}
|
||||
for index, name := range value.Names {
|
||||
_ = scope.Define(&Symbol{Name: name, Kind: VariableSymbol, Type: valueTypes[index], Mutable: value.Mutable})
|
||||
|
|
@ -298,13 +304,34 @@ func (resolver *semanticResolver) resolveStmts(stmts []Stmt, scope *Scope, class
|
|||
expected := Type(UnknownType{})
|
||||
if symbol, ok := scope.Lookup(value.Name); ok {
|
||||
expected = symbol.Type
|
||||
} else if class != nil && class.Fields[value.Name] != nil {
|
||||
expected = class.Fields[value.Name].Type
|
||||
} else {
|
||||
resolver.addDiagnostic(SemanticDiagnostic{Code: "undefined-variable", Message: "undefined variable " + value.Name, Severity: DiagnosticError, Span: SourceSpan{Start: value.Pos, End: value.Pos + len(value.Name)}})
|
||||
}
|
||||
value.Value, _ = resolver.resolveExpr(value.Value, scope, class, expected)
|
||||
stmts[index] = value
|
||||
case AddAssignStmt:
|
||||
_, local := scope.Lookup(value.Name)
|
||||
field := false
|
||||
if class != nil {
|
||||
_, field = class.Fields[value.Name]
|
||||
}
|
||||
if !local && !field {
|
||||
resolver.addDiagnostic(SemanticDiagnostic{Code: "undefined-variable", Message: "undefined variable " + value.Name, Severity: DiagnosticError, Span: SourceSpan{Start: value.Pos, End: value.Pos + len(value.Name)}})
|
||||
}
|
||||
value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{})
|
||||
stmts[index] = value
|
||||
case MultiAssignStmt:
|
||||
for index, name := range value.Names {
|
||||
if _, ok := scope.Lookup(name); !ok {
|
||||
position := 0
|
||||
if index < len(value.Positions) {
|
||||
position = value.Positions[index]
|
||||
}
|
||||
resolver.addDiagnostic(SemanticDiagnostic{Code: "undefined-variable", Message: "undefined variable " + name, Severity: DiagnosticError, Span: SourceSpan{Start: position, End: position + len(name)}})
|
||||
}
|
||||
}
|
||||
value.Value, _ = resolver.resolveExpr(value.Value, scope, class, UnknownType{})
|
||||
stmts[index] = value
|
||||
case ReturnStmt:
|
||||
|
|
@ -421,8 +448,11 @@ func (resolver *semanticResolver) resolveExpr(expr Expr, scope *Scope, class *Cl
|
|||
expr = value
|
||||
case LambdaExpr:
|
||||
lambdaScope := NewScope(scope)
|
||||
if value.ImplicitIt {
|
||||
_ = lambdaScope.Define(&Symbol{Name: "it", Kind: VariableSymbol, Type: UnknownType{}})
|
||||
}
|
||||
for _, param := range value.Params {
|
||||
typ, _ := resolver.program.ResolveType(param.Type)
|
||||
typ, _ := resolver.program.ResolveTypeRef(param.TypeRef)
|
||||
_ = lambdaScope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
|
||||
}
|
||||
resolver.resolveStmts(value.Body, lambdaScope, class, functionResult(expected))
|
||||
|
|
@ -443,15 +473,20 @@ func (resolver *semanticResolver) resolveExpr(expr Expr, scope *Scope, class *Cl
|
|||
}
|
||||
}
|
||||
meaning, symbol := resolver.meaning(expr, scope)
|
||||
if ident, ok := expr.(IdentExpr); ok && meaning == UnresolvedExpr && isUnknownType(typ) && !isSemanticBuiltin(ident.Name) {
|
||||
resolver.addDiagnostic(undefinedDiagnostic(ident.Name, ident.Pos))
|
||||
}
|
||||
resolver.validateEnumExpression(expr, meaning)
|
||||
resolver.validateGenericCall(expr)
|
||||
if meaning == GoCallExpr && isResultType(expected) {
|
||||
typ = expected
|
||||
}
|
||||
if nullable, ok := typ.(NullableType); ok && typeEqual(nullable.Element, expected) {
|
||||
typ = expected
|
||||
}
|
||||
semantic := &HIRExpr{Type: typ, Meaning: meaning, Symbol: symbol}
|
||||
semantic := &HIRExpr{ID: len(resolver.program.HIR.Expressions) + 1, Type: typ, Meaning: meaning, Symbol: symbol}
|
||||
semantic.Node = resolver.hirNode(expr, semantic)
|
||||
resolver.program.HIR.Expressions = append(resolver.program.HIR.Expressions, semantic)
|
||||
return withExprMeta(expr, semantic), typ
|
||||
}
|
||||
|
||||
|
|
@ -699,6 +734,31 @@ func (resolver *semanticResolver) fail(err error) {
|
|||
}
|
||||
}
|
||||
|
||||
func (resolver *semanticResolver) addDiagnostic(diagnostic SemanticDiagnostic) {
|
||||
for _, existing := range resolver.program.Diagnostics {
|
||||
if existing.Code == diagnostic.Code && existing.Span.Start == diagnostic.Span.Start && existing.Message == diagnostic.Message {
|
||||
return
|
||||
}
|
||||
}
|
||||
resolver.program.Diagnostics = append(resolver.program.Diagnostics, diagnostic)
|
||||
}
|
||||
|
||||
func isSemanticBuiltin(name string) bool {
|
||||
return semanticBuiltins[name]
|
||||
}
|
||||
|
||||
var semanticBuiltins = map[string]bool{
|
||||
"println": true, "runCatching": true, "Channel": true,
|
||||
"listOf": true, "mutableListOf": true, "mapOf": true, "mutableMapOf": true,
|
||||
"append": true, "keys": true, "goAssert": true, "len": true, "cap": true,
|
||||
"make": true, "new": true, "copy": true, "delete": true, "close": true,
|
||||
"panic": true, "recover": true, "string": true, "int": true, "float64": true, "bool": true,
|
||||
"sql": true, "set": true, "now": true, "Result": true, "ByteSlice": true,
|
||||
"runBlocking": true, "coroutineScope": true, "launch": true, "async": true,
|
||||
"delay": true, "withTimeout": true, "isActive": true,
|
||||
"continue": true, "break": true,
|
||||
}
|
||||
|
||||
func (resolver *semanticResolver) validateEnumExpression(expr Expr, meaning ExprMeaning) {
|
||||
if meaning != EnumConstructionExpr {
|
||||
return
|
||||
|
|
@ -745,6 +805,30 @@ func (resolver *semanticResolver) validateEnumExpression(expr Expr, meaning Expr
|
|||
}
|
||||
}
|
||||
|
||||
func (resolver *semanticResolver) validateGenericCall(expr Expr) {
|
||||
call, ok := expr.(CallExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ident, ok := call.Callee.(IdentExpr)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var params []string
|
||||
found := false
|
||||
if function, ok := resolver.program.Functions[ident.Name]; ok {
|
||||
params = function.TypeParams
|
||||
found = true
|
||||
}
|
||||
if class, ok := resolver.program.ClassInfo[ident.Name]; ok {
|
||||
params = class.TypeParams
|
||||
found = true
|
||||
}
|
||||
if found && len(call.TypeArgs) > 0 && len(call.TypeArgs) != len(params) {
|
||||
resolver.fail(fmt.Errorf("%s expects %d type arguments, got %d", ident.Name, len(params), len(call.TypeArgs)))
|
||||
}
|
||||
}
|
||||
|
||||
func collectionElement(typ Type) Type {
|
||||
if generic, ok := typ.(GenericType); ok && len(generic.Args) > 0 {
|
||||
return generic.Args[len(generic.Args)-1]
|
||||
|
|
|
|||
|
|
@ -17,7 +17,14 @@ func Parse(input string) (*Program, error) {
|
|||
return nil, err
|
||||
}
|
||||
p := &parser{tokens: tokens}
|
||||
return p.parseProgram()
|
||||
program, err := p.parseProgram()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := hydrateTypeRefs(program); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return program, nil
|
||||
}
|
||||
|
||||
func (p *parser) parseProgram() (*Program, error) {
|
||||
|
|
@ -292,6 +299,10 @@ func (p *parser) parseClass() (ClassDecl, error) {
|
|||
if err != nil {
|
||||
return ClassDecl{}, err
|
||||
}
|
||||
typeParams, err := p.parseTypeParameters()
|
||||
if err != nil {
|
||||
return ClassDecl{}, err
|
||||
}
|
||||
var fields []FieldDecl
|
||||
if p.match(tokenLParen) {
|
||||
fields, err = p.parseClassFields()
|
||||
|
|
@ -308,7 +319,7 @@ func (p *parser) parseClass() (ClassDecl, error) {
|
|||
return ClassDecl{}, err
|
||||
}
|
||||
if !p.match(tokenLBrace) {
|
||||
return ClassDecl{Name: name.lexeme, Data: data, Fields: fields, Parents: parents, Methods: nil}, nil
|
||||
return ClassDecl{Name: name.lexeme, TypeParams: typeParams, Data: data, Fields: fields, Parents: parents, Methods: nil}, nil
|
||||
}
|
||||
|
||||
var methods []FunctionDecl
|
||||
|
|
@ -329,7 +340,7 @@ func (p *parser) parseClass() (ClassDecl, error) {
|
|||
return ClassDecl{}, err
|
||||
}
|
||||
|
||||
return ClassDecl{Name: name.lexeme, Data: data, Fields: fields, Parents: parents, Methods: methods}, nil
|
||||
return ClassDecl{Name: name.lexeme, TypeParams: typeParams, Data: data, Fields: fields, Parents: parents, Methods: methods}, nil
|
||||
}
|
||||
|
||||
func (p *parser) parseClassParents() ([]string, error) {
|
||||
|
|
@ -467,6 +478,7 @@ func (p *parser) parseFunction() (FunctionDecl, error) {
|
|||
|
||||
return FunctionDecl{
|
||||
Name: signature.Name,
|
||||
TypeParams: signature.TypeParams,
|
||||
Params: signature.Params,
|
||||
ReturnType: signature.ReturnType,
|
||||
Body: body,
|
||||
|
|
@ -483,6 +495,10 @@ func (p *parser) parseFunctionSignature() (FunctionSignature, error) {
|
|||
if err != nil {
|
||||
return FunctionSignature{}, err
|
||||
}
|
||||
typeParams, err := p.parseTypeParameters()
|
||||
if err != nil {
|
||||
return FunctionSignature{}, err
|
||||
}
|
||||
if _, err := p.expect(tokenLParen, "expected '('"); err != nil {
|
||||
return FunctionSignature{}, err
|
||||
}
|
||||
|
|
@ -505,12 +521,39 @@ func (p *parser) parseFunctionSignature() (FunctionSignature, error) {
|
|||
|
||||
return FunctionSignature{
|
||||
Name: name.lexeme,
|
||||
TypeParams: typeParams,
|
||||
Params: params,
|
||||
ReturnType: returnType,
|
||||
Suspend: suspend,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *parser) parseTypeParameters() ([]string, error) {
|
||||
if !p.match(tokenLt) {
|
||||
return nil, nil
|
||||
}
|
||||
var params []string
|
||||
seen := map[string]bool{}
|
||||
for {
|
||||
name, err := p.expect(tokenIdent, "expected type parameter")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if seen[name.lexeme] {
|
||||
return nil, fmt.Errorf("duplicate type parameter %s", name.lexeme)
|
||||
}
|
||||
seen[name.lexeme] = true
|
||||
params = append(params, name.lexeme)
|
||||
if !p.match(tokenComma) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if _, err := p.expect(tokenGt, "expected '>' after type parameters"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func (p *parser) parseParams() ([]Param, error) {
|
||||
var params []Param
|
||||
if p.check(tokenRParen) {
|
||||
|
|
@ -528,7 +571,11 @@ func (p *parser) parseParams() ([]Param, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params = append(params, Param{Name: name.lexeme, Type: typ})
|
||||
ref, err := ParseTypeRef(typ)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
params = append(params, Param{Name: name.lexeme, Type: typ, TypeRef: ref})
|
||||
if !p.match(tokenComma) {
|
||||
return params, nil
|
||||
}
|
||||
|
|
@ -832,7 +879,7 @@ func (p *parser) parseVarDecl(mutable bool) (Stmt, error) {
|
|||
return nil, err
|
||||
}
|
||||
if len(names) == 1 {
|
||||
return VarDecl{Mutable: mutable, Name: name, Type: typ, Value: value}, nil
|
||||
return VarDecl{Mutable: mutable, Name: name, Pos: names[0].pos, Type: typ, Value: value}, nil
|
||||
}
|
||||
decl := MultiVarDecl{Mutable: mutable, Names: make([]string, 0, len(names)), Value: value}
|
||||
for _, name := range names {
|
||||
|
|
@ -908,7 +955,7 @@ func (p *parser) parsePrefix() (Expr, error) {
|
|||
tok := p.advance()
|
||||
switch tok.kind {
|
||||
case tokenIdent:
|
||||
return p.parsePostfix(IdentExpr{Name: tok.lexeme})
|
||||
return p.parsePostfix(IdentExpr{Name: tok.lexeme, Pos: tok.pos})
|
||||
case tokenInt:
|
||||
return IntExpr{Value: tok.lexeme}, nil
|
||||
case tokenFloat:
|
||||
|
|
@ -999,6 +1046,10 @@ func (p *parser) parseLambdaParams() ([]Param, bool, error) {
|
|||
return nil, false, err
|
||||
}
|
||||
param.Type = typ
|
||||
param.TypeRef, err = ParseTypeRef(typ)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
params = append(params, param)
|
||||
if !p.match(tokenComma) {
|
||||
|
|
@ -1028,13 +1079,13 @@ func (p *parser) parsePostfix(expr Expr) (Expr, error) {
|
|||
if !selectorName(name.lexeme) {
|
||||
return nil, fmt.Errorf("expected selector name at %d, found %q", name.pos, name.lexeme)
|
||||
}
|
||||
expr = SelectorExpr{Receiver: expr, Name: name.lexeme}
|
||||
expr = SelectorExpr{Receiver: expr, Name: name.lexeme, NamePos: name.pos}
|
||||
case p.match(tokenSafeDot):
|
||||
name := p.advance()
|
||||
if !selectorName(name.lexeme) {
|
||||
return nil, fmt.Errorf("expected selector name at %d, found %q", name.pos, name.lexeme)
|
||||
}
|
||||
expr = SafeSelectorExpr{Receiver: expr, Name: name.lexeme}
|
||||
expr = SafeSelectorExpr{Receiver: expr, Name: name.lexeme, NamePos: name.pos}
|
||||
case p.match(tokenDoubleBang):
|
||||
expr = NonNullExpr{Value: expr}
|
||||
case p.match(tokenQuestion):
|
||||
|
|
|
|||
|
|
@ -33,7 +33,11 @@ func (checker *mutabilityChecker) checkFunction(function *FunctionDecl, class *C
|
|||
_ = checker.scope.Define(&Symbol{Name: "this", Kind: VariableSymbol, Type: ClassType{Class: class}})
|
||||
}
|
||||
for _, param := range function.Params {
|
||||
typ, _ := checker.semantic.ResolveType(param.Type)
|
||||
typeParams := append([]string{}, function.TypeParams...)
|
||||
if class != nil {
|
||||
typeParams = append(class.TypeParams, typeParams...)
|
||||
}
|
||||
typ, _ := checker.semantic.ResolveTypeRefWithParams(param.TypeRef, typeParams)
|
||||
_ = checker.scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
|
||||
}
|
||||
return checker.checkStmts(function.Body)
|
||||
|
|
@ -46,7 +50,7 @@ func (checker *mutabilityChecker) checkStmts(statements []Stmt) error {
|
|||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
typ, _ := checker.semantic.ResolveType(value.Type)
|
||||
typ, _ := checker.semantic.ResolveTypeRef(value.TypeRef)
|
||||
_ = checker.scope.Define(&Symbol{Name: value.Name, Kind: VariableSymbol, Type: typ, Mutable: value.Mutable})
|
||||
case MultiVarDecl:
|
||||
if err := checker.checkExpr(value.Value); err != nil {
|
||||
|
|
@ -214,7 +218,7 @@ func (checker *mutabilityChecker) checkExpr(expr Expr) error {
|
|||
_ = checker.scope.Define(&Symbol{Name: "it", Kind: VariableSymbol, Type: UnknownType{}})
|
||||
}
|
||||
for _, param := range value.Params {
|
||||
typ, _ := checker.semantic.ResolveType(param.Type)
|
||||
typ, _ := checker.semantic.ResolveTypeRef(param.TypeRef)
|
||||
_ = checker.scope.Define(&Symbol{Name: param.Name, Kind: VariableSymbol, Type: typ})
|
||||
}
|
||||
return checker.checkStmts(value.Body)
|
||||
|
|
|
|||
|
|
@ -48,26 +48,48 @@ func (scope *Scope) Lookup(name string) (*Symbol, bool) {
|
|||
}
|
||||
|
||||
type ClassSymbol struct {
|
||||
Name string
|
||||
Decl *ClassDecl
|
||||
Fields map[string]*Symbol
|
||||
Methods map[string]*Symbol
|
||||
Name string
|
||||
TypeParams []string
|
||||
Decl *ClassDecl
|
||||
Fields map[string]*Symbol
|
||||
Methods map[string]*Symbol
|
||||
}
|
||||
|
||||
type SemanticProgram struct {
|
||||
Syntax *Program
|
||||
Global *Scope
|
||||
Classes map[string]ClassDecl
|
||||
ClassInfo map[string]*ClassSymbol
|
||||
Functions map[string]FunctionDecl
|
||||
Enums map[string]EnumDecl
|
||||
Imports map[string]bool
|
||||
GoPackages map[string]*gotypes.Package
|
||||
HIR *HIRProgram
|
||||
Mappings *mappingState
|
||||
Syntax *Program
|
||||
Global *Scope
|
||||
Classes map[string]ClassDecl
|
||||
ClassInfo map[string]*ClassSymbol
|
||||
Functions map[string]FunctionDecl
|
||||
Enums map[string]EnumDecl
|
||||
Imports map[string]bool
|
||||
GoPackages map[string]*gotypes.Package
|
||||
HIR *HIRProgram
|
||||
Mappings *mappingState
|
||||
Diagnostics []SemanticDiagnostic
|
||||
}
|
||||
|
||||
func Analyze(program *Program) (*SemanticProgram, error) {
|
||||
semantic, diagnostics := AnalyzeWithContext(program, nil)
|
||||
if len(diagnostics) > 0 {
|
||||
return nil, diagnostics[0]
|
||||
}
|
||||
return semantic, nil
|
||||
}
|
||||
|
||||
func AnalyzeWithContext(program *Program, additional []*Program) (*SemanticProgram, []SemanticDiagnostic) {
|
||||
semantic, err := analyzeProgram(program, additional)
|
||||
if err != nil {
|
||||
return semantic, []SemanticDiagnostic{diagnosticForError("semantic-error", err)}
|
||||
}
|
||||
diagnostics := semantic.Diagnostics
|
||||
if len(diagnostics) > 0 {
|
||||
return semantic, diagnostics
|
||||
}
|
||||
return semantic, nil
|
||||
}
|
||||
|
||||
func analyzeProgram(program *Program, additional []*Program) (*SemanticProgram, error) {
|
||||
semantic := &SemanticProgram{
|
||||
Syntax: program,
|
||||
Global: NewScope(nil),
|
||||
|
|
@ -79,67 +101,94 @@ func Analyze(program *Program) (*SemanticProgram, error) {
|
|||
GoPackages: map[string]*gotypes.Package{},
|
||||
Mappings: &mappingState{functions: map[string]string{}},
|
||||
}
|
||||
for index := range program.Classes {
|
||||
decl := &program.Classes[index]
|
||||
class := &ClassSymbol{Name: decl.Name, Decl: decl, Fields: map[string]*Symbol{}, Methods: map[string]*Symbol{}}
|
||||
semantic.Classes[decl.Name] = *decl
|
||||
semantic.ClassInfo[decl.Name] = class
|
||||
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: ClassSymbolKind, Type: ClassType{Class: class}, Decl: decl}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for index := range program.Enums {
|
||||
decl := &program.Enums[index]
|
||||
semantic.Enums[decl.Name] = *decl
|
||||
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: EnumSymbolKind, Type: NamedType{Name: decl.Name}, Decl: decl}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for _, imported := range program.Imports {
|
||||
name := imported.Alias
|
||||
if name == "" {
|
||||
name = defaultImportAlias(imported)
|
||||
}
|
||||
semantic.Imports[name] = true
|
||||
if importedPackage, err := importGoPackage(imported); err == nil {
|
||||
semantic.GoPackages[name] = importedPackage
|
||||
}
|
||||
if existing, ok := semantic.Global.Lookup(name); ok && existing.Kind == ImportSymbolKind {
|
||||
continue
|
||||
}
|
||||
if err := semantic.Global.Define(&Symbol{Name: name, Kind: ImportSymbolKind, Type: NamedType{Name: name}, Decl: imported}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for index := range program.Functions {
|
||||
decl := &program.Functions[index]
|
||||
semantic.Functions[decl.Name] = *decl
|
||||
typ, err := semantic.functionType(decl.Params, decl.ReturnType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("function %s: %w", decl.Name, err)
|
||||
}
|
||||
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: FunctionSymbolKind, Type: typ, Decl: decl}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
for index := range program.Classes {
|
||||
decl := &program.Classes[index]
|
||||
class := semantic.ClassInfo[decl.Name]
|
||||
for fieldIndex := range decl.Fields {
|
||||
field := &decl.Fields[fieldIndex]
|
||||
typ, err := semantic.ResolveType(field.Type)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field %s.%s: %w", decl.Name, field.Name, err)
|
||||
programs := append([]*Program{program}, additional...)
|
||||
for _, source := range programs {
|
||||
for index := range source.Classes {
|
||||
decl := &source.Classes[index]
|
||||
class := &ClassSymbol{Name: decl.Name, TypeParams: decl.TypeParams, Decl: decl, Fields: map[string]*Symbol{}, Methods: map[string]*Symbol{}}
|
||||
semantic.Classes[decl.Name] = *decl
|
||||
semantic.ClassInfo[decl.Name] = class
|
||||
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: ClassSymbolKind, Type: ClassType{Class: class}, Decl: decl}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
class.Fields[field.Name] = &Symbol{Name: field.Name, Kind: VariableSymbol, Type: typ, Mutable: field.Mutable, Decl: field}
|
||||
}
|
||||
for methodIndex := range decl.Methods {
|
||||
method := &decl.Methods[methodIndex]
|
||||
typ, err := semantic.functionType(method.Params, method.ReturnType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("method %s.%s: %w", decl.Name, method.Name, err)
|
||||
}
|
||||
for _, source := range programs {
|
||||
for index := range source.Enums {
|
||||
decl := &source.Enums[index]
|
||||
semantic.Enums[decl.Name] = *decl
|
||||
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: EnumSymbolKind, Type: NamedType{Name: decl.Name}, Decl: decl}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, source := range programs {
|
||||
for _, imported := range source.Imports {
|
||||
name := imported.Alias
|
||||
if name == "" {
|
||||
name = defaultImportAlias(imported)
|
||||
}
|
||||
semantic.Imports[name] = true
|
||||
if importedPackage, err := importGoPackage(imported); err == nil {
|
||||
semantic.GoPackages[name] = importedPackage
|
||||
}
|
||||
if existing, ok := semantic.Global.Lookup(name); ok && existing.Kind == ImportSymbolKind {
|
||||
continue
|
||||
}
|
||||
if err := semantic.Global.Define(&Symbol{Name: name, Kind: ImportSymbolKind, Type: NamedType{Name: name}, Decl: imported}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, source := range programs {
|
||||
for index := range source.Embeds {
|
||||
embedded := &source.Embeds[index]
|
||||
typ, err := semantic.ResolveType(embedded.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, exists := semantic.Global.Lookup(embedded.Name); !exists {
|
||||
_ = semantic.Global.Define(&Symbol{Name: embedded.Name, Kind: VariableSymbol, Type: typ, Decl: embedded})
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, source := range programs {
|
||||
for index := range source.Functions {
|
||||
decl := &source.Functions[index]
|
||||
semantic.Functions[decl.Name] = *decl
|
||||
typ, err := semantic.functionType(decl.TypeParams, decl.Params, decl.ReturnType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("function %s: %w", decl.Name, err)
|
||||
}
|
||||
if err := semantic.Global.Define(&Symbol{Name: decl.Name, Kind: FunctionSymbolKind, Type: typ, Decl: decl}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, source := range programs {
|
||||
for index := range source.Classes {
|
||||
decl := &source.Classes[index]
|
||||
class := semantic.ClassInfo[decl.Name]
|
||||
for fieldIndex := range decl.Fields {
|
||||
field := &decl.Fields[fieldIndex]
|
||||
typ, err := semantic.ResolveTypeRefWithParams(field.TypeRef, decl.TypeParams)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field %s.%s: %w", decl.Name, field.Name, err)
|
||||
}
|
||||
class.Fields[field.Name] = &Symbol{Name: field.Name, Kind: VariableSymbol, Type: typ, Mutable: field.Mutable, Decl: field}
|
||||
}
|
||||
for methodIndex := range decl.Methods {
|
||||
method := &decl.Methods[methodIndex]
|
||||
if len(method.TypeParams) > 0 {
|
||||
return nil, fmt.Errorf("generic methods are not supported; declare type parameters on class %s", decl.Name)
|
||||
}
|
||||
methodParams := append(append([]string{}, decl.TypeParams...), method.TypeParams...)
|
||||
typ, err := semantic.functionType(methodParams, method.Params, method.ReturnType)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("method %s.%s: %w", decl.Name, method.Name, err)
|
||||
}
|
||||
class.Methods[method.Name] = &Symbol{Name: method.Name, Kind: FunctionSymbolKind, Type: typ, Decl: method}
|
||||
}
|
||||
class.Methods[method.Name] = &Symbol{Name: method.Name, Kind: FunctionSymbolKind, Type: typ, Decl: method}
|
||||
}
|
||||
}
|
||||
if err := validateMutability(semantic); err != nil {
|
||||
|
|
@ -164,6 +213,30 @@ func (semantic *SemanticProgram) ResolveType(text string) (Type, error) {
|
|||
return resolved, nil
|
||||
}
|
||||
|
||||
func (semantic *SemanticProgram) ResolveTypeRef(ref TypeRef) (Type, error) {
|
||||
return semantic.ResolveTypeRefWithParams(ref, nil)
|
||||
}
|
||||
|
||||
func (semantic *SemanticProgram) ResolveTypeRefWithParams(ref TypeRef, params []string) (Type, error) {
|
||||
typ := ref.Syntax
|
||||
if typ == nil {
|
||||
parsed, err := ParseType(ref.Source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
typ = parsed
|
||||
}
|
||||
paramSet := map[string]bool{}
|
||||
for _, param := range params {
|
||||
paramSet[param] = true
|
||||
}
|
||||
resolved := resolveClassTypes(resolveTypeParameters(typ, paramSet), semantic.ClassInfo)
|
||||
if err := validateNoClassPointer(resolved); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
func validateNoClassPointer(typ Type) error {
|
||||
switch value := typ.(type) {
|
||||
case GoPointerType:
|
||||
|
|
@ -190,20 +263,24 @@ func validateNoClassPointer(typ Type) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (semantic *SemanticProgram) functionType(params []Param, result string) (Type, error) {
|
||||
func (semantic *SemanticProgram) functionType(typeParams []string, params []Param, result string) (Type, error) {
|
||||
paramTypes := make([]Type, len(params))
|
||||
for index, param := range params {
|
||||
typ, err := semantic.ResolveType(param.Type)
|
||||
typ, err := semantic.ResolveTypeRefWithParams(param.TypeRef, typeParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
paramTypes[index] = typ
|
||||
}
|
||||
resultType, err := semantic.ResolveType(result)
|
||||
resultRef, err := ParseTypeRef(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FunctionType{Params: paramTypes, Result: resultType}, nil
|
||||
resultType, err := semantic.ResolveTypeRefWithParams(resultRef, typeParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return FunctionType{TypeParams: typeParams, Params: paramTypes, Result: resultType}, nil
|
||||
}
|
||||
|
||||
func (semantic *SemanticProgram) GoType(text string) string {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,14 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
|||
return resolve(value.TypeArgs[0])
|
||||
}
|
||||
receiverType := semantic.TypeOf(selector.Receiver, environment)
|
||||
if channel, ok := receiverType.(GenericType); ok && channel.Base.String() == "Channel" && len(channel.Args) == 1 {
|
||||
if selector.Name == "read" {
|
||||
return channel.Args[0]
|
||||
}
|
||||
if selector.Name == "send" {
|
||||
return NamedType{Name: "Unit"}
|
||||
}
|
||||
}
|
||||
if iterator, ok := receiverType.(GenericType); ok && iterator.Base.String() == "GotlinSQLIterator" && len(iterator.Args) == 1 {
|
||||
switch selector.Name {
|
||||
case "next":
|
||||
|
|
@ -78,10 +86,10 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
|||
return NamedType{Name: "Unit"}
|
||||
}
|
||||
}
|
||||
if class := classTypeOf(receiverType); class != nil {
|
||||
if class, bindings := classInstance(receiverType); class != nil {
|
||||
if method, ok := class.Methods[selector.Name]; ok {
|
||||
if function, ok := method.Type.(FunctionType); ok {
|
||||
return function.Result
|
||||
return substituteType(function.Result, bindings)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -90,16 +98,62 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
|||
}
|
||||
}
|
||||
if ident, ok := value.Callee.(IdentExpr); ok {
|
||||
switch ident.Name {
|
||||
case "Channel":
|
||||
if len(value.TypeArgs) == 1 {
|
||||
return GenericType{Base: NamedType{Name: "Channel"}, Args: []Type{resolve(value.TypeArgs[0])}}
|
||||
}
|
||||
case "listOf", "mutableListOf":
|
||||
element := Type(UnknownType{})
|
||||
if len(value.TypeArgs) == 1 {
|
||||
element = resolve(value.TypeArgs[0])
|
||||
} else if len(value.Args) > 0 {
|
||||
element = semantic.TypeOf(value.Args[0], environment)
|
||||
}
|
||||
return GenericType{Base: NamedType{Name: "List"}, Args: []Type{element}}
|
||||
case "mapOf", "mutableMapOf":
|
||||
key, item := Type(UnknownType{}), Type(UnknownType{})
|
||||
if len(value.TypeArgs) == 2 {
|
||||
key, item = resolve(value.TypeArgs[0]), resolve(value.TypeArgs[1])
|
||||
} else if len(value.Args) >= 2 {
|
||||
key, item = semantic.TypeOf(value.Args[0], environment), semantic.TypeOf(value.Args[1], environment)
|
||||
}
|
||||
return GenericType{Base: NamedType{Name: "Map"}, Args: []Type{key, item}}
|
||||
case "ByteSlice":
|
||||
return NamedType{Name: "ByteSlice"}
|
||||
case "async":
|
||||
if len(value.TypeArgs) == 1 {
|
||||
return GenericType{Base: NamedType{Name: "Deferred"}, Args: []Type{resolve(value.TypeArgs[0])}}
|
||||
}
|
||||
}
|
||||
if ident.Name == "keys" && len(value.Args) == 1 {
|
||||
if mapping, ok := semantic.TypeOf(value.Args[0], environment).(GenericType); ok && (mapping.Base.String() == "Map" || mapping.Base.String() == "MutableMap") && len(mapping.Args) == 2 {
|
||||
return GenericType{Base: NamedType{Name: "List"}, Args: []Type{mapping.Args[0]}}
|
||||
}
|
||||
}
|
||||
if function, ok := semantic.Functions[ident.Name]; ok {
|
||||
return resolve(function.ReturnType)
|
||||
if symbol, ok := semantic.Global.Lookup(ident.Name); ok && symbol.Kind == FunctionSymbolKind {
|
||||
if function, ok := symbol.Type.(FunctionType); ok {
|
||||
bindings := semantic.callTypeBindings(function.TypeParams, function.Params, value.TypeArgs, value.Args, environment)
|
||||
return substituteType(function.Result, bindings)
|
||||
}
|
||||
}
|
||||
if class, ok := semantic.ClassInfo[ident.Name]; ok {
|
||||
return ClassType{Class: class}
|
||||
if len(class.TypeParams) == 0 {
|
||||
return ClassType{Class: class}
|
||||
}
|
||||
params := make([]Type, 0, len(class.Fields))
|
||||
for _, field := range class.Decl.Fields {
|
||||
params = append(params, class.Fields[field.Name].Type)
|
||||
}
|
||||
bindings := semantic.callTypeBindings(class.TypeParams, params, value.TypeArgs, value.Args, environment)
|
||||
args := make([]Type, len(class.TypeParams))
|
||||
for index, name := range class.TypeParams {
|
||||
args[index] = bindings[name]
|
||||
if args[index] == nil {
|
||||
args[index] = TypeParameterType{Name: name}
|
||||
}
|
||||
}
|
||||
return GenericType{Base: ClassType{Class: class}, Args: args}
|
||||
}
|
||||
}
|
||||
if sqlType, ok := sqlChainResultType(value); ok {
|
||||
|
|
@ -121,9 +175,9 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
|||
return NamedType{Name: receiver.Name}
|
||||
}
|
||||
}
|
||||
if class := classTypeOf(semantic.TypeOf(value.Receiver, environment)); class != nil {
|
||||
if class, bindings := classInstance(semantic.TypeOf(value.Receiver, environment)); class != nil {
|
||||
if field, ok := class.Fields[value.Name]; ok {
|
||||
return field.Type
|
||||
return substituteType(field.Type, bindings)
|
||||
}
|
||||
}
|
||||
if field := semantic.goFieldType(semantic.TypeOf(value.Receiver, environment), value.Name); !isUnknownType(field) {
|
||||
|
|
@ -169,13 +223,43 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
|||
}
|
||||
|
||||
func classTypeOf(typ Type) *ClassSymbol {
|
||||
class, _ := classInstance(typ)
|
||||
return class
|
||||
}
|
||||
|
||||
func classInstance(typ Type) (*ClassSymbol, map[string]Type) {
|
||||
switch value := typ.(type) {
|
||||
case ClassType:
|
||||
return value.Class
|
||||
return value.Class, map[string]Type{}
|
||||
case NullableType:
|
||||
return classTypeOf(value.Element)
|
||||
return classInstance(value.Element)
|
||||
case GenericType:
|
||||
if class, ok := value.Base.(ClassType); ok {
|
||||
bindings := map[string]Type{}
|
||||
for index, name := range class.Class.TypeParams {
|
||||
if index < len(value.Args) {
|
||||
bindings[name] = value.Args[index]
|
||||
}
|
||||
}
|
||||
return class.Class, bindings
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (semantic *SemanticProgram) callTypeBindings(typeParams []string, params []Type, explicit []string, args []Expr, environment TypeEnvironment) map[string]Type {
|
||||
bindings := map[string]Type{}
|
||||
for index, argument := range explicit {
|
||||
if index < len(typeParams) {
|
||||
bindings[typeParams[index]] = func() Type { typ, _ := semantic.ResolveType(argument); return typ }()
|
||||
}
|
||||
}
|
||||
for index, argument := range args {
|
||||
if index < len(params) {
|
||||
inferTypeBindings(params[index], semantic.TypeOf(argument, environment), bindings)
|
||||
}
|
||||
}
|
||||
return bindings
|
||||
}
|
||||
|
||||
func nullableSemanticType(typ Type) Type {
|
||||
|
|
|
|||
248
internal/lang/type_refs.go
Normal file
248
internal/lang/type_refs.go
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
package lang
|
||||
|
||||
import "fmt"
|
||||
|
||||
func hydrateTypeRefs(program *Program) error {
|
||||
for index := range program.Embeds {
|
||||
ref, err := ParseTypeRef(program.Embeds[index].Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
program.Embeds[index].TypeRef = ref
|
||||
}
|
||||
for enumIndex := range program.Enums {
|
||||
for variantIndex := range program.Enums[enumIndex].Variants {
|
||||
variant := &program.Enums[enumIndex].Variants[variantIndex]
|
||||
variant.PayloadRefs = make([]TypeRef, len(variant.PayloadTypes))
|
||||
for index, text := range variant.PayloadTypes {
|
||||
ref, err := ParseTypeRef(text)
|
||||
if err != nil {
|
||||
return fmt.Errorf("enum %s.%s: %w", program.Enums[enumIndex].Name, variant.Name, err)
|
||||
}
|
||||
variant.PayloadRefs[index] = ref
|
||||
}
|
||||
}
|
||||
}
|
||||
for interfaceIndex := range program.Interfaces {
|
||||
for methodIndex := range program.Interfaces[interfaceIndex].Methods {
|
||||
if err := hydrateSignatureRefs(&program.Interfaces[interfaceIndex].Methods[methodIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for classIndex := range program.Classes {
|
||||
class := &program.Classes[classIndex]
|
||||
for fieldIndex := range class.Fields {
|
||||
ref, err := ParseTypeRef(class.Fields[fieldIndex].Type)
|
||||
if err != nil {
|
||||
return fmt.Errorf("field %s.%s: %w", class.Name, class.Fields[fieldIndex].Name, err)
|
||||
}
|
||||
class.Fields[fieldIndex].TypeRef = ref
|
||||
}
|
||||
for methodIndex := range class.Methods {
|
||||
if err := hydrateFunctionRefs(&class.Methods[methodIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
for index := range program.Functions {
|
||||
if err := hydrateFunctionRefs(&program.Functions[index]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hydrateSignatureRefs(signature *FunctionSignature) error {
|
||||
for index := range signature.Params {
|
||||
ref, err := ParseTypeRef(signature.Params[index].Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signature.Params[index].TypeRef = ref
|
||||
}
|
||||
ref, err := ParseTypeRef(signature.ReturnType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
signature.ReturnRef = ref
|
||||
return nil
|
||||
}
|
||||
|
||||
func hydrateFunctionRefs(function *FunctionDecl) error {
|
||||
for index := range function.Params {
|
||||
ref, err := ParseTypeRef(function.Params[index].Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
function.Params[index].TypeRef = ref
|
||||
}
|
||||
ref, err := ParseTypeRef(function.ReturnType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
function.ReturnRef = ref
|
||||
return hydrateStmtTypeRefs(function.Body)
|
||||
}
|
||||
|
||||
func hydrateStmtTypeRefs(statements []Stmt) error {
|
||||
for index, statement := range statements {
|
||||
switch value := statement.(type) {
|
||||
case VarDecl:
|
||||
ref, err := ParseTypeRef(value.Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value.TypeRef = ref
|
||||
if err := hydrateExprTypeRefs(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
statements[index] = value
|
||||
case MultiVarDecl:
|
||||
if err := hydrateExprTypeRefs(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case AssignStmt:
|
||||
if err := hydrateExprTypeRefs(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case AddAssignStmt:
|
||||
if err := hydrateExprTypeRefs(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case MultiAssignStmt:
|
||||
if err := hydrateExprTypeRefs(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case ReturnStmt:
|
||||
if value.Value != nil {
|
||||
if err := hydrateExprTypeRefs(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case ThrowStmt:
|
||||
if err := hydrateExprTypeRefs(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case DeferStmt:
|
||||
if err := hydrateExprTypeRefs(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case ExprStmt:
|
||||
if err := hydrateExprTypeRefs(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
case IfStmt:
|
||||
if err := hydrateExprTypeRefs(value.Cond); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hydrateStmtTypeRefs(value.Then); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hydrateStmtTypeRefs(value.Else); err != nil {
|
||||
return err
|
||||
}
|
||||
case WhileStmt:
|
||||
if err := hydrateExprTypeRefs(value.Cond); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hydrateStmtTypeRefs(value.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
case ForEachStmt:
|
||||
if err := hydrateExprTypeRefs(value.Source); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hydrateStmtTypeRefs(value.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
case MatchStmt:
|
||||
if err := hydrateExprTypeRefs(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, matchCase := range value.Cases {
|
||||
if err := hydrateStmtTypeRefs(matchCase.Body); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case TryCatchStmt:
|
||||
ref, err := ParseTypeRef(value.CatchType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value.CatchRef = ref
|
||||
statements[index] = value
|
||||
if err := hydrateStmtTypeRefs(value.TryBody); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := hydrateStmtTypeRefs(value.CatchBody); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func hydrateExprTypeRefs(expression Expr) error {
|
||||
switch value := expression.(type) {
|
||||
case UnaryExpr:
|
||||
return hydrateExprTypeRefs(value.Value)
|
||||
case NonNullExpr:
|
||||
return hydrateExprTypeRefs(value.Value)
|
||||
case TryExpr:
|
||||
return hydrateExprTypeRefs(value.Value)
|
||||
case BinaryExpr:
|
||||
if err := hydrateExprTypeRefs(value.Left); err != nil {
|
||||
return err
|
||||
}
|
||||
return hydrateExprTypeRefs(value.Right)
|
||||
case CallExpr:
|
||||
if err := hydrateExprTypeRefs(value.Callee); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, argument := range value.Args {
|
||||
if err := hydrateExprTypeRefs(argument); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, argument := range value.NamedArgs {
|
||||
if err := hydrateExprTypeRefs(argument.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case SelectorExpr:
|
||||
return hydrateExprTypeRefs(value.Receiver)
|
||||
case SafeSelectorExpr:
|
||||
return hydrateExprTypeRefs(value.Receiver)
|
||||
case IndexExpr:
|
||||
if err := hydrateExprTypeRefs(value.Receiver); err != nil {
|
||||
return err
|
||||
}
|
||||
return hydrateExprTypeRefs(value.Index)
|
||||
case EnumVariantExpr:
|
||||
for _, item := range value.Values {
|
||||
if err := hydrateExprTypeRefs(item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case MatchExpr:
|
||||
if err := hydrateExprTypeRefs(value.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, matchCase := range value.Cases {
|
||||
if err := hydrateExprTypeRefs(matchCase.Value); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
case LambdaExpr:
|
||||
for index := range value.Params {
|
||||
ref, err := ParseTypeRef(value.Params[index].Type)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value.Params[index].TypeRef = ref
|
||||
}
|
||||
return hydrateStmtTypeRefs(value.Body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -10,6 +10,19 @@ type Type interface {
|
|||
String() string
|
||||
}
|
||||
|
||||
type TypeRef struct {
|
||||
Source string
|
||||
Syntax Type
|
||||
}
|
||||
|
||||
func ParseTypeRef(source string) (TypeRef, error) {
|
||||
typ, err := ParseType(source)
|
||||
if err != nil {
|
||||
return TypeRef{}, err
|
||||
}
|
||||
return TypeRef{Source: source, Syntax: typ}, nil
|
||||
}
|
||||
|
||||
type UnknownType struct{}
|
||||
|
||||
func (UnknownType) typeNode() {}
|
||||
|
|
@ -20,6 +33,11 @@ type NamedType struct{ Name string }
|
|||
func (NamedType) typeNode() {}
|
||||
func (t NamedType) String() string { return t.Name }
|
||||
|
||||
type TypeParameterType struct{ Name string }
|
||||
|
||||
func (TypeParameterType) typeNode() {}
|
||||
func (t TypeParameterType) String() string { return t.Name }
|
||||
|
||||
type ClassType struct{ Class *ClassSymbol }
|
||||
|
||||
func (ClassType) typeNode() {}
|
||||
|
|
@ -36,8 +54,9 @@ func (GoPointerType) typeNode() {}
|
|||
func (t GoPointerType) String() string { return "*" + t.Element.String() }
|
||||
|
||||
type FunctionType struct {
|
||||
Params []Type
|
||||
Result Type
|
||||
TypeParams []string
|
||||
Params []Type
|
||||
Result Type
|
||||
}
|
||||
|
||||
func (FunctionType) typeNode() {}
|
||||
|
|
@ -54,6 +73,17 @@ type GenericType struct {
|
|||
Args []Type
|
||||
}
|
||||
|
||||
type TupleType struct{ Elements []Type }
|
||||
|
||||
func (TupleType) typeNode() {}
|
||||
func (t TupleType) String() string {
|
||||
elements := make([]string, len(t.Elements))
|
||||
for index, element := range t.Elements {
|
||||
elements[index] = element.String()
|
||||
}
|
||||
return "(" + strings.Join(elements, ", ") + ")"
|
||||
}
|
||||
|
||||
func (GenericType) typeNode() {}
|
||||
func (t GenericType) String() string {
|
||||
args := make([]string, 0, len(t.Args))
|
||||
|
|
@ -125,6 +155,8 @@ func resolveClassTypes(typ Type, classes map[string]*ClassSymbol) Type {
|
|||
return ClassType{Class: class}
|
||||
}
|
||||
return value
|
||||
case TypeParameterType:
|
||||
return value
|
||||
case NullableType:
|
||||
return NullableType{Element: resolveClassTypes(value.Element, classes)}
|
||||
case GoPointerType:
|
||||
|
|
@ -141,6 +173,46 @@ func resolveClassTypes(typ Type, classes map[string]*ClassSymbol) Type {
|
|||
args[i] = resolveClassTypes(arg, classes)
|
||||
}
|
||||
return GenericType{Base: resolveClassTypes(value.Base, classes), Args: args}
|
||||
case TupleType:
|
||||
elements := make([]Type, len(value.Elements))
|
||||
for index, element := range value.Elements {
|
||||
elements[index] = resolveClassTypes(element, classes)
|
||||
}
|
||||
return TupleType{Elements: elements}
|
||||
default:
|
||||
return typ
|
||||
}
|
||||
}
|
||||
|
||||
func resolveTypeParameters(typ Type, params map[string]bool) Type {
|
||||
switch value := typ.(type) {
|
||||
case NamedType:
|
||||
if params[value.Name] {
|
||||
return TypeParameterType{Name: value.Name}
|
||||
}
|
||||
return value
|
||||
case NullableType:
|
||||
return NullableType{Element: resolveTypeParameters(value.Element, params)}
|
||||
case GoPointerType:
|
||||
return GoPointerType{Element: resolveTypeParameters(value.Element, params)}
|
||||
case FunctionType:
|
||||
resolved := make([]Type, len(value.Params))
|
||||
for index, param := range value.Params {
|
||||
resolved[index] = resolveTypeParameters(param, params)
|
||||
}
|
||||
return FunctionType{TypeParams: value.TypeParams, Params: resolved, Result: resolveTypeParameters(value.Result, params)}
|
||||
case GenericType:
|
||||
args := make([]Type, len(value.Args))
|
||||
for index, arg := range value.Args {
|
||||
args[index] = resolveTypeParameters(arg, params)
|
||||
}
|
||||
return GenericType{Base: resolveTypeParameters(value.Base, params), Args: args}
|
||||
case TupleType:
|
||||
elements := make([]Type, len(value.Elements))
|
||||
for index, element := range value.Elements {
|
||||
elements[index] = resolveTypeParameters(element, params)
|
||||
}
|
||||
return TupleType{Elements: elements}
|
||||
default:
|
||||
return typ
|
||||
}
|
||||
|
|
@ -148,6 +220,63 @@ func resolveClassTypes(typ Type, classes map[string]*ClassSymbol) Type {
|
|||
|
||||
func typeEqual(left, right Type) bool { return left.String() == right.String() }
|
||||
|
||||
func substituteType(typ Type, bindings map[string]Type) Type {
|
||||
switch value := typ.(type) {
|
||||
case TypeParameterType:
|
||||
if bound, ok := bindings[value.Name]; ok {
|
||||
return bound
|
||||
}
|
||||
return value
|
||||
case NullableType:
|
||||
return NullableType{Element: substituteType(value.Element, bindings)}
|
||||
case GoPointerType:
|
||||
return GoPointerType{Element: substituteType(value.Element, bindings)}
|
||||
case GenericType:
|
||||
args := make([]Type, len(value.Args))
|
||||
for index, arg := range value.Args {
|
||||
args[index] = substituteType(arg, bindings)
|
||||
}
|
||||
return GenericType{Base: substituteType(value.Base, bindings), Args: args}
|
||||
case TupleType:
|
||||
elements := make([]Type, len(value.Elements))
|
||||
for index, element := range value.Elements {
|
||||
elements[index] = substituteType(element, bindings)
|
||||
}
|
||||
return TupleType{Elements: elements}
|
||||
case FunctionType:
|
||||
params := make([]Type, len(value.Params))
|
||||
for index, param := range value.Params {
|
||||
params[index] = substituteType(param, bindings)
|
||||
}
|
||||
return FunctionType{TypeParams: value.TypeParams, Params: params, Result: substituteType(value.Result, bindings)}
|
||||
default:
|
||||
return typ
|
||||
}
|
||||
}
|
||||
|
||||
func inferTypeBindings(parameter, argument Type, bindings map[string]Type) {
|
||||
switch expected := parameter.(type) {
|
||||
case TypeParameterType:
|
||||
if _, exists := bindings[expected.Name]; !exists && !isUnknownType(argument) {
|
||||
bindings[expected.Name] = argument
|
||||
}
|
||||
case NullableType:
|
||||
if actual, ok := argument.(NullableType); ok {
|
||||
inferTypeBindings(expected.Element, actual.Element, bindings)
|
||||
}
|
||||
case GenericType:
|
||||
actual, ok := argument.(GenericType)
|
||||
if !ok || expected.Base.String() != actual.Base.String() {
|
||||
return
|
||||
}
|
||||
for index := range expected.Args {
|
||||
if index < len(actual.Args) {
|
||||
inferTypeBindings(expected.Args[index], actual.Args[index], bindings)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func renderGoType(typ Type) string {
|
||||
switch value := typ.(type) {
|
||||
case UnknownType:
|
||||
|
|
@ -201,7 +330,12 @@ func renderGoType(typ Type) string {
|
|||
}
|
||||
return "*GotlinSQLIterator[" + argument + "]"
|
||||
}
|
||||
if class, ok := value.Base.(ClassType); ok {
|
||||
return "*" + class.Class.Name + "[" + strings.Join(args, ", ") + "]"
|
||||
}
|
||||
return base + "[" + strings.Join(args, ", ") + "]"
|
||||
case TypeParameterType:
|
||||
return value.Name
|
||||
case NamedType:
|
||||
switch value.Name {
|
||||
case "Int":
|
||||
|
|
@ -225,6 +359,12 @@ func renderGoType(typ Type) string {
|
|||
default:
|
||||
return value.Name
|
||||
}
|
||||
case TupleType:
|
||||
elements := make([]string, len(value.Elements))
|
||||
for index, element := range value.Elements {
|
||||
elements[index] = renderGoType(element)
|
||||
}
|
||||
return "(" + strings.Join(elements, ", ") + ")"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@ fun create(): Service { return Service(Repository()) }`)
|
|||
if program.Classes[1].Fields[0].Type != "Repository" || program.Functions[0].ReturnType != "Service" {
|
||||
t.Fatalf("syntax types were rewritten: %#v %#v", program.Classes[1].Fields[0], program.Functions[0])
|
||||
}
|
||||
if _, ok := program.Classes[1].Fields[0].TypeRef.Syntax.(NamedType); !ok {
|
||||
t.Fatalf("field syntax TypeRef was not parsed: %#v", program.Classes[1].Fields[0].TypeRef)
|
||||
}
|
||||
if _, err := GenerateGo(program); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -67,6 +70,9 @@ fun parse(value: String): Result<Int, Error> {
|
|||
if semantic.HIR == nil || len(semantic.HIR.Functions) != 1 {
|
||||
t.Fatal("typed HIR function was not created")
|
||||
}
|
||||
if len(semantic.HIR.Expressions) == 0 {
|
||||
t.Fatal("standalone HIR expression arena is empty")
|
||||
}
|
||||
decl := program.Functions[0].Body[0].(VarDecl)
|
||||
attempt := decl.Value.(TryExpr)
|
||||
if attempt.Meta.Semantic == nil || attempt.Meta.Semantic.Meaning != PropagateResultExpr || attempt.Meta.Semantic.Type.String() != "Int" {
|
||||
|
|
@ -75,6 +81,9 @@ fun parse(value: String): Result<Int, Error> {
|
|||
if _, ok := attempt.Meta.Semantic.Node.(HIRPropagateResult); !ok {
|
||||
t.Fatalf("propagation did not lower to HIRPropagateResult: %#v", attempt.Meta.Semantic.Node)
|
||||
}
|
||||
if semantic.HIR.Expressions[attempt.Meta.Semantic.ID-1] != attempt.Meta.Semantic {
|
||||
t.Fatal("syntax annotation does not reference the HIR-owned expression")
|
||||
}
|
||||
call := attempt.Value.(CallExpr)
|
||||
if call.Meta.Semantic == nil || call.Meta.Semantic.Meaning != GoCallExpr || call.Meta.Semantic.Type.String() != "Result<Int, Error>" {
|
||||
t.Fatalf("external call was not resolved: %#v", call.Meta.Semantic)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ for (const declaration of [
|
|||
|
||||
const prefixes = new Set(Object.values(snippets).map((snippet) => snippet.prefix));
|
||||
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"
|
||||
]) {
|
||||
assert(prefixes.has(prefix), `snippets are missing prefix ${prefix}`);
|
||||
|
|
|
|||
|
|
@ -36,6 +36,22 @@
|
|||
],
|
||||
"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": {
|
||||
"prefix": "tablerow",
|
||||
"body": [
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue