Skip to content

Commit bbec724

Browse files
Copilotwzshiming
andcommitted
Add generics support - type parameters, generic types and functions
Co-authored-by: wzshiming <[email protected]>
1 parent 4968528 commit bbec724

16 files changed

Lines changed: 352 additions & 22 deletions

.github/workflows/go.yml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,6 @@ jobs:
1414
strategy:
1515
matrix:
1616
go-version:
17-
- '1.13'
18-
- '1.14'
19-
- '1.15'
20-
- '1.16'
21-
- '1.17'
2217
- '1.18'
2318
- '1.19'
2419
- '1.20'

README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ Golang source code parsing, usage like reflect package
1515

1616
[Examples](https://github.com/wzshiming/gotype/blob/master/cmd/pkgimport/main.go)
1717

18-
## TODO
19-
20-
- Supports generics
21-
2218
## License
2319

2420
Licensed under the MIT License. See [LICENSE](https://github.com/wzshiming/gotype/blob/master/LICENSE) for the full license text.

README_cn.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ Golang 源代码解析,像反射包一样使用
1515

1616
[示例](https://github.com/wzshiming/gotype/blob/master/cmd/pkgimport/main.go)
1717

18-
## TODO
19-
20-
- 支持泛型
21-
2218
## 许可证
2319

2420
软包根据MIT License。有关完整的许可证文本,请参阅[LICENSE](https://github.com/wzshiming/gotype/blob/master/LICENSE)

kind.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,5 @@ const (
5151
Field // a Struct Field
5252
Scope // package or func body
5353
Declaration // a top-level function, variable, or constant.
54+
TypeParam // a type parameter in a generic type or function
5455
)

kind_string.go

Lines changed: 6 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

other_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ func TestOther(t *testing.T) {
1515
"github.com/wzshiming/gotype/testdata/kind",
1616
"github.com/wzshiming/gotype/testdata/type",
1717
"github.com/wzshiming/gotype/testdata/pkg",
18+
"github.com/wzshiming/gotype/testdata/generics",
1819
"./testdata/value",
1920
"./testdata/kind",
2021
"./testdata/type",
2122
"./testdata/pkg",
23+
"./testdata/generics",
2224
}
2325
for _, src := range testpath {
2426
testAll(t, src)
@@ -133,6 +135,20 @@ func testType(t *testing.T, fset *token.FileSet, v Type) {
133135
if !ok {
134136
t.Fatal(pos, "Error not found: ", to)
135137
}
138+
case "TypeParam":
139+
if len(method) < 2 {
140+
t.Fatal(pos, "Error TypeParam num: ", to)
141+
}
142+
i, err := strconv.ParseInt(method[1], 10, 64)
143+
if err != nil {
144+
t.Fatal(pos, "Error TypeParam: ", err)
145+
}
146+
if v.NumTypeParam() <= int(i) {
147+
t.Fatal(pos, "Error Out of index range: ", to)
148+
}
149+
v = v.TypeParam(int(i))
150+
case "Constraint":
151+
v = v.Constraint()
136152
default:
137153
t.Fatal(pos, "Error to: ", to)
138154
}
@@ -204,5 +220,11 @@ func testType(t *testing.T, fset *token.FileSet, v Type) {
204220
t.Fatal(pos, "Error tag:", num, ":", data)
205221
}
206222
}
223+
if data, ok := tag.Lookup("NumTypeParam"); ok {
224+
num := fmt.Sprint(v.NumTypeParam())
225+
if data != num {
226+
t.Fatal(pos, "Error num type param:", num, ":", data)
227+
}
228+
}
207229
}
208230
}

parser.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,20 @@ func (r *parser) parseType(info *infoFile, decl *ast.GenDecl) {
101101
comment := s.Comment
102102

103103
tt := r.evalType(info, s.Type)
104+
105+
// Handle type parameters for generic types
106+
if s.TypeParams != nil && s.TypeParams.NumFields() > 0 {
107+
typeParams := make([]Type, 0, s.TypeParams.NumFields())
108+
for _, field := range s.TypeParams.List {
109+
constraint := r.evalType(info, field.Type)
110+
for _, name := range field.Names {
111+
param := newTypeParam(name.Name, constraint)
112+
typeParams = append(typeParams, param)
113+
}
114+
}
115+
tt = newTypeGeneric(tt, typeParams)
116+
}
117+
104118
if s.Assign == 0 && tt.Kind() != Interface {
105119
tt = newTypeNamed(s.Name.Name, tt, info)
106120
} else {

parser_types.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,15 @@ func (r *parser) evalType(info *infoFile, expr ast.Expr) (ret Type) {
6868
case Array, Chan, Map, Ptr, Slice:
6969
return typ.Elem()
7070
}
71-
return nil
71+
return typ
72+
case *ast.IndexListExpr:
73+
// Generic type instantiation with multiple type arguments
74+
// e.g., Pair[string, int]
75+
typ := r.evalType(info, t.X)
76+
if typ == nil {
77+
return nil
78+
}
79+
return typ
7280
case *ast.SliceExpr:
7381
return r.evalType(info, t.X)
7482
case *ast.TypeAssertExpr:
@@ -180,6 +188,20 @@ func (r *parser) evalType(info *infoFile, expr ast.Expr) (ret Type) {
180188
return s
181189
case *ast.FuncType:
182190
s := &typeFunc{}
191+
192+
// Handle type parameters for generic functions
193+
if t.TypeParams != nil && t.TypeParams.NumFields() > 0 {
194+
typeParams := make([]Type, 0, t.TypeParams.NumFields())
195+
for _, field := range t.TypeParams.List {
196+
constraint := r.evalType(info, field.Type)
197+
for _, name := range field.Names {
198+
param := newTypeParam(name.Name, constraint)
199+
typeParams = append(typeParams, param)
200+
}
201+
}
202+
s.typeParams = typeParams
203+
}
204+
183205
if t.Params != nil {
184206
list := t.Params.List
185207
for pk, v := range list {

testdata/generics/a.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package generics
2+
3+
// Generic type with single type parameter
4+
// String:"Box[T any]"
5+
// NumTypeParam:"1"
6+
// To:"TypeParam:0" Name:"T" Kind:"TypeParam" String:"T any"
7+
// To:"TypeParam:0,Constraint" Name:"any"
8+
type Box[T any] struct {
9+
Value T
10+
}
11+
12+
// Generic type with constraint
13+
// String:"Comparable[T comparable]"
14+
// NumTypeParam:"1"
15+
// To:"TypeParam:0" Name:"T" Kind:"TypeParam" String:"T comparable"
16+
// To:"TypeParam:0,Constraint" Name:"comparable"
17+
type Comparable[T comparable] struct {
18+
Value T
19+
}
20+
21+
// Generic type with multiple type parameters
22+
// String:"Pair[K comparable, V any]"
23+
// NumTypeParam:"2"
24+
// To:"TypeParam:0" Name:"K" Kind:"TypeParam" String:"K comparable"
25+
// To:"TypeParam:0,Constraint" Name:"comparable"
26+
// To:"TypeParam:1" Name:"V" Kind:"TypeParam" String:"V any"
27+
// To:"TypeParam:1,Constraint" Name:"any"
28+
type Pair[K comparable, V any] struct {
29+
Key K
30+
Value V
31+
}
32+
33+
// Generic function
34+
// String:"Max[T comparable]"
35+
// To:"Declaration" NumTypeParam:"1" String:"func[T comparable](a, b) (_)"
36+
// To:"Declaration,TypeParam:0" Name:"T" Kind:"TypeParam" String:"T comparable"
37+
// To:"Declaration,TypeParam:0,Constraint" Name:"comparable"
38+
func Max[T comparable](a, b T) T {
39+
if a > b {
40+
return a
41+
}
42+
return b
43+
}
44+
45+
// Generic function with multiple type parameters
46+
// String:"Map[K comparable, V any]"
47+
// To:"Declaration" NumTypeParam:"2" String:"func[K comparable, V any](m, f) (_)"
48+
// To:"Declaration,TypeParam:0" Name:"K" Kind:"TypeParam" String:"K comparable"
49+
// To:"Declaration,TypeParam:0,Constraint" Name:"comparable"
50+
// To:"Declaration,TypeParam:1" Name:"V" Kind:"TypeParam" String:"V any"
51+
// To:"Declaration,TypeParam:1,Constraint" Name:"any"
52+
func Map[K comparable, V any](m map[K]V, f func(V) V) map[K]V {
53+
result := make(map[K]V)
54+
for k, v := range m {
55+
result[k] = f(v)
56+
}
57+
return result
58+
}
59+
60+
// Custom interface constraint
61+
type Stringer interface {
62+
String() string
63+
}
64+
65+
// Generic type with custom interface constraint
66+
// String:"StringableBox[T Stringer]"
67+
// NumTypeParam:"1"
68+
// To:"TypeParam:0" Name:"T" Kind:"TypeParam" String:"T Stringer"
69+
// To:"TypeParam:0,Constraint" Name:"Stringer" Kind:"Interface"
70+
type StringableBox[T Stringer] struct {
71+
Value T
72+
}
73+
74+
// Generic function with custom interface constraint
75+
// String:"Stringify[T Stringer]"
76+
// To:"Declaration" NumTypeParam:"1" String:"func[T Stringer](value) (_)"
77+
// To:"Declaration,TypeParam:0" Name:"T" Kind:"TypeParam" String:"T Stringer"
78+
// To:"Declaration,TypeParam:0,Constraint" Name:"Stringer" Kind:"Interface"
79+
func Stringify[T Stringer](value T) string {
80+
return value.String()
81+
}

types.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,18 @@ type Type interface {
163163

164164
// Comment returns the type's comment within its package.
165165
Comment() *ast.CommentGroup
166+
167+
// NumTypeParam returns the number of type parameters for a generic type or function.
168+
// It returns 0 for non-generic types.
169+
NumTypeParam() int
170+
171+
// TypeParam returns the i'th type parameter.
172+
// It panics if i is not in the range [0, NumTypeParam()).
173+
TypeParam(int) Type
174+
175+
// Constraint returns the constraint of a type parameter.
176+
// It panics if the type's Kind is not TypeParam.
177+
Constraint() Type
166178
}
167179

168180
type types []Type
@@ -345,3 +357,15 @@ func (t *typeBase) Doc() *ast.CommentGroup {
345357
func (t *typeBase) Comment() *ast.CommentGroup {
346358
return nil
347359
}
360+
361+
func (t *typeBase) NumTypeParam() int {
362+
return 0
363+
}
364+
365+
func (t *typeBase) TypeParam(int) Type {
366+
panic("TypeParam of non-generic type")
367+
}
368+
369+
func (t *typeBase) Constraint() Type {
370+
panic("Constraint of non-type-parameter type")
371+
}

0 commit comments

Comments
 (0)