diff --git a/go.mod b/go.mod index 601571a642b..d84a74f95e4 100644 --- a/go.mod +++ b/go.mod @@ -176,7 +176,7 @@ require ( github.com/gofrs/uuid v4.0.0+incompatible // indirect github.com/golang-jwt/jwt/v4 v4.2.0 // indirect github.com/google/btree v1.0.1 // indirect - github.com/google/cel-go v0.12.3 // indirect + github.com/google/cel-go v0.12.4 // indirect github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/gax-go/v2 v2.1.1 // indirect @@ -390,7 +390,7 @@ replace ( github.com/golangplus/testing => github.com/golangplus/testing v1.0.0 github.com/google/btree => github.com/google/btree v1.0.1 github.com/google/cadvisor => github.com/google/cadvisor v0.44.1 - github.com/google/cel-go => github.com/google/cel-go v0.12.3 + github.com/google/cel-go => github.com/google/cel-go v0.12.4 github.com/google/gnostic => github.com/google/gnostic v0.5.7-v3refs github.com/google/go-cmp => github.com/google/go-cmp v0.5.6 github.com/google/gofuzz => github.com/google/gofuzz v1.1.0 diff --git a/go.sum b/go.sum index f070d07d661..ba7c8cfe44a 100644 --- a/go.sum +++ b/go.sum @@ -218,8 +218,8 @@ github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= github.com/google/cadvisor v0.44.1 h1:hsAxDZOY+5xSCXH12d/G9cxYTfP+32cMT3J7aatrgDY= github.com/google/cadvisor v0.44.1/go.mod h1:GQ9KQfz0iNHQk3D6ftzJWK4TXabfIgM10Oy3FkR+Gzg= -github.com/google/cel-go v0.12.3 h1:t7A5dK8X/wV/Bex9DzB70QQsIJ9xDcN/CFr/uExOOuw= -github.com/google/cel-go v0.12.3/go.mod h1:Av7CU6r6X3YmcHR9GXqVDaEJYfEtSxl6wvIjUQTriCw= +github.com/google/cel-go v0.12.4 h1:YINKfuHZ8n72tPOqSPZBwGiDpew2CJS48mdM5W8LZQU= +github.com/google/cel-go v0.12.4/go.mod h1:Av7CU6r6X3YmcHR9GXqVDaEJYfEtSxl6wvIjUQTriCw= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= diff --git a/staging/src/k8s.io/apiextensions-apiserver/go.mod b/staging/src/k8s.io/apiextensions-apiserver/go.mod index 40a712c1bd8..72d875d40b8 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/go.mod +++ b/staging/src/k8s.io/apiextensions-apiserver/go.mod @@ -7,7 +7,7 @@ go 1.18 require ( github.com/emicklei/go-restful/v3 v3.8.0 github.com/gogo/protobuf v1.3.2 - github.com/google/cel-go v0.12.3 + github.com/google/cel-go v0.12.4 github.com/google/gnostic v0.5.7-v3refs github.com/google/go-cmp v0.5.6 github.com/google/gofuzz v1.1.0 diff --git a/staging/src/k8s.io/apiextensions-apiserver/go.sum b/staging/src/k8s.io/apiextensions-apiserver/go.sum index 1307af62191..35da630174b 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/go.sum +++ b/staging/src/k8s.io/apiextensions-apiserver/go.sum @@ -198,8 +198,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= -github.com/google/cel-go v0.12.3 h1:t7A5dK8X/wV/Bex9DzB70QQsIJ9xDcN/CFr/uExOOuw= -github.com/google/cel-go v0.12.3/go.mod h1:Av7CU6r6X3YmcHR9GXqVDaEJYfEtSxl6wvIjUQTriCw= +github.com/google/cel-go v0.12.4 h1:YINKfuHZ8n72tPOqSPZBwGiDpew2CJS48mdM5W8LZQU= +github.com/google/cel-go v0.12.4/go.mod h1:Av7CU6r6X3YmcHR9GXqVDaEJYfEtSxl6wvIjUQTriCw= github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation.go index 5b9a1b5224c..46a035efe7d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/compilation.go @@ -24,10 +24,6 @@ import ( "github.com/google/cel-go/cel" "github.com/google/cel-go/checker" - "github.com/google/cel-go/checker/decls" - expr "google.golang.org/genproto/googleapis/api/expr/v1alpha1" - "google.golang.org/protobuf/proto" - apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library" @@ -84,7 +80,7 @@ func getBaseEnv() (*cel.Env, error) { // Validate function declarations once during base env initialization, // so they don't need to be evaluated each time a CEL rule is compiled. // This is a relatively expensive operation. - opts = append(opts, cel.EagerlyValidateDeclarations(true)) + opts = append(opts, cel.EagerlyValidateDeclarations(true), cel.DefaultUTCTimeZone(true)) opts = append(opts, library.ExtensionLibs...) initEnv, initEnvErr = cel.NewEnv(opts...) @@ -108,7 +104,7 @@ func Compile(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) ([] } celRules := s.Extensions.XValidations - var propDecls []*expr.Decl + var propDecls []cel.EnvOption var root *celmodel.DeclType var ok bool baseEnv, err := getBaseEnv() @@ -136,9 +132,9 @@ func Compile(s *schema.Structural, isResourceRoot bool, perCallLimit uint64) ([] } root = rootDecl.MaybeAssignTypeName(scopedTypeName) } - propDecls = append(propDecls, decls.NewVar(ScopedVarName, root.ExprType())) - propDecls = append(propDecls, decls.NewVar(OldScopedVarName, root.ExprType())) - opts = append(opts, cel.Declarations(propDecls...)) + propDecls = append(propDecls, cel.Variable(ScopedVarName, root.CelType())) + propDecls = append(propDecls, cel.Variable(OldScopedVarName, root.CelType())) + opts = append(opts, propDecls...) env, err := baseEnv.Extend(opts...) if err != nil { return nil, err @@ -165,7 +161,7 @@ func compileRule(rule apiextensions.ValidationRule, env *cel.Env, perCallLimit u compilationResult.Error = &Error{ErrorTypeInvalid, "compilation failed: " + issues.String()} return } - if !proto.Equal(ast.ResultType(), decls.Bool) { + if ast.OutputType() != cel.BoolType { compilationResult.Error = &Error{ErrorTypeInvalid, "cel expression must evaluate to a bool"} return } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/library_compatibility_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/library_compatibility_test.go index ddf9ef4e7fb..65473ff09a7 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/library_compatibility_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/library_compatibility_test.go @@ -20,13 +20,12 @@ import ( "testing" "github.com/google/cel-go/cel" - exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" ) func TestLibraryCompatibility(t *testing.T) { functionNames := map[string]struct{}{} - decls := map[cel.Library][]*exprpb.Decl{ + decls := map[cel.Library]map[string][]cel.FunctionOpt{ urlsLib: urlLibraryDecls, listsLib: listsLibraryDecls, regexLib: regexLibraryDecls, @@ -34,9 +33,9 @@ func TestLibraryCompatibility(t *testing.T) { if len(k8sExtensionLibs) != len(decls) { t.Errorf("Expected the same number of libraries in the ExtensionLibs as are tested for compatibility") } - for _, l := range decls { - for _, d := range l { - functionNames[d.GetName()] = struct{}{} + for _, decl := range decls { + for name := range decl { + functionNames[name] = struct{}{} } } diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/lists.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/lists.go index 1c0f60b5b00..2960e16e90a 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/lists.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/lists.go @@ -20,12 +20,10 @@ import ( "fmt" "github.com/google/cel-go/cel" - "github.com/google/cel-go/checker/decls" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/traits" "github.com/google/cel-go/interpreter/functions" - exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" ) // Lists provides a CEL function library extension of list utility functions. @@ -101,120 +99,84 @@ var listsLib = &lists{} type lists struct{} -var paramA = decls.NewTypeParamType("A") +var paramA = cel.TypeParamType("A") // CEL typeParams can be used to constraint to a specific trait (e.g. traits.ComparableType) if the 1st operand is the type to constrain. // But the functions we need to constrain are >, not just . -var summableTypes = map[string]*exprpb.Type{"int": decls.Int, "uint": decls.Uint, "double": decls.Double, "duration": decls.Duration} -var comparableTypes = map[string]*exprpb.Type{"bool": decls.Bool, "int": decls.Int, "uint": decls.Uint, "double": decls.Double, - "duration": decls.Duration, "timestamp": decls.Timestamp, "string": decls.String, "bytes": decls.Bytes} +// Make sure the order of overload set is deterministic +type namedCELType struct { + typeName string + celType *cel.Type +} + +var summableTypes = []namedCELType{ + {typeName: "int", celType: cel.IntType}, + {typeName: "uint", celType: cel.UintType}, + {typeName: "double", celType: cel.DoubleType}, + {typeName: "duration", celType: cel.DurationType}, +} + +var zeroValuesOfSummableTypes = map[string]ref.Val{ + "int": types.Int(0), + "uint": types.Uint(0), + "double": types.Double(0.0), + "duration": types.Duration{Duration: 0}, +} +var comparableTypes = []namedCELType{ + {typeName: "int", celType: cel.IntType}, + {typeName: "uint", celType: cel.UintType}, + {typeName: "double", celType: cel.DoubleType}, + {typeName: "bool", celType: cel.BoolType}, + {typeName: "duration", celType: cel.DurationType}, + {typeName: "timestamp", celType: cel.TimestampType}, + {typeName: "string", celType: cel.StringType}, + {typeName: "bytes", celType: cel.BytesType}, +} // WARNING: All library additions or modifications must follow // https://github.com/kubernetes/enhancements/tree/master/keps/sig-api-machinery/2876-crd-validation-expression-language#function-library-updates -var listsLibraryDecls = []*exprpb.Decl{ - decls.NewFunction("isSorted", - templatedOverloads(comparableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { - return decls.NewInstanceOverload(fmt.Sprintf("list_%s_is_sorted_bool", name), - []*exprpb.Type{decls.NewListType(paramType)}, - decls.Bool) - })..., - ), - decls.NewFunction("sum", - templatedOverloads(summableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { - return decls.NewInstanceOverload(fmt.Sprintf("list_%s_sum_%s", name, name), - []*exprpb.Type{decls.NewListType(paramType)}, - paramType) - })..., - ), - decls.NewFunction("max", - templatedOverloads(comparableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { - return decls.NewInstanceOverload(fmt.Sprintf("list_%s_max_%s", name, name), - []*exprpb.Type{decls.NewListType(paramType)}, - paramType) - })..., - ), - decls.NewFunction("min", - templatedOverloads(comparableTypes, func(name string, paramType *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload { - return decls.NewInstanceOverload(fmt.Sprintf("list_%s_min_%s", name, name), - []*exprpb.Type{decls.NewListType(paramType)}, - paramType) - })..., - ), - decls.NewFunction("indexOf", - decls.NewInstanceOverload("list_a_index_of_int", - []*exprpb.Type{decls.NewListType(paramA), paramA}, - decls.Int), - ), - decls.NewFunction("lastIndexOf", - decls.NewInstanceOverload("list_a_last_index_of_int", - []*exprpb.Type{decls.NewListType(paramA), paramA}, - decls.Int), - ), +var listsLibraryDecls = map[string][]cel.FunctionOpt{ + "isSorted": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt { + return cel.MemberOverload(fmt.Sprintf("list_%s_is_sorted_bool", name), + []*cel.Type{cel.ListType(paramType)}, cel.BoolType, cel.UnaryBinding(isSorted)) + }), + "sum": templatedOverloads(summableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt { + return cel.MemberOverload(fmt.Sprintf("list_%s_sum_%s", name, name), + []*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(func(list ref.Val) ref.Val { + return sum( + func() ref.Val { + return zeroValuesOfSummableTypes[name] + })(list) + })) + }), + "max": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt { + return cel.MemberOverload(fmt.Sprintf("list_%s_max_%s", name, name), + []*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(max())) + }), + "min": templatedOverloads(comparableTypes, func(name string, paramType *cel.Type) cel.FunctionOpt { + return cel.MemberOverload(fmt.Sprintf("list_%s_min_%s", name, name), + []*cel.Type{cel.ListType(paramType)}, paramType, cel.UnaryBinding(min())) + }), + "indexOf": { + cel.MemberOverload("list_a_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType, + cel.BinaryBinding(indexOf)), + }, + "lastIndexOf": { + cel.MemberOverload("list_a_last_index_of_int", []*cel.Type{cel.ListType(paramA), paramA}, cel.IntType, + cel.BinaryBinding(lastIndexOf)), + }, } func (*lists) CompileOptions() []cel.EnvOption { - return []cel.EnvOption{ - cel.Declarations(listsLibraryDecls...), + options := []cel.EnvOption{} + for name, overloads := range listsLibraryDecls { + options = append(options, cel.Function(name, overloads...)) } + return options } func (*lists) ProgramOptions() []cel.ProgramOption { - return []cel.ProgramOption{ - cel.Functions( - &functions.Overload{ - Operator: "isSorted", - Unary: isSorted, - }, - // if 'sum' is called directly, it is via dynamic dispatch, and we infer the type from the 1st element of the - // list if it has one, otherwise we return int64(0) - &functions.Overload{ - Operator: "sum", - Unary: dynSum(), - }, - // use overload names for sum so an initial accumulator value can be assigned to each - &functions.Overload{ - Operator: "list_int_sum_int", - Unary: sum(func() ref.Val { - return types.Int(0) - }), - }, - &functions.Overload{ - Operator: "list_uint_sum_uint", - Unary: sum(func() ref.Val { - return types.Uint(0) - }), - }, - &functions.Overload{ - Operator: "list_double_sum_double", - Unary: sum(func() ref.Val { - return types.Double(0.0) - }), - }, - &functions.Overload{ - Operator: "list_duration_sum_duration", - Unary: sum(func() ref.Val { - return types.Duration{Duration: 0} - }), - }, - &functions.Overload{ - Operator: "max", - Unary: max(), - }, - &functions.Overload{ - Operator: "min", - Unary: min(), - }, - // use overload names for indexOf and lastIndexOf to de-conflict with function of same name in strings extension library - &functions.Overload{ - Operator: "list_a_index_of_int", - Binary: indexOf, - }, - &functions.Overload{ - Operator: "list_a_last_index_of_int", - Binary: lastIndexOf, - }, - ), - } + return []cel.ProgramOption{} } func isSorted(val ref.Val) ref.Val { @@ -240,38 +202,6 @@ func isSorted(val ref.Val) ref.Val { return types.True } -func dynSum() functions.UnaryOp { - return func(val ref.Val) ref.Val { - iterable, ok := val.(traits.Iterable) - if !ok { - return types.MaybeNoSuchOverloadErr(val) - } - it := iterable.Iterator() - var initval ref.Val - if it.HasNext() == types.True { - first := it.Next() - switch first.Type() { - case types.IntType: - initval = types.Int(0) - case types.UintType: - initval = types.Uint(0) - case types.DoubleType: - initval = types.Double(0.0) - case types.DurationType: - initval = types.Duration{Duration: 0} - default: - return types.MaybeNoSuchOverloadErr(first) - } - } else { - initval = types.Int(0) - } - initFn := func() ref.Val { - return initval - } - return sum(initFn)(val) - } -} - func sum(init func() ref.Val) functions.UnaryOp { return func(val ref.Val) ref.Val { i := init() @@ -375,11 +305,11 @@ func lastIndexOf(list ref.Val, item ref.Val) ref.Val { // templatedOverloads returns overloads for each of the provided types. The template function is called with each type // name (map key) and type to construct the overloads. -func templatedOverloads(types map[string]*exprpb.Type, template func(name string, t *exprpb.Type) *exprpb.Decl_FunctionDecl_Overload) []*exprpb.Decl_FunctionDecl_Overload { - overloads := make([]*exprpb.Decl_FunctionDecl_Overload, len(types)) +func templatedOverloads(types []namedCELType, template func(name string, t *cel.Type) cel.FunctionOpt) []cel.FunctionOpt { + overloads := make([]cel.FunctionOpt, len(types)) i := 0 - for name, t := range types { - overloads[i] = template(name, t) + for _, t := range types { + overloads[i] = template(t.typeName, t.celType) i++ } return overloads diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/regex.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/regex.go index e44f82090ab..cbb4faac82a 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/regex.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/regex.go @@ -20,12 +20,9 @@ import ( "regexp" "github.com/google/cel-go/cel" - "github.com/google/cel-go/checker/decls" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/interpreter" - "github.com/google/cel-go/interpreter/functions" - exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" ) // Regex provides a CEL function library extension of regex utility functions. @@ -55,59 +52,33 @@ var regexLib = ®ex{} type regex struct{} -var regexLibraryDecls = []*exprpb.Decl{ - - decls.NewFunction("find", - decls.NewInstanceOverload("string_find_string", - []*exprpb.Type{decls.String, decls.String}, - decls.String), - ), - decls.NewFunction("findAll", - decls.NewInstanceOverload("string_find_all_string", - []*exprpb.Type{decls.String, decls.String}, - decls.NewListType(decls.String)), - decls.NewInstanceOverload("string_find_all_string_int", - []*exprpb.Type{decls.String, decls.String, decls.Int}, - decls.NewListType(decls.String)), - ), +var regexLibraryDecls = map[string][]cel.FunctionOpt{ + "find": { + cel.MemberOverload("string_find_string", []*cel.Type{cel.StringType, cel.StringType}, cel.StringType, + cel.BinaryBinding(find))}, + "findAll": { + cel.MemberOverload("string_find_all_string", []*cel.Type{cel.StringType, cel.StringType}, + cel.ListType(cel.StringType), + cel.BinaryBinding(func(str, regex ref.Val) ref.Val { + return findAll(str, regex, types.Int(-1)) + })), + cel.MemberOverload("string_find_all_string_int", + []*cel.Type{cel.StringType, cel.StringType, cel.IntType}, + cel.ListType(cel.StringType), + cel.FunctionBinding(findAll)), + }, } func (*regex) CompileOptions() []cel.EnvOption { - return []cel.EnvOption{ - cel.Declarations(regexLibraryDecls...), + options := []cel.EnvOption{} + for name, overloads := range regexLibraryDecls { + options = append(options, cel.Function(name, overloads...)) } + return options } func (*regex) ProgramOptions() []cel.ProgramOption { - return []cel.ProgramOption{ - cel.Functions( - &functions.Overload{ - Operator: "find", - Binary: find, - }, - &functions.Overload{ - Operator: "string_find_string", - Binary: find, - }, - &functions.Overload{ - Operator: "findAll", - Binary: func(str, regex ref.Val) ref.Val { - return findAll(str, regex, types.Int(-1)) - }, - Function: findAll, - }, - &functions.Overload{ - Operator: "string_find_all_string", - Binary: func(str, regex ref.Val) ref.Val { - return findAll(str, regex, types.Int(-1)) - }, - }, - &functions.Overload{ - Operator: "string_find_all_string_int", - Function: findAll, - }, - ), - } + return []cel.ProgramOption{} } func find(strVal ref.Val, regexVal ref.Val) ref.Val { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/urls.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/urls.go index 8e47bb45ae7..7744931422c 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/urls.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/library/urls.go @@ -20,12 +20,8 @@ import ( "net/url" "github.com/google/cel-go/cel" - "github.com/google/cel-go/checker/decls" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" - "github.com/google/cel-go/interpreter/functions" - exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" - "k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model" ) @@ -111,124 +107,44 @@ var urlsLib = &urls{} type urls struct{} -var urlLibraryDecls = []*exprpb.Decl{ - decls.NewFunction("url", - decls.NewOverload("string_to_url", - []*exprpb.Type{decls.String}, - model.URLObject), - ), - decls.NewFunction("getScheme", - decls.NewInstanceOverload("url_get_scheme", - []*exprpb.Type{model.URLObject}, - decls.String), - ), - decls.NewFunction("getHost", - decls.NewInstanceOverload("url_get_host", - []*exprpb.Type{model.URLObject}, - decls.String), - ), - decls.NewFunction("getHostname", - decls.NewInstanceOverload("url_get_hostname", - []*exprpb.Type{model.URLObject}, - decls.String), - ), - decls.NewFunction("getPort", - decls.NewInstanceOverload("url_get_port", - []*exprpb.Type{model.URLObject}, - decls.String), - ), - decls.NewFunction("getEscapedPath", - decls.NewInstanceOverload("url_get_escaped_path", - []*exprpb.Type{model.URLObject}, - decls.String), - ), - decls.NewFunction("getQuery", - decls.NewInstanceOverload("url_get_query", - []*exprpb.Type{model.URLObject}, - decls.NewMapType(decls.String, decls.NewListType(decls.String))), - ), - decls.NewFunction("isURL", - decls.NewOverload("is_url_string", - []*exprpb.Type{decls.String}, - decls.Bool), - ), +var urlLibraryDecls = map[string][]cel.FunctionOpt{ + "url": { + cel.Overload("string_to_url", []*cel.Type{cel.StringType}, model.URLType, + cel.UnaryBinding(stringToUrl))}, + "getScheme": { + cel.MemberOverload("url_get_scheme", []*cel.Type{model.URLType}, cel.StringType, + cel.UnaryBinding(getScheme))}, + "getHost": { + cel.MemberOverload("url_get_host", []*cel.Type{model.URLType}, cel.StringType, + cel.UnaryBinding(getHost))}, + "getHostname": { + cel.MemberOverload("url_get_hostname", []*cel.Type{model.URLType}, cel.StringType, + cel.UnaryBinding(getHostname))}, + "getPort": { + cel.MemberOverload("url_get_port", []*cel.Type{model.URLType}, cel.StringType, + cel.UnaryBinding(getPort))}, + "getEscapedPath": { + cel.MemberOverload("url_get_escaped_path", []*cel.Type{model.URLType}, cel.StringType, + cel.UnaryBinding(getEscapedPath))}, + "getQuery": { + cel.MemberOverload("url_get_query", []*cel.Type{model.URLType}, + cel.MapType(cel.StringType, cel.ListType(cel.StringType)), + cel.UnaryBinding(getQuery))}, + "isURL": { + cel.Overload("is_url_string", []*cel.Type{cel.StringType}, cel.BoolType, + cel.UnaryBinding(isURL))}, } func (*urls) CompileOptions() []cel.EnvOption { - return []cel.EnvOption{ - cel.Declarations(urlLibraryDecls...), + options := []cel.EnvOption{} + for name, overloads := range urlLibraryDecls { + options = append(options, cel.Function(name, overloads...)) } + return options } func (*urls) ProgramOptions() []cel.ProgramOption { - return []cel.ProgramOption{ - cel.Functions( - &functions.Overload{ - Operator: "url", - Unary: stringToUrl, - }, - &functions.Overload{ - Operator: "string_to_url", - Unary: stringToUrl, - }, - &functions.Overload{ - Operator: "getScheme", - Unary: getScheme, - }, - &functions.Overload{ - Operator: "url_get_scheme", - Unary: getScheme, - }, - &functions.Overload{ - Operator: "getHost", - Unary: getHost, - }, - &functions.Overload{ - Operator: "url_get_host", - Unary: getHost, - }, - &functions.Overload{ - Operator: "getHostname", - Unary: getHostname, - }, - &functions.Overload{ - Operator: "url_get_hostname", - Unary: getHostname, - }, - &functions.Overload{ - Operator: "getPort", - Unary: getPort, - }, - &functions.Overload{ - Operator: "url_get_port", - Unary: getPort, - }, - &functions.Overload{ - Operator: "getEscapedPath", - Unary: getEscapedPath, - }, - &functions.Overload{ - Operator: "url_get_escaped_path", - Unary: getEscapedPath, - }, - &functions.Overload{ - Operator: "getQuery", - Unary: getQuery, - }, - &functions.Overload{ - Operator: "url_get_query", - Unary: getQuery, - }, - &functions.Overload{ - Operator: "isURL", - Unary: isURL, - }, - &functions.Overload{ - Operator: "is_url_string", - Unary: isURL, - }, - ), - } + return []cel.ProgramOption{} } func stringToUrl(arg ref.Val) ref.Val { diff --git a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go index 1207ca9afed..06f52a7350c 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/pkg/apiserver/schema/cel/validation_test.go @@ -1605,8 +1605,7 @@ func TestValidationExpressions(t *testing.T) { "dyn([1, 2]).sum() == 3", "dyn([1.0, 2.0]).sum() == 3.0", - // TODO: enable once type system fix it made to CEL - //"[].sum() == 0", // An empty list returns an 0 int + "[].sum() == 0", // An empty list returns an 0 int }, errors: map[string]string{ // return an error for min/max on empty list diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas.go index b6a92a938e8..9f17b5ce50d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas.go @@ -17,7 +17,7 @@ package model import ( "time" - "github.com/google/cel-go/checker/decls" + "github.com/google/cel-go/cel" "github.com/google/cel-go/common/types" "k8s.io/apiextensions-apiserver/pkg/apiserver/schema" @@ -72,7 +72,7 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { // To validate requirements on both the int and string representation: // `type(intOrStringField) == int ? intOrStringField < 5 : double(intOrStringField.replace('%', '')) < 0.5 // - dyn := newSimpleType("dyn", decls.Dyn, nil) + dyn := newSimpleType("dyn", cel.DynType, nil) // handle x-kubernetes-int-or-string by returning the max length of the largest possible string dyn.MaxElements = maxRequestSizeBytes - 2 return dyn @@ -149,7 +149,7 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { if s.ValueValidation != nil { switch s.ValueValidation.Format { case "byte": - byteWithMaxLength := newSimpleType("bytes", decls.Bytes, types.Bytes([]byte{})) + byteWithMaxLength := newSimpleType("bytes", cel.BytesType, types.Bytes([]byte{})) if s.ValueValidation.MaxLength != nil { byteWithMaxLength.MaxElements = zeroIfNegative(*s.ValueValidation.MaxLength) } else { @@ -157,16 +157,16 @@ func SchemaDeclType(s *schema.Structural, isResourceRoot bool) *DeclType { } return byteWithMaxLength case "duration": - durationWithMaxLength := newSimpleType("duration", decls.Duration, types.Duration{Duration: time.Duration(0)}) + durationWithMaxLength := newSimpleType("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)}) durationWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s) return durationWithMaxLength case "date", "date-time": - timestampWithMaxLength := newSimpleType("timestamp", decls.Timestamp, types.Timestamp{Time: time.Time{}}) + timestampWithMaxLength := newSimpleType("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}) timestampWithMaxLength.MaxElements = estimateMaxStringLengthPerRequest(s) return timestampWithMaxLength } } - strWithMaxLength := newSimpleType("string", decls.String, types.String("")) + strWithMaxLength := newSimpleType("string", cel.StringType, types.String("")) if s.ValueValidation != nil && s.ValueValidation.MaxLength != nil { // multiply the user-provided max length by 4 in the case of an otherwise-untyped string // we do this because the OpenAPIv3 spec indicates that maxLength is specified in runes/code points, diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas_test.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas_test.go index 2490911a785..6aff03d2e3d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/schemas_test.go @@ -107,8 +107,16 @@ func TestSchemaDeclTypes(t *testing.T) { t.Errorf("missing type in rule types: %s", exp) continue } - if !proto.Equal(expType.ExprType(), actType.ExprType()) { - t.Errorf("incompatible CEL types. got=%v, wanted=%v", actType.ExprType(), expType.ExprType()) + expT, err := expType.ExprType() + if err != nil { + t.Errorf("fail to get cel type: %s", err) + } + actT, err := actType.ExprType() + if err != nil { + t.Errorf("fail to get cel type: %s", err) + } + if !proto.Equal(expT, actT) { + t.Errorf("incompatible CEL types. got=%v, wanted=%v", expT, actT) } } } diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types.go index 2f0a5b59dcd..f5eb7da976a 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types.go @@ -20,7 +20,6 @@ import ( "time" "github.com/google/cel-go/cel" - "github.com/google/cel-go/checker/decls" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" "github.com/google/cel-go/common/types/traits" @@ -41,7 +40,7 @@ func NewListType(elem *DeclType, maxItems int64) *DeclType { name: "list", ElemType: elem, MaxElements: maxItems, - exprType: decls.NewListType(elem.ExprType()), + celType: cel.ListType(elem.CelType()), defaultValue: NewListValue(), } } @@ -53,7 +52,7 @@ func NewMapType(key, elem *DeclType, maxProperties int64) *DeclType { KeyType: key, ElemType: elem, MaxElements: maxProperties, - exprType: decls.NewMapType(key.ExprType(), elem.ExprType()), + celType: cel.MapType(key.CelType(), elem.CelType()), defaultValue: NewMapValue(), } } @@ -63,39 +62,17 @@ func NewObjectType(name string, fields map[string]*DeclField) *DeclType { t := &DeclType{ name: name, Fields: fields, - exprType: decls.NewObjectType(name), + celType: cel.ObjectType(name), traitMask: traits.FieldTesterType | traits.IndexerType, } t.defaultValue = NewObjectValue(t) return t } -// NewObjectTypeRef returns a reference to an object type by name -func NewObjectTypeRef(name string) *DeclType { - t := &DeclType{ - name: name, - exprType: decls.NewObjectType(name), - traitMask: traits.FieldTesterType | traits.IndexerType, - } - return t -} - -// NewTypeParam creates a type parameter type with a simple name. -// -// Type parameters are resolved at compilation time to concrete types, or CEL 'dyn' type if no -// type assignment can be inferred. -func NewTypeParam(name string) *DeclType { - return &DeclType{ - name: name, - TypeParam: true, - exprType: decls.NewTypeParamType(name), - } -} - -func newSimpleType(name string, exprType *exprpb.Type, zeroVal ref.Val) *DeclType { +func newSimpleType(name string, celType *cel.Type, zeroVal ref.Val) *DeclType { return &DeclType{ name: name, - exprType: exprType, + celType: celType, defaultValue: zeroVal, } } @@ -112,7 +89,7 @@ type DeclType struct { Metadata map[string]string MaxElements int64 - exprType *exprpb.Type + celType *cel.Type traitMask int defaultValue ref.Val } @@ -157,7 +134,7 @@ func (t *DeclType) MaybeAssignTypeName(name string) *DeclType { ElemType: t.ElemType, TypeParam: t.TypeParam, Metadata: t.Metadata, - exprType: decls.NewObjectType(name), + celType: cel.ObjectType(name), traitMask: t.traitMask, defaultValue: t.defaultValue, } @@ -182,8 +159,13 @@ func (t *DeclType) MaybeAssignTypeName(name string) *DeclType { } // ExprType returns the CEL expression type of this declaration. -func (t *DeclType) ExprType() *exprpb.Type { - return t.exprType +func (t *DeclType) ExprType() (*exprpb.Type, error) { + return cel.TypeToExprType(t.celType) +} + +// CelType returns the CEL type of this declaration. +func (t *DeclType) CelType() *cel.Type { + return t.celType } // FindField returns the DeclField with the given name if present. @@ -369,7 +351,11 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) { } for name, declType := range rt.ruleSchemaDeclTypes.types { tpType, found := tp.FindType(name) - if found && !proto.Equal(tpType, declType.ExprType()) { + expT, err := declType.ExprType() + if err != nil { + return nil, fmt.Errorf("fail to get cel type: %s", err) + } + if found && !proto.Equal(tpType, expT) { return nil, fmt.Errorf( "type %s definition differs between CEL environment and rule", name) } @@ -377,9 +363,7 @@ func (rt *RuleTypes) EnvOptions(tp ref.TypeProvider) ([]cel.EnvOption, error) { return []cel.EnvOption{ cel.CustomTypeProvider(rtWithTypes), cel.CustomTypeAdapter(rtWithTypes), - cel.Declarations( - decls.NewVar("rule", rt.ruleSchemaDeclTypes.root.ExprType()), - ), + cel.Variable("rule", rt.ruleSchemaDeclTypes.root.CelType()), }, nil } @@ -396,7 +380,11 @@ func (rt *RuleTypes) FindType(typeName string) (*exprpb.Type, bool) { } declType, found := rt.findDeclType(typeName) if found { - return declType.ExprType(), found + expT, err := declType.ExprType() + if err != nil { + return expT, false + } + return expT, found } return rt.TypeProvider.FindType(typeName) } @@ -424,15 +412,23 @@ func (rt *RuleTypes) FindFieldType(typeName, fieldName string) (*ref.FieldType, f, found := st.Fields[fieldName] if found { ft := f.Type + expT, err := ft.ExprType() + if err != nil { + return nil, false + } return &ref.FieldType{ - Type: ft.ExprType(), + Type: expT, }, true } // This could be a dynamic map. if st.IsMap() { et := st.ElemType + expT, err := et.ExprType() + if err != nil { + return nil, false + } return &ref.FieldType{ - Type: et.ExprType(), + Type: expT, }, true } return nil, false @@ -517,42 +513,42 @@ type schemaTypeProvider struct { var ( // AnyType is equivalent to the CEL 'protobuf.Any' type in that the value may have any of the // types supported. - AnyType = newSimpleType("any", decls.Any, nil) + AnyType = newSimpleType("any", cel.AnyType, nil) // BoolType is equivalent to the CEL 'bool' type. - BoolType = newSimpleType("bool", decls.Bool, types.False) + BoolType = newSimpleType("bool", cel.BoolType, types.False) // BytesType is equivalent to the CEL 'bytes' type. - BytesType = newSimpleType("bytes", decls.Bytes, types.Bytes([]byte{})) + BytesType = newSimpleType("bytes", cel.BytesType, types.Bytes([]byte{})) // DoubleType is equivalent to the CEL 'double' type which is a 64-bit floating point value. - DoubleType = newSimpleType("double", decls.Double, types.Double(0)) + DoubleType = newSimpleType("double", cel.DoubleType, types.Double(0)) // DurationType is equivalent to the CEL 'duration' type. - DurationType = newSimpleType("duration", decls.Duration, types.Duration{Duration: time.Duration(0)}) + DurationType = newSimpleType("duration", cel.DurationType, types.Duration{Duration: time.Duration(0)}) // DateType is equivalent to the CEL 'date' type. - DateType = newSimpleType("date", decls.Timestamp, types.Timestamp{Time: time.Time{}}) + DateType = newSimpleType("date", cel.TimestampType, types.Timestamp{Time: time.Time{}}) // DynType is the equivalent of the CEL 'dyn' concept which indicates that the type will be // determined at runtime rather than compile time. - DynType = newSimpleType("dyn", decls.Dyn, nil) + DynType = newSimpleType("dyn", cel.DynType, nil) // IntType is equivalent to the CEL 'int' type which is a 64-bit signed int. - IntType = newSimpleType("int", decls.Int, types.IntZero) + IntType = newSimpleType("int", cel.IntType, types.IntZero) // NullType is equivalent to the CEL 'null_type'. - NullType = newSimpleType("null_type", decls.Null, types.NullValue) + NullType = newSimpleType("null_type", cel.NullType, types.NullValue) // StringType is equivalent to the CEL 'string' type which is expected to be a UTF-8 string. // StringType values may either be string literals or expression strings. - StringType = newSimpleType("string", decls.String, types.String("")) + StringType = newSimpleType("string", cel.StringType, types.String("")) // TimestampType corresponds to the well-known protobuf.Timestamp type supported within CEL. - TimestampType = newSimpleType("timestamp", decls.Timestamp, types.Timestamp{Time: time.Time{}}) + TimestampType = newSimpleType("timestamp", cel.TimestampType, types.Timestamp{Time: time.Time{}}) // UintType is equivalent to the CEL 'uint' type. - UintType = newSimpleType("uint", decls.Uint, types.Uint(0)) + UintType = newSimpleType("uint", cel.UintType, types.Uint(0)) // ListType is equivalent to the CEL 'list' type. ListType = NewListType(AnyType, noMaxLength) diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types_test.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types_test.go index 66756bfda95..207b4befa3d 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types_test.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/types_test.go @@ -37,8 +37,12 @@ func TestTypes_ListType(t *testing.T) { if list.ElemType.TypeName() != "string" { t.Errorf("got %s, wanted elem type of string", list.ElemType.TypeName()) } - if list.ExprType().GetListType() == nil { - t.Errorf("got %v, wanted CEL list type", list.ExprType()) + expT, err := list.ExprType() + if err != nil { + t.Errorf("fail to get cel type: %s", err) + } + if expT.GetListType() == nil { + t.Errorf("got %v, wanted CEL list type", expT) } } @@ -59,8 +63,12 @@ func TestTypes_MapType(t *testing.T) { if mp.ElemType.TypeName() != "int" { t.Errorf("got %s, wanted elem type of int", mp.ElemType.TypeName()) } - if mp.ExprType().GetMapType() == nil { - t.Errorf("got %v, wanted CEL map type", mp.ExprType()) + expT, err := mp.ExprType() + if err != nil { + t.Errorf("fail to get cel type: %s", err) + } + if expT.GetMapType() == nil { + t.Errorf("got %v, wanted CEL map type", expT) } } diff --git a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/url.go b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/url.go index 34d2d0eab7c..5e2eba7e3f2 100644 --- a/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/url.go +++ b/staging/src/k8s.io/apiextensions-apiserver/third_party/forked/celopenapi/model/url.go @@ -19,6 +19,7 @@ import ( "net/url" "reflect" + "github.com/google/cel-go/cel" "github.com/google/cel-go/checker/decls" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" @@ -32,6 +33,7 @@ type URL struct { var ( URLObject = decls.NewObjectType("kubernetes.URL") typeValue = types.NewTypeValue("kubernetes.URL") + URLType = cel.ObjectType("kubernetes.URL") ) // ConvertToNative implements ref.Val.ConvertToNative. diff --git a/vendor/github.com/google/cel-go/cel/decls.go b/vendor/github.com/google/cel-go/cel/decls.go index 55532788372..f2df721d076 100644 --- a/vendor/github.com/google/cel-go/cel/decls.go +++ b/vendor/github.com/google/cel-go/cel/decls.go @@ -21,6 +21,7 @@ import ( "github.com/google/cel-go/checker/decls" "github.com/google/cel-go/common/types" "github.com/google/cel-go/common/types/ref" + "github.com/google/cel-go/common/types/traits" "github.com/google/cel-go/interpreter/functions" exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1" @@ -162,7 +163,7 @@ type Type struct { // isAssignableRuntimeType function determines whether the runtime type (with erasure) is assignable to this type. // A nil value for the isAssignableRuntimeType function falls back to the equality of the type or type name. - isAssignableRuntimeType func(other ref.Type) bool + isAssignableRuntimeType func(other ref.Val) bool } // IsAssignableType determines whether the current type is type-check assignable from the input fromType. @@ -177,11 +178,11 @@ func (t *Type) IsAssignableType(fromType *Type) bool { // // At runtime, parameterized types are erased and so a function which type-checks to support a map(string, string) // will have a runtime assignable type of a map. -func (t *Type) IsAssignableRuntimeType(runtimeType ref.Type) bool { +func (t *Type) IsAssignableRuntimeType(val ref.Val) bool { if t.isAssignableRuntimeType != nil { - return t.isAssignableRuntimeType(runtimeType) + return t.isAssignableRuntimeType(val) } - return t.defaultIsAssignableRuntimeType(runtimeType) + return t.defaultIsAssignableRuntimeType(val) } // String returns a human-readable definition of the type name. @@ -221,7 +222,7 @@ func (t *Type) equals(other *Type) bool { // - The from types are the same instance // - The target type is dynamic // - The fromType has the same kind and type name as the target type, and all parameters of the target type -// are IsAssignableType() from the parameters of the fromType. +// are IsAssignableType() from the parameters of the fromType. func (t *Type) defaultIsAssignableType(fromType *Type) bool { if t == fromType || t.isDyn() { return true @@ -240,8 +241,40 @@ func (t *Type) defaultIsAssignableType(fromType *Type) bool { return true } -func (t *Type) defaultIsAssignableRuntimeType(runtimeType ref.Type) bool { - return t.runtimeType == runtimeType || t.isDyn() || t.runtimeType.TypeName() == runtimeType.TypeName() +// defaultIsAssignableRuntimeType inspects the type and in the case of list and map elements, the key and element types +// to determine whether a ref.Val is assignable to the declared type for a function signature. +func (t *Type) defaultIsAssignableRuntimeType(val ref.Val) bool { + valType := val.Type() + if !(t.runtimeType == valType || t.isDyn() || t.runtimeType.TypeName() == valType.TypeName()) { + return false + } + switch t.runtimeType { + case types.ListType: + elemType := t.parameters[0] + l := val.(traits.Lister) + if l.Size() == types.IntZero { + return true + } + it := l.Iterator() + for it.HasNext() == types.True { + elemVal := it.Next() + return elemType.IsAssignableRuntimeType(elemVal) + } + case types.MapType: + keyType := t.parameters[0] + elemType := t.parameters[1] + m := val.(traits.Mapper) + if m.Size() == types.IntZero { + return true + } + it := m.Iterator() + for it.HasNext() == types.True { + keyVal := it.Next() + elemVal := m.Get(keyVal) + return keyType.IsAssignableRuntimeType(keyVal) && elemType.IsAssignableRuntimeType(elemVal) + } + } + return true } // ListType creates an instances of a list type value with the provided element type. @@ -273,7 +306,7 @@ func NullableType(wrapped *Type) *Type { isAssignableType: func(other *Type) bool { return NullType.IsAssignableType(other) || wrapped.IsAssignableType(other) }, - isAssignableRuntimeType: func(other ref.Type) bool { + isAssignableRuntimeType: func(other ref.Val) bool { return NullType.IsAssignableRuntimeType(other) || wrapped.IsAssignableRuntimeType(other) }, } @@ -328,12 +361,26 @@ func Variable(name string, t *Type) EnvOption { // One key difference with using Function() is that each FunctionDecl provided will handle dynamic // dispatch based on the type-signatures of the overloads provided which means overload resolution at // runtime is handled out of the box rather than via a custom binding for overload resolution via -// Functions(). +// Functions(): +// +// - Overloads are searched in the order they are declared +// - Dynamic dispatch for lists and maps is limited by inspection of the list and map contents +// at runtime. Empty lists and maps will result in a 'default dispatch' +// - In the event that a default dispatch occurs, the first overload provided is the one invoked +// +// If you intend to use overloads which differentiate based on the key or element type of a list or +// map, consider using a generic function instead: e.g. func(list(T)) or func(map(K, V)) as this +// will allow your implementation to determine how best to handle dispatch and the default behavior +// for empty lists and maps whose contents cannot be inspected. +// +// For functions which use parameterized opaque types (abstract types), consider using a singleton +// function which is capable of inspecting the contents of the type and resolving the appropriate +// overload as CEL can only make inferences by type-name regarding such types. func Function(name string, opts ...FunctionOpt) EnvOption { return func(e *Env) (*Env, error) { fn := &functionDecl{ name: name, - overloads: map[string]*overloadDecl{}, + overloads: []*overloadDecl{}, options: opts, } err := fn.init() @@ -510,7 +557,7 @@ func OverloadOperandTrait(trait int) OverloadOpt { type functionDecl struct { name string - overloads map[string]*overloadDecl + overloads []*overloadDecl options []FunctionOpt singleton *functions.Overload initialized bool @@ -591,22 +638,22 @@ func (f *functionDecl) bindings() ([]*functions.Overload, error) { // performs dynamic dispatch to the proper overload based on the argument types. bindings := append([]*functions.Overload{}, overloads...) funcDispatch := func(args ...ref.Val) ref.Val { - for _, overloadDecl := range f.overloads { - if !overloadDecl.matchesRuntimeSignature(args...) { + for _, o := range f.overloads { + if !o.matchesRuntimeSignature(args...) { continue } switch len(args) { case 1: - if overloadDecl.unaryOp != nil { - return overloadDecl.unaryOp(args[0]) + if o.unaryOp != nil { + return o.unaryOp(args[0]) } case 2: - if overloadDecl.binaryOp != nil { - return overloadDecl.binaryOp(args[0], args[1]) + if o.binaryOp != nil { + return o.binaryOp(args[0], args[1]) } } - if overloadDecl.functionOp != nil { - return overloadDecl.functionOp(args...) + if o.functionOp != nil { + return o.functionOp(args...) } // eventually this will fall through to the noSuchOverload below. } @@ -639,14 +686,12 @@ func (f *functionDecl) merge(other *functionDecl) (*functionDecl, error) { } merged := &functionDecl{ name: f.name, - overloads: map[string]*overloadDecl{}, + overloads: make([]*overloadDecl, len(f.overloads)), options: []FunctionOpt{}, initialized: true, singleton: f.singleton, } - for id, o := range f.overloads { - merged.overloads[id] = o - } + copy(merged.overloads, f.overloads) for _, o := range other.overloads { err := merged.addOverload(o) if err != nil { @@ -666,20 +711,21 @@ func (f *functionDecl) merge(other *functionDecl) (*functionDecl, error) { // however, if the function signatures are identical, the implementation may be rewritten as its // difficult to compare functions by object identity. func (f *functionDecl) addOverload(overload *overloadDecl) error { - for id, o := range f.overloads { - if id != overload.id && o.signatureOverlaps(overload) { + for index, o := range f.overloads { + if o.id != overload.id && o.signatureOverlaps(overload) { return fmt.Errorf("overload signature collision in function %s: %s collides with %s", f.name, o.id, overload.id) } - if id == overload.id { + if o.id == overload.id { if o.signatureEquals(overload) && o.nonStrict == overload.nonStrict { // Allow redefinition of an overload implementation so long as the signatures match. - f.overloads[id] = overload + f.overloads[index] = overload + return nil } else { return fmt.Errorf("overload redefinition in function. %s: %s has multiple definitions", f.name, o.id) } } } - f.overloads[overload.id] = overload + f.overloads = append(f.overloads, overload) return nil } @@ -757,19 +803,19 @@ func (o *overloadDecl) matchesRuntimeUnarySignature(arg ref.Val) bool { if o.nonStrict && types.IsUnknownOrError(arg) { return true } - return o.argTypes[0].IsAssignableRuntimeType(arg.Type()) && (o.operandTrait == 0 || arg.Type().HasTrait(o.operandTrait)) + return o.argTypes[0].IsAssignableRuntimeType(arg) && (o.operandTrait == 0 || arg.Type().HasTrait(o.operandTrait)) } // matchesRuntimeBinarySignature indicates whether the argument types are runtime assiganble to the overload's expected arguments. func (o *overloadDecl) matchesRuntimeBinarySignature(arg1, arg2 ref.Val) bool { if o.nonStrict { if types.IsUnknownOrError(arg1) { - return types.IsUnknownOrError(arg2) || o.argTypes[1].IsAssignableRuntimeType(arg2.Type()) + return types.IsUnknownOrError(arg2) || o.argTypes[1].IsAssignableRuntimeType(arg2) } - } else if !o.argTypes[1].IsAssignableRuntimeType(arg2.Type()) { + } else if !o.argTypes[1].IsAssignableRuntimeType(arg2) { return false } - return o.argTypes[0].IsAssignableRuntimeType(arg1.Type()) && (o.operandTrait == 0 || arg1.Type().HasTrait(o.operandTrait)) + return o.argTypes[0].IsAssignableRuntimeType(arg1) && (o.operandTrait == 0 || arg1.Type().HasTrait(o.operandTrait)) } // matchesRuntimeSignature indicates whether the argument types are runtime assiganble to the overload's expected arguments. @@ -785,7 +831,7 @@ func (o *overloadDecl) matchesRuntimeSignature(args ...ref.Val) bool { if o.nonStrict && types.IsUnknownOrError(arg) { continue } - allArgsMatch = allArgsMatch && o.argTypes[i].IsAssignableRuntimeType(arg.Type()) + allArgsMatch = allArgsMatch && o.argTypes[i].IsAssignableRuntimeType(arg) } arg := args[0] diff --git a/vendor/modules.txt b/vendor/modules.txt index e18dd78d563..dec720faeaa 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -416,7 +416,7 @@ github.com/google/cadvisor/utils/sysfs github.com/google/cadvisor/utils/sysinfo github.com/google/cadvisor/version github.com/google/cadvisor/watcher -# github.com/google/cel-go v0.12.3 => github.com/google/cel-go v0.12.3 +# github.com/google/cel-go v0.12.4 => github.com/google/cel-go v0.12.4 ## explicit; go 1.17 github.com/google/cel-go/cel github.com/google/cel-go/checker @@ -2679,7 +2679,7 @@ sigs.k8s.io/yaml # github.com/golangplus/testing => github.com/golangplus/testing v1.0.0 # github.com/google/btree => github.com/google/btree v1.0.1 # github.com/google/cadvisor => github.com/google/cadvisor v0.44.1 -# github.com/google/cel-go => github.com/google/cel-go v0.12.3 +# github.com/google/cel-go => github.com/google/cel-go v0.12.4 # github.com/google/gnostic => github.com/google/gnostic v0.5.7-v3refs # github.com/google/go-cmp => github.com/google/go-cmp v0.5.6 # github.com/google/gofuzz => github.com/google/gofuzz v1.1.0