package lang import ( "fmt" "strconv" "strings" ) type mappingPair struct{ source, target, function string } type mappingState struct { functions map[string]string pairs []mappingPair } func (g *goGenerator) mappingTopLevelTarget(target string) string { if _, ok := g.classForType(target); ok && !strings.HasPrefix(target, "*") { return "*" + target } return target } func (g *goGenerator) ensureMapping(source, target, path string) (string, error) { key := source + "->" + target if function, ok := g.mappings.functions[key]; ok { return function, nil } if err := g.validateMapping(source, target, path, map[string]bool{}); err != nil { return "", err } function := fmt.Sprintf("gotlinMap%d", len(g.mappings.pairs)+1) g.mappings.functions[key] = function g.mappings.pairs = append(g.mappings.pairs, mappingPair{source: source, target: target, function: function}) return function, nil } func (g *goGenerator) validateMapping(source, target, path string, seen map[string]bool) error { if source == target { return nil } key := source + "->" + target if seen[key] { return nil } seen[key] = true if strings.HasSuffix(source, "?") || strings.HasSuffix(target, "?") { if strings.HasSuffix(source, "?") && !strings.HasSuffix(target, "?") { return mappingError(path, source, target) } return g.validateMapping(strings.TrimSuffix(source, "?"), strings.TrimSuffix(target, "?"), path, seen) } if sourceBase, sourceArgs, ok := parseGenericType(source); ok { targetBase, targetArgs, targetOK := parseGenericType(target) if !targetOK || sourceBase != targetBase || len(sourceArgs) != len(targetArgs) { return mappingError(path, source, target) } for i := range sourceArgs { if sourceBase == "Map" || sourceBase == "MutableMap" { if i == 0 && sourceArgs[i] != targetArgs[i] { return mappingError(path+".", sourceArgs[i], targetArgs[i]) } } if err := g.validateMapping(sourceArgs[i], targetArgs[i], path+"[]", seen); err != nil { return err } } return nil } sourceClass, sourceClassOK := g.classForType(source) targetClass, targetClassOK := g.classForType(target) if sourceClassOK || targetClassOK { if !sourceClassOK || !targetClassOK { return mappingError(path, source, target) } for _, targetField := range targetClass.Fields { sourceField, ok := classFieldByName(sourceClass, targetField.Name) fieldPath := path + "." + targetField.Name if !ok { return fmt.Errorf("cannot map %s: source field is missing for %s.%s", fieldPath, targetClass.Name, targetField.Name) } if err := g.validateMapping(sourceField.Type, targetField.Type, fieldPath, seen); err != nil { return err } } return nil } sourceEnum, sourceEnumOK := g.enums[strings.TrimPrefix(source, "*")] targetEnum, targetEnumOK := g.enums[strings.TrimPrefix(target, "*")] if sourceEnumOK || targetEnumOK { if sourceEnumOK && enumIsString(sourceEnum) && target == "String" { return nil } if targetEnumOK && enumIsString(targetEnum) && source == "String" { return nil } if !sourceEnumOK || !targetEnumOK { return mappingError(path, source, target) } for _, sourceVariant := range sourceEnum.Variants { targetVariant := enumVariant(targetEnum, sourceVariant.Name) variantPath := path + "." + sourceVariant.Name if targetVariant == nil { return fmt.Errorf("cannot map %s: target enum %s has no compatible variant", variantPath, targetEnum.Name) } if len(sourceVariant.PayloadTypes) != len(targetVariant.PayloadTypes) { return fmt.Errorf("cannot map %s: payload count %d is incompatible with %d", variantPath, len(sourceVariant.PayloadTypes), len(targetVariant.PayloadTypes)) } for i := range sourceVariant.PayloadTypes { if err := g.validateMapping(sourceVariant.PayloadTypes[i], targetVariant.PayloadTypes[i], fmt.Sprintf("%s[%d]", variantPath, i), seen); err != nil { return err } } } return nil } return mappingError(path, source, target) } func mappingError(path, source, target string) error { return fmt.Errorf("cannot map %s: %s is incompatible with %s", path, source, target) } func classFieldByName(class ClassDecl, name string) (FieldDecl, bool) { for _, field := range class.Fields { if field.Name == name { return field, true } } return FieldDecl{}, false } func mappingFieldName(class ClassDecl, field FieldDecl) string { if class.Data && !field.Private { return exportedGoName(field.Name) } return field.Name } func (g *goGenerator) emitMapping(pair mappingPair) error { g.line(fmt.Sprintf("func %s(source %s) %s {", pair.function, mapGoType(pair.source), mapGoType(pair.target))) g.indentLevel++ if sourceEnum, ok := g.enums[strings.TrimPrefix(pair.source, "*")]; ok { if enumIsString(sourceEnum) && pair.target == "String" { g.line("return string(source)") g.indentLevel-- g.line("}") return nil } targetEnum := g.enums[strings.TrimPrefix(pair.target, "*")] if enumIsString(sourceEnum) && enumIsString(targetEnum) { g.line("return " + targetEnum.Name + "(source)") g.indentLevel-- g.line("}") return nil } g.line("switch value := source.(type) {") g.indentLevel++ for _, sourceVariant := range sourceEnum.Variants { targetVariant := enumVariant(targetEnum, sourceVariant.Name) g.line("case *" + sourceEnum.Name + sourceVariant.Name + ":") g.indentLevel++ fields := make([]string, 0, len(sourceVariant.PayloadTypes)) for i := range sourceVariant.PayloadTypes { expr, err := g.mappingExpr(fmt.Sprintf("value.Value%d", i), sourceVariant.PayloadTypes[i], targetVariant.PayloadTypes[i], sourceEnum.Name+"."+sourceVariant.Name) if err != nil { return err } fields = append(fields, fmt.Sprintf("Value%d: %s", i, expr)) } g.line("return &" + targetEnum.Name + targetVariant.Name + "{" + strings.Join(fields, ", ") + "}") g.indentLevel-- } g.indentLevel-- g.line("}") g.line(`panic("unreachable enum mapping")`) } else if targetEnum, ok := g.enums[strings.TrimPrefix(pair.target, "*")]; ok && pair.source == "String" && enumIsString(targetEnum) { g.line("switch source {") g.indentLevel++ for _, variant := range targetEnum.Variants { g.line("case " + strconv.Quote(enumStringValue(variant)) + ":") g.indentLevel++ g.line("return " + targetEnum.Name + variant.Name) g.indentLevel-- } g.indentLevel-- g.line("}") g.line(`panic("unknown enum string: " + source)`) } else { expr, err := g.mappingExpr("source", pair.source, pair.target, strings.TrimPrefix(pair.source, "*")) if err != nil { return err } g.line("return " + expr) } g.indentLevel-- g.line("}") return nil } func (g *goGenerator) mappingExpr(expr, source, target, path string) (string, error) { if source == target { return expr, nil } if strings.HasSuffix(source, "?") || strings.HasSuffix(target, "?") { sourceInner := strings.TrimSuffix(source, "?") targetInner := strings.TrimSuffix(target, "?") inner, err := g.mappingExpr("*value", sourceInner, targetInner, path) if err != nil { return "", err } if strings.HasSuffix(source, "?") { return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; mapped := %s; return &mapped }(%s)", mapGoType(source), mapGoType(target), inner, expr), nil } inner, err = g.mappingExpr("value", sourceInner, targetInner, path) if err != nil { return "", err } return fmt.Sprintf("func(value %s) %s { mapped := %s; return &mapped }(%s)", mapGoType(source), mapGoType(target), inner, expr), nil } if sourceBase, sourceArgs, ok := parseGenericType(source); ok { _, targetArgs, _ := parseGenericType(target) if sourceBase == "List" || sourceBase == "MutableList" { item, err := g.mappingExpr("item", sourceArgs[0], targetArgs[0], path+"[]") if err != nil { return "", err } return fmt.Sprintf("func(values %s) %s { var result %s; for _, item := range values { result = append(result, %s) }; return result }(%s)", mapGoType(source), mapGoType(target), mapGoType(target), item, expr), nil } if sourceBase == "Map" || sourceBase == "MutableMap" { value, err := g.mappingExpr("item", sourceArgs[1], targetArgs[1], path+"[]") if err != nil { return "", err } return fmt.Sprintf("func(values %s) %s { result := make(%s, len(values)); for key, item := range values { result[key] = %s }; return result }(%s)", mapGoType(source), mapGoType(target), mapGoType(target), value, expr), nil } } if sourceClass, ok := g.classForType(source); ok { targetClass, _ := g.classForType(target) fields := make([]string, 0, len(targetClass.Fields)) for _, targetField := range targetClass.Fields { sourceField, _ := classFieldByName(sourceClass, targetField.Name) mapped, err := g.mappingExpr(expr+"."+mappingFieldName(sourceClass, sourceField), sourceField.Type, targetField.Type, path+"."+targetField.Name) if err != nil { return "", err } fields = append(fields, mappingFieldName(targetClass, targetField)+": "+mapped) } literal := targetClass.Name + "{" + strings.Join(fields, ", ") + "}" if strings.HasPrefix(target, "*") { literal = "&" + literal } if strings.HasPrefix(source, "*") { return fmt.Sprintf("func(value %s) %s { if value == nil { return nil }; return %s }(%s)", mapGoType(source), mapGoType(target), strings.ReplaceAll(literal, expr+".", "value."), expr), nil } return literal, nil } if _, ok := g.enums[strings.TrimPrefix(source, "*")]; ok { function, err := g.ensureMapping(source, target, path) if err != nil { return "", err } return function + "(" + expr + ")", nil } if targetEnum, ok := g.enums[strings.TrimPrefix(target, "*")]; ok && source == "String" && enumIsString(targetEnum) { function, err := g.ensureMapping(source, target, path) if err != nil { return "", err } return function + "(" + expr + ")", nil } return "", mappingError(path, source, target) }