Introduce typed semantic analysis pipeline

This commit is contained in:
pavel 2026-08-27 19:28:24 +02:00
commit f4cd4f4458
30 changed files with 2079 additions and 2381 deletions

View file

@ -306,6 +306,9 @@ 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))
}
state.symbols = indexSymbols(text, program)
state.diagnostics = semanticDiagnostics(text, program, state.symbols)
if _, err := lang.GenerateGo(program); err != nil {
@ -681,38 +684,6 @@ func indexSymbols(text string, program *lang.Program) []symbol {
})
}
}
workerRanges := findAllLineMatches(lines, regexp.MustCompile(`^\s*worker\s+([A-Za-z_][A-Za-z0-9_]*)`))
for i, decl := range program.Workers {
r := rng{}
if i < len(workerRanges) {
r = workerRanges[i]
}
symbols = append(symbols, symbol{
Name: decl.Name,
Kind: symbolKindClass,
Detail: renderWorkerSignature(decl),
Range: r,
})
for _, field := range decl.Fields {
symbols = append(symbols, symbol{
Name: field.Name,
Kind: symbolKindField,
Detail: renderWorkerFieldSignature(decl.Name, field),
Range: r,
Targets: []string{decl.Name},
})
}
for _, method := range decl.Methods {
symbols = append(symbols, symbol{
Name: method.Name,
Kind: symbolKindMethod,
Detail: renderMethodSignature(decl.Name, method),
Range: r,
Targets: []string{decl.Name},
})
}
}
funcRanges := findAllLineMatches(lines, regexp.MustCompile(`^\s*fun\s+([A-Za-z_][A-Za-z0-9_]*)`))
for i, fn := range program.Functions {
detail := renderFunctionSignature(fn)
@ -809,9 +780,6 @@ func semanticDiagnosticsWithPackage(text string, program *lang.Program, symbols
for _, decl := range program.Classes {
types[decl.Name] = true
}
for _, decl := range program.Workers {
types[decl.Name] = true
}
for _, decl := range program.Enums {
types[decl.Name] = true
}
@ -825,9 +793,6 @@ func semanticDiagnosticsWithPackage(text string, program *lang.Program, symbols
for _, decl := range sibling.Classes {
types[decl.Name] = true
}
for _, decl := range sibling.Workers {
types[decl.Name] = true
}
for _, decl := range sibling.Enums {
types[decl.Name] = true
}
@ -846,17 +811,6 @@ func semanticDiagnosticsWithPackage(text string, program *lang.Program, symbols
diagnostics = append(diagnostics, functionSemanticDiagnostics(text, method, functions, imports, types, fields)...)
}
}
for _, worker := range program.Workers {
fields := map[string]bool{
"this": true,
}
for _, field := range worker.Fields {
fields[field.Name] = true
}
for _, method := range worker.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 {
@ -913,8 +867,6 @@ func functionSemanticDiagnostics(text string, fn lang.FunctionDecl, functions ma
}
case lang.ThrowStmt:
walkExpr(s.Value, scope)
case lang.GoStmt:
walkExpr(s.Value, scope)
case lang.DeferStmt:
walkExpr(s.Value, scope)
case lang.ExprStmt:
@ -934,13 +886,6 @@ func functionSemanticDiagnostics(text string, fn lang.FunctionDecl, functions ma
bodyScope := copyScope(scope)
bodyScope[s.Name] = true
walkStmts(s.Body, bodyScope)
case lang.SelectStmt:
for _, c := range s.Cases {
walkExpr(c.Source, scope)
caseScope := copyScope(scope)
caseScope["it"] = true
walkStmts(c.Body, caseScope)
}
case lang.MatchStmt:
walkExpr(s.Value, scope)
for _, matchCase := range s.Cases {
@ -973,25 +918,6 @@ func functionSemanticDiagnostics(text string, fn lang.FunctionDecl, functions ma
walkExpr(e.Left, scope)
walkExpr(e.Right, scope)
case lang.CallExpr:
if ident, ok := e.Callee.(lang.IdentExpr); ok {
switch ident.Name {
case "after", "every":
if len(e.Args) != 1 {
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, ident.Name, ident.Name+"(ms) expects exactly one Int argument"))
}
if len(e.Args) == 1 {
switch e.Args[0].(type) {
case lang.StringExpr, lang.BoolExpr, lang.NullExpr:
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, ident.Name, ident.Name+"(ms) expects an Int argument"))
}
}
if ident.Name == "every" && len(e.Args) == 1 {
if value, ok := staticIntExprValue(e.Args[0]); ok && value <= 0 {
diagnostics = append(diagnostics, undefinedNameDiagnostic(text, ident.Name, "every(ms) requires ms > 0"))
}
}
}
}
walkExpr(e.Callee, scope)
for _, arg := range e.Args {
walkExpr(arg, scope)
@ -1339,10 +1265,6 @@ func renderClassSignature(decl lang.ClassDecl) string {
return b.String()
}
func renderWorkerSignature(decl lang.WorkerDecl) string {
return "worker " + decl.Name
}
func renderMethodSignature(className string, fn lang.FunctionDecl) string {
return className + "." + renderFunctionSignature(fn)
}
@ -1355,21 +1277,6 @@ func renderFieldSignature(className string, field lang.FieldDecl) string {
return className + "." + keyword + " " + field.Name + ": " + field.Type
}
func renderWorkerFieldSignature(workerName string, field lang.WorkerFieldDecl) string {
keyword := "val"
if field.Mutable {
keyword = "var"
}
typ := field.Type
if typ == "" {
typ = inferWorkerFieldType(field)
}
if typ != "" {
return workerName + "." + keyword + " " + field.Name + ": " + typ
}
return workerName + "." + keyword + " " + field.Name
}
func renderVariableSignature(name string, mutable bool, typ string) string {
keyword := "val"
if mutable {
@ -1390,10 +1297,6 @@ func collectVariableDeclInfos(program *lang.Program) []variableDeclInfo {
for _, class := range program.Classes {
classes[class.Name] = class
}
workers := map[string]lang.WorkerDecl{}
for _, worker := range program.Workers {
workers[worker.Name] = worker
}
var out []variableDeclInfo
var walkStmts func(stmts []lang.Stmt, scope map[string]string)
@ -1409,14 +1312,9 @@ func collectVariableDeclInfos(program *lang.Program) []variableDeclInfo {
if _, ok := classes[callee.Name]; ok {
return []string{callee.Name}
}
if _, ok := workers[callee.Name]; ok {
return []string{callee.Name}
}
switch callee.Name {
case "println":
return []string{"Unit"}
case "after", "every":
return []string{"Channel<time.Time>"}
case "Channel":
if len(call.TypeArgs) == 1 {
return []string{"Channel<" + call.TypeArgs[0] + ">"}
@ -1562,16 +1460,6 @@ func collectVariableDeclInfos(program *lang.Program) []variableDeclInfo {
}
}
}
if worker, ok := workers[trimmed]; ok {
for _, field := range worker.Fields {
if field.Name == e.Name {
if field.Type != "" {
return field.Type
}
return inferWorkerFieldType(field)
}
}
}
return ""
default:
return ""
@ -1584,7 +1472,12 @@ func collectVariableDeclInfos(program *lang.Program) []variableDeclInfo {
case lang.VarDecl:
typ := s.Type
if typ == "" {
typ = inferExprType(s.Value, scope)
resolved := lang.ResolvedType(s.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
@ -1613,16 +1506,6 @@ func collectVariableDeclInfos(program *lang.Program) []variableDeclInfo {
case lang.WhileStmt:
bodyScope := copyTypeScope(scope)
walkStmts(s.Body, bodyScope)
case lang.SelectStmt:
for _, c := range s.Cases {
caseScope := copyTypeScope(scope)
if elemType, ok := channelElementType(inferExprType(c.Source, scope)); ok {
caseScope["it"] = elemType
} else {
caseScope["it"] = "any"
}
walkStmts(c.Body, caseScope)
}
case lang.TryCatchStmt:
tryScope := copyTypeScope(scope)
walkStmts(s.TryBody, tryScope)
@ -1653,23 +1536,6 @@ func collectVariableDeclInfos(program *lang.Program) []variableDeclInfo {
walkStmts(method.Body, scope)
}
}
for _, worker := range program.Workers {
fieldScope := map[string]string{}
for _, field := range worker.Fields {
typ := field.Type
if typ == "" {
typ = inferWorkerFieldType(field)
}
fieldScope[field.Name] = typ
}
for _, method := range worker.Methods {
scope := copyTypeScope(fieldScope)
for _, param := range method.Params {
scope[param.Name] = param.Type
}
walkStmts(method.Body, scope)
}
}
return out
}
@ -1693,44 +1559,6 @@ func channelElementType(typ string) (string, bool) {
return inner, true
}
func staticIntExprValue(expr lang.Expr) (int, bool) {
switch e := expr.(type) {
case lang.IntExpr:
v, err := strconv.Atoi(e.Value)
if err != nil {
return 0, false
}
return v, true
case lang.UnaryExpr:
if e.Op != "-" {
return 0, false
}
v, ok := staticIntExprValue(e.Value)
if !ok {
return 0, false
}
return -v, true
default:
return 0, false
}
}
func inferWorkerFieldType(field lang.WorkerFieldDecl) string {
if field.Type != "" {
return field.Type
}
switch field.Value.(type) {
case lang.IntExpr:
return "Int"
case lang.StringExpr:
return "String"
case lang.BoolExpr:
return "Boolean"
default:
return ""
}
}
func selectorPathLang(expr lang.Expr) (string, bool) {
switch e := expr.(type) {
case lang.IdentExpr:
@ -2199,8 +2027,6 @@ var builtinDetails = map[string]string{
"println": "fun println(value: Any): Unit",
"runCatching": "fun runCatching(block: () -> Unit): Result",
"Channel": "fun Channel<T>(capacity: Int = 0): Channel<T>",
"after": "fun after(ms: Int): Channel<time.Time>",
"every": "fun every(ms: Int): Channel<time.Time>",
"listOf": "fun listOf<T>(values: T...): List<T>",
"mutableListOf": "fun mutableListOf<T>(values: T...): MutableList<T>",
"mapOf": "fun mapOf<K, V>(pairs: Any...): Map<K, V>",