Preserve Gotlin semantics across packages
This commit is contained in:
parent
5b30e49486
commit
acb42a5702
10 changed files with 525 additions and 34 deletions
|
|
@ -11,17 +11,25 @@ import (
|
|||
)
|
||||
|
||||
func GenerateGo(program *Program) ([]byte, error) {
|
||||
return generateGo(program, "")
|
||||
return generateGo(program, "", nil)
|
||||
}
|
||||
|
||||
func GenerateGoMain(program *Program) ([]byte, error) {
|
||||
return generateGo(program, "main")
|
||||
return generateGo(program, "main", nil)
|
||||
}
|
||||
|
||||
func generateGo(program *Program, packageOverride string) ([]byte, error) {
|
||||
semantic, err := Analyze(program)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func GenerateGoWithMetadata(program *Program, metadata []*PackageMetadata) ([]byte, error) {
|
||||
return generateGo(program, "", metadata)
|
||||
}
|
||||
|
||||
func GenerateGoMainWithMetadata(program *Program, metadata []*PackageMetadata) ([]byte, error) {
|
||||
return generateGo(program, "main", metadata)
|
||||
}
|
||||
|
||||
func generateGo(program *Program, packageOverride string, metadata []*PackageMetadata) ([]byte, error) {
|
||||
semantic, diagnostics := AnalyzeWithMetadata(program, metadata)
|
||||
if len(diagnostics) > 0 {
|
||||
return nil, diagnostics[0]
|
||||
}
|
||||
g := goGenerator{semantic: semantic}
|
||||
if err := g.program(program, packageOverride); err != nil {
|
||||
|
|
@ -1260,10 +1268,15 @@ func (g *goGenerator) expr(expr Expr, expectedType string) (string, error) {
|
|||
if ident, ok := e.Receiver.(IdentExpr); ok && g.semantic.Imports[ident.Name] {
|
||||
name = exportedGoName(name)
|
||||
} else if class, ok := g.classForType(g.exprType(e.Receiver)); ok {
|
||||
for _, field := range class.Fields {
|
||||
if field.Name == name && class.Data && !field.Private {
|
||||
name = exportedGoName(name)
|
||||
break
|
||||
resolvedType, _ := g.semantic.ResolveType(g.exprType(e.Receiver))
|
||||
if isImportedClassType(resolvedType) {
|
||||
name = exportedGoName(name)
|
||||
} else {
|
||||
for _, field := range class.Fields {
|
||||
if field.Name == name && class.Data && !field.Private {
|
||||
name = exportedGoName(name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ident, ok := e.Receiver.(IdentExpr); ok && (ident.Name == "this" || ident.Name == "self") && g.currentClass != nil {
|
||||
|
|
@ -2445,10 +2458,15 @@ func (g *goGenerator) isExternalGoCall(callee Expr) bool {
|
|||
}
|
||||
|
||||
func (g *goGenerator) classForType(typ string) (ClassDecl, bool) {
|
||||
typ = strings.TrimPrefix(typ, "*")
|
||||
typ = strings.TrimSuffix(typ, "?")
|
||||
class, ok := g.semantic.Classes[typ]
|
||||
return class, ok
|
||||
resolved, err := g.semantic.ResolveType(typ)
|
||||
if err != nil {
|
||||
return ClassDecl{}, false
|
||||
}
|
||||
class, _ := classInstance(resolved)
|
||||
if class == nil || class.Decl == nil {
|
||||
return ClassDecl{}, false
|
||||
}
|
||||
return *class.Decl, true
|
||||
}
|
||||
|
||||
func (g *goGenerator) isDefined(name string) bool {
|
||||
|
|
|
|||
|
|
@ -590,6 +590,11 @@ func (resolver *semanticResolver) meaning(expr Expr, scope *Scope) (ExprMeaning,
|
|||
return MappingExpression, nil
|
||||
}
|
||||
if receiver, ok := selector.Receiver.(IdentExpr); ok {
|
||||
if pack := resolver.program.Packages[receiver.Name]; pack != nil {
|
||||
if function := pack.Function(selector.Name); function != nil {
|
||||
return GotlinCallExpr, function
|
||||
}
|
||||
}
|
||||
if receiver.Name == "Result" {
|
||||
return EnumConstructionExpr, nil
|
||||
}
|
||||
|
|
@ -601,7 +606,7 @@ func (resolver *semanticResolver) meaning(expr Expr, scope *Scope) (ExprMeaning,
|
|||
return GoCallExpr, nil
|
||||
}
|
||||
if class, _ := classInstance(ResolvedType(selector.Receiver)); class != nil {
|
||||
if method, ok := class.Methods[selector.Name]; ok {
|
||||
if method := class.Method(selector.Name); method != nil {
|
||||
return MethodCallExpr, method
|
||||
}
|
||||
}
|
||||
|
|
|
|||
210
internal/lang/metadata.go
Normal file
210
internal/lang/metadata.go
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
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)
|
||||
}
|
||||
64
internal/lang/metadata_test.go
Normal file
64
internal/lang/metadata_test.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package lang
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestImportedGotlinClassesPreserveReferenceSemantics(t *testing.T) {
|
||||
library, err := Parse(`package platform
|
||||
class Lifecycle { fun stopping(): Boolean { return false } }
|
||||
fun createLifecycle(): Lifecycle { return Lifecycle() }`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
metadata, err := BuildPackageMetadata(library, "example/platform")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
consumer, err := Parse(`package service
|
||||
import platform "example/platform"
|
||||
class Worker(val lifecycle: platform.Lifecycle) {
|
||||
fun stopped(): Boolean { return lifecycle.stopping() }
|
||||
}
|
||||
fun create(): platform.Lifecycle { return platform.createLifecycle() }`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
semantic, diagnostics := AnalyzeWithMetadata(consumer, []*PackageMetadata{metadata})
|
||||
if len(diagnostics) != 0 {
|
||||
t.Fatalf("diagnostics: %#v", diagnostics)
|
||||
}
|
||||
fieldType := semantic.ClassInfo["Worker"].Fields["lifecycle"].Type
|
||||
if _, ok := fieldType.(ImportedClassType); !ok {
|
||||
t.Fatalf("type = %#v", fieldType)
|
||||
}
|
||||
output, err := GenerateGoWithMetadata(consumer, []*PackageMetadata{metadata})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, expected := range []string{"lifecycle *platform.Lifecycle", "func create() *platform.Lifecycle", "platform.CreateLifecycle()"} {
|
||||
if !strings.Contains(string(output), expected) {
|
||||
t.Fatalf("missing %q:\n%s", expected, output)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestImportedGotlinClassRejectsExplicitPointer(t *testing.T) {
|
||||
library, err := Parse(`package platform class Lifecycle`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
metadata, err := BuildPackageMetadata(library, "example/platform")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
consumer, err := Parse(`package service import platform "example/platform" fun use(value: *platform.Lifecycle) {}`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, diagnostics := AnalyzeWithMetadata(consumer, []*PackageMetadata{metadata})
|
||||
if len(diagnostics) == 0 || !strings.Contains(diagnostics[0].Message, "already reference-valued") {
|
||||
t.Fatalf("diagnostics: %#v", diagnostics)
|
||||
}
|
||||
}
|
||||
|
|
@ -56,6 +56,14 @@ type ClassSymbol struct {
|
|||
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
|
||||
|
|
@ -69,6 +77,7 @@ type SemanticProgram struct {
|
|||
Mappings *mappingState
|
||||
Diagnostics []SemanticDiagnostic
|
||||
FunctionEffects map[string]Effect
|
||||
Packages map[string]*PackageSymbol
|
||||
}
|
||||
|
||||
func Analyze(program *Program) (*SemanticProgram, error) {
|
||||
|
|
@ -80,7 +89,19 @@ func Analyze(program *Program) (*SemanticProgram, error) {
|
|||
}
|
||||
|
||||
func AnalyzeWithContext(program *Program, additional []*Program) (*SemanticProgram, []SemanticDiagnostic) {
|
||||
semantic, err := analyzeProgram(program, additional)
|
||||
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)}
|
||||
}
|
||||
|
|
@ -91,7 +112,7 @@ func AnalyzeWithContext(program *Program, additional []*Program) (*SemanticProgr
|
|||
return semantic, nil
|
||||
}
|
||||
|
||||
func analyzeProgram(program *Program, additional []*Program) (*SemanticProgram, error) {
|
||||
func analyzeProgram(program *Program, additional []*Program, metadata []*PackageMetadata) (*SemanticProgram, error) {
|
||||
semantic := &SemanticProgram{
|
||||
Syntax: program,
|
||||
Global: NewScope(nil),
|
||||
|
|
@ -103,6 +124,7 @@ func analyzeProgram(program *Program, additional []*Program) (*SemanticProgram,
|
|||
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 {
|
||||
|
|
@ -132,6 +154,9 @@ func analyzeProgram(program *Program, additional []*Program) (*SemanticProgram,
|
|||
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
|
||||
}
|
||||
|
|
@ -210,7 +235,7 @@ func (semantic *SemanticProgram) ResolveType(text string) (Type, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resolved := resolveClassTypes(typ, semantic.ClassInfo)
|
||||
resolved := resolveImportedTypes(resolveClassTypes(typ, semantic.ClassInfo), semantic.Packages)
|
||||
if err := validateNoClassPointer(resolved); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -234,7 +259,7 @@ func (semantic *SemanticProgram) ResolveTypeRefWithParams(ref TypeRef, params []
|
|||
for _, param := range params {
|
||||
paramSet[param] = true
|
||||
}
|
||||
resolved := resolveClassTypes(resolveTypeParameters(typ, paramSet), semantic.ClassInfo)
|
||||
resolved := resolveImportedTypes(resolveClassTypes(resolveTypeParameters(typ, paramSet), semantic.ClassInfo), semantic.Packages)
|
||||
if err := validateNoClassPointer(resolved); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -244,8 +269,11 @@ func (semantic *SemanticProgram) ResolveTypeRefWithParams(ref TypeRef, params []
|
|||
func validateNoClassPointer(typ Type) error {
|
||||
switch value := typ.(type) {
|
||||
case GoPointerType:
|
||||
if class, ok := value.Element.(ClassType); ok {
|
||||
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:
|
||||
|
|
|
|||
|
|
@ -44,6 +44,13 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
|||
case CallExpr:
|
||||
if selector, ok := value.Callee.(SelectorExpr); ok {
|
||||
if receiver, ok := selector.Receiver.(IdentExpr); ok {
|
||||
if pack := semantic.Packages[receiver.Name]; pack != nil {
|
||||
if function := pack.Function(selector.Name); function != nil {
|
||||
if signature, ok := function.Type.(FunctionType); ok {
|
||||
return signature.Result
|
||||
}
|
||||
}
|
||||
}
|
||||
if semantic.Imports[receiver.Name] {
|
||||
if function, ok := semantic.goSelectorType(receiver.Name, selector.Name).(FunctionType); ok {
|
||||
return function.Result
|
||||
|
|
@ -87,7 +94,7 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
|||
}
|
||||
}
|
||||
if class, bindings := classInstance(receiverType); class != nil {
|
||||
if method, ok := class.Methods[selector.Name]; ok {
|
||||
if method := class.Method(selector.Name); method != nil {
|
||||
if function, ok := method.Type.(FunctionType); ok {
|
||||
return substituteType(function.Result, bindings)
|
||||
}
|
||||
|
|
@ -178,7 +185,7 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
|||
}
|
||||
}
|
||||
if class, bindings := classInstance(semantic.TypeOf(value.Receiver, environment)); class != nil {
|
||||
if field, ok := class.Fields[value.Name]; ok {
|
||||
if field := class.Field(value.Name); field != nil {
|
||||
return substituteType(field.Type, bindings)
|
||||
}
|
||||
}
|
||||
|
|
@ -233,6 +240,8 @@ func classInstance(typ Type) (*ClassSymbol, map[string]Type) {
|
|||
switch value := typ.(type) {
|
||||
case ClassType:
|
||||
return value.Class, map[string]Type{}
|
||||
case ImportedClassType:
|
||||
return value.Class, map[string]Type{}
|
||||
case NullableType:
|
||||
return classInstance(value.Element)
|
||||
case GenericType:
|
||||
|
|
@ -245,6 +254,15 @@ func classInstance(typ Type) (*ClassSymbol, map[string]Type) {
|
|||
}
|
||||
return class.Class, bindings
|
||||
}
|
||||
if class, ok := value.Base.(ImportedClassType); ok {
|
||||
bindings := map[string]Type{}
|
||||
for index, name := range class.Class.TypeParams {
|
||||
if index < len(value.Args) {
|
||||
bindings[name] = value.Args[index]
|
||||
}
|
||||
}
|
||||
return class.Class, bindings
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,14 @@ type ClassType struct{ Class *ClassSymbol }
|
|||
func (ClassType) typeNode() {}
|
||||
func (t ClassType) String() string { return t.Class.Name }
|
||||
|
||||
type ImportedClassType struct {
|
||||
Package string
|
||||
Class *ClassSymbol
|
||||
}
|
||||
|
||||
func (ImportedClassType) typeNode() {}
|
||||
func (t ImportedClassType) String() string { return t.Package + "." + t.Class.Name }
|
||||
|
||||
type NullableType struct{ Element Type }
|
||||
|
||||
func (NullableType) typeNode() {}
|
||||
|
|
@ -185,6 +193,39 @@ func resolveClassTypes(typ Type, classes map[string]*ClassSymbol) Type {
|
|||
}
|
||||
}
|
||||
|
||||
func resolveImportedTypes(typ Type, packages map[string]*PackageSymbol) Type {
|
||||
switch value := typ.(type) {
|
||||
case NamedType:
|
||||
parts := strings.Split(value.Name, ".")
|
||||
if len(parts) == 2 {
|
||||
if pack := packages[parts[0]]; pack != nil {
|
||||
if class := pack.Classes[parts[1]]; class != nil {
|
||||
return ImportedClassType{Package: parts[0], Class: class}
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
case NullableType:
|
||||
return NullableType{Element: resolveImportedTypes(value.Element, packages)}
|
||||
case GoPointerType:
|
||||
return GoPointerType{Element: resolveImportedTypes(value.Element, packages)}
|
||||
case GenericType:
|
||||
args := make([]Type, len(value.Args))
|
||||
for index, arg := range value.Args {
|
||||
args[index] = resolveImportedTypes(arg, packages)
|
||||
}
|
||||
return GenericType{Base: resolveImportedTypes(value.Base, packages), Args: args}
|
||||
case FunctionType:
|
||||
params := make([]Type, len(value.Params))
|
||||
for index, param := range value.Params {
|
||||
params[index] = resolveImportedTypes(param, packages)
|
||||
}
|
||||
return FunctionType{TypeParams: value.TypeParams, Params: params, Result: resolveImportedTypes(value.Result, packages), Effects: value.Effects}
|
||||
default:
|
||||
return typ
|
||||
}
|
||||
}
|
||||
|
||||
func resolveTypeParameters(typ Type, params map[string]bool) Type {
|
||||
switch value := typ.(type) {
|
||||
case NamedType:
|
||||
|
|
@ -221,6 +262,19 @@ func resolveTypeParameters(typ Type, params map[string]bool) Type {
|
|||
|
||||
func typeEqual(left, right Type) bool { return left.String() == right.String() }
|
||||
|
||||
func isImportedClassType(typ Type) bool {
|
||||
switch value := typ.(type) {
|
||||
case ImportedClassType:
|
||||
return true
|
||||
case NullableType:
|
||||
return isImportedClassType(value.Element)
|
||||
case GenericType:
|
||||
_, ok := value.Base.(ImportedClassType)
|
||||
return ok
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func substituteType(typ Type, bindings map[string]Type) Type {
|
||||
switch value := typ.(type) {
|
||||
case TypeParameterType:
|
||||
|
|
@ -284,10 +338,12 @@ func renderGoType(typ Type) string {
|
|||
return ""
|
||||
case ClassType:
|
||||
return "*" + value.Class.Name
|
||||
case ImportedClassType:
|
||||
return "*" + value.Package + "." + value.Class.Name
|
||||
case NullableType:
|
||||
element := renderGoType(value.Element)
|
||||
switch value.Element.(type) {
|
||||
case ClassType, GoPointerType:
|
||||
case ClassType, ImportedClassType, GoPointerType:
|
||||
return element
|
||||
}
|
||||
if element == "error" || element == "any" {
|
||||
|
|
@ -334,6 +390,9 @@ func renderGoType(typ Type) string {
|
|||
if class, ok := value.Base.(ClassType); ok {
|
||||
return "*" + class.Class.Name + "[" + strings.Join(args, ", ") + "]"
|
||||
}
|
||||
if class, ok := value.Base.(ImportedClassType); ok {
|
||||
return "*" + class.Package + "." + class.Class.Name + "[" + strings.Join(args, ", ") + "]"
|
||||
}
|
||||
return base + "[" + strings.Join(args, ", ") + "]"
|
||||
case TypeParameterType:
|
||||
return value.Name
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue