mirror of
https://github.com/ceph/ceph-csi.git
synced 2024-11-14 18:30:21 +00:00
07b05616a0
Bumps [k8s.io/kubernetes](https://github.com/kubernetes/kubernetes) from 1.26.2 to 1.27.2. - [Release notes](https://github.com/kubernetes/kubernetes/releases) - [Commits](https://github.com/kubernetes/kubernetes/compare/v1.26.2...v1.27.2) --- updated-dependencies: - dependency-name: k8s.io/kubernetes dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com>
1180 lines
37 KiB
Go
1180 lines
37 KiB
Go
// Copyright 2022 Google LLC
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package cel
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"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"
|
|
)
|
|
|
|
// Kind indicates a CEL type's kind which is used to differentiate quickly between simple and complex types.
|
|
type Kind uint
|
|
|
|
const (
|
|
// DynKind represents a dynamic type. This kind only exists at type-check time.
|
|
DynKind Kind = iota
|
|
|
|
// AnyKind represents a google.protobuf.Any type. This kind only exists at type-check time.
|
|
AnyKind
|
|
|
|
// BoolKind represents a boolean type.
|
|
BoolKind
|
|
|
|
// BytesKind represents a bytes type.
|
|
BytesKind
|
|
|
|
// DoubleKind represents a double type.
|
|
DoubleKind
|
|
|
|
// DurationKind represents a CEL duration type.
|
|
DurationKind
|
|
|
|
// IntKind represents an integer type.
|
|
IntKind
|
|
|
|
// ListKind represents a list type.
|
|
ListKind
|
|
|
|
// MapKind represents a map type.
|
|
MapKind
|
|
|
|
// NullTypeKind represents a null type.
|
|
NullTypeKind
|
|
|
|
// OpaqueKind represents an abstract type which has no accessible fields.
|
|
OpaqueKind
|
|
|
|
// StringKind represents a string type.
|
|
StringKind
|
|
|
|
// StructKind represents a structured object with typed fields.
|
|
StructKind
|
|
|
|
// TimestampKind represents a a CEL time type.
|
|
TimestampKind
|
|
|
|
// TypeKind represents the CEL type.
|
|
TypeKind
|
|
|
|
// TypeParamKind represents a parameterized type whose type name will be resolved at type-check time, if possible.
|
|
TypeParamKind
|
|
|
|
// UintKind represents a uint type.
|
|
UintKind
|
|
)
|
|
|
|
var (
|
|
// AnyType represents the google.protobuf.Any type.
|
|
AnyType = &Type{
|
|
kind: AnyKind,
|
|
runtimeType: types.NewTypeValue("google.protobuf.Any"),
|
|
}
|
|
// BoolType represents the bool type.
|
|
BoolType = &Type{
|
|
kind: BoolKind,
|
|
runtimeType: types.BoolType,
|
|
}
|
|
// BytesType represents the bytes type.
|
|
BytesType = &Type{
|
|
kind: BytesKind,
|
|
runtimeType: types.BytesType,
|
|
}
|
|
// DoubleType represents the double type.
|
|
DoubleType = &Type{
|
|
kind: DoubleKind,
|
|
runtimeType: types.DoubleType,
|
|
}
|
|
// DurationType represents the CEL duration type.
|
|
DurationType = &Type{
|
|
kind: DurationKind,
|
|
runtimeType: types.DurationType,
|
|
}
|
|
// DynType represents a dynamic CEL type whose type will be determined at runtime from context.
|
|
DynType = &Type{
|
|
kind: DynKind,
|
|
runtimeType: types.NewTypeValue("dyn"),
|
|
}
|
|
// IntType represents the int type.
|
|
IntType = &Type{
|
|
kind: IntKind,
|
|
runtimeType: types.IntType,
|
|
}
|
|
// NullType represents the type of a null value.
|
|
NullType = &Type{
|
|
kind: NullTypeKind,
|
|
runtimeType: types.NullType,
|
|
}
|
|
// StringType represents the string type.
|
|
StringType = &Type{
|
|
kind: StringKind,
|
|
runtimeType: types.StringType,
|
|
}
|
|
// TimestampType represents the time type.
|
|
TimestampType = &Type{
|
|
kind: TimestampKind,
|
|
runtimeType: types.TimestampType,
|
|
}
|
|
// TypeType represents a CEL type
|
|
TypeType = &Type{
|
|
kind: TypeKind,
|
|
runtimeType: types.TypeType,
|
|
}
|
|
//UintType represents a uint type.
|
|
UintType = &Type{
|
|
kind: UintKind,
|
|
runtimeType: types.UintType,
|
|
}
|
|
)
|
|
|
|
// Type holds a reference to a runtime type with an optional type-checked set of type parameters.
|
|
type Type struct {
|
|
// kind indicates general category of the type.
|
|
kind Kind
|
|
|
|
// runtimeType is the runtime type of the declaration.
|
|
runtimeType ref.Type
|
|
|
|
// parameters holds the optional type-checked set of type parameters that are used during static analysis.
|
|
parameters []*Type
|
|
|
|
// isAssignableType function determines whether one type is assignable to this type.
|
|
// A nil value for the isAssignableType function falls back to equality of kind, runtimeType, and parameters.
|
|
isAssignableType func(other *Type) bool
|
|
|
|
// 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.Val) bool
|
|
}
|
|
|
|
// IsAssignableType determines whether the current type is type-check assignable from the input fromType.
|
|
func (t *Type) IsAssignableType(fromType *Type) bool {
|
|
if t.isAssignableType != nil {
|
|
return t.isAssignableType(fromType)
|
|
}
|
|
return t.defaultIsAssignableType(fromType)
|
|
}
|
|
|
|
// IsAssignableRuntimeType determines whether the current type is runtime assignable from the input runtimeType.
|
|
//
|
|
// 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(val ref.Val) bool {
|
|
if t.isAssignableRuntimeType != nil {
|
|
return t.isAssignableRuntimeType(val)
|
|
}
|
|
return t.defaultIsAssignableRuntimeType(val)
|
|
}
|
|
|
|
// String returns a human-readable definition of the type name.
|
|
func (t *Type) String() string {
|
|
if len(t.parameters) == 0 {
|
|
return t.runtimeType.TypeName()
|
|
}
|
|
params := make([]string, len(t.parameters))
|
|
for i, p := range t.parameters {
|
|
params[i] = p.String()
|
|
}
|
|
return fmt.Sprintf("%s(%s)", t.runtimeType.TypeName(), strings.Join(params, ", "))
|
|
}
|
|
|
|
// isDyn indicates whether the type is dynamic in any way.
|
|
func (t *Type) isDyn() bool {
|
|
return t.kind == DynKind || t.kind == AnyKind || t.kind == TypeParamKind
|
|
}
|
|
|
|
// equals indicates whether two types have the same kind, type name, and parameters.
|
|
func (t *Type) equals(other *Type) bool {
|
|
if t.kind != other.kind ||
|
|
t.runtimeType.TypeName() != other.runtimeType.TypeName() ||
|
|
len(t.parameters) != len(other.parameters) {
|
|
return false
|
|
}
|
|
for i, p := range t.parameters {
|
|
if !p.equals(other.parameters[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// defaultIsAssignableType provides the standard definition of what it means for one type to be assignable to another
|
|
// where any of the following may return a true result:
|
|
// - 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.
|
|
func (t *Type) defaultIsAssignableType(fromType *Type) bool {
|
|
if t == fromType || t.isDyn() {
|
|
return true
|
|
}
|
|
if t.kind != fromType.kind ||
|
|
t.runtimeType.TypeName() != fromType.runtimeType.TypeName() ||
|
|
len(t.parameters) != len(fromType.parameters) {
|
|
return false
|
|
}
|
|
for i, tp := range t.parameters {
|
|
fp := fromType.parameters[i]
|
|
if !tp.IsAssignableType(fp) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// 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.
|
|
func ListType(elemType *Type) *Type {
|
|
return &Type{
|
|
kind: ListKind,
|
|
runtimeType: types.ListType,
|
|
parameters: []*Type{elemType},
|
|
}
|
|
}
|
|
|
|
// MapType creates an instance of a map type value with the provided key and value types.
|
|
func MapType(keyType, valueType *Type) *Type {
|
|
return &Type{
|
|
kind: MapKind,
|
|
runtimeType: types.MapType,
|
|
parameters: []*Type{keyType, valueType},
|
|
}
|
|
}
|
|
|
|
// NullableType creates an instance of a nullable type with the provided wrapped type.
|
|
//
|
|
// Note: only primitive types are supported as wrapped types.
|
|
func NullableType(wrapped *Type) *Type {
|
|
return &Type{
|
|
kind: wrapped.kind,
|
|
runtimeType: wrapped.runtimeType,
|
|
parameters: wrapped.parameters,
|
|
isAssignableType: func(other *Type) bool {
|
|
return NullType.IsAssignableType(other) || wrapped.IsAssignableType(other)
|
|
},
|
|
isAssignableRuntimeType: func(other ref.Val) bool {
|
|
return NullType.IsAssignableRuntimeType(other) || wrapped.IsAssignableRuntimeType(other)
|
|
},
|
|
}
|
|
}
|
|
|
|
// OpaqueType creates an abstract parameterized type with a given name.
|
|
func OpaqueType(name string, params ...*Type) *Type {
|
|
return &Type{
|
|
kind: OpaqueKind,
|
|
runtimeType: types.NewTypeValue(name),
|
|
parameters: params,
|
|
}
|
|
}
|
|
|
|
// ObjectType creates a type references to an externally defined type, e.g. a protobuf message type.
|
|
func ObjectType(typeName string) *Type {
|
|
return &Type{
|
|
kind: StructKind,
|
|
runtimeType: types.NewObjectTypeValue(typeName),
|
|
}
|
|
}
|
|
|
|
// TypeParamType creates a parameterized type instance.
|
|
func TypeParamType(paramName string) *Type {
|
|
return &Type{
|
|
kind: TypeParamKind,
|
|
runtimeType: types.NewTypeValue(paramName),
|
|
}
|
|
}
|
|
|
|
// Variable creates an instance of a variable declaration with a variable name and type.
|
|
func Variable(name string, t *Type) EnvOption {
|
|
return func(e *Env) (*Env, error) {
|
|
et, err := TypeToExprType(t)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
e.declarations = append(e.declarations, decls.NewVar(name, et))
|
|
return e, nil
|
|
}
|
|
}
|
|
|
|
// Function defines a function and overloads with optional singleton or per-overload bindings.
|
|
//
|
|
// Using Function is roughly equivalent to calling Declarations() to declare the function signatures
|
|
// and Functions() to define the function bindings, if they have been defined. Specifying the
|
|
// same function name more than once will result in the aggregation of the function overloads. If any
|
|
// signatures conflict between the existing and new function definition an error will be raised.
|
|
// However, if the signatures are identical and the overload ids are the same, the redefinition will
|
|
// be considered a no-op.
|
|
//
|
|
// 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():
|
|
//
|
|
// - 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: []*overloadDecl{},
|
|
options: opts,
|
|
}
|
|
err := fn.init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = functionDeclToExprDecl(fn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if existing, found := e.functions[fn.name]; found {
|
|
fn, err = existing.merge(fn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
e.functions[name] = fn
|
|
return e, nil
|
|
}
|
|
}
|
|
|
|
// FunctionOpt defines a functional option for configuring a function declaration.
|
|
type FunctionOpt func(*functionDecl) (*functionDecl, error)
|
|
|
|
// SingletonUnaryBinding creates a singleton function defintion to be used for all function overloads.
|
|
//
|
|
// Note, this approach works well if operand is expected to have a specific trait which it implements,
|
|
// e.g. traits.ContainerType. Otherwise, prefer per-overload function bindings.
|
|
func SingletonUnaryBinding(fn functions.UnaryOp, traits ...int) FunctionOpt {
|
|
trait := 0
|
|
for _, t := range traits {
|
|
trait = trait | t
|
|
}
|
|
return func(f *functionDecl) (*functionDecl, error) {
|
|
if f.singleton != nil {
|
|
return nil, fmt.Errorf("function already has a singleton binding: %s", f.name)
|
|
}
|
|
f.singleton = &functions.Overload{
|
|
Operator: f.name,
|
|
Unary: fn,
|
|
OperandTrait: trait,
|
|
}
|
|
return f, nil
|
|
}
|
|
}
|
|
|
|
// SingletonBinaryImpl creates a singleton function definition to be used with all function overloads.
|
|
//
|
|
// Note, this approach works well if operand is expected to have a specific trait which it implements,
|
|
// e.g. traits.ContainerType. Otherwise, prefer per-overload function bindings.
|
|
func SingletonBinaryImpl(fn functions.BinaryOp, traits ...int) FunctionOpt {
|
|
trait := 0
|
|
for _, t := range traits {
|
|
trait = trait | t
|
|
}
|
|
return func(f *functionDecl) (*functionDecl, error) {
|
|
if f.singleton != nil {
|
|
return nil, fmt.Errorf("function already has a singleton binding: %s", f.name)
|
|
}
|
|
f.singleton = &functions.Overload{
|
|
Operator: f.name,
|
|
Binary: fn,
|
|
OperandTrait: trait,
|
|
}
|
|
return f, nil
|
|
}
|
|
}
|
|
|
|
// SingletonFunctionImpl creates a singleton function definition to be used with all function overloads.
|
|
//
|
|
// Note, this approach works well if operand is expected to have a specific trait which it implements,
|
|
// e.g. traits.ContainerType. Otherwise, prefer per-overload function bindings.
|
|
func SingletonFunctionImpl(fn functions.FunctionOp, traits ...int) FunctionOpt {
|
|
trait := 0
|
|
for _, t := range traits {
|
|
trait = trait | t
|
|
}
|
|
return func(f *functionDecl) (*functionDecl, error) {
|
|
if f.singleton != nil {
|
|
return nil, fmt.Errorf("function already has a singleton binding: %s", f.name)
|
|
}
|
|
f.singleton = &functions.Overload{
|
|
Operator: f.name,
|
|
Function: fn,
|
|
OperandTrait: trait,
|
|
}
|
|
return f, nil
|
|
}
|
|
}
|
|
|
|
// Overload defines a new global overload with an overload id, argument types, and result type. Through the
|
|
// use of OverloadOpt options, the overload may also be configured with a binding, an operand trait, and to
|
|
// be non-strict.
|
|
//
|
|
// Note: function bindings should be commonly configured with Overload instances whereas operand traits and
|
|
// strict-ness should be rare occurrences.
|
|
func Overload(overloadID string, args []*Type, resultType *Type, opts ...OverloadOpt) FunctionOpt {
|
|
return newOverload(overloadID, false, args, resultType, opts...)
|
|
}
|
|
|
|
// MemberOverload defines a new receiver-style overload (or member function) with an overload id, argument types,
|
|
// and result type. Through the use of OverloadOpt options, the overload may also be configured with a binding,
|
|
// an operand trait, and to be non-strict.
|
|
//
|
|
// Note: function bindings should be commonly configured with Overload instances whereas operand traits and
|
|
// strict-ness should be rare occurrences.
|
|
func MemberOverload(overloadID string, args []*Type, resultType *Type, opts ...OverloadOpt) FunctionOpt {
|
|
return newOverload(overloadID, true, args, resultType, opts...)
|
|
}
|
|
|
|
// OverloadOpt is a functional option for configuring a function overload.
|
|
type OverloadOpt func(*overloadDecl) (*overloadDecl, error)
|
|
|
|
// UnaryBinding provides the implementation of a unary overload. The provided function is protected by a runtime
|
|
// type-guard which ensures runtime type agreement between the overload signature and runtime argument types.
|
|
func UnaryBinding(binding functions.UnaryOp) OverloadOpt {
|
|
return func(o *overloadDecl) (*overloadDecl, error) {
|
|
if o.hasBinding() {
|
|
return nil, fmt.Errorf("overload already has a binding: %s", o.id)
|
|
}
|
|
if len(o.argTypes) != 1 {
|
|
return nil, fmt.Errorf("unary function bound to non-unary overload: %s", o.id)
|
|
}
|
|
o.unaryOp = binding
|
|
return o, nil
|
|
}
|
|
}
|
|
|
|
// BinaryBinding provides the implementation of a binary overload. The provided function is protected by a runtime
|
|
// type-guard which ensures runtime type agreement between the overload signature and runtime argument types.
|
|
func BinaryBinding(binding functions.BinaryOp) OverloadOpt {
|
|
return func(o *overloadDecl) (*overloadDecl, error) {
|
|
if o.hasBinding() {
|
|
return nil, fmt.Errorf("overload already has a binding: %s", o.id)
|
|
}
|
|
if len(o.argTypes) != 2 {
|
|
return nil, fmt.Errorf("binary function bound to non-binary overload: %s", o.id)
|
|
}
|
|
o.binaryOp = binding
|
|
return o, nil
|
|
}
|
|
}
|
|
|
|
// FunctionBinding provides the implementation of a variadic overload. The provided function is protected by a runtime
|
|
// type-guard which ensures runtime type agreement between the overload signature and runtime argument types.
|
|
func FunctionBinding(binding functions.FunctionOp) OverloadOpt {
|
|
return func(o *overloadDecl) (*overloadDecl, error) {
|
|
if o.hasBinding() {
|
|
return nil, fmt.Errorf("overload already has a binding: %s", o.id)
|
|
}
|
|
o.functionOp = binding
|
|
return o, nil
|
|
}
|
|
}
|
|
|
|
// OverloadIsNonStrict enables the function to be called with error and unknown argument values.
|
|
//
|
|
// Note: do not use this option unless absoluately necessary as it should be an uncommon feature.
|
|
func OverloadIsNonStrict() OverloadOpt {
|
|
return func(o *overloadDecl) (*overloadDecl, error) {
|
|
o.nonStrict = true
|
|
return o, nil
|
|
}
|
|
}
|
|
|
|
// OverloadOperandTrait configures a set of traits which the first argument to the overload must implement in order to be
|
|
// successfully invoked.
|
|
func OverloadOperandTrait(trait int) OverloadOpt {
|
|
return func(o *overloadDecl) (*overloadDecl, error) {
|
|
o.operandTrait = trait
|
|
return o, nil
|
|
}
|
|
}
|
|
|
|
type functionDecl struct {
|
|
name string
|
|
overloads []*overloadDecl
|
|
options []FunctionOpt
|
|
singleton *functions.Overload
|
|
initialized bool
|
|
}
|
|
|
|
// init ensures that a function's options have been applied.
|
|
//
|
|
// This function is used in both the environment configuration and internally for function merges.
|
|
func (f *functionDecl) init() error {
|
|
if f.initialized {
|
|
return nil
|
|
}
|
|
f.initialized = true
|
|
|
|
var err error
|
|
for _, opt := range f.options {
|
|
f, err = opt(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if len(f.overloads) == 0 {
|
|
return fmt.Errorf("function %s must have at least one overload", f.name)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// bindings produces a set of function bindings, if any are defined.
|
|
func (f *functionDecl) bindings() ([]*functions.Overload, error) {
|
|
overloads := []*functions.Overload{}
|
|
nonStrict := false
|
|
for _, o := range f.overloads {
|
|
if o.hasBinding() {
|
|
overload := &functions.Overload{
|
|
Operator: o.id,
|
|
Unary: o.guardedUnaryOp(f.name),
|
|
Binary: o.guardedBinaryOp(f.name),
|
|
Function: o.guardedFunctionOp(f.name),
|
|
OperandTrait: o.operandTrait,
|
|
NonStrict: o.nonStrict,
|
|
}
|
|
overloads = append(overloads, overload)
|
|
nonStrict = nonStrict || o.nonStrict
|
|
}
|
|
}
|
|
if f.singleton != nil {
|
|
if len(overloads) != 0 {
|
|
return nil, fmt.Errorf("singleton function incompatible with specialized overloads: %s", f.name)
|
|
}
|
|
return []*functions.Overload{
|
|
{
|
|
Operator: f.name,
|
|
Unary: f.singleton.Unary,
|
|
Binary: f.singleton.Binary,
|
|
Function: f.singleton.Function,
|
|
OperandTrait: f.singleton.OperandTrait,
|
|
},
|
|
}, nil
|
|
}
|
|
if len(overloads) == 0 {
|
|
return overloads, nil
|
|
}
|
|
// Single overload. Replicate an entry for it using the function name as well.
|
|
if len(overloads) == 1 {
|
|
if overloads[0].Operator == f.name {
|
|
return overloads, nil
|
|
}
|
|
return append(overloads, &functions.Overload{
|
|
Operator: f.name,
|
|
Unary: overloads[0].Unary,
|
|
Binary: overloads[0].Binary,
|
|
Function: overloads[0].Function,
|
|
NonStrict: overloads[0].NonStrict,
|
|
OperandTrait: overloads[0].OperandTrait,
|
|
}), nil
|
|
}
|
|
// All of the defined overloads are wrapped into a top-level function which
|
|
// 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 _, o := range f.overloads {
|
|
if !o.matchesRuntimeSignature(args...) {
|
|
continue
|
|
}
|
|
switch len(args) {
|
|
case 1:
|
|
if o.unaryOp != nil {
|
|
return o.unaryOp(args[0])
|
|
}
|
|
case 2:
|
|
if o.binaryOp != nil {
|
|
return o.binaryOp(args[0], args[1])
|
|
}
|
|
}
|
|
if o.functionOp != nil {
|
|
return o.functionOp(args...)
|
|
}
|
|
// eventually this will fall through to the noSuchOverload below.
|
|
}
|
|
return noSuchOverload(f.name, args...)
|
|
}
|
|
function := &functions.Overload{
|
|
Operator: f.name,
|
|
Function: funcDispatch,
|
|
NonStrict: nonStrict,
|
|
}
|
|
return append(bindings, function), nil
|
|
}
|
|
|
|
// merge one function declaration with another.
|
|
//
|
|
// If a function is extended, by say adding new overloads to an existing function, then it is merged with the
|
|
// prior definition of the function at which point its overloads must not collide with pre-existing overloads
|
|
// and its bindings (singleton, or per-overload) must not conflict with previous definitions either.
|
|
func (f *functionDecl) merge(other *functionDecl) (*functionDecl, error) {
|
|
if f.name != other.name {
|
|
return nil, fmt.Errorf("cannot merge unrelated functions. %s and %s", f.name, other.name)
|
|
}
|
|
err := f.init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
err = other.init()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
merged := &functionDecl{
|
|
name: f.name,
|
|
overloads: make([]*overloadDecl, len(f.overloads)),
|
|
options: []FunctionOpt{},
|
|
initialized: true,
|
|
singleton: f.singleton,
|
|
}
|
|
copy(merged.overloads, f.overloads)
|
|
for _, o := range other.overloads {
|
|
err := merged.addOverload(o)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("function declaration merge failed: %v", err)
|
|
}
|
|
}
|
|
if other.singleton != nil {
|
|
if merged.singleton != nil {
|
|
return nil, fmt.Errorf("function already has a binding: %s", f.name)
|
|
}
|
|
merged.singleton = other.singleton
|
|
}
|
|
return merged, nil
|
|
}
|
|
|
|
// addOverload ensures that the new overload does not collide with an existing overload signature;
|
|
// 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 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 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[index] = overload
|
|
return nil
|
|
} else {
|
|
return fmt.Errorf("overload redefinition in function. %s: %s has multiple definitions", f.name, o.id)
|
|
}
|
|
}
|
|
}
|
|
f.overloads = append(f.overloads, overload)
|
|
return nil
|
|
}
|
|
|
|
func noSuchOverload(funcName string, args ...ref.Val) ref.Val {
|
|
argTypes := make([]string, len(args))
|
|
for i, arg := range args {
|
|
argTypes[i] = arg.Type().TypeName()
|
|
}
|
|
signature := strings.Join(argTypes, ", ")
|
|
return types.NewErr("no such overload: %s(%s)", funcName, signature)
|
|
}
|
|
|
|
// overloadDecl contains all of the relevant information regarding a specific function overload.
|
|
type overloadDecl struct {
|
|
id string
|
|
argTypes []*Type
|
|
resultType *Type
|
|
memberFunction bool
|
|
|
|
// binding options, optional but encouraged.
|
|
unaryOp functions.UnaryOp
|
|
binaryOp functions.BinaryOp
|
|
functionOp functions.FunctionOp
|
|
|
|
// behavioral options, uncommon
|
|
nonStrict bool
|
|
operandTrait int
|
|
}
|
|
|
|
func (o *overloadDecl) hasBinding() bool {
|
|
return o.unaryOp != nil || o.binaryOp != nil || o.functionOp != nil
|
|
}
|
|
|
|
// guardedUnaryOp creates an invocation guard around the provided unary operator, if one is defined.
|
|
func (o *overloadDecl) guardedUnaryOp(funcName string) functions.UnaryOp {
|
|
if o.unaryOp == nil {
|
|
return nil
|
|
}
|
|
return func(arg ref.Val) ref.Val {
|
|
if !o.matchesRuntimeUnarySignature(arg) {
|
|
return noSuchOverload(funcName, arg)
|
|
}
|
|
return o.unaryOp(arg)
|
|
}
|
|
}
|
|
|
|
// guardedBinaryOp creates an invocation guard around the provided binary operator, if one is defined.
|
|
func (o *overloadDecl) guardedBinaryOp(funcName string) functions.BinaryOp {
|
|
if o.binaryOp == nil {
|
|
return nil
|
|
}
|
|
return func(arg1, arg2 ref.Val) ref.Val {
|
|
if !o.matchesRuntimeBinarySignature(arg1, arg2) {
|
|
return noSuchOverload(funcName, arg1, arg2)
|
|
}
|
|
return o.binaryOp(arg1, arg2)
|
|
}
|
|
}
|
|
|
|
// guardedFunctionOp creates an invocation guard around the provided variadic function binding, if one is provided.
|
|
func (o *overloadDecl) guardedFunctionOp(funcName string) functions.FunctionOp {
|
|
if o.functionOp == nil {
|
|
return nil
|
|
}
|
|
return func(args ...ref.Val) ref.Val {
|
|
if !o.matchesRuntimeSignature(args...) {
|
|
return noSuchOverload(funcName, args...)
|
|
}
|
|
return o.functionOp(args...)
|
|
}
|
|
}
|
|
|
|
// matchesRuntimeUnarySignature indicates whether the argument type is runtime assiganble to the overload's expected argument.
|
|
func (o *overloadDecl) matchesRuntimeUnarySignature(arg ref.Val) bool {
|
|
if o.nonStrict && types.IsUnknownOrError(arg) {
|
|
return true
|
|
}
|
|
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)
|
|
}
|
|
} else if !o.argTypes[1].IsAssignableRuntimeType(arg2) {
|
|
return false
|
|
}
|
|
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.
|
|
func (o *overloadDecl) matchesRuntimeSignature(args ...ref.Val) bool {
|
|
if len(args) != len(o.argTypes) {
|
|
return false
|
|
}
|
|
if len(args) == 0 {
|
|
return true
|
|
}
|
|
allArgsMatch := true
|
|
for i, arg := range args {
|
|
if o.nonStrict && types.IsUnknownOrError(arg) {
|
|
continue
|
|
}
|
|
allArgsMatch = allArgsMatch && o.argTypes[i].IsAssignableRuntimeType(arg)
|
|
}
|
|
|
|
arg := args[0]
|
|
return allArgsMatch && (o.operandTrait == 0 || (o.nonStrict && types.IsUnknownOrError(arg)) || arg.Type().HasTrait(o.operandTrait))
|
|
}
|
|
|
|
// signatureEquals indicates whether one overload has an identical signature to another overload.
|
|
//
|
|
// Providing a duplicate signature is not an issue, but an overloapping signature is problematic.
|
|
func (o *overloadDecl) signatureEquals(other *overloadDecl) bool {
|
|
if o.id != other.id || o.memberFunction != other.memberFunction || len(o.argTypes) != len(other.argTypes) {
|
|
return false
|
|
}
|
|
for i, at := range o.argTypes {
|
|
oat := other.argTypes[i]
|
|
if !at.equals(oat) {
|
|
return false
|
|
}
|
|
}
|
|
return o.resultType.equals(other.resultType)
|
|
}
|
|
|
|
// signatureOverlaps indicates whether one overload has an overlapping signature with another overload.
|
|
//
|
|
// The 'other' overload must first be checked for equality before determining whether it overlaps in order to be completely accurate.
|
|
func (o *overloadDecl) signatureOverlaps(other *overloadDecl) bool {
|
|
if o.memberFunction != other.memberFunction || len(o.argTypes) != len(other.argTypes) {
|
|
return false
|
|
}
|
|
argsOverlap := true
|
|
for i, argType := range o.argTypes {
|
|
otherArgType := other.argTypes[i]
|
|
argsOverlap = argsOverlap &&
|
|
(argType.IsAssignableType(otherArgType) ||
|
|
otherArgType.IsAssignableType(argType))
|
|
}
|
|
return argsOverlap
|
|
}
|
|
|
|
func newOverload(overloadID string, memberFunction bool, args []*Type, resultType *Type, opts ...OverloadOpt) FunctionOpt {
|
|
return func(f *functionDecl) (*functionDecl, error) {
|
|
overload := &overloadDecl{
|
|
id: overloadID,
|
|
argTypes: args,
|
|
resultType: resultType,
|
|
memberFunction: memberFunction,
|
|
}
|
|
var err error
|
|
for _, opt := range opts {
|
|
overload, err = opt(overload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
err = f.addOverload(overload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return f, nil
|
|
}
|
|
}
|
|
|
|
func maybeWrapper(t *Type, pbType *exprpb.Type) *exprpb.Type {
|
|
if t.IsAssignableType(NullType) {
|
|
return decls.NewWrapperType(pbType)
|
|
}
|
|
return pbType
|
|
}
|
|
|
|
// TypeToExprType converts a CEL-native type representation to a protobuf CEL Type representation.
|
|
func TypeToExprType(t *Type) (*exprpb.Type, error) {
|
|
switch t.kind {
|
|
case AnyKind:
|
|
return decls.Any, nil
|
|
case BoolKind:
|
|
return maybeWrapper(t, decls.Bool), nil
|
|
case BytesKind:
|
|
return maybeWrapper(t, decls.Bytes), nil
|
|
case DoubleKind:
|
|
return maybeWrapper(t, decls.Double), nil
|
|
case DurationKind:
|
|
return decls.Duration, nil
|
|
case DynKind:
|
|
return decls.Dyn, nil
|
|
case IntKind:
|
|
return maybeWrapper(t, decls.Int), nil
|
|
case ListKind:
|
|
et, err := TypeToExprType(t.parameters[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return decls.NewListType(et), nil
|
|
case MapKind:
|
|
kt, err := TypeToExprType(t.parameters[0])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vt, err := TypeToExprType(t.parameters[1])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return decls.NewMapType(kt, vt), nil
|
|
case NullTypeKind:
|
|
return decls.Null, nil
|
|
case OpaqueKind:
|
|
params := make([]*exprpb.Type, len(t.parameters))
|
|
for i, p := range t.parameters {
|
|
pt, err := TypeToExprType(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
params[i] = pt
|
|
}
|
|
return decls.NewAbstractType(t.runtimeType.TypeName(), params...), nil
|
|
case StringKind:
|
|
return maybeWrapper(t, decls.String), nil
|
|
case StructKind:
|
|
switch t.runtimeType.TypeName() {
|
|
case "google.protobuf.Any":
|
|
return decls.Any, nil
|
|
case "google.protobuf.Duration":
|
|
return decls.Duration, nil
|
|
case "google.protobuf.Timestamp":
|
|
return decls.Timestamp, nil
|
|
case "google.protobuf.Value":
|
|
return decls.Dyn, nil
|
|
case "google.protobuf.ListValue":
|
|
return decls.NewListType(decls.Dyn), nil
|
|
case "google.protobuf.Struct":
|
|
return decls.NewMapType(decls.String, decls.Dyn), nil
|
|
case "google.protobuf.BoolValue":
|
|
return decls.NewWrapperType(decls.Bool), nil
|
|
case "google.protobuf.BytesValue":
|
|
return decls.NewWrapperType(decls.Bytes), nil
|
|
case "google.protobuf.DoubleValue", "google.protobuf.FloatValue":
|
|
return decls.NewWrapperType(decls.Double), nil
|
|
case "google.protobuf.Int32Value", "google.protobuf.Int64Value":
|
|
return decls.NewWrapperType(decls.Int), nil
|
|
case "google.protobuf.StringValue":
|
|
return decls.NewWrapperType(decls.String), nil
|
|
case "google.protobuf.UInt32Value", "google.protobuf.UInt64Value":
|
|
return decls.NewWrapperType(decls.Uint), nil
|
|
default:
|
|
return decls.NewObjectType(t.runtimeType.TypeName()), nil
|
|
}
|
|
case TimestampKind:
|
|
return decls.Timestamp, nil
|
|
case TypeParamKind:
|
|
return decls.NewTypeParamType(t.runtimeType.TypeName()), nil
|
|
case TypeKind:
|
|
return decls.NewTypeType(decls.Dyn), nil
|
|
case UintKind:
|
|
return maybeWrapper(t, decls.Uint), nil
|
|
}
|
|
return nil, fmt.Errorf("missing type conversion to proto: %v", t)
|
|
}
|
|
|
|
// ExprTypeToType converts a protobuf CEL type representation to a CEL-native type representation.
|
|
func ExprTypeToType(t *exprpb.Type) (*Type, error) {
|
|
switch t.GetTypeKind().(type) {
|
|
case *exprpb.Type_Dyn:
|
|
return DynType, nil
|
|
case *exprpb.Type_AbstractType_:
|
|
paramTypes := make([]*Type, len(t.GetAbstractType().GetParameterTypes()))
|
|
for i, p := range t.GetAbstractType().GetParameterTypes() {
|
|
pt, err := ExprTypeToType(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
paramTypes[i] = pt
|
|
}
|
|
return OpaqueType(t.GetAbstractType().GetName(), paramTypes...), nil
|
|
case *exprpb.Type_ListType_:
|
|
et, err := ExprTypeToType(t.GetListType().GetElemType())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ListType(et), nil
|
|
case *exprpb.Type_MapType_:
|
|
kt, err := ExprTypeToType(t.GetMapType().GetKeyType())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
vt, err := ExprTypeToType(t.GetMapType().GetValueType())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return MapType(kt, vt), nil
|
|
case *exprpb.Type_MessageType:
|
|
switch t.GetMessageType() {
|
|
case "google.protobuf.Any":
|
|
return AnyType, nil
|
|
case "google.protobuf.Duration":
|
|
return DurationType, nil
|
|
case "google.protobuf.Timestamp":
|
|
return TimestampType, nil
|
|
case "google.protobuf.Value":
|
|
return DynType, nil
|
|
case "google.protobuf.ListValue":
|
|
return ListType(DynType), nil
|
|
case "google.protobuf.Struct":
|
|
return MapType(StringType, DynType), nil
|
|
case "google.protobuf.BoolValue":
|
|
return NullableType(BoolType), nil
|
|
case "google.protobuf.BytesValue":
|
|
return NullableType(BytesType), nil
|
|
case "google.protobuf.DoubleValue", "google.protobuf.FloatValue":
|
|
return NullableType(DoubleType), nil
|
|
case "google.protobuf.Int32Value", "google.protobuf.Int64Value":
|
|
return NullableType(IntType), nil
|
|
case "google.protobuf.StringValue":
|
|
return NullableType(StringType), nil
|
|
case "google.protobuf.UInt32Value", "google.protobuf.UInt64Value":
|
|
return NullableType(UintType), nil
|
|
default:
|
|
return ObjectType(t.GetMessageType()), nil
|
|
}
|
|
case *exprpb.Type_Null:
|
|
return NullType, nil
|
|
case *exprpb.Type_Primitive:
|
|
switch t.GetPrimitive() {
|
|
case exprpb.Type_BOOL:
|
|
return BoolType, nil
|
|
case exprpb.Type_BYTES:
|
|
return BytesType, nil
|
|
case exprpb.Type_DOUBLE:
|
|
return DoubleType, nil
|
|
case exprpb.Type_INT64:
|
|
return IntType, nil
|
|
case exprpb.Type_STRING:
|
|
return StringType, nil
|
|
case exprpb.Type_UINT64:
|
|
return UintType, nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported primitive type: %v", t)
|
|
}
|
|
case *exprpb.Type_TypeParam:
|
|
return TypeParamType(t.GetTypeParam()), nil
|
|
case *exprpb.Type_Type:
|
|
return TypeType, nil
|
|
case *exprpb.Type_WellKnown:
|
|
switch t.GetWellKnown() {
|
|
case exprpb.Type_ANY:
|
|
return AnyType, nil
|
|
case exprpb.Type_DURATION:
|
|
return DurationType, nil
|
|
case exprpb.Type_TIMESTAMP:
|
|
return TimestampType, nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported well-known type: %v", t)
|
|
}
|
|
case *exprpb.Type_Wrapper:
|
|
t, err := ExprTypeToType(&exprpb.Type{TypeKind: &exprpb.Type_Primitive{Primitive: t.GetWrapper()}})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NullableType(t), nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported type: %v", t)
|
|
}
|
|
}
|
|
|
|
// ExprDeclToDeclaration converts a protobuf CEL declaration to a CEL-native declaration, either a Variable or Function.
|
|
func ExprDeclToDeclaration(d *exprpb.Decl) (EnvOption, error) {
|
|
switch d.GetDeclKind().(type) {
|
|
case *exprpb.Decl_Function:
|
|
overloads := d.GetFunction().GetOverloads()
|
|
opts := make([]FunctionOpt, len(overloads))
|
|
for i, o := range overloads {
|
|
args := make([]*Type, len(o.GetParams()))
|
|
for j, p := range o.GetParams() {
|
|
a, err := ExprTypeToType(p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
args[j] = a
|
|
}
|
|
res, err := ExprTypeToType(o.GetResultType())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
opts[i] = Overload(o.GetOverloadId(), args, res)
|
|
}
|
|
return Function(d.GetName(), opts...), nil
|
|
case *exprpb.Decl_Ident:
|
|
t, err := ExprTypeToType(d.GetIdent().GetType())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return Variable(d.GetName(), t), nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported decl: %v", d)
|
|
}
|
|
|
|
}
|
|
|
|
func functionDeclToExprDecl(f *functionDecl) (*exprpb.Decl, error) {
|
|
overloads := make([]*exprpb.Decl_FunctionDecl_Overload, len(f.overloads))
|
|
i := 0
|
|
for _, o := range f.overloads {
|
|
paramNames := map[string]struct{}{}
|
|
argTypes := make([]*exprpb.Type, len(o.argTypes))
|
|
for j, a := range o.argTypes {
|
|
collectParamNames(paramNames, a)
|
|
at, err := TypeToExprType(a)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
argTypes[j] = at
|
|
}
|
|
collectParamNames(paramNames, o.resultType)
|
|
resultType, err := TypeToExprType(o.resultType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(paramNames) == 0 {
|
|
if o.memberFunction {
|
|
overloads[i] = decls.NewInstanceOverload(o.id, argTypes, resultType)
|
|
} else {
|
|
overloads[i] = decls.NewOverload(o.id, argTypes, resultType)
|
|
}
|
|
} else {
|
|
params := []string{}
|
|
for pn := range paramNames {
|
|
params = append(params, pn)
|
|
}
|
|
if o.memberFunction {
|
|
overloads[i] = decls.NewParameterizedInstanceOverload(o.id, argTypes, resultType, params)
|
|
} else {
|
|
overloads[i] = decls.NewParameterizedOverload(o.id, argTypes, resultType, params)
|
|
}
|
|
}
|
|
i++
|
|
}
|
|
return decls.NewFunction(f.name, overloads...), nil
|
|
}
|
|
|
|
func collectParamNames(paramNames map[string]struct{}, arg *Type) {
|
|
if arg.kind == TypeParamKind {
|
|
paramNames[arg.runtimeType.TypeName()] = struct{}{}
|
|
}
|
|
for _, param := range arg.parameters {
|
|
collectParamNames(paramNames, param)
|
|
}
|
|
}
|