From a8198100aad58bae407c65863b0cb5f39a064fff Mon Sep 17 00:00:00 2001 From: Tiago Natel Date: Wed, 6 Dec 2023 17:57:26 +0000 Subject: [PATCH 1/2] chore(perf): optimise tm_alltrue() and tm_anytrue(). Signed-off-by: Tiago Natel --- globals/globals_test.go | 129 +++++++++++++++++++++++++++++++++++ stdlib/collection.go | 145 ++++++++++++++++++++++++++++++++++++++++ stdlib/funcs.go | 4 ++ 3 files changed, 278 insertions(+) create mode 100644 stdlib/collection.go diff --git a/globals/globals_test.go b/globals/globals_test.go index 0d9f136b5..b3e937738 100644 --- a/globals/globals_test.go +++ b/globals/globals_test.go @@ -2870,6 +2870,135 @@ func TestLoadGlobals(t *testing.T) { ), }, }, + { + name: "tm_alltrue with literal list of bool", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_alltrue([true, 1==1, false==false, !false])`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, + { + name: "tm_alltrue with literal tuple containing non-boolean", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_alltrue([true, 1==1, "string", {}])`), + ), + }, + }, + wantErr: errors.E(globals.ErrEval), + }, + { + name: "tm_alltrue with literal for-loop", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_alltrue([for i in [true, 1==1, !false] : i])`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, + { + name: "tm_alltrue with funcall", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_alltrue(tm_distinct([true, !false, 1==1]))`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, + + { + name: "tm_anytrue with literal list of bool", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_anytrue([false, 1!=1, false==false, !false])`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, + { + name: "tm_anytrue with literal tuple containing non-boolean", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_anytrue([false, 1!=1, "string", {}])`), + ), + }, + }, + wantErr: errors.E(globals.ErrEval), + }, + { + name: "tm_anytrue with literal for-loop", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_anytrue([for i in [false, 1!=1, !false] : i])`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, + { + name: "tm_anytrue with funcall", + layout: []string{"s:stack"}, + configs: []hclconfig{ + { + path: "/stack", + add: Globals( + Expr("val", `tm_anytrue(tm_distinct([false, false, 1==1]))`), + ), + }, + }, + want: map[string]*hclwrite.Block{ + "/stack": Globals( + EvalExpr(t, "val", `true`), + ), + }, + }, { name: "globals.map label conflicts with global name", layout: []string{"s:stack"}, diff --git a/stdlib/collection.go b/stdlib/collection.go new file mode 100644 index 000000000..80fcde3e0 --- /dev/null +++ b/stdlib/collection.go @@ -0,0 +1,145 @@ +// Copyright 2023 Terramate GmbH +// SPDX-License-Identifier: MPL-2.0 + +package stdlib + +import ( + "github.com/hashicorp/hcl/v2/ext/customdecode" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/terramate-io/terramate/errors" + "github.com/terramate-io/terramate/hcl/eval" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/function" +) + +// AllTrueFunc implements the `tm_alltrue()` function. +func AllTrueFunc() function.Function { + return function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: customdecode.ExpressionClosureType, + }, + }, + Type: function.StaticReturnType(cty.Bool), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + argClosure := customdecode.ExpressionClosureFromVal(args[0]) + evalctx := eval.NewContextFrom(argClosure.EvalContext) + listExpression, ok := argClosure.Expression.(*hclsyntax.TupleConsExpr) + if !ok { + v, err := evalctx.Eval(argClosure.Expression) + if err != nil { + return cty.False, err + } + if !v.Type().IsListType() && !v.Type().IsTupleType() { + return cty.False, errors.E(`Invalid value for "list" parameter: %s`, v.Type().FriendlyName()) + } + result := true + i := 0 + for it := v.ElementIterator(); it.Next(); { + _, v := it.Element() + if !v.IsKnown() { + return cty.UnknownVal(cty.Bool), nil + } + if v.IsNull() { + return cty.False, nil + } + if !v.Type().Equals(cty.Bool) { + return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1) + } + result = result && v.True() + if !result { + return cty.False, nil + } + i++ + } + return cty.True, nil + } + + result := true + for i, expr := range listExpression.Exprs { + v, err := evalctx.Eval(expr) + if err != nil { + return cty.False, err + } + if v.IsNull() { + return cty.False, nil + } + if !v.Type().Equals(cty.Bool) { + return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1) + } + result = result && v.True() + if !result { + return cty.False, nil + } + } + return cty.True, nil + }, + }) +} + +// AnyTrueFunc implements the `tm_anytrue()` function. +func AnyTrueFunc() function.Function { + return function.New(&function.Spec{ + Params: []function.Parameter{ + { + Name: "list", + Type: customdecode.ExpressionClosureType, + }, + }, + Type: function.StaticReturnType(cty.Bool), + Impl: func(args []cty.Value, retType cty.Type) (ret cty.Value, err error) { + argClosure := customdecode.ExpressionClosureFromVal(args[0]) + evalctx := eval.NewContextFrom(argClosure.EvalContext) + listExpression, ok := argClosure.Expression.(*hclsyntax.TupleConsExpr) + if !ok { + v, err := evalctx.Eval(argClosure.Expression) + if err != nil { + return cty.False, err + } + if !v.Type().IsListType() && !v.Type().IsTupleType() { + return cty.False, errors.E(`Invalid value for "list" parameter: %s`, v.Type().FriendlyName()) + } + result := false + i := 0 + for it := v.ElementIterator(); it.Next(); { + _, v := it.Element() + if !v.IsKnown() { + continue + } + if v.IsNull() { + continue + } + if !v.Type().Equals(cty.Bool) { + return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1) + } + result = result || v.True() + if result { + return cty.True, nil + } + i++ + } + return cty.False, nil + } + + result := false + for i, expr := range listExpression.Exprs { + v, err := evalctx.Eval(expr) + if err != nil { + return cty.False, err + } + if v.IsNull() { + continue + } + if !v.Type().Equals(cty.Bool) { + return cty.False, errors.E(`Invalid value for "list" parameter: element %d: bool required`, i+1) + } + result = result || v.True() + if result { + return cty.True, nil + } + } + return cty.False, nil + }, + }) +} diff --git a/stdlib/funcs.go b/stdlib/funcs.go index f9c2e7d02..4f90f948a 100644 --- a/stdlib/funcs.go +++ b/stdlib/funcs.go @@ -67,6 +67,10 @@ func Functions(basedir string) map[string]function.Function { // sane ternary tmfuncs["tm_ternary"] = TernaryFunc() + // optimized collection functions + tmfuncs["tm_alltrue"] = AllTrueFunc() + tmfuncs["tm_anytrue"] = AnyTrueFunc() + // optimized try tmfuncs["tm_try"] = TryFunc() From 4a91ab9208d5ecf48c4704f2e34d5b64b9cb9d92 Mon Sep 17 00:00:00 2001 From: i4k Date: Wed, 23 Oct 2024 20:35:34 +0100 Subject: [PATCH 2/2] chore: merge with base Signed-off-by: i4k --- stdlib/collection.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/collection.go b/stdlib/collection.go index 80fcde3e0..26417789d 100644 --- a/stdlib/collection.go +++ b/stdlib/collection.go @@ -4,8 +4,8 @@ package stdlib import ( - "github.com/hashicorp/hcl/v2/ext/customdecode" - "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/terramate-io/hcl/v2/ext/customdecode" + "github.com/terramate-io/hcl/v2/hclsyntax" "github.com/terramate-io/terramate/errors" "github.com/terramate-io/terramate/hcl/eval" "github.com/zclconf/go-cty/cty"