From 1b1b454ce440eba8b61f3f179917182af819cfd4 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 17 Aug 2023 12:40:29 +0300 Subject: [PATCH] compiler: disallow generic method receiver Either non-pointer or pointer, both cases are disallowed to be generic. Need to be reverted and properly handled within the scope of #2376. Signed-off-by: Anna Shaleva --- pkg/compiler/analysis.go | 35 ++++++++++++++++++++++++++++++++ pkg/compiler/generics_test.go | 38 +++++++++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+) create mode 100644 pkg/compiler/generics_test.go diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 0dfefd2e6..e4ac01a94 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -19,6 +19,8 @@ var ( ErrMissingExportedParamName = errors.New("exported method is not allowed to have unnamed parameter") // ErrInvalidExportedRetCount is returned when exported contract method has invalid return values count. ErrInvalidExportedRetCount = errors.New("exported method is not allowed to have more than one return value") + // ErrGenericsUnsuppored is returned when generics-related tokens are encountered. + ErrGenericsUnsuppored = errors.New("generics are currently unsupported, please, see the https://github.com/nspcc-dev/neo-go/issues/2376") ) var ( @@ -361,6 +363,13 @@ func (c *codegen) analyzeFuncAndGlobalVarUsage() funcUsage { case *ast.FuncDecl: name := c.getFuncNameFromDecl(pkgPath, n) + // filter out generic functions + err := c.checkGenericsFuncDecl(n, name) + if err != nil { + c.prog.Err = err + return false // Program is invalid. + } + // exported functions and methods are always assumed to be used if isMain && n.Name.IsExported() || isInitFunc(n) || isDeployFunc(n) { diff[name] = true @@ -535,6 +544,32 @@ func (c *codegen) analyzeFuncAndGlobalVarUsage() funcUsage { return usage } +// checkGenericFuncDecl checks whether provided ast.FuncDecl has generic code. +func (c *codegen) checkGenericsFuncDecl(n *ast.FuncDecl, funcName string) error { + var errGenerics error + + // Generic function receiver. + if n.Recv != nil { + switch t := n.Recv.List[0].Type.(type) { + case *ast.StarExpr: + switch t.X.(type) { + case *ast.IndexExpr: + // func (x *Pointer[T]) Load() *T + errGenerics = errors.New("generic pointer function receiver") + } + case *ast.IndexExpr: + // func (x Structure[T]) Load() *T + errGenerics = errors.New("generic function receiver") + } + } + + if errGenerics != nil { + return fmt.Errorf("%w: %s has %s", ErrGenericsUnsuppored, funcName, errGenerics.Error()) + } + + return nil +} + // nodeContext contains ast node with the corresponding import map, type info and package information // required to retrieve fully qualified node name (if so). type nodeContext struct { diff --git a/pkg/compiler/generics_test.go b/pkg/compiler/generics_test.go new file mode 100644 index 000000000..6a73d65c5 --- /dev/null +++ b/pkg/compiler/generics_test.go @@ -0,0 +1,38 @@ +package compiler_test + +import ( + "strings" + "testing" + + "github.com/nspcc-dev/neo-go/pkg/compiler" + "github.com/stretchr/testify/require" +) + +func TestGenericMethodReceiver(t *testing.T) { + t.Run("star expression", func(t *testing.T) { + src := ` + package receiver + type Pointer[T any] struct { + value T + } + func (x *Pointer[T]) Load() *T { + return &x.value + } +` + _, _, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil) + require.ErrorIs(t, err, compiler.ErrGenericsUnsuppored) + }) + t.Run("ident expression", func(t *testing.T) { + src := ` + package receiver + type Pointer[T any] struct { + value T + } + func (x Pointer[T]) Load() *T { + return &x.value + } +` + _, _, err := compiler.CompileWithOptions("foo.go", strings.NewReader(src), nil) + require.ErrorIs(t, err, compiler.ErrGenericsUnsuppored) + }) +}