ceph-csi/vendor/github.com/google/cel-go/common/decls/decls.go

845 lines
28 KiB
Go
Raw Normal View History

// Copyright 2023 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 decls contains function and variable declaration structs and helper methods.
package decls
import (
"fmt"
"strings"
chkdecls "github.com/google/cel-go/checker/decls"
"github.com/google/cel-go/common/functions"
"github.com/google/cel-go/common/types"
"github.com/google/cel-go/common/types/ref"
exprpb "google.golang.org/genproto/googleapis/api/expr/v1alpha1"
)
// NewFunction creates a new function declaration with a set of function options to configure overloads
// and function definitions (implementations).
//
// Functions are checked for name collisions and singleton redefinition.
func NewFunction(name string, opts ...FunctionOpt) (*FunctionDecl, error) {
fn := &FunctionDecl{
name: name,
overloads: map[string]*OverloadDecl{},
overloadOrdinals: []string{},
}
var err error
for _, opt := range opts {
fn, err = opt(fn)
if err != nil {
return nil, err
}
}
if len(fn.overloads) == 0 {
return nil, fmt.Errorf("function %s must have at least one overload", name)
}
return fn, nil
}
// FunctionDecl defines a function name, overload set, and optionally a singleton definition for all
// overload instances.
type FunctionDecl struct {
name string
// overloads associated with the function name.
overloads map[string]*OverloadDecl
// singleton implementation of the function for all overloads.
//
// If this option is set, an error will occur if any overloads specify a per-overload implementation
// or if another function with the same name attempts to redefine the singleton.
singleton *functions.Overload
// disableTypeGuards is a performance optimization to disable detailed runtime type checks which could
// add overhead on common operations. Setting this option true leaves error checks and argument checks
// intact.
disableTypeGuards bool
// state indicates that the binding should be provided as a declaration, as a runtime binding, or both.
state declarationState
// overloadOrdinals indicates the order in which the overload was declared.
overloadOrdinals []string
}
type declarationState int
const (
declarationStateUnset declarationState = iota
declarationDisabled
declarationEnabled
)
// Name returns the function name in human-readable terms, e.g. 'contains' of 'math.least'
func (f *FunctionDecl) Name() string {
if f == nil {
return ""
}
return f.name
}
// IsDeclarationDisabled indicates that the function implementation should be added to the dispatcher, but the
// declaration should not be exposed for use in expressions.
func (f *FunctionDecl) IsDeclarationDisabled() bool {
return f.state == declarationDisabled
}
// Merge combines an existing 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 == other {
return f, nil
}
if f.Name() != other.Name() {
return nil, fmt.Errorf("cannot merge unrelated functions. %s and %s", f.Name(), other.Name())
}
merged := &FunctionDecl{
name: f.Name(),
overloads: make(map[string]*OverloadDecl, len(f.overloads)),
singleton: f.singleton,
overloadOrdinals: make([]string, len(f.overloads)),
// if one function is expecting type-guards and the other is not, then they
// must not be disabled.
disableTypeGuards: f.disableTypeGuards && other.disableTypeGuards,
// default to the current functions declaration state.
state: f.state,
}
// If the other state indicates that the declaration should be explicitly enabled or
// disabled, then update the merged state with the most recent value.
if other.state != declarationStateUnset {
merged.state = other.state
}
// baseline copy of the overloads and their ordinals
copy(merged.overloadOrdinals, f.overloadOrdinals)
for oID, o := range f.overloads {
merged.overloads[oID] = o
}
// overloads and their ordinals are added from the left
for _, oID := range other.overloadOrdinals {
o := other.overloads[oID]
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 && merged.singleton != other.singleton {
return nil, fmt.Errorf("function already has a singleton 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 {
if f == nil {
return fmt.Errorf("nil function cannot add overload: %s", overload.ID())
}
for oID, o := range f.overloads {
if oID != overload.ID() && o.SignatureOverlaps(overload) {
return fmt.Errorf("overload signature collision in function %s: %s collides with %s", f.Name(), oID, overload.ID())
}
if oID == overload.ID() {
if o.SignatureEquals(overload) && o.IsNonStrict() == overload.IsNonStrict() {
// Allow redefinition of an overload implementation so long as the signatures match.
f.overloads[oID] = overload
return nil
}
return fmt.Errorf("overload redefinition in function. %s: %s has multiple definitions", f.Name(), oID)
}
}
f.overloadOrdinals = append(f.overloadOrdinals, overload.ID())
f.overloads[overload.ID()] = overload
return nil
}
// OverloadDecls returns the overload declarations in the order in which they were declared.
func (f *FunctionDecl) OverloadDecls() []*OverloadDecl {
if f == nil {
return []*OverloadDecl{}
}
overloads := make([]*OverloadDecl, 0, len(f.overloads))
for _, oID := range f.overloadOrdinals {
overloads = append(overloads, f.overloads[oID])
}
return overloads
}
// Bindings produces a set of function bindings, if any are defined.
func (f *FunctionDecl) Bindings() ([]*functions.Overload, error) {
if f == nil {
return []*functions.Overload{}, nil
}
overloads := []*functions.Overload{}
nonStrict := false
for _, oID := range f.overloadOrdinals {
o := f.overloads[oID]
if o.hasBinding() {
overload := &functions.Overload{
Operator: o.ID(),
Unary: o.guardedUnaryOp(f.Name(), f.disableTypeGuards),
Binary: o.guardedBinaryOp(f.Name(), f.disableTypeGuards),
Function: o.guardedFunctionOp(f.Name(), f.disableTypeGuards),
OperandTrait: o.OperandTrait(),
NonStrict: o.IsNonStrict(),
}
overloads = append(overloads, overload)
nonStrict = nonStrict || o.IsNonStrict()
}
}
if f.singleton != nil {
if len(overloads) != 0 {
return nil, fmt.Errorf("singleton function incompatible with specialized overloads: %s", f.Name())
}
overloads = []*functions.Overload{
{
Operator: f.Name(),
Unary: f.singleton.Unary,
Binary: f.singleton.Binary,
Function: f.singleton.Function,
OperandTrait: f.singleton.OperandTrait,
},
}
// fall-through to return single overload case.
}
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 _, oID := range f.overloadOrdinals {
o := f.overloads[oID]
// During dynamic dispatch over multiple functions, signature agreement checks
// are preserved in order to assist with the function resolution step.
switch len(args) {
case 1:
if o.unaryOp != nil && o.matchesRuntimeSignature( /* disableTypeGuards=*/ false, args...) {
return o.unaryOp(args[0])
}
case 2:
if o.binaryOp != nil && o.matchesRuntimeSignature( /* disableTypeGuards=*/ false, args...) {
return o.binaryOp(args[0], args[1])
}
}
if o.functionOp != nil && o.matchesRuntimeSignature( /* disableTypeGuards=*/ false, args...) {
return o.functionOp(args...)
}
// eventually this will fall through to the noSuchOverload below.
}
return MaybeNoSuchOverload(f.Name(), args...)
}
function := &functions.Overload{
Operator: f.Name(),
Function: funcDispatch,
NonStrict: nonStrict,
}
return append(bindings, function), nil
}
// MaybeNoSuchOverload determines whether to propagate an error if one is provided as an argument, or
// to return an unknown set, or to produce a new error for a missing function signature.
func MaybeNoSuchOverload(funcName string, args ...ref.Val) ref.Val {
argTypes := make([]string, len(args))
var unk *types.Unknown = nil
for i, arg := range args {
if types.IsError(arg) {
return arg
}
if types.IsUnknown(arg) {
unk = types.MergeUnknowns(arg.(*types.Unknown), unk)
}
argTypes[i] = arg.Type().TypeName()
}
if unk != nil {
return unk
}
signature := strings.Join(argTypes, ", ")
return types.NewErr("no such overload: %s(%s)", funcName, signature)
}
// FunctionOpt defines a functional option for mutating a function declaration.
type FunctionOpt func(*FunctionDecl) (*FunctionDecl, error)
// DisableTypeGuards disables automatically generated function invocation guards on direct overload calls.
// Type guards remain on during dynamic dispatch for parsed-only expressions.
func DisableTypeGuards(value bool) FunctionOpt {
return func(fn *FunctionDecl) (*FunctionDecl, error) {
fn.disableTypeGuards = value
return fn, nil
}
}
// DisableDeclaration indicates that the function declaration should be disabled, but the runtime function
// binding should be provided. Marking a function as runtime-only is a safe way to manage deprecations
// of function declarations while still preserving the runtime behavior for previously compiled expressions.
func DisableDeclaration(value bool) FunctionOpt {
return func(fn *FunctionDecl) (*FunctionDecl, error) {
if value {
fn.state = declarationDisabled
} else {
fn.state = declarationEnabled
}
return fn, nil
}
}
// SingletonUnaryBinding creates a singleton function definition 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
}
}
// SingletonBinaryBinding 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 SingletonBinaryBinding(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
}
}
// SingletonFunctionBinding 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 SingletonFunctionBinding(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 []*types.Type, resultType *types.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 []*types.Type, resultType *types.Type,
opts ...OverloadOpt) FunctionOpt {
return newOverload(overloadID, true, args, resultType, opts...)
}
func newOverload(overloadID string,
memberFunction bool, args []*types.Type, resultType *types.Type,
opts ...OverloadOpt) FunctionOpt {
return func(f *FunctionDecl) (*FunctionDecl, error) {
overload, err := newOverloadInternal(overloadID, memberFunction, args, resultType, opts...)
if err != nil {
return nil, err
}
err = f.AddOverload(overload)
if err != nil {
return nil, err
}
return f, nil
}
}
func newOverloadInternal(overloadID string,
memberFunction bool, args []*types.Type, resultType *types.Type,
opts ...OverloadOpt) (*OverloadDecl, error) {
overload := &OverloadDecl{
id: overloadID,
argTypes: args,
resultType: resultType,
isMemberFunction: memberFunction,
}
var err error
for _, opt := range opts {
overload, err = opt(overload)
if err != nil {
return nil, err
}
}
return overload, nil
}
// OverloadDecl contains the definition of a single overload id with a specific signature, and an optional
// implementation.
type OverloadDecl struct {
id string
argTypes []*types.Type
resultType *types.Type
isMemberFunction bool
// nonStrict indicates that the function will accept error and unknown arguments as inputs.
nonStrict bool
// operandTrait indicates whether the member argument should have a specific type-trait.
//
// This is useful for creating overloads which operate on a type-interface rather than a concrete type.
operandTrait int
// Function implementation options. Optional, but encouraged.
// unaryOp is a function binding that takes a single argument.
unaryOp functions.UnaryOp
// binaryOp is a function binding that takes two arguments.
binaryOp functions.BinaryOp
// functionOp is a catch-all for zero-arity and three-plus arity functions.
functionOp functions.FunctionOp
}
// ID mirrors the overload signature and provides a unique id which may be referenced within the type-checker
// and interpreter to optimize performance.
//
// The ID format is usually one of two styles:
// global: <functionName>_<argType>_<argTypeN>
// member: <memberType>_<functionName>_<argType>_<argTypeN>
func (o *OverloadDecl) ID() string {
if o == nil {
return ""
}
return o.id
}
// ArgTypes contains the set of argument types expected by the overload.
//
// For member functions ArgTypes[0] represents the member operand type.
func (o *OverloadDecl) ArgTypes() []*types.Type {
if o == nil {
return emptyArgs
}
return o.argTypes
}
// IsMemberFunction indicates whether the overload is a member function
func (o *OverloadDecl) IsMemberFunction() bool {
if o == nil {
return false
}
return o.isMemberFunction
}
// IsNonStrict returns whether the overload accepts errors and unknown values as arguments.
func (o *OverloadDecl) IsNonStrict() bool {
if o == nil {
return false
}
return o.nonStrict
}
// OperandTrait returns the trait mask of the first operand to the overload call, e.g.
// `traits.Indexer`
func (o *OverloadDecl) OperandTrait() int {
if o == nil {
return 0
}
return o.operandTrait
}
// ResultType indicates the output type from calling the function.
func (o *OverloadDecl) ResultType() *types.Type {
if o == nil {
// *types.Type is nil-safe
return nil
}
return o.resultType
}
// TypeParams returns the type parameter names associated with the overload.
func (o *OverloadDecl) TypeParams() []string {
typeParams := map[string]struct{}{}
collectParamNames(typeParams, o.ResultType())
for _, arg := range o.ArgTypes() {
collectParamNames(typeParams, arg)
}
params := make([]string, 0, len(typeParams))
for param := range typeParams {
params = append(params, param)
}
return params
}
// SignatureEquals determines whether the incoming overload declaration signature is equal to the current signature.
//
// Result type, operand trait, and strict-ness are not considered as part of signature equality.
func (o *OverloadDecl) SignatureEquals(other *OverloadDecl) bool {
if o == other {
return true
}
if o.ID() != other.ID() || o.IsMemberFunction() != other.IsMemberFunction() || len(o.ArgTypes()) != len(other.ArgTypes()) {
return false
}
for i, at := range o.ArgTypes() {
oat := other.ArgTypes()[i]
if !at.IsEquivalentType(oat) {
return false
}
}
return o.ResultType().IsEquivalentType(other.ResultType())
}
// SignatureOverlaps indicates whether two functions have non-equal, but overloapping function signatures.
//
// For example, list(dyn) collides with list(string) since the 'dyn' type can contain a 'string' type.
func (o *OverloadDecl) SignatureOverlaps(other *OverloadDecl) bool {
if o.IsMemberFunction() != other.IsMemberFunction() || 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
}
// hasBinding indicates whether the overload already has a definition.
func (o *OverloadDecl) hasBinding() bool {
return o != nil && (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, disableTypeGuards bool) functions.UnaryOp {
if o.unaryOp == nil {
return nil
}
return func(arg ref.Val) ref.Val {
if !o.matchesRuntimeUnarySignature(disableTypeGuards, arg) {
return MaybeNoSuchOverload(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, disableTypeGuards bool) functions.BinaryOp {
if o.binaryOp == nil {
return nil
}
return func(arg1, arg2 ref.Val) ref.Val {
if !o.matchesRuntimeBinarySignature(disableTypeGuards, arg1, arg2) {
return MaybeNoSuchOverload(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, disableTypeGuards bool) functions.FunctionOp {
if o.functionOp == nil {
return nil
}
return func(args ...ref.Val) ref.Val {
if !o.matchesRuntimeSignature(disableTypeGuards, args...) {
return MaybeNoSuchOverload(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(disableTypeGuards bool, arg ref.Val) bool {
return matchRuntimeArgType(o.IsNonStrict(), disableTypeGuards, o.ArgTypes()[0], arg) &&
matchOperandTrait(o.OperandTrait(), arg)
}
// matchesRuntimeBinarySignature indicates whether the argument types are runtime assiganble to the overload's expected arguments.
func (o *OverloadDecl) matchesRuntimeBinarySignature(disableTypeGuards bool, arg1, arg2 ref.Val) bool {
return matchRuntimeArgType(o.IsNonStrict(), disableTypeGuards, o.ArgTypes()[0], arg1) &&
matchRuntimeArgType(o.IsNonStrict(), disableTypeGuards, o.ArgTypes()[1], arg2) &&
matchOperandTrait(o.OperandTrait(), arg1)
}
// matchesRuntimeSignature indicates whether the argument types are runtime assiganble to the overload's expected arguments.
func (o *OverloadDecl) matchesRuntimeSignature(disableTypeGuards bool, args ...ref.Val) bool {
if len(args) != len(o.ArgTypes()) {
return false
}
if len(args) == 0 {
return true
}
for i, arg := range args {
if !matchRuntimeArgType(o.IsNonStrict(), disableTypeGuards, o.ArgTypes()[i], arg) {
return false
}
}
return matchOperandTrait(o.OperandTrait(), args[0])
}
func matchRuntimeArgType(nonStrict, disableTypeGuards bool, argType *types.Type, arg ref.Val) bool {
if nonStrict && (disableTypeGuards || types.IsUnknownOrError(arg)) {
return true
}
if types.IsUnknownOrError(arg) {
return false
}
return disableTypeGuards || argType.IsAssignableRuntimeType(arg)
}
func matchOperandTrait(trait int, arg ref.Val) bool {
return trait == 0 || arg.Type().HasTrait(trait) || types.IsUnknownOrError(arg)
}
// 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
}
}
// NewConstant creates a new constant declaration.
func NewConstant(name string, t *types.Type, v ref.Val) *VariableDecl {
return &VariableDecl{name: name, varType: t, value: v}
}
// NewVariable creates a new variable declaration.
func NewVariable(name string, t *types.Type) *VariableDecl {
return &VariableDecl{name: name, varType: t}
}
// VariableDecl defines a variable declaration which may optionally have a constant value.
type VariableDecl struct {
name string
varType *types.Type
value ref.Val
}
// Name returns the fully-qualified variable name
func (v *VariableDecl) Name() string {
if v == nil {
return ""
}
return v.name
}
// Type returns the types.Type value associated with the variable.
func (v *VariableDecl) Type() *types.Type {
if v == nil {
// types.Type is nil-safe
return nil
}
return v.varType
}
// Value returns the constant value associated with the declaration.
func (v *VariableDecl) Value() ref.Val {
if v == nil {
return nil
}
return v.value
}
// DeclarationIsEquivalent returns true if one variable declaration has the same name and same type as the input.
func (v *VariableDecl) DeclarationIsEquivalent(other *VariableDecl) bool {
if v == other {
return true
}
return v.Name() == other.Name() && v.Type().IsEquivalentType(other.Type())
}
// VariableDeclToExprDecl converts a go-native variable declaration into a protobuf-type variable declaration.
func VariableDeclToExprDecl(v *VariableDecl) (*exprpb.Decl, error) {
varType, err := types.TypeToExprType(v.Type())
if err != nil {
return nil, err
}
return chkdecls.NewVar(v.Name(), varType), nil
}
// TypeVariable creates a new type identifier for use within a types.Provider
func TypeVariable(t *types.Type) *VariableDecl {
return NewVariable(t.TypeName(), types.NewTypeTypeWithParam(t))
}
// FunctionDeclToExprDecl converts a go-native function declaration into a protobuf-typed function declaration.
func FunctionDeclToExprDecl(f *FunctionDecl) (*exprpb.Decl, error) {
overloads := make([]*exprpb.Decl_FunctionDecl_Overload, len(f.overloads))
for i, oID := range f.overloadOrdinals {
o := f.overloads[oID]
paramNames := map[string]struct{}{}
argTypes := make([]*exprpb.Type, len(o.ArgTypes()))
for j, a := range o.ArgTypes() {
collectParamNames(paramNames, a)
at, err := types.TypeToExprType(a)
if err != nil {
return nil, err
}
argTypes[j] = at
}
collectParamNames(paramNames, o.ResultType())
resultType, err := types.TypeToExprType(o.ResultType())
if err != nil {
return nil, err
}
if len(paramNames) == 0 {
if o.IsMemberFunction() {
overloads[i] = chkdecls.NewInstanceOverload(oID, argTypes, resultType)
} else {
overloads[i] = chkdecls.NewOverload(oID, argTypes, resultType)
}
} else {
params := []string{}
for pn := range paramNames {
params = append(params, pn)
}
if o.IsMemberFunction() {
overloads[i] = chkdecls.NewParameterizedInstanceOverload(oID, argTypes, resultType, params)
} else {
overloads[i] = chkdecls.NewParameterizedOverload(oID, argTypes, resultType, params)
}
}
}
return chkdecls.NewFunction(f.Name(), overloads...), nil
}
func collectParamNames(paramNames map[string]struct{}, arg *types.Type) {
if arg.Kind() == types.TypeParamKind {
paramNames[arg.TypeName()] = struct{}{}
}
for _, param := range arg.Parameters() {
collectParamNames(paramNames, param)
}
}
var (
emptyArgs = []*types.Type{}
)