Move LSP semantics into typed analysis
This commit is contained in:
parent
f4cd4f4458
commit
bac1183593
26 changed files with 1232 additions and 637 deletions
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue