Repository: swaggo/swag (go)

Issue:
Bug: Generics used with function scoped types use incorrect schema names
**Describe the bug**
After the #1866 PR, using generics with function scoped types returns wrong schema names which collide and overwrites each other.

**To Reproduce**
I created a simple project to reproduce the bug. You can follow the README to reproduce it.
Repo: https://github.com/berk-karaal/swag-generic-func-scoped-structs-bug

**Expected behavior**
Generics with function scoped types use correct schema names.

**Your swag version**
I use the HEAD of the `master` branch. Current latest commit hash: `10030b02b48c59c73e6c4fd07e3256b61aff339f`.
No issue before this commit.

**Your go version**
1.22.6

**Desktop (please complete the following information):**
 - OS: Fedora 40


Pull request:
Fix generics used with function scoped types
**Describe the PR**
In https://github.com/swaggo/swag/pull/1866 PR, the `SchemaName` field added to `TypeSpecDef` struct is not set for function scoped types. This PR sets `TypeSpecDef.SchemaName` for function scoped types too.

Also added some tests for the cases this PR fixes.

**Relation issue**
fixes #1882 


Patch:
diff --git a/packages.go b/packages.go
index 479b263..74439d4 100644
--- a/packages.go
+++ b/packages.go
@@ -252,6 +252,8 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as
 										pkgDefs.uniqueDefinitions[fullName] = nil
 										anotherTypeDef.NotUnique = true
 										pkgDefs.uniqueDefinitions[anotherTypeDef.TypeName()] = anotherTypeDef
+										anotherTypeDef.SetSchemaName()
+
 										typeSpecDef.NotUnique = true
 										fullName = typeSpecDef.TypeName()
 										pkgDefs.uniqueDefinitions[fullName] = typeSpecDef
@@ -261,6 +263,8 @@ func (pkgDefs *PackagesDefinitions) parseFunctionScopedTypesFromFile(astFile *as
 									functionScopedTypes[typeSpec.Name.Name] = typeSpecDef
 								}
 
+								typeSpecDef.SetSchemaName()
+
 								if pkgDefs.packages[typeSpecDef.PkgPath] == nil {
 									pkgDefs.packages[typeSpecDef.PkgPath] = NewPackageDefinitions(astFile.Name.Name, typeSpecDef.PkgPath).AddTypeSpec(fullName, typeSpecDef)
 								} else if _, ok = pkgDefs.packages[typeSpecDef.PkgPath].TypeDefinitions[fullName]; !ok {


Test patch:
diff --git a/generics_test.go b/generics_test.go
index 033bcf5..fecaa4d 100644
--- a/generics_test.go
+++ b/generics_test.go
@@ -134,6 +134,22 @@ func TestParseGenericsPackageAlias(t *testing.T) {
 	assert.Equal(t, string(expected), string(b))
 }
 
+func TestParseGenericsFunctionScoped(t *testing.T) {
+	t.Parallel()
+
+	searchDir := "testdata/generics_function_scoped"
+	expected, err := os.ReadFile(filepath.Join(searchDir, "expected.json"))
+	assert.NoError(t, err)
+
+	p := New()
+	err = p.ParseAPI(searchDir, mainAPIFile, defaultParseDepth)
+	assert.NoError(t, err)
+	b, err := json.MarshalIndent(p.swagger, "", "    ")
+
+	assert.NoError(t, err)
+	assert.Equal(t, string(expected), string(b))
+}
+
 func TestParametrizeStruct(t *testing.T) {
 	pd := PackagesDefinitions{
 		packages:          make(map[string]*PackageDefinitions),
diff --git a/testdata/generics_function_scoped/api/api.go b/testdata/generics_function_scoped/api/api.go
new file mode 100644
index 0000000..904bce3
--- /dev/null
+++ b/testdata/generics_function_scoped/api/api.go
@@ -0,0 +1,75 @@
+package api
+
+import (
+	"net/http"
+
+	"github.com/swaggo/swag/testdata/generics_function_scoped/types"
+)
+
+// @Summary Generic Response
+// @Produce  json
+// @Success 200 {object} types.GenericResponse[api.GetGeneric.User]
+// @Success 201 {object} types.GenericResponse[api.GetGeneric.Post]
+// @Router / [get]
+func GetGeneric(w http.ResponseWriter, r *http.Request) {
+	type User struct {
+		Username int    `json:"username"`
+		Email    string `json:"email"`
+	}
+	type Post struct {
+		Slug  int    `json:"slug"`
+		Title string `json:"title"`
+	}
+
+	_ = types.GenericResponse[any]{}
+}
+
+// @Summary Generic Response With Custom Type Names
+// @Produce  json
+// @Success 200 {object} types.GenericResponse[api.GetGenericRenamed.User]
+// @Success 201 {object} types.GenericResponse[api.GetGenericRenamed.Post]
+// @Router /renamed [get]
+func GetGenericRenamed(w http.ResponseWriter, r *http.Request) {
+	type User struct {
+		Username int    `json:"username"`
+		Email    string `json:"email"`
+	} // @Name RenamedUserData
+	type Post struct {
+		Slug  int    `json:"slug"`
+		Title string `json:"title"`
+	} // @Name RenamedPostData
+
+	_ = types.GenericResponse[any]{}
+}
+
+// @Summary Multiple Generic Response
+// @Produce  json
+// @Success 200 {object} types.GenericMultiResponse[api.GetGenericMulti.MyStructA, api.GetGenericMulti.MyStructB]
+// @Success 201 {object} types.GenericMultiResponse[api.GetGenericMulti.MyStructB, api.GetGenericMulti.MyStructA]
+// @Router /multi [get]
+func GetGenericMulti(w http.ResponseWriter, r *http.Request) {
+	type MyStructA struct {
+		SomeFieldA string `json:"some_field_a"`
+	}
+	type MyStructB struct {
+		SomeFieldB string `json:"some_field_b"`
+	}
+
+	_ = types.GenericMultiResponse[any, any]{}
+}
+
+// @Summary Multiple Generic Response With Custom Type Names
+// @Produce  json
+// @Success 200 {object} types.GenericMultiResponse[api.GetGenericMultiRenamed.MyStructA, api.GetGenericMultiRenamed.MyStructB]
+// @Success 201 {object} types.GenericMultiResponse[api.GetGenericMultiRenamed.MyStructB, api.GetGenericMultiRenamed.MyStructA]
+// @Router /multi-renamed [get]
+func GetGenericMultiRenamed(w http.ResponseWriter, r *http.Request) {
+	type MyStructA struct {
+		SomeFieldA string `json:"some_field_a"`
+	} // @Name NameForMyStructA
+	type MyStructB struct {
+		SomeFieldB string `json:"some_field_b"`
+	} // @Name NameForMyStructB
+
+	_ = types.GenericMultiResponse[any, any]{}
+}
diff --git a/testdata/generics_function_scoped/expected.json b/testdata/generics_function_scoped/expected.json
new file mode 100644
index 0000000..3fb99fa
--- /dev/null
+++ b/testdata/generics_function_scoped/expected.json
@@ -0,0 +1,279 @@
+{
+    "swagger": "2.0",
+    "info": {
+        "description": "This is a sample server.",
+        "title": "Swagger Example API",
+        "contact": {},
+        "version": "1.0"
+    },
+    "host": "localhost:8080",
+    "basePath": "/api",
+    "paths": {
+        "/": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Generic Response",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-api_GetGeneric_User"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-api_GetGeneric_Post"
+                        }
+                    }
+                }
+            }
+        },
+        "/multi": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Multiple Generic Response",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-api_GetGenericMulti_MyStructA-api_GetGenericMulti_MyStructB"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-api_GetGenericMulti_MyStructB-api_GetGenericMulti_MyStructA"
+                        }
+                    }
+                }
+            }
+        },
+        "/multi-renamed": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Multiple Generic Response With Custom Type Names",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-NameForMyStructA-NameForMyStructB"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericMultiResponse-NameForMyStructB-NameForMyStructA"
+                        }
+                    }
+                }
+            }
+        },
+        "/renamed": {
+            "get": {
+                "produces": [
+                    "application/json"
+                ],
+                "summary": "Generic Response With Custom Type Names",
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-RenamedUserData"
+                        }
+                    },
+                    "201": {
+                        "description": "Created",
+                        "schema": {
+                            "$ref": "#/definitions/types.GenericResponse-RenamedPostData"
+                        }
+                    }
+                }
+            }
+        }
+    },
+    "definitions": {
+        "NameForMyStructA": {
+            "type": "object",
+            "properties": {
+                "some_field_a": {
+                    "type": "string"
+                }
+            }
+        },
+        "NameForMyStructB": {
+            "type": "object",
+            "properties": {
+                "some_field_b": {
+                    "type": "string"
+                }
+            }
+        },
+        "RenamedPostData": {
+            "type": "object",
+            "properties": {
+                "slug": {
+                    "type": "integer"
+                },
+                "title": {
+                    "type": "string"
+                }
+            }
+        },
+        "RenamedUserData": {
+            "type": "object",
+            "properties": {
+                "email": {
+                    "type": "string"
+                },
+                "username": {
+                    "type": "integer"
+                }
+            }
+        },
+        "api.GetGeneric.Post": {
+            "type": "object",
+            "properties": {
+                "slug": {
+                    "type": "integer"
+                },
+                "title": {
+                    "type": "string"
+                }
+            }
+        },
+        "api.GetGeneric.User": {
+            "type": "object",
+            "properties": {
+                "email": {
+                    "type": "string"
+                },
+                "username": {
+                    "type": "integer"
+                }
+            }
+        },
+        "api.GetGenericMulti.MyStructA": {
+            "type": "object",
+            "properties": {
+                "some_field_a": {
+                    "type": "string"
+                }
+            }
+        },
+        "api.GetGenericMulti.MyStructB": {
+            "type": "object",
+            "properties": {
+                "some_field_b": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-NameForMyStructA-NameForMyStructB": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/NameForMyStructA"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/NameForMyStructB"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-NameForMyStructB-NameForMyStructA": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/NameForMyStructB"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/NameForMyStructA"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-api_GetGenericMulti_MyStructA-api_GetGenericMulti_MyStructB": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructA"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructB"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericMultiResponse-api_GetGenericMulti_MyStructB-api_GetGenericMulti_MyStructA": {
+            "type": "object",
+            "properties": {
+                "data_t": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructB"
+                },
+                "data_x": {
+                    "$ref": "#/definitions/api.GetGenericMulti.MyStructA"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-RenamedPostData": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/RenamedPostData"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-RenamedUserData": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/RenamedUserData"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-api_GetGeneric_Post": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/api.GetGeneric.Post"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        },
+        "types.GenericResponse-api_GetGeneric_User": {
+            "type": "object",
+            "properties": {
+                "data": {
+                    "$ref": "#/definitions/api.GetGeneric.User"
+                },
+                "status": {
+                    "type": "string"
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/testdata/generics_function_scoped/main.go b/testdata/generics_function_scoped/main.go
new file mode 100644
index 0000000..7dd44c3
--- /dev/null
+++ b/testdata/generics_function_scoped/main.go
@@ -0,0 +1,20 @@
+package main
+
+import (
+	"net/http"
+
+	"github.com/swaggo/swag/testdata/generics_function_scoped/api"
+)
+
+// @title Swagger Example API
+// @version 1.0
+// @description This is a sample server.
+// @host localhost:8080
+// @basePath /api
+func main() {
+	http.HandleFunc("/", api.GetGeneric)
+	http.HandleFunc("/renamed", api.GetGenericRenamed)
+	http.HandleFunc("/multi", api.GetGenericMulti)
+	http.HandleFunc("/multi-renamed", api.GetGenericMulti)
+	http.ListenAndServe(":8080", nil)
+}
diff --git a/testdata/generics_function_scoped/types/response.go b/testdata/generics_function_scoped/types/response.go
new file mode 100644
index 0000000..d12e532
--- /dev/null
+++ b/testdata/generics_function_scoped/types/response.go
@@ -0,0 +1,12 @@
+package types
+
+type GenericResponse[T any] struct {
+	Status string `json:"status"`
+	Data   T      `json:"data"`
+}
+
+type GenericMultiResponse[T any, X any] struct {
+	Status string `json:"status"`
+	DataT  T      `json:"data_t"`
+	DataX  X      `json:"data_x"`
+}