diff --git a/internal/cmd/genop/genop.go b/internal/cmd/genop/genop.go index a3263d994..cc9466e54 100644 --- a/internal/cmd/genop/genop.go +++ b/internal/cmd/genop/genop.go @@ -535,6 +535,7 @@ func {{$name}}(n *node) { typ := n.typ.concrete().TypeOf() isInterface := n.typ.TypeOf().Kind() == reflect.Interface c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() {{- if or (eq $op.Name "==") (eq $op.Name "!=") }} @@ -621,9 +622,39 @@ func {{$name}}(n *node) { } return } + + // Do not attempt to optimize '==' or '!=' if an operand is an interface. + // This will preserve proper dynamic type checking at runtime. For static types, + // type checks are already performed, so bypass them if possible. + if t0.Kind() == reflect.Interface || t1.Kind() == reflect.Interface { + v0 := genValue(c0) + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + if i0 {{$op.Name}} i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).SetBool(i0 {{$op.Name}} i1) + return tnext + } + } + return + } {{- end}} - switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); { + switch { case isString(t0) || isString(t1): switch { case isInterface: diff --git a/interp/ast.go b/interp/ast.go index 32117f3c9..9bfa12634 100644 --- a/interp/ast.go +++ b/interp/ast.go @@ -282,6 +282,7 @@ var actions = [...]string{ aDec: "--", aEqual: "==", aGreater: ">", + aGreaterEqual: ">=", aGetFunc: "getFunc", aGetIndex: "getIndex", aGetMethod: "getMethod", @@ -290,6 +291,7 @@ var actions = [...]string{ aLand: "&&", aLor: "||", aLower: "<", + aLowerEqual: "<=", aMethod: "Method", aMul: "*", aMulAssign: "*=", diff --git a/interp/interp_eval_test.go b/interp/interp_eval_test.go index 86a193a77..d7d98a500 100644 --- a/interp/interp_eval_test.go +++ b/interp/interp_eval_test.go @@ -450,6 +450,14 @@ func TestEvalComparison(t *testing.T) { }, {src: `1 > _`, err: "1:28: cannot use _ as value"}, {src: `(_) > 1`, err: "1:28: cannot use _ as value"}, + {src: `v := interface{}(2); v == 2`, res: "true"}, + {src: `v := interface{}(2); v > 1`, err: "1:49: invalid operation: operator > not defined on interface{}"}, + {src: `v := interface{}(int64(2)); v == 2`, res: "false"}, + {src: `v := interface{}(int64(2)); v != 2`, res: "true"}, + {src: `v := interface{}(2.3); v == 2.3`, res: "true"}, + {src: `v := interface{}(float32(2.3)); v != 2.3`, res: "true"}, + {src: `v := interface{}("hello"); v == "hello"`, res: "true"}, + {src: `v := interface{}("hello"); v < "hellp"`, err: "1:55: invalid operation: operator < not defined on interface{}"}, }) } diff --git a/interp/op.go b/interp/op.go index 4d37a0f42..424223a5d 100644 --- a/interp/op.go +++ b/interp/op.go @@ -2624,6 +2624,7 @@ func equal(n *node) { typ := n.typ.concrete().TypeOf() isInterface := n.typ.TypeOf().Kind() == reflect.Interface c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() if c0.typ.cat == aliasT || c1.typ.cat == aliasT { switch { @@ -2709,7 +2710,37 @@ func equal(n *node) { return } - switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); { + // Do not attempt to optimize '==' or '!=' if an operand is an interface. + // This will preserve proper dynamic type checking at runtime. For static types, + // type checks are already performed, so bypass them if possible. + if t0.Kind() == reflect.Interface || t1.Kind() == reflect.Interface { + v0 := genValue(c0) + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + if i0 == i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).SetBool(i0 == i1) + return tnext + } + } + return + } + + switch { case isString(t0) || isString(t1): switch { case isInterface: @@ -3193,8 +3224,9 @@ func greater(n *node) { typ := n.typ.concrete().TypeOf() isInterface := n.typ.TypeOf().Kind() == reflect.Interface c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() - switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); { + switch { case isString(t0) || isString(t1): switch { case isInterface: @@ -3520,8 +3552,9 @@ func greaterEqual(n *node) { typ := n.typ.concrete().TypeOf() isInterface := n.typ.TypeOf().Kind() == reflect.Interface c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() - switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); { + switch { case isString(t0) || isString(t1): switch { case isInterface: @@ -3847,8 +3880,9 @@ func lower(n *node) { typ := n.typ.concrete().TypeOf() isInterface := n.typ.TypeOf().Kind() == reflect.Interface c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() - switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); { + switch { case isString(t0) || isString(t1): switch { case isInterface: @@ -4174,8 +4208,9 @@ func lowerEqual(n *node) { typ := n.typ.concrete().TypeOf() isInterface := n.typ.TypeOf().Kind() == reflect.Interface c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() - switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); { + switch { case isString(t0) || isString(t1): switch { case isInterface: @@ -4501,6 +4536,7 @@ func notEqual(n *node) { typ := n.typ.concrete().TypeOf() isInterface := n.typ.TypeOf().Kind() == reflect.Interface c0, c1 := n.child[0], n.child[1] + t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf() if c0.typ.cat == aliasT || c1.typ.cat == aliasT { switch { @@ -4586,7 +4622,37 @@ func notEqual(n *node) { return } - switch t0, t1 := c0.typ.TypeOf(), c1.typ.TypeOf(); { + // Do not attempt to optimize '==' or '!=' if an operand is an interface. + // This will preserve proper dynamic type checking at runtime. For static types, + // type checks are already performed, so bypass them if possible. + if t0.Kind() == reflect.Interface || t1.Kind() == reflect.Interface { + v0 := genValue(c0) + v1 := genValue(c1) + if n.fnext != nil { + fnext := getExec(n.fnext) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + if i0 != i1 { + dest(f).SetBool(true) + return tnext + } + dest(f).SetBool(false) + return fnext + } + } else { + dest := genValue(n) + n.exec = func(f *frame) bltn { + i0 := v0(f).Interface() + i1 := v1(f).Interface() + dest(f).SetBool(i0 != i1) + return tnext + } + } + return + } + + switch { case isString(t0) || isString(t1): switch { case isInterface: diff --git a/interp/typecheck.go b/interp/typecheck.go index 4a238981d..0c3e9a4ab 100644 --- a/interp/typecheck.go +++ b/interp/typecheck.go @@ -202,7 +202,7 @@ func (check typecheck) comparison(n *node) error { if typ.isNil() { typ = c1.typ } - return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, typ.id(), ".") + return n.cfgErrorf("invalid operation: operator %v not defined on %s", n.action, typ.id()) } return nil } diff --git a/interp/value.go b/interp/value.go index d050668ae..ec7bb6b5e 100644 --- a/interp/value.go +++ b/interp/value.go @@ -596,9 +596,5 @@ func genComplex(n *node) func(*frame) complex128 { func genValueString(n *node) func(*frame) (reflect.Value, string) { value := genValue(n) - if n.typ.TypeOf().Kind() == reflect.Interface { - return func(f *frame) (reflect.Value, string) { v := value(f); return v, v.Elem().String() } - } - return func(f *frame) (reflect.Value, string) { v := value(f); return v, v.String() } }