gotlin/internal/lang/symbols.go

334 lines
10 KiB
Go

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)
}