Skip to content

Commit 8bb9ab7

Browse files
committed
Properly check if interface is any
Fixes #744
1 parent 8f1e880 commit 8bb9ab7

File tree

7 files changed

+67
-4
lines changed

7 files changed

+67
-4
lines changed

checker/checker.go

+8
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,14 @@ func (v *checker) MemberNode(node *ast.MemberNode) Nature {
535535
}
536536
}
537537

538+
// Not found.
539+
540+
if name, ok := node.Property.(*ast.StringNode); ok {
541+
if node.Method {
542+
return v.error(node, "type %v has no method %v", base, name.Value)
543+
}
544+
return v.error(node, "type %v has no field %v", base, name.Value)
545+
}
538546
return v.error(node, "type %v[%v] is undefined", base, prop)
539547
}
540548

checker/checker_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1055,7 +1055,7 @@ func TestCheck_builtin_without_call(t *testing.T) {
10551055
err string
10561056
}{
10571057
{`len + 1`, "invalid operation: + (mismatched types func(...interface {}) (interface {}, error) and int) (1:5)\n | len + 1\n | ....^"},
1058-
{`string.A`, "type func(interface {}) string[string] is undefined (1:8)\n | string.A\n | .......^"},
1058+
{`string.A`, "type func(interface {}) string has no field A (1:8)\n | string.A\n | .......^"},
10591059
}
10601060

10611061
for _, test := range tests {

checker/nature/nature.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func (n Nature) Elem() Nature {
7474
func (n Nature) AssignableTo(nt Nature) bool {
7575
if n.Nil {
7676
// Untyped nil is assignable to any interface, but implements only the empty interface.
77-
if nt.Type != nil && nt.Type.Kind() == reflect.Interface {
77+
if isAny(nt) {
7878
return true
7979
}
8080
}
@@ -84,6 +84,13 @@ func (n Nature) AssignableTo(nt Nature) bool {
8484
return n.Type.AssignableTo(nt.Type)
8585
}
8686

87+
func (n Nature) NumMethods() int {
88+
if n.Type == nil {
89+
return 0
90+
}
91+
return n.Type.NumMethod()
92+
}
93+
8794
func (n Nature) MethodByName(name string) (Nature, bool) {
8895
if n.Type == nil {
8996
return unknown, false

checker/nature/utils.go

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ import (
66
"github.com/expr-lang/expr/internal/deref"
77
)
88

9+
func isAny(nt Nature) bool {
10+
return nt.Kind() == reflect.Interface && nt.NumMethods() == 0
11+
}
12+
913
func fieldName(field reflect.StructField) string {
1014
if taggedName := field.Tag.Get("expr"); taggedName != "" {
1115
return taggedName

checker/types.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,16 @@ func isUnknown(nt Nature) bool {
7474
switch {
7575
case nt.Type == nil && !nt.Nil:
7676
return true
77-
case nt.Kind() == reflect.Interface:
77+
case isAny(nt):
7878
return true
7979
}
8080
return false
8181
}
8282

83+
func isAny(nt Nature) bool {
84+
return nt.Kind() == reflect.Interface && nt.NumMethods() == 0
85+
}
86+
8387
func isInteger(nt Nature) bool {
8488
switch nt.Kind() {
8589
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:

test/interface_method/interface_method_test.go renamed to test/interface/interface_method_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package interface_method_test
1+
package interface_test
22

33
import (
44
"testing"

test/interface/interface_test.go

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package interface_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/expr-lang/expr"
7+
"github.com/expr-lang/expr/internal/testify/assert"
8+
)
9+
10+
type StoreInterface interface {
11+
Get(string) int
12+
}
13+
14+
type StoreImpt struct{}
15+
16+
func (f StoreImpt) Get(s string) int {
17+
return 42
18+
}
19+
20+
func (f StoreImpt) Set(s string, i int) bool {
21+
return true
22+
}
23+
24+
type Env struct {
25+
Store StoreInterface `expr:"store"`
26+
}
27+
28+
func TestInterfaceHide(t *testing.T) {
29+
var env Env
30+
p, err := expr.Compile(`store.Get("foo")`, expr.Env(env))
31+
assert.NoError(t, err)
32+
33+
out, err := expr.Run(p, Env{Store: StoreImpt{}})
34+
assert.NoError(t, err)
35+
assert.Equal(t, 42, out)
36+
37+
_, err = expr.Compile(`store.Set("foo", 100)`, expr.Env(env))
38+
assert.Error(t, err)
39+
assert.Contains(t, err.Error(), "type interface_test.StoreInterface has no method Set")
40+
}

0 commit comments

Comments
 (0)