diff --git a/.gitignore b/.gitignore index e9243da..4709826 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ cover.html # macOS system files .DS_Store + +.idea \ No newline at end of file diff --git a/internal/convgen/parse/parser.go b/internal/convgen/parse/parser.go index 735b23f..709f988 100644 --- a/internal/convgen/parse/parser.go +++ b/internal/convgen/parse/parser.go @@ -1,6 +1,7 @@ package parse import ( + "errors" "fmt" "go/ast" "go/build/constraint" @@ -98,7 +99,10 @@ func (p *Parser) ParseFunc(expr ast.Expr, hasErr bool) (typeinfo.Func, error) { return nil, codefmt.Errorf(p, expr, "cannot use %c as function", expr) } - obj := p.Pkg().TypesInfo.ObjectOf(id) + obj, err := p.getFuncObj(id, expr) + if err != nil { + return nil, codefmt.Errorf(p, expr, "%s", err.Error()) + } fn_, err := typeinfo.FuncOf[typeinfo.BothXY](obj) if err != nil { return nil, codefmt.Errorf(p, expr, "%s", err.Error()) @@ -133,9 +137,12 @@ func (p *Parser) ParseErrWrap(expr ast.Expr) (typeinfo.Func, error) { return nil, codefmt.Errorf(p, expr, "cannot use %c as error wrapper", expr) } - obj := p.Pkg().TypesInfo.ObjectOf(id) - if _, ok := obj.(*types.Nil); ok { - return nil, codefmt.Errorf(p, expr, "cannot use nil as error wrapper") + obj, err := p.getFuncObj(id, expr) + if err != nil { + if errors.Is(err, errNilFuncObj) { + return nil, codefmt.Errorf(p, expr, "cannot use nil as error wrapper") + } + return nil, codefmt.Errorf(p, expr, "%s", err.Error()) } fn_, err := typeinfo.FuncOf[typeinfo.OnlyX](obj) @@ -204,6 +211,48 @@ func (p *Parser) ConvgenGoFiles() []*ast.File { return files } +var errNilFuncObj = errors.New("nil function object") + +// getFuncObj returns the object of the function in the expression. If the expression is a generic function instance, it creates an object with a generic name, +// but a function literal signature +func (p *Parser) getFuncObj(id *ast.Ident, expr ast.Expr) (types.Object, error) { + obj := p.Pkg().TypesInfo.ObjectOf(id) + if _, ok := obj.(*types.Nil); ok { + return nil, errNilFuncObj + } + objType := obj.Type() + declarationSignature, ok := objType.(*types.Signature) // This holds the generic function declaration (func[T any](T) string) + if !ok { + return nil, fmt.Errorf("cannot get object signature of type %s", objType.String()) + } + instanceType := p.Pkg().TypesInfo.TypeOf(expr) + instanceSignature, ok := instanceType.(*types.Signature) // This holds the instance (func (int) string) + if !ok { + return nil, fmt.Errorf("cannot get object signature of type %s", instanceType.String()) + } + if declarationSignature.TypeParams().Len() > 0 { + var name strings.Builder + name.WriteString(obj.Name()) + switch e := expr.(type) { + case *ast.IndexExpr: + name.WriteString("[") + name.WriteString(codefmt.New(p.pkg).Expr(e.Index)) + name.WriteString("]") + case *ast.IndexListExpr: + name.WriteString("[") + for i, idx := range e.Indices { + if i > 0 { + name.WriteString(", ") + } + name.WriteString(codefmt.New(p.pkg).Expr(idx)) + } + name.WriteString("]") + } + return types.NewFunc(obj.Pos(), obj.Pkg(), name.String(), instanceSignature), nil + } + return obj, nil +} + // hasGoBuildConvgen checks if the file has a "//go:build convgen" constraint. func hasGoBuildConvgen(file *ast.File) bool { ok := false @@ -242,6 +291,14 @@ func tailIdent(expr ast.Expr) (*ast.Ident, bool) { // foo.bar.baz // ^^^ return tailIdent(expr.Sel) + case *ast.IndexExpr: + // foo[T] + // ^^^ + return tailIdent(expr.X) + case *ast.IndexListExpr: + // foo[T, U] + // ^^^ + return tailIdent(expr.X) } return nil, false } diff --git a/testdata/analysis/DuplicateImportFunc/testdata.go b/testdata/analysis/DuplicateImportFunc/testdata.go index 4174930..80bd54a 100644 --- a/testdata/analysis/DuplicateImportFunc/testdata.go +++ b/testdata/analysis/DuplicateImportFunc/testdata.go @@ -12,6 +12,8 @@ func int2string(int) string { return "" } func string2int(string) (int, error) { return 0, nil } +func Identity[T any](x T) T { return x } + type ( TheInt = int MyInt int @@ -23,6 +25,11 @@ var _ = convgen.Module( convgen.ImportFunc(int2string), // want `duplicate int to string converter` convgen.ImportFunc(func(int) string { return "" }), // want `duplicate int to string converter` + // Generic function instantiations + convgen.ImportFunc(Identity[string]), // ok + convgen.ImportFunc(Identity[string]), // want `duplicate string to string converter` + convgen.ImportFunc(Identity[int]), // ok + // Type aliases and defined types convgen.ImportFunc(func(TheInt) string { return "" }), // want `duplicate TheInt to string converter` convgen.ImportFunc(func(MyInt) string { return "" }), // ok, because MyInt is different from int diff --git a/testdata/program/ImportFuncGeneric/main/main.go b/testdata/program/ImportFuncGeneric/main/main.go new file mode 100644 index 0000000..7b4376f --- /dev/null +++ b/testdata/program/ImportFuncGeneric/main/main.go @@ -0,0 +1,60 @@ +//go:build convgen + +package main + +import ( + "fmt" + "github.com/sublee/convgen" +) + +type FooStr struct { + Value string +} + +type BarStr struct { + Value Generic[string] +} + +type FooInt struct { + Value int +} + +type BarInt struct { + Value Generic[int] +} + +type Generic[T any] struct { + Value T +} + +func wrapGeneric[T any](x T) Generic[T] { + return Generic[T]{Value: x} +} + +func wrapGenericErr[T any](x T) (Generic[T], error) { + return Generic[T]{Value: x}, nil +} + +func identity[T any](x T) T { + return x +} + +var mod = convgen.Module( + convgen.ImportFunc(wrapGeneric[string]), + convgen.ImportFuncErr(wrapGenericErr[int]), + convgen.ImportFunc(identity[string]), +) +var IdentityTest = convgen.Struct[FooStr, FooStr](mod) +var WrapTest = convgen.Struct[FooStr, BarStr](mod) +var WrapErrTest = convgen.StructErr[FooInt, BarInt](mod) + +func main() { + foo := IdentityTest(FooStr{"42"}) + fmt.Println(foo.Value) + + bar := WrapTest(FooStr{"42"}) + fmt.Println(bar.Value.Value) + + baz, _ := WrapErrTest(FooInt{42}) + fmt.Println(baz.Value.Value) +} diff --git a/testdata/program/ImportFuncGeneric/want/program_output.txt b/testdata/program/ImportFuncGeneric/want/program_output.txt new file mode 100644 index 0000000..ea5986f --- /dev/null +++ b/testdata/program/ImportFuncGeneric/want/program_output.txt @@ -0,0 +1,3 @@ +42 +42 +42 \ No newline at end of file