Move LSP semantics into typed analysis

This commit is contained in:
pavel 2026-08-27 21:28:11 +02:00
commit bac1183593
26 changed files with 1232 additions and 637 deletions

View file

@ -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]