Skip to content

Commit 35bdb68

Browse files
committed
Do not optimize filter_map if #index used
Fixes #758
1 parent e8bd7f8 commit 35bdb68

File tree

4 files changed

+104
-32
lines changed

4 files changed

+104
-32
lines changed

expr_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -2763,3 +2763,24 @@ func TestExpr_env_types_map_error(t *testing.T) {
27632763
_, err = expr.Run(program, envTypes)
27642764
require.Error(t, err)
27652765
}
2766+
2767+
func TestIssue758_FilterMapIndex(t *testing.T) {
2768+
env := map[string]interface{}{}
2769+
2770+
exprStr := `
2771+
let a_map = 0..5 | filter(# % 2 == 0) | map(#index);
2772+
let b_filter = 0..5 | filter(# % 2 == 0);
2773+
let b_map = b_filter | map(#index);
2774+
[a_map, b_map]
2775+
`
2776+
2777+
result, err := expr.Eval(exprStr, env)
2778+
require.NoError(t, err)
2779+
2780+
expected := []interface{}{
2781+
[]interface{}{0, 1, 2},
2782+
[]interface{}{0, 1, 2},
2783+
}
2784+
2785+
require.Equal(t, expected, result)
2786+
}

optimizer/filter_map.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ type filterMap struct{}
99
func (*filterMap) Visit(node *Node) {
1010
if mapBuiltin, ok := (*node).(*BuiltinNode); ok &&
1111
mapBuiltin.Name == "map" &&
12-
len(mapBuiltin.Arguments) == 2 {
12+
len(mapBuiltin.Arguments) == 2 &&
13+
Find(mapBuiltin.Arguments[1], isIndexPointer) == nil {
1314
if predicate, ok := mapBuiltin.Arguments[1].(*PredicateNode); ok {
1415
if filter, ok := mapBuiltin.Arguments[0].(*BuiltinNode); ok &&
1516
filter.Name == "filter" &&
@@ -23,3 +24,10 @@ func (*filterMap) Visit(node *Node) {
2324
}
2425
}
2526
}
27+
28+
func isIndexPointer(node Node) bool {
29+
if pointer, ok := node.(*PointerNode); ok && pointer.Name == "index" {
30+
return true
31+
}
32+
return false
33+
}

optimizer/filter_map_test.go

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package optimizer_test
2+
3+
import (
4+
"testing"
5+
6+
. "github.com/expr-lang/expr/ast"
7+
"github.com/expr-lang/expr/internal/testify/assert"
8+
"github.com/expr-lang/expr/internal/testify/require"
9+
"github.com/expr-lang/expr/optimizer"
10+
"github.com/expr-lang/expr/parser"
11+
)
12+
13+
func TestOptimize_filter_map(t *testing.T) {
14+
tree, err := parser.Parse(`map(filter(users, .Name == "Bob"), .Age)`)
15+
require.NoError(t, err)
16+
17+
err = optimizer.Optimize(&tree.Node, nil)
18+
require.NoError(t, err)
19+
20+
expected := &BuiltinNode{
21+
Name: "filter",
22+
Arguments: []Node{
23+
&IdentifierNode{Value: "users"},
24+
&PredicateNode{
25+
Node: &BinaryNode{
26+
Operator: "==",
27+
Left: &MemberNode{
28+
Node: &PointerNode{},
29+
Property: &StringNode{Value: "Name"},
30+
},
31+
Right: &StringNode{Value: "Bob"},
32+
},
33+
},
34+
},
35+
Map: &MemberNode{
36+
Node: &PointerNode{},
37+
Property: &StringNode{Value: "Age"},
38+
},
39+
}
40+
41+
assert.Equal(t, Dump(expected), Dump(tree.Node))
42+
}
43+
44+
func TestOptimize_filter_map_with_index_pointer(t *testing.T) {
45+
tree, err := parser.Parse(`map(filter(users, true), #index)`)
46+
require.NoError(t, err)
47+
48+
err = optimizer.Optimize(&tree.Node, nil)
49+
require.NoError(t, err)
50+
51+
expected := &BuiltinNode{
52+
Name: "map",
53+
Arguments: []Node{
54+
&BuiltinNode{
55+
Name: "filter",
56+
Arguments: []Node{
57+
&IdentifierNode{Value: "users"},
58+
&PredicateNode{
59+
Node: &BoolNode{Value: true},
60+
},
61+
},
62+
Throws: false,
63+
Map: nil,
64+
},
65+
&PredicateNode{
66+
Node: &PointerNode{Name: "index"},
67+
},
68+
},
69+
Throws: false,
70+
Map: nil,
71+
}
72+
73+
assert.Equal(t, Dump(expected), Dump(tree.Node))
74+
}

optimizer/optimizer_test.go

-31
Original file line numberDiff line numberDiff line change
@@ -312,37 +312,6 @@ func TestOptimize_filter_last(t *testing.T) {
312312
assert.Equal(t, ast.Dump(expected), ast.Dump(tree.Node))
313313
}
314314

315-
func TestOptimize_filter_map(t *testing.T) {
316-
tree, err := parser.Parse(`map(filter(users, .Name == "Bob"), .Age)`)
317-
require.NoError(t, err)
318-
319-
err = optimizer.Optimize(&tree.Node, nil)
320-
require.NoError(t, err)
321-
322-
expected := &ast.BuiltinNode{
323-
Name: "filter",
324-
Arguments: []ast.Node{
325-
&ast.IdentifierNode{Value: "users"},
326-
&ast.PredicateNode{
327-
Node: &ast.BinaryNode{
328-
Operator: "==",
329-
Left: &ast.MemberNode{
330-
Node: &ast.PointerNode{},
331-
Property: &ast.StringNode{Value: "Name"},
332-
},
333-
Right: &ast.StringNode{Value: "Bob"},
334-
},
335-
},
336-
},
337-
Map: &ast.MemberNode{
338-
Node: &ast.PointerNode{},
339-
Property: &ast.StringNode{Value: "Age"},
340-
},
341-
}
342-
343-
assert.Equal(t, ast.Dump(expected), ast.Dump(tree.Node))
344-
}
345-
346315
func TestOptimize_filter_map_first(t *testing.T) {
347316
tree, err := parser.Parse(`first(map(filter(users, .Name == "Bob"), .Age))`)
348317
require.NoError(t, err)

0 commit comments

Comments
 (0)