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 <shaleva.ann@nspcc.ru>
This commit is contained in:
Anna Shaleva 2023-08-17 12:40:29 +03:00
parent 415d44792a
commit 1b1b454ce4
2 changed files with 73 additions and 0 deletions

View file

@ -19,6 +19,8 @@ var (
ErrMissingExportedParamName = errors.New("exported method is not allowed to have unnamed parameter") 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 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") 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 ( var (
@ -361,6 +363,13 @@ func (c *codegen) analyzeFuncAndGlobalVarUsage() funcUsage {
case *ast.FuncDecl: case *ast.FuncDecl:
name := c.getFuncNameFromDecl(pkgPath, n) 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 // exported functions and methods are always assumed to be used
if isMain && n.Name.IsExported() || isInitFunc(n) || isDeployFunc(n) { if isMain && n.Name.IsExported() || isInitFunc(n) || isDeployFunc(n) {
diff[name] = true diff[name] = true
@ -535,6 +544,32 @@ func (c *codegen) analyzeFuncAndGlobalVarUsage() funcUsage {
return usage 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 // nodeContext contains ast node with the corresponding import map, type info and package information
// required to retrieve fully qualified node name (if so). // required to retrieve fully qualified node name (if so).
type nodeContext struct { type nodeContext struct {

View file

@ -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)
})
}