package lang import "strings" const PackageMetadataVersion = 1 type PackageMetadata struct { Version int `json:"version"` ImportPath string `json:"importPath"` PackageName string `json:"packageName"` Classes []ClassMetadata `json:"classes,omitempty"` Enums []EnumMetadata `json:"enums,omitempty"` Functions []FunctionMetadata `json:"functions,omitempty"` } type ClassMetadata struct { Name string `json:"name"` TypeParams []string `json:"typeParams,omitempty"` Data bool `json:"data,omitempty"` Fields []FieldMetadata `json:"fields,omitempty"` Methods []FunctionMetadata `json:"methods,omitempty"` } type FieldMetadata struct { Name string `json:"name"` Type string `json:"type"` Mutable bool `json:"mutable,omitempty"` } type FunctionMetadata struct { Name string `json:"name"` TypeParams []string `json:"typeParams,omitempty"` Params []ParamMetadata `json:"params,omitempty"` Result string `json:"result"` Effects Effect `json:"effects,omitempty"` } type ParamMetadata struct { Name string `json:"name"` Type string `json:"type"` } type EnumMetadata struct { Name string `json:"name"` Variants []EnumVariantMetadata `json:"variants"` } type EnumVariantMetadata struct { Name string `json:"name"` PayloadTypes []string `json:"payloadTypes,omitempty"` StringValue string `json:"stringValue,omitempty"` } func BuildPackageMetadata(program *Program, importPath string) (*PackageMetadata, error) { semantic, err := Analyze(program) if err != nil { return nil, err } metadata := &PackageMetadata{Version: PackageMetadataVersion, ImportPath: importPath, PackageName: goPackageName(program.PackagePath)} for index := range program.Classes { class := &program.Classes[index] item := ClassMetadata{Name: class.Name, TypeParams: class.TypeParams, Data: class.Data} for _, field := range class.Fields { item.Fields = append(item.Fields, FieldMetadata{Name: field.Name, Type: field.Type, Mutable: field.Mutable}) } for methodIndex := range class.Methods { method := &class.Methods[methodIndex] item.Methods = append(item.Methods, metadataFunction(method, semantic.FunctionEffects[class.Name+"."+method.Name])) } metadata.Classes = append(metadata.Classes, item) } for _, enum := range program.Enums { item := EnumMetadata{Name: enum.Name} for _, variant := range enum.Variants { item.Variants = append(item.Variants, EnumVariantMetadata{Name: variant.Name, PayloadTypes: variant.PayloadTypes, StringValue: variant.StringValue}) } metadata.Enums = append(metadata.Enums, item) } for index := range program.Functions { function := &program.Functions[index] metadata.Functions = append(metadata.Functions, metadataFunction(function, semantic.FunctionEffects[function.Name])) } return metadata, nil } func metadataFunction(function *FunctionDecl, effects Effect) FunctionMetadata { item := FunctionMetadata{Name: function.Name, TypeParams: function.TypeParams, Result: function.ReturnType, Effects: effects} for _, param := range function.Params { item.Params = append(item.Params, ParamMetadata{Name: param.Name, Type: param.Type}) } return item } func metadataForImport(imported ImportDecl, metadata []*PackageMetadata) *PackageMetadata { path := strings.Trim(imported.Path, `"`) if !strings.Contains(path, "/") { path = importPathToGoPath(path) } for _, candidate := range metadata { if candidate != nil && candidate.ImportPath == path { return candidate } } return nil } func packageSymbolFromMetadata(alias string, metadata *PackageMetadata) *PackageSymbol { pack := &PackageSymbol{Alias: alias, Metadata: metadata, Classes: map[string]*ClassSymbol{}, Functions: map[string]*Symbol{}, Enums: map[string]EnumDecl{}} for _, item := range metadata.Classes { decl := &ClassDecl{Name: item.Name, TypeParams: item.TypeParams, Data: item.Data} class := &ClassSymbol{Name: item.Name, TypeParams: item.TypeParams, Decl: decl, Fields: map[string]*Symbol{}, Methods: map[string]*Symbol{}} pack.Classes[item.Name] = class } for _, item := range metadata.Classes { class := pack.Classes[item.Name] for _, field := range item.Fields { typ := resolvePackageType(field.Type, alias, pack, item.TypeParams) decl := FieldDecl{Name: field.Name, Type: field.Type, Mutable: field.Mutable} class.Decl.Fields = append(class.Decl.Fields, decl) class.Fields[field.Name] = &Symbol{Name: field.Name, Kind: VariableSymbol, Type: typ, Mutable: field.Mutable, Decl: &decl} } for _, method := range item.Methods { typ := metadataFunctionType(method, alias, pack, item.TypeParams) class.Methods[method.Name] = &Symbol{Name: method.Name, Kind: FunctionSymbolKind, Type: typ, Effects: method.Effects} } } for _, item := range metadata.Functions { typ := metadataFunctionType(item, alias, pack, nil) pack.Functions[item.Name] = &Symbol{Name: item.Name, Kind: FunctionSymbolKind, Type: typ, Effects: item.Effects} } for _, item := range metadata.Enums { decl := EnumDecl{Name: item.Name} for _, variant := range item.Variants { decl.Variants = append(decl.Variants, EnumVariant{Name: variant.Name, PayloadTypes: variant.PayloadTypes, StringValue: variant.StringValue}) } pack.Enums[item.Name] = decl } return pack } func (pack *PackageSymbol) Function(name string) *Symbol { if function := pack.Functions[name]; function != nil { return function } return pack.Functions[exportedGoName(name)] } func (class *ClassSymbol) Method(name string) *Symbol { if method := class.Methods[name]; method != nil { return method } return class.Methods[exportedGoName(name)] } func (class *ClassSymbol) Field(name string) *Symbol { if field := class.Fields[name]; field != nil { return field } return class.Fields[exportedGoName(name)] } func metadataFunctionType(function FunctionMetadata, alias string, pack *PackageSymbol, enclosing []string) FunctionType { params := append(append([]string{}, enclosing...), function.TypeParams...) values := make([]Type, len(function.Params)) for index, param := range function.Params { values[index] = resolvePackageType(param.Type, alias, pack, params) } return FunctionType{TypeParams: function.TypeParams, Params: values, Result: resolvePackageType(function.Result, alias, pack, params), Effects: function.Effects} } func resolvePackageType(text, alias string, pack *PackageSymbol, params []string) Type { typ, err := ParseType(text) if err != nil { return UnknownType{} } paramSet := map[string]bool{} for _, param := range params { paramSet[param] = true } typ = resolveTypeParameters(typ, paramSet) var resolve func(Type) Type resolve = func(value Type) Type { switch item := value.(type) { case NamedType: if class := pack.Classes[item.Name]; class != nil { return ImportedClassType{Package: alias, Class: class} } return item case NullableType: return NullableType{Element: resolve(item.Element)} case GoPointerType: return GoPointerType{Element: resolve(item.Element)} case GenericType: args := make([]Type, len(item.Args)) for index, arg := range item.Args { args[index] = resolve(arg) } return GenericType{Base: resolve(item.Base), Args: args} case FunctionType: args := make([]Type, len(item.Params)) for index, arg := range item.Params { args[index] = resolve(arg) } return FunctionType{TypeParams: item.TypeParams, Params: args, Result: resolve(item.Result), Effects: item.Effects} default: return value } } return resolve(typ) }