1
0
mirror of https://github.com/ceph/ceph-csi.git synced 2024-12-25 06:20:24 +00:00
ceph-csi/vendor/github.com/google/cel-go/cel/decls.go

1180 lines
37 KiB
Go
Raw Normal View History

// 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)
}
}