Preserve Gotlin semantics across packages
This commit is contained in:
parent
5b30e49486
commit
acb42a5702
10 changed files with 525 additions and 34 deletions
12
README.md
12
README.md
|
|
@ -12,6 +12,10 @@ The compiler keeps source spelling in its syntax AST, then builds lexical
|
||||||
symbols, structural semantic types, resolved expression meanings, and typed
|
symbols, structural semantic types, resolved expression meanings, and typed
|
||||||
HIR before Go emission. Class reference semantics live in `ClassType`; only
|
HIR before Go emission. Class reference semantics live in `ClassType`; only
|
||||||
the semantic Type-to-Go mapping turns a class such as `User` into `*User`.
|
the semantic Type-to-Go mapping turns a class such as `User` into `*User`.
|
||||||
|
Separately compiled Gotlin packages publish a versioned `.gti.json` interface;
|
||||||
|
imports load that interface before falling back to `go/types`, preserving class
|
||||||
|
reference semantics, generics, enums, function signatures, and inferred effects
|
||||||
|
across package boundaries.
|
||||||
|
|
||||||
## Supported language slice
|
## Supported language slice
|
||||||
|
|
||||||
|
|
@ -268,6 +272,14 @@ go run ./cmd/gotlinc build -src ./examples/hello.gt -o /tmp/hello.go
|
||||||
go run /tmp/hello.go
|
go run /tmp/hello.go
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Emit or consume package interfaces with repeatable metadata flags:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
gotlinc build -src -metadata-output platform.gti.json \
|
||||||
|
-metadata-package example/platform platform.gt -o platform.go
|
||||||
|
gotlinc build -src -metadata platform.gti.json service.gt -o service.go
|
||||||
|
```
|
||||||
|
|
||||||
Run directly:
|
Run directly:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
|
||||||
|
|
@ -326,27 +326,43 @@ func (s *server) buildDocumentState(uri, text string) documentState {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
state.program = program
|
state.program = program
|
||||||
programs, symbols := s.packageContext(uri, program.PackagePath, text)
|
programs, symbols, metadata := s.packageContext(uri, program.PackagePath, text)
|
||||||
_, semanticDiagnostics := lang.AnalyzeWithContext(program, programs)
|
_, semanticDiagnostics := lang.AnalyzeWithContextAndMetadata(program, programs, metadata)
|
||||||
state.symbols = indexSymbols(text, program)
|
state.symbols = indexSymbols(text, program)
|
||||||
state.packageSymbols = symbols
|
state.packageSymbols = symbols
|
||||||
state.diagnostics = diagnosticsFromSemantic(text, semanticDiagnostics)
|
state.diagnostics = diagnosticsFromSemantic(text, semanticDiagnostics)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) packageContext(currentURI, packagePath, currentText string) ([]*lang.Program, []symbol) {
|
func (s *server) packageContext(currentURI, packagePath, currentText string) ([]*lang.Program, []symbol, []*lang.PackageMetadata) {
|
||||||
path, ok := filePathFromURI(currentURI)
|
path, ok := filePathFromURI(currentURI)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, nil
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
root := nearestWorkspaceRoot(filepath.Dir(path))
|
||||||
|
if root == "" {
|
||||||
|
root = nearestModuleRoot(filepath.Dir(path))
|
||||||
}
|
}
|
||||||
root := nearestModuleRoot(filepath.Dir(path))
|
|
||||||
if root == "" {
|
if root == "" {
|
||||||
root = filepath.Dir(path)
|
root = filepath.Dir(path)
|
||||||
}
|
}
|
||||||
var programs []*lang.Program
|
var programs []*lang.Program
|
||||||
var symbols []symbol
|
var symbols []symbol
|
||||||
|
var metadata []*lang.PackageMetadata
|
||||||
_ = filepath.WalkDir(root, func(candidate string, entry os.DirEntry, err error) error {
|
_ = filepath.WalkDir(root, func(candidate string, entry os.DirEntry, err error) error {
|
||||||
if err != nil || entry.IsDir() || filepath.Ext(candidate) != ".gt" {
|
if err != nil || entry.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(candidate, ".gti.json") {
|
||||||
|
if body, readErr := os.ReadFile(candidate); readErr == nil {
|
||||||
|
var item lang.PackageMetadata
|
||||||
|
if json.Unmarshal(body, &item) == nil && item.Version == lang.PackageMetadataVersion {
|
||||||
|
metadata = append(metadata, &item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if filepath.Ext(candidate) != ".gt" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
candidateURI := fileURI(candidate)
|
candidateURI := fileURI(candidate)
|
||||||
|
|
@ -374,7 +390,20 @@ func (s *server) packageContext(currentURI, packagePath, currentText string) ([]
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
return programs, symbols
|
return programs, symbols, metadata
|
||||||
|
}
|
||||||
|
|
||||||
|
func nearestWorkspaceRoot(directory string) string {
|
||||||
|
for {
|
||||||
|
if _, err := os.Stat(filepath.Join(directory, "go.work")); err == nil {
|
||||||
|
return directory
|
||||||
|
}
|
||||||
|
parent := filepath.Dir(directory)
|
||||||
|
if parent == directory {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
directory = parent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func nearestModuleRoot(directory string) string {
|
func nearestModuleRoot(directory string) string {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
@ -32,6 +33,10 @@ func runBuild(args []string) {
|
||||||
fs := flag.NewFlagSet("build", flag.ExitOnError)
|
fs := flag.NewFlagSet("build", flag.ExitOnError)
|
||||||
srcOnly := fs.Bool("src", false, "emit Go source instead of building an executable")
|
srcOnly := fs.Bool("src", false, "emit Go source instead of building an executable")
|
||||||
outPath := fs.String("o", "", "output file path")
|
outPath := fs.String("o", "", "output file path")
|
||||||
|
metadataOutput := fs.String("metadata-output", "", "write Gotlin package metadata")
|
||||||
|
metadataPackage := fs.String("metadata-package", "", "import path recorded in package metadata")
|
||||||
|
var metadataInputs stringListFlag
|
||||||
|
fs.Var(&metadataInputs, "metadata", "Gotlin package metadata dependency (repeatable)")
|
||||||
fs.Usage = func() {
|
fs.Usage = func() {
|
||||||
fmt.Fprintln(os.Stderr, "usage: gotlinc build [-src] [-o output] <input.gt>")
|
fmt.Fprintln(os.Stderr, "usage: gotlinc build [-src] [-o output] <input.gt>")
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +53,7 @@ func runBuild(args []string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
inputPath := fs.Arg(0)
|
inputPath := fs.Arg(0)
|
||||||
goSrc := compileFiles(fs.Args(), !*srcOnly)
|
goSrc := compileFilesWithMetadata(fs.Args(), !*srcOnly, metadataInputs, *metadataOutput, *metadataPackage)
|
||||||
|
|
||||||
if *srcOnly {
|
if *srcOnly {
|
||||||
if *outPath == "" {
|
if *outPath == "" {
|
||||||
|
|
@ -117,6 +122,10 @@ func compileFile(inputPath string, forceMain bool) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func compileFiles(inputPaths []string, forceMain bool) string {
|
func compileFiles(inputPaths []string, forceMain bool) string {
|
||||||
|
return compileFilesWithMetadata(inputPaths, forceMain, nil, "", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func compileFilesWithMetadata(inputPaths []string, forceMain bool, metadataPaths []string, metadataOutput, metadataPackage string) string {
|
||||||
program := &lang.Program{}
|
program := &lang.Program{}
|
||||||
var firstSource string
|
var firstSource string
|
||||||
for _, inputPath := range inputPaths {
|
for _, inputPath := range inputPaths {
|
||||||
|
|
@ -146,12 +155,27 @@ func compileFiles(inputPaths []string, forceMain bool) string {
|
||||||
firstSource = string(src)
|
firstSource = string(src)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
metadata := loadMetadata(metadataPaths)
|
||||||
|
if metadataOutput != "" {
|
||||||
|
packageMetadata, err := lang.BuildPackageMetadata(program, metadataPackage)
|
||||||
|
if err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
encoded, err := json.MarshalIndent(packageMetadata, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
encoded = append(encoded, '\n')
|
||||||
|
if err := os.WriteFile(metadataOutput, encoded, 0o644); err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
var goSrc []byte
|
var goSrc []byte
|
||||||
var err error
|
var err error
|
||||||
if forceMain {
|
if forceMain {
|
||||||
goSrc, err = lang.GenerateGoMain(program)
|
goSrc, err = lang.GenerateGoMainWithMetadata(program, metadata)
|
||||||
} else {
|
} else {
|
||||||
goSrc, err = lang.GenerateGo(program)
|
goSrc, err = lang.GenerateGoWithMetadata(program, metadata)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fail(err)
|
fail(err)
|
||||||
|
|
@ -162,6 +186,30 @@ func compileFiles(inputPaths []string, forceMain bool) string {
|
||||||
return string(goSrc)
|
return string(goSrc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type stringListFlag []string
|
||||||
|
|
||||||
|
func (values *stringListFlag) String() string { return strings.Join(*values, ",") }
|
||||||
|
func (values *stringListFlag) Set(value string) error { *values = append(*values, value); return nil }
|
||||||
|
|
||||||
|
func loadMetadata(paths []string) []*lang.PackageMetadata {
|
||||||
|
metadata := make([]*lang.PackageMetadata, 0, len(paths))
|
||||||
|
for _, path := range paths {
|
||||||
|
body, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
fail(err)
|
||||||
|
}
|
||||||
|
var item lang.PackageMetadata
|
||||||
|
if err := json.Unmarshal(body, &item); err != nil {
|
||||||
|
fail(fmt.Errorf("load metadata %s: %w", path, err))
|
||||||
|
}
|
||||||
|
if item.Version != lang.PackageMetadataVersion {
|
||||||
|
fail(fmt.Errorf("metadata %s uses unsupported version %d", path, item.Version))
|
||||||
|
}
|
||||||
|
metadata = append(metadata, &item)
|
||||||
|
}
|
||||||
|
return metadata
|
||||||
|
}
|
||||||
|
|
||||||
func buildExecutable(goSrc string, outputPath string) error {
|
func buildExecutable(goSrc string, outputPath string) error {
|
||||||
tmpFile, err := os.CreateTemp("", "gotlinc-build-*.go")
|
tmpFile, err := os.CreateTemp("", "gotlinc-build-*.go")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -202,7 +250,7 @@ func normalizeBuildArgs(args []string) ([]string, error) {
|
||||||
switch {
|
switch {
|
||||||
case arg == "-src":
|
case arg == "-src":
|
||||||
flags = append(flags, arg)
|
flags = append(flags, arg)
|
||||||
case arg == "-o":
|
case arg == "-o" || arg == "-metadata" || arg == "-metadata-output" || arg == "-metadata-package":
|
||||||
if i+1 >= len(args) {
|
if i+1 >= len(args) {
|
||||||
return nil, fmt.Errorf("missing value for -o")
|
return nil, fmt.Errorf("missing value for -o")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,17 +11,25 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateGo(program *Program) ([]byte, error) {
|
func GenerateGo(program *Program) ([]byte, error) {
|
||||||
return generateGo(program, "")
|
return generateGo(program, "", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenerateGoMain(program *Program) ([]byte, error) {
|
func GenerateGoMain(program *Program) ([]byte, error) {
|
||||||
return generateGo(program, "main")
|
return generateGo(program, "main", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateGo(program *Program, packageOverride string) ([]byte, error) {
|
func GenerateGoWithMetadata(program *Program, metadata []*PackageMetadata) ([]byte, error) {
|
||||||
semantic, err := Analyze(program)
|
return generateGo(program, "", metadata)
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
|
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}
|
g := goGenerator{semantic: semantic}
|
||||||
if err := g.program(program, packageOverride); err != nil {
|
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] {
|
if ident, ok := e.Receiver.(IdentExpr); ok && g.semantic.Imports[ident.Name] {
|
||||||
name = exportedGoName(name)
|
name = exportedGoName(name)
|
||||||
} else if class, ok := g.classForType(g.exprType(e.Receiver)); ok {
|
} else if class, ok := g.classForType(g.exprType(e.Receiver)); ok {
|
||||||
for _, field := range class.Fields {
|
resolvedType, _ := g.semantic.ResolveType(g.exprType(e.Receiver))
|
||||||
if field.Name == name && class.Data && !field.Private {
|
if isImportedClassType(resolvedType) {
|
||||||
name = exportedGoName(name)
|
name = exportedGoName(name)
|
||||||
break
|
} 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 {
|
} 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) {
|
func (g *goGenerator) classForType(typ string) (ClassDecl, bool) {
|
||||||
typ = strings.TrimPrefix(typ, "*")
|
resolved, err := g.semantic.ResolveType(typ)
|
||||||
typ = strings.TrimSuffix(typ, "?")
|
if err != nil {
|
||||||
class, ok := g.semantic.Classes[typ]
|
return ClassDecl{}, false
|
||||||
return class, ok
|
}
|
||||||
|
class, _ := classInstance(resolved)
|
||||||
|
if class == nil || class.Decl == nil {
|
||||||
|
return ClassDecl{}, false
|
||||||
|
}
|
||||||
|
return *class.Decl, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *goGenerator) isDefined(name string) bool {
|
func (g *goGenerator) isDefined(name string) bool {
|
||||||
|
|
|
||||||
|
|
@ -590,6 +590,11 @@ func (resolver *semanticResolver) meaning(expr Expr, scope *Scope) (ExprMeaning,
|
||||||
return MappingExpression, nil
|
return MappingExpression, nil
|
||||||
}
|
}
|
||||||
if receiver, ok := selector.Receiver.(IdentExpr); ok {
|
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" {
|
if receiver.Name == "Result" {
|
||||||
return EnumConstructionExpr, nil
|
return EnumConstructionExpr, nil
|
||||||
}
|
}
|
||||||
|
|
@ -601,7 +606,7 @@ func (resolver *semanticResolver) meaning(expr Expr, scope *Scope) (ExprMeaning,
|
||||||
return GoCallExpr, nil
|
return GoCallExpr, nil
|
||||||
}
|
}
|
||||||
if class, _ := classInstance(ResolvedType(selector.Receiver)); class != 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
|
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
|
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 {
|
type SemanticProgram struct {
|
||||||
Syntax *Program
|
Syntax *Program
|
||||||
Global *Scope
|
Global *Scope
|
||||||
|
|
@ -69,6 +77,7 @@ type SemanticProgram struct {
|
||||||
Mappings *mappingState
|
Mappings *mappingState
|
||||||
Diagnostics []SemanticDiagnostic
|
Diagnostics []SemanticDiagnostic
|
||||||
FunctionEffects map[string]Effect
|
FunctionEffects map[string]Effect
|
||||||
|
Packages map[string]*PackageSymbol
|
||||||
}
|
}
|
||||||
|
|
||||||
func Analyze(program *Program) (*SemanticProgram, error) {
|
func Analyze(program *Program) (*SemanticProgram, error) {
|
||||||
|
|
@ -80,7 +89,19 @@ func Analyze(program *Program) (*SemanticProgram, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func AnalyzeWithContext(program *Program, additional []*Program) (*SemanticProgram, []SemanticDiagnostic) {
|
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 {
|
if err != nil {
|
||||||
return semantic, []SemanticDiagnostic{diagnosticForError("semantic-error", err)}
|
return semantic, []SemanticDiagnostic{diagnosticForError("semantic-error", err)}
|
||||||
}
|
}
|
||||||
|
|
@ -91,7 +112,7 @@ func AnalyzeWithContext(program *Program, additional []*Program) (*SemanticProgr
|
||||||
return semantic, nil
|
return semantic, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func analyzeProgram(program *Program, additional []*Program) (*SemanticProgram, error) {
|
func analyzeProgram(program *Program, additional []*Program, metadata []*PackageMetadata) (*SemanticProgram, error) {
|
||||||
semantic := &SemanticProgram{
|
semantic := &SemanticProgram{
|
||||||
Syntax: program,
|
Syntax: program,
|
||||||
Global: NewScope(nil),
|
Global: NewScope(nil),
|
||||||
|
|
@ -103,6 +124,7 @@ func analyzeProgram(program *Program, additional []*Program) (*SemanticProgram,
|
||||||
GoPackages: map[string]*gotypes.Package{},
|
GoPackages: map[string]*gotypes.Package{},
|
||||||
Mappings: &mappingState{functions: map[string]string{}},
|
Mappings: &mappingState{functions: map[string]string{}},
|
||||||
FunctionEffects: map[string]Effect{},
|
FunctionEffects: map[string]Effect{},
|
||||||
|
Packages: map[string]*PackageSymbol{},
|
||||||
}
|
}
|
||||||
programs := append([]*Program{program}, additional...)
|
programs := append([]*Program{program}, additional...)
|
||||||
for _, source := range programs {
|
for _, source := range programs {
|
||||||
|
|
@ -132,6 +154,9 @@ func analyzeProgram(program *Program, additional []*Program) (*SemanticProgram,
|
||||||
name = defaultImportAlias(imported)
|
name = defaultImportAlias(imported)
|
||||||
}
|
}
|
||||||
semantic.Imports[name] = true
|
semantic.Imports[name] = true
|
||||||
|
if packageMetadata := metadataForImport(imported, metadata); packageMetadata != nil {
|
||||||
|
semantic.Packages[name] = packageSymbolFromMetadata(name, packageMetadata)
|
||||||
|
}
|
||||||
if importedPackage, err := importGoPackage(imported); err == nil {
|
if importedPackage, err := importGoPackage(imported); err == nil {
|
||||||
semantic.GoPackages[name] = importedPackage
|
semantic.GoPackages[name] = importedPackage
|
||||||
}
|
}
|
||||||
|
|
@ -210,7 +235,7 @@ func (semantic *SemanticProgram) ResolveType(text string) (Type, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resolved := resolveClassTypes(typ, semantic.ClassInfo)
|
resolved := resolveImportedTypes(resolveClassTypes(typ, semantic.ClassInfo), semantic.Packages)
|
||||||
if err := validateNoClassPointer(resolved); err != nil {
|
if err := validateNoClassPointer(resolved); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -234,7 +259,7 @@ func (semantic *SemanticProgram) ResolveTypeRefWithParams(ref TypeRef, params []
|
||||||
for _, param := range params {
|
for _, param := range params {
|
||||||
paramSet[param] = true
|
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 {
|
if err := validateNoClassPointer(resolved); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -244,8 +269,11 @@ func (semantic *SemanticProgram) ResolveTypeRefWithParams(ref TypeRef, params []
|
||||||
func validateNoClassPointer(typ Type) error {
|
func validateNoClassPointer(typ Type) error {
|
||||||
switch value := typ.(type) {
|
switch value := typ.(type) {
|
||||||
case GoPointerType:
|
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)
|
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)
|
return validateNoClassPointer(value.Element)
|
||||||
case NullableType:
|
case NullableType:
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,13 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
||||||
case CallExpr:
|
case CallExpr:
|
||||||
if selector, ok := value.Callee.(SelectorExpr); ok {
|
if selector, ok := value.Callee.(SelectorExpr); ok {
|
||||||
if receiver, ok := selector.Receiver.(IdentExpr); 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 semantic.Imports[receiver.Name] {
|
||||||
if function, ok := semantic.goSelectorType(receiver.Name, selector.Name).(FunctionType); ok {
|
if function, ok := semantic.goSelectorType(receiver.Name, selector.Name).(FunctionType); ok {
|
||||||
return function.Result
|
return function.Result
|
||||||
|
|
@ -87,7 +94,7 @@ func (semantic *SemanticProgram) TypeOf(expr Expr, environment TypeEnvironment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if class, bindings := classInstance(receiverType); class != nil {
|
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 {
|
if function, ok := method.Type.(FunctionType); ok {
|
||||||
return substituteType(function.Result, bindings)
|
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 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)
|
return substituteType(field.Type, bindings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -233,6 +240,8 @@ func classInstance(typ Type) (*ClassSymbol, map[string]Type) {
|
||||||
switch value := typ.(type) {
|
switch value := typ.(type) {
|
||||||
case ClassType:
|
case ClassType:
|
||||||
return value.Class, map[string]Type{}
|
return value.Class, map[string]Type{}
|
||||||
|
case ImportedClassType:
|
||||||
|
return value.Class, map[string]Type{}
|
||||||
case NullableType:
|
case NullableType:
|
||||||
return classInstance(value.Element)
|
return classInstance(value.Element)
|
||||||
case GenericType:
|
case GenericType:
|
||||||
|
|
@ -245,6 +254,15 @@ func classInstance(typ Type) (*ClassSymbol, map[string]Type) {
|
||||||
}
|
}
|
||||||
return class.Class, bindings
|
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
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,14 @@ type ClassType struct{ Class *ClassSymbol }
|
||||||
func (ClassType) typeNode() {}
|
func (ClassType) typeNode() {}
|
||||||
func (t ClassType) String() string { return t.Class.Name }
|
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 }
|
type NullableType struct{ Element Type }
|
||||||
|
|
||||||
func (NullableType) typeNode() {}
|
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 {
|
func resolveTypeParameters(typ Type, params map[string]bool) Type {
|
||||||
switch value := typ.(type) {
|
switch value := typ.(type) {
|
||||||
case NamedType:
|
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 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 {
|
func substituteType(typ Type, bindings map[string]Type) Type {
|
||||||
switch value := typ.(type) {
|
switch value := typ.(type) {
|
||||||
case TypeParameterType:
|
case TypeParameterType:
|
||||||
|
|
@ -284,10 +338,12 @@ func renderGoType(typ Type) string {
|
||||||
return ""
|
return ""
|
||||||
case ClassType:
|
case ClassType:
|
||||||
return "*" + value.Class.Name
|
return "*" + value.Class.Name
|
||||||
|
case ImportedClassType:
|
||||||
|
return "*" + value.Package + "." + value.Class.Name
|
||||||
case NullableType:
|
case NullableType:
|
||||||
element := renderGoType(value.Element)
|
element := renderGoType(value.Element)
|
||||||
switch value.Element.(type) {
|
switch value.Element.(type) {
|
||||||
case ClassType, GoPointerType:
|
case ClassType, ImportedClassType, GoPointerType:
|
||||||
return element
|
return element
|
||||||
}
|
}
|
||||||
if element == "error" || element == "any" {
|
if element == "error" || element == "any" {
|
||||||
|
|
@ -334,6 +390,9 @@ func renderGoType(typ Type) string {
|
||||||
if class, ok := value.Base.(ClassType); ok {
|
if class, ok := value.Base.(ClassType); ok {
|
||||||
return "*" + class.Class.Name + "[" + strings.Join(args, ", ") + "]"
|
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, ", ") + "]"
|
return base + "[" + strings.Join(args, ", ") + "]"
|
||||||
case TypeParameterType:
|
case TypeParameterType:
|
||||||
return value.Name
|
return value.Name
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue