diff --git a/generics.go b/generics.go index 45cb426a9..66c84b85c 100644 --- a/generics.go +++ b/generics.go @@ -144,7 +144,7 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi parametrizedTypeSpec.TypeSpec.Name = ident - newType := resolveGenericType(original.TypeSpec.Type, genericParamTypeDefs) + newType := pkgDefs.resolveGenericType(original.File, original.TypeSpec.Type, genericParamTypeDefs, parseDependency) genericDefinitionsMutex.Lock() defer genericDefinitionsMutex.Unlock() @@ -197,22 +197,33 @@ func splitStructName(fullGenericForm string) (string, []string) { return genericTypeName, genericParams } -func resolveGenericType(expr ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) ast.Expr { +func (pkgDefs *PackagesDefinitions) resolveGenericType(file *ast.File, expr ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec, parseDependency bool) ast.Expr { switch astExpr := expr.(type) { case *ast.Ident: if genTypeSpec, ok := genericParamTypeDefs[astExpr.Name]; ok { - if genTypeSpec.ArrayDepth > 0 { - genTypeSpec.ArrayDepth-- - return &ast.ArrayType{Elt: resolveGenericType(expr, genericParamTypeDefs)} + retType := genTypeSpec.Type() + for i := 0; i < genTypeSpec.ArrayDepth; i++ { + retType = &ast.ArrayType{Elt: retType} } - return genTypeSpec.Type() + return retType } case *ast.ArrayType: return &ast.ArrayType{ - Elt: resolveGenericType(astExpr.Elt, genericParamTypeDefs), + Elt: pkgDefs.resolveGenericType(file, astExpr.Elt, genericParamTypeDefs, parseDependency), Len: astExpr.Len, Lbrack: astExpr.Lbrack, } + case *ast.StarExpr: + return &ast.StarExpr{ + Star: astExpr.Star, + X: pkgDefs.resolveGenericType(file, astExpr.X, genericParamTypeDefs, parseDependency), + } + case *ast.IndexExpr, *ast.IndexListExpr: + fullGenericName, _ := getGenericFieldType(file, expr, genericParamTypeDefs) + typeDef := pkgDefs.findGenericTypeSpec(fullGenericName, file, parseDependency) + if typeDef != nil { + return typeDef.TypeSpec.Type + } case *ast.StructType: newStructTypeDef := &ast.StructType{ Struct: astExpr.Struct, @@ -225,37 +236,51 @@ func resolveGenericType(expr ast.Expr, genericParamTypeDefs map[string]*genericT for _, field := range astExpr.Fields.List { newField := &ast.Field{ + Type: field.Type, Doc: field.Doc, Names: field.Names, Tag: field.Tag, Comment: field.Comment, } - newField.Type = resolveGenericType(field.Type, genericParamTypeDefs) - if newField.Type == nil { - newField.Type = field.Type - } + newField.Type = pkgDefs.resolveGenericType(file, field.Type, genericParamTypeDefs, parseDependency) newStructTypeDef.Fields.List = append(newStructTypeDef.Fields.List, newField) } return newStructTypeDef } - return nil + return expr } -func getExtendedGenericFieldType(file *ast.File, field ast.Expr) (string, error) { +func getExtendedGenericFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) { switch fieldType := field.(type) { case *ast.ArrayType: - fieldName, err := getExtendedGenericFieldType(file, fieldType.Elt) + fieldName, err := getExtendedGenericFieldType(file, fieldType.Elt, genericParamTypeDefs) return "[]" + fieldName, err case *ast.StarExpr: - return getExtendedGenericFieldType(file, fieldType.X) + return getExtendedGenericFieldType(file, fieldType.X, genericParamTypeDefs) + case *ast.Ident: + if genericParamTypeDefs != nil { + if typeSpec, ok := genericParamTypeDefs[fieldType.Name]; ok { + return typeSpec.Name, nil + } + } + if fieldType.Obj == nil { + return fieldType.Name, nil + } + + tSpec := &TypeSpecDef{ + File: file, + TypeSpec: fieldType.Obj.Decl.(*ast.TypeSpec), + PkgPath: file.Name.Name, + } + return tSpec.FullName(), nil default: return getFieldType(file, field) } } -func getGenericFieldType(file *ast.File, field ast.Expr) (string, error) { +func getGenericFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) { var fullName string var baseName string var err error @@ -268,7 +293,7 @@ func getGenericFieldType(file *ast.File, field ast.Expr) (string, error) { fullName = baseName + "[" for _, index := range fieldType.Indices { - fieldName, err := getExtendedGenericFieldType(file, index) + fieldName, err := getExtendedGenericFieldType(file, index, genericParamTypeDefs) if err != nil { return "", err } @@ -283,7 +308,7 @@ func getGenericFieldType(file *ast.File, field ast.Expr) (string, error) { return "", err } - indexName, err := getExtendedGenericFieldType(file, fieldType.Index) + indexName, err := getExtendedGenericFieldType(file, fieldType.Index, genericParamTypeDefs) if err != nil { return "", err } @@ -307,27 +332,27 @@ func getGenericFieldType(file *ast.File, field ast.Expr) (string, error) { } func getGenericTypeName(file *ast.File, field ast.Expr) (string, error) { - switch indexType := field.(type) { + switch fieldType := field.(type) { case *ast.Ident: - if indexType.Obj == nil { - return getFieldType(file, field) + if fieldType.Obj == nil { + return fieldType.Name, nil } tSpec := &TypeSpecDef{ File: file, - TypeSpec: indexType.Obj.Decl.(*ast.TypeSpec), + TypeSpec: fieldType.Obj.Decl.(*ast.TypeSpec), PkgPath: file.Name.Name, } return tSpec.FullName(), nil case *ast.ArrayType: tSpec := &TypeSpecDef{ File: file, - TypeSpec: indexType.Elt.(*ast.Ident).Obj.Decl.(*ast.TypeSpec), + TypeSpec: fieldType.Elt.(*ast.Ident).Obj.Decl.(*ast.TypeSpec), PkgPath: file.Name.Name, } return tSpec.FullName(), nil case *ast.SelectorExpr: - return fmt.Sprintf("%s.%s", indexType.X.(*ast.Ident).Name, indexType.Sel.Name), nil + return fmt.Sprintf("%s.%s", fieldType.X.(*ast.Ident).Name, fieldType.Sel.Name), nil } return "", fmt.Errorf("unknown type %#v", field) } @@ -344,10 +369,10 @@ func (parser *Parser) parseGenericTypeExpr(file *ast.File, typeExpr ast.Expr) (* case *ast.MapType: case *ast.FuncType: case *ast.IndexExpr: - name, err := getExtendedGenericFieldType(file, expr) + name, err := getExtendedGenericFieldType(file, expr, nil) if err == nil { if schema, err := parser.getTypeSchema(name, file, false); err == nil { - return spec.MapProperty(schema), nil + return schema, nil } } diff --git a/generics_other.go b/generics_other.go index 43acc9577..e82ef7329 100644 --- a/generics_other.go +++ b/generics_other.go @@ -9,6 +9,12 @@ import ( "go/ast" ) +type genericTypeSpec struct { + ArrayDepth int + TypeSpec *TypeSpecDef + Name string +} + func typeSpecFullName(typeSpecDef *TypeSpecDef) string { return typeSpecDef.FullName() } @@ -17,7 +23,7 @@ func (pkgDefs *PackagesDefinitions) parametrizeGenericType(file *ast.File, origi return original } -func getGenericFieldType(file *ast.File, field ast.Expr) (string, error) { +func getGenericFieldType(file *ast.File, field ast.Expr, genericParamTypeDefs map[string]*genericTypeSpec) (string, error) { return "", fmt.Errorf("unknown field type %#v", field) } diff --git a/parser.go b/parser.go index 582a53536..c1bbb90d0 100644 --- a/parser.go +++ b/parser.go @@ -1331,7 +1331,7 @@ func getFieldType(file *ast.File, field ast.Expr) (string, error) { return fullName, nil default: - return getGenericFieldType(file, field) + return getGenericFieldType(file, field, nil) } } diff --git a/testdata/generics_property/api/api.go b/testdata/generics_property/api/api.go index 4c0ceb80c..e68938f81 100644 --- a/testdata/generics_property/api/api.go +++ b/testdata/generics_property/api/api.go @@ -22,6 +22,8 @@ type CreateMovie struct { Producer types.Field[*Person] Audience Audience[Person] AudienceNames Audience[string] + Detail1 types.Field[types.Field[Person]] + Detail2 types.Field[types.Field[string]] } type Person struct { diff --git a/testdata/generics_property/expected.json b/testdata/generics_property/expected.json index 237159d82..36acc18ca 100644 --- a/testdata/generics_property/expected.json +++ b/testdata/generics_property/expected.json @@ -120,6 +120,12 @@ "cameraPeople": { "$ref": "#/definitions/types.Field-array_api_Person" }, + "detail1": { + "$ref": "#/definitions/types.Field-types_Field_api_Person" + }, + "detail2": { + "$ref": "#/definitions/types.Field-types_Field_string" + }, "directors": { "$ref": "#/definitions/types.Field-array_api_Person" }, @@ -167,6 +173,26 @@ "properties": { "value": { "$ref": "#/definitions/api.Person" + }, + "value2": { + "$ref": "#/definitions/api.Person" + }, + "value3": { + "type": "array", + "items": { + "$ref": "#/definitions/api.Person" + } + }, + "value4": { + "type": "object", + "properties": { + "subValue1": { + "$ref": "#/definitions/api.Person" + }, + "subValue2": { + "type": "string" + } + } } } }, @@ -178,6 +204,32 @@ "items": { "$ref": "#/definitions/api.Person" } + }, + "value2": { + "type": "array", + "items": { + "$ref": "#/definitions/api.Person" + } + }, + "value3": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/api.Person" + } + } + }, + "value4": { + "type": "object", + "properties": { + "subValue1": { + "$ref": "#/definitions/api.Person" + }, + "subValue2": { + "type": "string" + } + } } } }, @@ -189,6 +241,32 @@ "items": { "$ref": "#/definitions/types.Post" } + }, + "value2": { + "type": "array", + "items": { + "$ref": "#/definitions/types.Post" + } + }, + "value3": { + "type": "array", + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/types.Post" + } + } + }, + "value4": { + "type": "object", + "properties": { + "subValue1": { + "$ref": "#/definitions/types.Post" + }, + "subValue2": { + "type": "string" + } + } } } }, @@ -197,6 +275,82 @@ "properties": { "value": { "type": "string" + }, + "value2": { + "type": "string" + }, + "value3": { + "type": "array", + "items": { + "type": "string" + } + }, + "value4": { + "type": "object", + "properties": { + "subValue1": { + "type": "string" + }, + "subValue2": { + "type": "string" + } + } + } + } + }, + "types.Field-types_Field_api_Person": { + "type": "object", + "properties": { + "value": { + "$ref": "#/definitions/types.Field-api_Person" + }, + "value2": { + "$ref": "#/definitions/types.Field-api_Person" + }, + "value3": { + "type": "array", + "items": { + "$ref": "#/definitions/types.Field-api_Person" + } + }, + "value4": { + "type": "object", + "properties": { + "subValue1": { + "$ref": "#/definitions/types.Field-api_Person" + }, + "subValue2": { + "type": "string" + } + } + } + } + }, + "types.Field-types_Field_string": { + "type": "object", + "properties": { + "value": { + "$ref": "#/definitions/types.Field-string" + }, + "value2": { + "$ref": "#/definitions/types.Field-string" + }, + "value3": { + "type": "array", + "items": { + "$ref": "#/definitions/types.Field-string" + } + }, + "value4": { + "type": "object", + "properties": { + "subValue1": { + "$ref": "#/definitions/types.Field-string" + }, + "subValue2": { + "type": "string" + } + } } } }, diff --git a/testdata/generics_property/types/post.go b/testdata/generics_property/types/post.go index 84850e7ee..732863e79 100644 --- a/testdata/generics_property/types/post.go +++ b/testdata/generics_property/types/post.go @@ -1,7 +1,15 @@ package types +type SubField1[T any, T2 any] struct { + SubValue1 T + SubValue2 T2 +} + type Field[T any] struct { - Value T + Value T + Value2 *T + Value3 []T + Value4 SubField1[T, string] } type APIBase struct {