Skip to content

Commit a819810

Browse files
committed
chore(perf): optimise tm_alltrue() and tm_anytrue().
Signed-off-by: Tiago Natel <[email protected]>
1 parent 8a6ea2e commit a819810

File tree

3 files changed

+278
-0
lines changed

3 files changed

+278
-0
lines changed

globals/globals_test.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2870,6 +2870,135 @@ func TestLoadGlobals(t *testing.T) {
28702870
),
28712871
},
28722872
},
2873+
{
2874+
name: "tm_alltrue with literal list of bool",
2875+
layout: []string{"s:stack"},
2876+
configs: []hclconfig{
2877+
{
2878+
path: "/stack",
2879+
add: Globals(
2880+
Expr("val", `tm_alltrue([true, 1==1, false==false, !false])`),
2881+
),
2882+
},
2883+
},
2884+
want: map[string]*hclwrite.Block{
2885+
"/stack": Globals(
2886+
EvalExpr(t, "val", `true`),
2887+
),
2888+
},
2889+
},
2890+
{
2891+
name: "tm_alltrue with literal tuple containing non-boolean",
2892+
layout: []string{"s:stack"},
2893+
configs: []hclconfig{
2894+
{
2895+
path: "/stack",
2896+
add: Globals(
2897+
Expr("val", `tm_alltrue([true, 1==1, "string", {}])`),
2898+
),
2899+
},
2900+
},
2901+
wantErr: errors.E(globals.ErrEval),
2902+
},
2903+
{
2904+
name: "tm_alltrue with literal for-loop",
2905+
layout: []string{"s:stack"},
2906+
configs: []hclconfig{
2907+
{
2908+
path: "/stack",
2909+
add: Globals(
2910+
Expr("val", `tm_alltrue([for i in [true, 1==1, !false] : i])`),
2911+
),
2912+
},
2913+
},
2914+
want: map[string]*hclwrite.Block{
2915+
"/stack": Globals(
2916+
EvalExpr(t, "val", `true`),
2917+
),
2918+
},
2919+
},
2920+
{
2921+
name: "tm_alltrue with funcall",
2922+
layout: []string{"s:stack"},
2923+
configs: []hclconfig{
2924+
{
2925+
path: "/stack",
2926+
add: Globals(
2927+
Expr("val", `tm_alltrue(tm_distinct([true, !false, 1==1]))`),
2928+
),
2929+
},
2930+
},
2931+
want: map[string]*hclwrite.Block{
2932+
"/stack": Globals(
2933+
EvalExpr(t, "val", `true`),
2934+
),
2935+
},
2936+
},
2937+
2938+
{
2939+
name: "tm_anytrue with literal list of bool",
2940+
layout: []string{"s:stack"},
2941+
configs: []hclconfig{
2942+
{
2943+
path: "/stack",
2944+
add: Globals(
2945+
Expr("val", `tm_anytrue([false, 1!=1, false==false, !false])`),
2946+
),
2947+
},
2948+
},
2949+
want: map[string]*hclwrite.Block{
2950+
"/stack": Globals(
2951+
EvalExpr(t, "val", `true`),
2952+
),
2953+
},
2954+
},
2955+
{
2956+
name: "tm_anytrue with literal tuple containing non-boolean",
2957+
layout: []string{"s:stack"},
2958+
configs: []hclconfig{
2959+
{
2960+
path: "/stack",
2961+
add: Globals(
2962+
Expr("val", `tm_anytrue([false, 1!=1, "string", {}])`),
2963+
),
2964+
},
2965+
},
2966+
wantErr: errors.E(globals.ErrEval),
2967+
},
2968+
{
2969+
name: "tm_anytrue with literal for-loop",
2970+
layout: []string{"s:stack"},
2971+
configs: []hclconfig{
2972+
{
2973+
path: "/stack",
2974+
add: Globals(
2975+
Expr("val", `tm_anytrue([for i in [false, 1!=1, !false] : i])`),
2976+
),
2977+
},
2978+
},
2979+
want: map[string]*hclwrite.Block{
2980+
"/stack": Globals(
2981+
EvalExpr(t, "val", `true`),
2982+
),
2983+
},
2984+
},
2985+
{
2986+
name: "tm_anytrue with funcall",
2987+
layout: []string{"s:stack"},
2988+
configs: []hclconfig{
2989+
{
2990+
path: "/stack",
2991+
add: Globals(
2992+
Expr("val", `tm_anytrue(tm_distinct([false, false, 1==1]))`),
2993+
),
2994+
},
2995+
},
2996+
want: map[string]*hclwrite.Block{
2997+
"/stack": Globals(
2998+
EvalExpr(t, "val", `true`),
2999+
),
3000+
},
3001+
},
28733002
{
28743003
name: "globals.map label conflicts with global name",
28753004
layout: []string{"s:stack"},

stdlib/collection.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// Copyright 2023 Terramate GmbH
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package stdlib
5+
6+
import (
7+
"github.com/hashicorp/hcl/v2/ext/customdecode"
8+
"github.com/hashicorp/hcl/v2/hclsyntax"
9+
"github.com/terramate-io/terramate/errors"
10+
"github.com/terramate-io/terramate/hcl/eval"
11+
"github.com/zclconf/go-cty/cty"
12+
"github.com/zclconf/go-cty/cty/function"
13+
)
14+
15+
// AllTrueFunc implements the `tm_alltrue()` function.
16+
func AllTrueFunc() function.Function {
17+
return function.New(&function.Spec{
18+
Params: []function.Parameter{
19+
{
20+
Name: "list",
21+
Type: customdecode.ExpressionClosureType,
22+
},
23+
},
24+
Type: function.StaticReturnType(cty.Bool),
25+
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
26+
argClosure := customdecode.ExpressionClosureFromVal(args[0])
27+
evalctx := eval.NewContextFrom(argClosure.EvalContext)
28+
listExpression, ok := argClosure.Expression.(*hclsyntax.TupleConsExpr)
29+
if !ok {
30+
v, err := evalctx.Eval(argClosure.Expression)
31+
if err != nil {
32+
return cty.False, err
33+
}
34+
if !v.Type().IsListType() && !v.Type().IsTupleType() {
35+
return cty.False, errors.E(`Invalid value for "list" parameter: %s`, v.Type().FriendlyName())
36+
}
37+
result := true
38+
i := 0
39+
for it := v.ElementIterator(); it.Next(); {
40+
_, v := it.Element()
41+
if !v.IsKnown() {
42+
return cty.UnknownVal(cty.Bool), nil
43+
}
44+
if v.IsNull() {
45+
return cty.False, nil
46+
}
47+
if !v.Type().Equals(cty.Bool) {
48+
return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1)
49+
}
50+
result = result && v.True()
51+
if !result {
52+
return cty.False, nil
53+
}
54+
i++
55+
}
56+
return cty.True, nil
57+
}
58+
59+
result := true
60+
for i, expr := range listExpression.Exprs {
61+
v, err := evalctx.Eval(expr)
62+
if err != nil {
63+
return cty.False, err
64+
}
65+
if v.IsNull() {
66+
return cty.False, nil
67+
}
68+
if !v.Type().Equals(cty.Bool) {
69+
return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1)
70+
}
71+
result = result && v.True()
72+
if !result {
73+
return cty.False, nil
74+
}
75+
}
76+
return cty.True, nil
77+
},
78+
})
79+
}
80+
81+
// AnyTrueFunc implements the `tm_anytrue()` function.
82+
func AnyTrueFunc() function.Function {
83+
return function.New(&function.Spec{
84+
Params: []function.Parameter{
85+
{
86+
Name: "list",
87+
Type: customdecode.ExpressionClosureType,
88+
},
89+
},
90+
Type: function.StaticReturnType(cty.Bool),
91+
Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) {
92+
argClosure := customdecode.ExpressionClosureFromVal(args[0])
93+
evalctx := eval.NewContextFrom(argClosure.EvalContext)
94+
listExpression, ok := argClosure.Expression.(*hclsyntax.TupleConsExpr)
95+
if !ok {
96+
v, err := evalctx.Eval(argClosure.Expression)
97+
if err != nil {
98+
return cty.False, err
99+
}
100+
if !v.Type().IsListType() && !v.Type().IsTupleType() {
101+
return cty.False, errors.E(`Invalid value for "list" parameter: %s`, v.Type().FriendlyName())
102+
}
103+
result := false
104+
i := 0
105+
for it := v.ElementIterator(); it.Next(); {
106+
_, v := it.Element()
107+
if !v.IsKnown() {
108+
continue
109+
}
110+
if v.IsNull() {
111+
continue
112+
}
113+
if !v.Type().Equals(cty.Bool) {
114+
return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1)
115+
}
116+
result = result || v.True()
117+
if result {
118+
return cty.True, nil
119+
}
120+
i++
121+
}
122+
return cty.False, nil
123+
}
124+
125+
result := false
126+
for i, expr := range listExpression.Exprs {
127+
v, err := evalctx.Eval(expr)
128+
if err != nil {
129+
return cty.False, err
130+
}
131+
if v.IsNull() {
132+
continue
133+
}
134+
if !v.Type().Equals(cty.Bool) {
135+
return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1)
136+
}
137+
result = result || v.True()
138+
if result {
139+
return cty.True, nil
140+
}
141+
}
142+
return cty.False, nil
143+
},
144+
})
145+
}

stdlib/funcs.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ func Functions(basedir string) map[string]function.Function {
6767
// sane ternary
6868
tmfuncs["tm_ternary"] = TernaryFunc()
6969

70+
// optimized collection functions
71+
tmfuncs["tm_alltrue"] = AllTrueFunc()
72+
tmfuncs["tm_anytrue"] = AnyTrueFunc()
73+
7074
// optimized try
7175
tmfuncs["tm_try"] = TryFunc()
7276

0 commit comments

Comments
 (0)