Skip to content

Commit f6a3d06

Browse files
authored
Remove redundant fields from CallNode and BinaryNode (#504)
1 parent 95ec882 commit f6a3d06

File tree

9 files changed

+214
-184
lines changed

9 files changed

+214
-184
lines changed

ast/node.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package ast
22

33
import (
44
"reflect"
5-
"regexp"
65

76
"github.com/expr-lang/expr/file"
87
)
@@ -104,10 +103,9 @@ type UnaryNode struct {
104103
// BinaryNode represents a binary operator.
105104
type BinaryNode struct {
106105
base
107-
Operator string // Operator of the binary operator. Like "+" in "foo + bar" or "matches" in "foo matches bar".
108-
Left Node // Left node of the binary operator.
109-
Right Node // Right node of the binary operator.
110-
Regexp *regexp.Regexp // Internal. Regexp of the "matches" operator. Like "f.+".
106+
Operator string // Operator of the binary operator. Like "+" in "foo + bar" or "matches" in "foo matches bar".
107+
Left Node // Left node of the binary operator.
108+
Right Node // Right node of the binary operator.
111109
}
112110

113111
// ChainNode represents an optional chaining group.
@@ -151,20 +149,17 @@ type SliceNode struct {
151149
// CallNode represents a function or a method call.
152150
type CallNode struct {
153151
base
154-
Callee Node // Node of the call. Like "foo" in "foo()".
155-
Arguments []Node // Arguments of the call.
156-
Typed int // Internal. Used to indicate compiler what type is one of vm.FuncTypes.
157-
Fast bool // Internal. Used to indicate compiler what this call is a fast call.
158-
Func *Function // Internal. Used to pass function information from type checker to compiler.
152+
Callee Node // Node of the call. Like "foo" in "foo()".
153+
Arguments []Node // Arguments of the call.
159154
}
160155

161156
// BuiltinNode represents a builtin function call.
162157
type BuiltinNode struct {
163158
base
164159
Name string // Name of the builtin function. Like "len" in "len(foo)".
165160
Arguments []Node // Arguments of the builtin function.
166-
Throws bool // Internal. If true then accessing a field or array index can throw an error. Used by optimizer.
167-
Map Node // Internal. Used by optimizer to fold filter() and map() builtins.
161+
Throws bool // If true then accessing a field or array index can throw an error. Used by optimizer.
162+
Map Node // Used by optimizer to fold filter() and map() builtins.
168163
}
169164

170165
// ClosureNode represents a predicate.

checker/checker.go

Lines changed: 8 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"github.com/expr-lang/expr/conf"
1111
"github.com/expr-lang/expr/file"
1212
"github.com/expr-lang/expr/parser"
13-
"github.com/expr-lang/expr/vm"
1413
)
1514

1615
func Check(tree *parser.Tree, config *conf.Config) (t reflect.Type, err error) {
@@ -374,11 +373,10 @@ func (v *checker) BinaryNode(node *ast.BinaryNode) (reflect.Type, info) {
374373

375374
case "matches":
376375
if s, ok := node.Right.(*ast.StringNode); ok {
377-
r, err := regexp.Compile(s.Value)
376+
_, err := regexp.Compile(s.Value)
378377
if err != nil {
379378
return v.error(node, err.Error())
380379
}
381-
node.Regexp = r
382380
}
383381
if isString(l) && isString(r) {
384382
return boolType, info{}
@@ -549,7 +547,6 @@ func (v *checker) functionReturnType(node *ast.CallNode) (reflect.Type, info) {
549547
fn, fnInfo := v.visit(node.Callee)
550548

551549
if fnInfo.fn != nil {
552-
node.Func = fnInfo.fn
553550
return v.checkFunction(fnInfo.fn, node, node.Arguments)
554551
}
555552

@@ -571,33 +568,13 @@ func (v *checker) functionReturnType(node *ast.CallNode) (reflect.Type, info) {
571568
case reflect.Interface:
572569
return anyType, info{}
573570
case reflect.Func:
574-
inputParamsCount := 1 // for functions
575-
if fnInfo.method {
576-
inputParamsCount = 2 // for methods
577-
}
578-
// TODO: Deprecate OpCallFast and move fn(...any) any to TypedFunc list.
579-
// To do this we need add support for variadic arguments in OpCallTyped.
580-
if !isAny(fn) &&
581-
fn.IsVariadic() &&
582-
fn.NumIn() == inputParamsCount &&
583-
fn.NumOut() == 1 &&
584-
fn.Out(0).Kind() == reflect.Interface {
585-
rest := fn.In(fn.NumIn() - 1) // function has only one param for functions and two for methods
586-
if kind(rest) == reflect.Slice && rest.Elem().Kind() == reflect.Interface {
587-
node.Fast = true
588-
}
589-
}
590-
591571
outType, err := v.checkArguments(fnName, fn, fnInfo.method, node.Arguments, node)
592572
if err != nil {
593573
if v.err == nil {
594574
v.err = err
595575
}
596576
return anyType, info{}
597577
}
598-
599-
v.findTypedFunc(node, fn, fnInfo.method)
600-
601578
return outType, info{}
602579
}
603580
return v.error(node, "%v is not callable", fn)
@@ -883,7 +860,13 @@ func (v *checker) checkFunction(f *ast.Function, node ast.Node, arguments []ast.
883860
return v.error(node, "no matching overload for %v", f.Name)
884861
}
885862

886-
func (v *checker) checkArguments(name string, fn reflect.Type, method bool, arguments []ast.Node, node ast.Node) (reflect.Type, *file.Error) {
863+
func (v *checker) checkArguments(
864+
name string,
865+
fn reflect.Type,
866+
method bool,
867+
arguments []ast.Node,
868+
node ast.Node,
869+
) (reflect.Type, *file.Error) {
887870
if isAny(fn) {
888871
return anyType, nil
889872
}
@@ -1122,44 +1105,3 @@ func (v *checker) PairNode(node *ast.PairNode) (reflect.Type, info) {
11221105
v.visit(node.Value)
11231106
return nilType, info{}
11241107
}
1125-
1126-
func (v *checker) findTypedFunc(node *ast.CallNode, fn reflect.Type, method bool) {
1127-
// OnCallTyped doesn't work for functions with variadic arguments,
1128-
// and doesn't work named function, like `type MyFunc func() int`.
1129-
// In PkgPath() is an empty string, it's unnamed function.
1130-
if !fn.IsVariadic() && fn.PkgPath() == "" {
1131-
fnNumIn := fn.NumIn()
1132-
fnInOffset := 0
1133-
if method {
1134-
fnNumIn--
1135-
fnInOffset = 1
1136-
}
1137-
funcTypes:
1138-
for i := range vm.FuncTypes {
1139-
if i == 0 {
1140-
continue
1141-
}
1142-
typed := reflect.ValueOf(vm.FuncTypes[i]).Elem().Type()
1143-
if typed.Kind() != reflect.Func {
1144-
continue
1145-
}
1146-
if typed.NumOut() != fn.NumOut() {
1147-
continue
1148-
}
1149-
for j := 0; j < typed.NumOut(); j++ {
1150-
if typed.Out(j) != fn.Out(j) {
1151-
continue funcTypes
1152-
}
1153-
}
1154-
if typed.NumIn() != fnNumIn {
1155-
continue
1156-
}
1157-
for j := 0; j < typed.NumIn(); j++ {
1158-
if typed.In(j) != fn.In(j+fnInOffset) {
1159-
continue funcTypes
1160-
}
1161-
}
1162-
node.Typed = i
1163-
}
1164-
}
1165-
}

checker/checker_test.go

Lines changed: 7 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,11 @@ unknown pointer #unknown (1:11)
554554
cannot use int as type string in array (1:4)
555555
| 42 in ["a", "b", "c"]
556556
| ...^
557+
558+
"foo" matches "[+"
559+
error parsing regexp: missing closing ]: ` + "`[+`" + ` (1:7)
560+
| "foo" matches "[+"
561+
| ......^
557562
`
558563

559564
func TestCheck_error(t *testing.T) {
@@ -777,49 +782,6 @@ func TestCheck_TypeWeights(t *testing.T) {
777782
}
778783
}
779784

780-
func TestCheck_CallFastTyped(t *testing.T) {
781-
env := map[string]any{
782-
"fn": func([]any, string) string {
783-
return "foo"
784-
},
785-
}
786-
787-
tree, err := parser.Parse("fn([1, 2], 'bar')")
788-
require.NoError(t, err)
789-
790-
_, err = checker.Check(tree, conf.New(env))
791-
require.NoError(t, err)
792-
793-
require.False(t, tree.Node.(*ast.CallNode).Fast)
794-
require.Equal(t, 22, tree.Node.(*ast.CallNode).Typed)
795-
}
796-
797-
func TestCheck_CallFastTyped_Method(t *testing.T) {
798-
env := mock.Env{}
799-
800-
tree, err := parser.Parse("FuncTyped('bar')")
801-
require.NoError(t, err)
802-
803-
_, err = checker.Check(tree, conf.New(env))
804-
require.NoError(t, err)
805-
806-
require.False(t, tree.Node.(*ast.CallNode).Fast)
807-
require.Equal(t, 42, tree.Node.(*ast.CallNode).Typed)
808-
}
809-
810-
func TestCheck_CallTyped_excludes_named_functions(t *testing.T) {
811-
env := mock.Env{}
812-
813-
tree, err := parser.Parse("FuncNamed('bar')")
814-
require.NoError(t, err)
815-
816-
_, err = checker.Check(tree, conf.New(env))
817-
require.NoError(t, err)
818-
819-
require.False(t, tree.Node.(*ast.CallNode).Fast)
820-
require.Equal(t, 0, tree.Node.(*ast.CallNode).Typed)
821-
}
822-
823785
func TestCheck_works_with_nil_types(t *testing.T) {
824786
env := map[string]any{
825787
"null": nil,
@@ -908,8 +870,7 @@ func TestCheck_Function_types_are_checked(t *testing.T) {
908870

909871
_, err = checker.Check(tree, config)
910872
require.NoError(t, err)
911-
require.NotNil(t, tree.Node.(*ast.CallNode).Func)
912-
require.Equal(t, "add", tree.Node.(*ast.CallNode).Func.Name)
873+
require.Equal(t, reflect.Int, tree.Node.Type().Kind())
913874
})
914875
}
915876

@@ -943,8 +904,7 @@ func TestCheck_Function_without_types(t *testing.T) {
943904

944905
_, err = checker.Check(tree, config)
945906
require.NoError(t, err)
946-
require.NotNil(t, tree.Node.(*ast.CallNode).Func)
947-
require.Equal(t, "add", tree.Node.(*ast.CallNode).Func.Name)
907+
require.Equal(t, reflect.Interface, tree.Node.Type().Kind())
948908
}
949909

950910
func TestCheck_dont_panic_on_nil_arguments_for_builtins(t *testing.T) {

checker/info.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/expr-lang/expr/ast"
77
"github.com/expr-lang/expr/conf"
8+
"github.com/expr-lang/expr/vm"
89
)
910

1011
func FieldIndex(types conf.TypesTable, node ast.Node) (bool, []int, string) {
@@ -48,3 +49,80 @@ func MethodIndex(types conf.TypesTable, node ast.Node) (bool, int, string) {
4849
}
4950
return false, 0, ""
5051
}
52+
53+
func TypedFuncIndex(fn reflect.Type, method bool) (int, bool) {
54+
if fn == nil {
55+
return 0, false
56+
}
57+
if fn.Kind() != reflect.Func {
58+
return 0, false
59+
}
60+
// OnCallTyped doesn't work for functions with variadic arguments.
61+
if fn.IsVariadic() {
62+
return 0, false
63+
}
64+
// OnCallTyped doesn't work named function, like `type MyFunc func() int`.
65+
if fn.PkgPath() != "" { // If PkgPath() is not empty, it means that function is named.
66+
return 0, false
67+
}
68+
69+
fnNumIn := fn.NumIn()
70+
fnInOffset := 0
71+
if method {
72+
fnNumIn--
73+
fnInOffset = 1
74+
}
75+
76+
funcTypes:
77+
for i := range vm.FuncTypes {
78+
if i == 0 {
79+
continue
80+
}
81+
typed := reflect.ValueOf(vm.FuncTypes[i]).Elem().Type()
82+
if typed.Kind() != reflect.Func {
83+
continue
84+
}
85+
if typed.NumOut() != fn.NumOut() {
86+
continue
87+
}
88+
for j := 0; j < typed.NumOut(); j++ {
89+
if typed.Out(j) != fn.Out(j) {
90+
continue funcTypes
91+
}
92+
}
93+
if typed.NumIn() != fnNumIn {
94+
continue
95+
}
96+
for j := 0; j < typed.NumIn(); j++ {
97+
if typed.In(j) != fn.In(j+fnInOffset) {
98+
continue funcTypes
99+
}
100+
}
101+
return i, true
102+
}
103+
return 0, false
104+
}
105+
106+
func IsFastFunc(fn reflect.Type, method bool) bool {
107+
if fn == nil {
108+
return false
109+
}
110+
if fn.Kind() != reflect.Func {
111+
return false
112+
}
113+
numIn := 1
114+
if method {
115+
numIn = 2
116+
}
117+
if !isAny(fn) &&
118+
fn.IsVariadic() &&
119+
fn.NumIn() == numIn &&
120+
fn.NumOut() == 1 &&
121+
fn.Out(0).Kind() == reflect.Interface {
122+
rest := fn.In(fn.NumIn() - 1) // function has only one param for functions and two for methods
123+
if kind(rest) == reflect.Slice && rest.Elem().Kind() == reflect.Interface {
124+
return true
125+
}
126+
}
127+
return false
128+
}

checker/info_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package checker_test
2+
3+
import (
4+
"reflect"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/expr-lang/expr/checker"
10+
"github.com/expr-lang/expr/test/mock"
11+
)
12+
13+
func TestTypedFuncIndex(t *testing.T) {
14+
fn := func([]any, string) string {
15+
return "foo"
16+
}
17+
index, ok := checker.TypedFuncIndex(reflect.TypeOf(fn), false)
18+
require.True(t, ok)
19+
require.Equal(t, 22, index)
20+
}
21+
22+
func TestTypedFuncIndex_excludes_named_functions(t *testing.T) {
23+
var fn mock.MyFunc
24+
25+
_, ok := checker.TypedFuncIndex(reflect.TypeOf(fn), false)
26+
require.False(t, ok)
27+
}

0 commit comments

Comments
 (0)