Properly check if interface is any

Fixes #744
This commit is contained in:
Anton Medvedev
2025-03-07 22:56:41 +01:00
parent dea75b14d8
commit 02dc5c04fc
7 changed files with 67 additions and 4 deletions
+8
View File
@@ -535,6 +535,14 @@ func (v *checker) MemberNode(node *ast.MemberNode) Nature {
}
}
// Not found.
if name, ok := node.Property.(*ast.StringNode); ok {
if node.Method {
return v.error(node, "type %v has no method %v", base, name.Value)
}
return v.error(node, "type %v has no field %v", base, name.Value)
}
return v.error(node, "type %v[%v] is undefined", base, prop)
}
+1 -1
View File
@@ -1055,7 +1055,7 @@ func TestCheck_builtin_without_call(t *testing.T) {
err string
}{
{`len + 1`, "invalid operation: + (mismatched types func(...interface {}) (interface {}, error) and int) (1:5)\n | len + 1\n | ....^"},
{`string.A`, "type func(interface {}) string[string] is undefined (1:8)\n | string.A\n | .......^"},
{`string.A`, "type func(interface {}) string has no field A (1:8)\n | string.A\n | .......^"},
}
for _, test := range tests {
+8 -1
View File
@@ -74,7 +74,7 @@ func (n Nature) Elem() Nature {
func (n Nature) AssignableTo(nt Nature) bool {
if n.Nil {
// Untyped nil is assignable to any interface, but implements only the empty interface.
if nt.Type != nil && nt.Type.Kind() == reflect.Interface {
if isAny(nt) {
return true
}
}
@@ -84,6 +84,13 @@ func (n Nature) AssignableTo(nt Nature) bool {
return n.Type.AssignableTo(nt.Type)
}
func (n Nature) NumMethods() int {
if n.Type == nil {
return 0
}
return n.Type.NumMethod()
}
func (n Nature) MethodByName(name string) (Nature, bool) {
if n.Type == nil {
return unknown, false
+4
View File
@@ -6,6 +6,10 @@ import (
"github.com/expr-lang/expr/internal/deref"
)
func isAny(nt Nature) bool {
return nt.Kind() == reflect.Interface && nt.NumMethods() == 0
}
func fieldName(field reflect.StructField) string {
if taggedName := field.Tag.Get("expr"); taggedName != "" {
return taggedName
+5 -1
View File
@@ -74,12 +74,16 @@ func isUnknown(nt Nature) bool {
switch {
case nt.Type == nil && !nt.Nil:
return true
case nt.Kind() == reflect.Interface:
case isAny(nt):
return true
}
return false
}
func isAny(nt Nature) bool {
return nt.Kind() == reflect.Interface && nt.NumMethods() == 0
}
func isInteger(nt Nature) bool {
switch nt.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@@ -1,4 +1,4 @@
package interface_method_test
package interface_test
import (
"testing"
+40
View File
@@ -0,0 +1,40 @@
package interface_test
import (
"testing"
"github.com/expr-lang/expr"
"github.com/expr-lang/expr/internal/testify/assert"
)
type StoreInterface interface {
Get(string) int
}
type StoreImpt struct{}
func (f StoreImpt) Get(s string) int {
return 42
}
func (f StoreImpt) Set(s string, i int) bool {
return true
}
type Env struct {
Store StoreInterface `expr:"store"`
}
func TestInterfaceHide(t *testing.T) {
var env Env
p, err := expr.Compile(`store.Get("foo")`, expr.Env(env))
assert.NoError(t, err)
out, err := expr.Run(p, Env{Store: StoreImpt{}})
assert.NoError(t, err)
assert.Equal(t, 42, out)
_, err = expr.Compile(`store.Set("foo", 100)`, expr.Env(env))
assert.Error(t, err)
assert.Contains(t, err.Error(), "type interface_test.StoreInterface has no method Set")
}