package lang import ( "fmt" gotypes "go/types" ) type SymbolKind int const ( VariableSymbol SymbolKind = iota FunctionSymbolKind ClassSymbolKind EnumSymbolKind ImportSymbolKind ) type Symbol struct { Name string Kind SymbolKind Type Type Mutable bool Decl any Effects Effect } type Scope struct { Parent *Scope Symbols map[string]*Symbol } func NewScope(parent *Scope) *Scope { return &Scope{Parent: parent, Symbols: map[string]*Symbol{}} } func (scope *Scope) Define(symbol *Symbol) error { if _, exists := scope.Symbols[symbol.Name]; exists { return fmt.Errorf("duplicate symbol %s", symbol.Name) } scope.Symbols[symbol.Name] = symbol return nil } func (scope *Scope) Lookup(name string) (*Symbol, bool) { for current := scope; current != nil; current = current.Parent { if symbol, ok := current.Symbols[name]; ok { return symbol, true } } return nil, false } type ClassSymbol struct { Name string TypeParams []string Decl *ClassDecl Fields map[string]*Symbol Methods map[string]*Symbol } type PackageSymbol struct { Alias string Metadata *PackageMetadata Classes map[string]*ClassSymbol Functions map[string]*Symbol Enums map[string]EnumDecl } 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 Diagnostics []SemanticDiagnostic FunctionEffects map[string]Effect Packages map[string]*PackageSymbol } 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) { return analyzeWithMetadata(program, additional, nil) } func AnalyzeWithContextAndMetadata(program *Program, additional []*Program, metadata []*PackageMetadata) (*SemanticProgram, []SemanticDiagnostic) { return analyzeWithMetadata(program, additional, metadata) } func AnalyzeWithMetadata(program *Program, metadata []*PackageMetadata) (*SemanticProgram, []SemanticDiagnostic) { return analyzeWithMetadata(program, nil, metadata) } func analyzeWithMetadata(program *Program, additional []*Program, metadata []*PackageMetadata) (*SemanticProgram, []SemanticDiagnostic) { semantic, err := analyzeProgram(program, additional, metadata) 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, metadata []*PackageMetadata) (*SemanticProgram, error) { semantic := &SemanticProgram{ Syntax: program, Global: NewScope(nil), 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{}, Mappings: &mappingState{functions: map[string]string{}}, FunctionEffects: map[string]Effect{}, Packages: map[string]*PackageSymbol{}, } 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 } } } 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 packageMetadata := metadataForImport(imported, metadata); packageMetadata != nil { semantic.Packages[name] = packageSymbolFromMetadata(name, packageMetadata) } 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 decl.InferReturn { function := typ.(FunctionType) function.Result = UnknownType{} typ = function } 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) } if method.InferReturn { function := typ.(FunctionType) function.Result = UnknownType{} typ = function } class.Methods[method.Name] = &Symbol{Name: method.Name, Kind: FunctionSymbolKind, Type: typ, Decl: method} } } } if err := validateMutability(semantic); err != nil { return nil, err } resolver := semanticResolver{program: semantic} if err := resolver.resolve(); err != nil { return nil, err } inferCoroutineEffects(semantic) return semantic, nil } func (semantic *SemanticProgram) ResolveType(text string) (Type, error) { typ, err := ParseType(text) if err != nil { return nil, err } resolved := resolveImportedTypes(resolveClassTypes(typ, semantic.ClassInfo), semantic.Packages) if err := validateNoClassPointer(resolved); err != nil { return nil, err } 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 := resolveImportedTypes(resolveClassTypes(resolveTypeParameters(typ, paramSet), semantic.ClassInfo), semantic.Packages) if err := validateNoClassPointer(resolved); err != nil { return nil, err } return resolved, nil } func validateNoClassPointer(typ Type) error { switch value := typ.(type) { case GoPointerType: switch class := value.Element.(type) { case ClassType: return fmt.Errorf("Gotlin class %s is already reference-valued; remove '*'", class.Class.Name) case ImportedClassType: return fmt.Errorf("Gotlin class %s is already reference-valued; remove '*'", class.String()) } return validateNoClassPointer(value.Element) case NullableType: return validateNoClassPointer(value.Element) case GenericType: for _, arg := range value.Args { if err := validateNoClassPointer(arg); err != nil { return err } } case FunctionType: for _, param := range value.Params { if err := validateNoClassPointer(param); err != nil { return err } } return validateNoClassPointer(value.Result) } return nil } 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.ResolveTypeRefWithParams(param.TypeRef, typeParams) if err != nil { return nil, err } paramTypes[index] = typ } resultRef, err := ParseTypeRef(result) if err != nil { return nil, err } 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 { typ, err := semantic.ResolveType(text) if err != nil { return "" } return renderGoType(typ) }