Area: Runtime, Function Overloading, Type System
Description:
Currently, it appears impossible to register function overloads into the cel::FunctionRegistry that differ only in the specific parameter types of container arguments (like list, map, message, or opaque types identified by cel::Kind::kList, cel::Kind::kMap, cel::Kind::kStruct, cel::Kind::kOpaque), while having the same function name, receiver style, arity, and argument cel::Kind. Support for types like optional might also be affected depending on their internal representation.
Example Scenario:
Consider defining two functions in the type checker (OverloadDecl) with signatures like:
my_func(list<int>)
my_func(list<string>)
Both are non-member functions with one argument.
When attempting to register corresponding implementations using FunctionRegistry::Register, the second registration fails.
Root Cause:
The FunctionRegistry::Register method checks for existing overloads using FunctionRegistry::DescriptorRegistered. This function, in turn, relies on cel::FunctionDescriptor::ShapeMatches to compare the new descriptor with existing ones.
ShapeMatches compares:
receiver_style()
- Argument count (
types().size())
- Argument
cel::Kind for each parameter (e.g., kList, kMap, kOpaque).
In the example above, both list<int> and list<string> have the cel::Kind::kList. Therefore, ShapeMatches considers them identical in shape, DescriptorRegistered returns true for the second registration attempt, and Register returns absl::StatusCode::kAlreadyExists.
This limitation prevents users from defining and registering runtime function overloads that leverage the more precise type information available in cel::Type (used during type checking) for container types.
Impact:
Users cannot implement fine-grained function overloading based on specific container element/key/value types at the runtime registration level using the standard FunctionRegistry.
The "Empty Container" Problem:
A potential workaround might involve registering a single "dispatcher" function implementation (overload_x) for the generic Kind (e.g., my_func(list) where the descriptor uses Kind::kList). Inside this function, one could inspect the runtime cel::Value's actual type using value->GetRuntimeType() to dispatch to the correct underlying implementation (e.g., the one for list<int> or list<string>).
However, this approach faces a significant challenge with empty containers:
- If a non-empty
list<int> is passed, value->GetRuntimeType() would likely return a ListType whose element_type() corresponds to int64, allowing correct dispatch.
- If an empty list (
[]) is passed, the resulting cel::Value's runtime type (value->GetRuntimeType()) might return a ListType whose element_type() is DynType (corresponding to TypeKind::kDyn), as suggested by the default construction of ListType and the existence of DynType. In this case, the dispatcher function lacks the necessary information to determine whether the caller intended this empty list to be notionally a list<int> or a list<string>, making correct dispatch impossible based solely on the runtime value's potentially dynamic type information. A similar issue arises for empty maps where key/value types might resolve to DynType.
Potential Solution Idea & Challenges (As discussed):
One theoretical approach to resolve the empty container ambiguity could involve utilizing the type information generated by the type checker, which is often stored in maps like reference_map or type_map within the AstImpl. This map contains the precise cel::Type inferred for expressions, including empty literal containers.
However, pursuing this faces hurdles:
- Information Availability: The current
cel::runtime::Program evaluation interface does not seem to provide a standard mechanism to pass this detailed AST type map information (type_map/reference_map) from the type checking phase into the runtime execution environment (e.g., to the cel::vm::ExecutionFrame or ActivationInterface).
- Dispatch Mechanism: Even if this type information were available at runtime, the
FunctionRegistry's current lookup and the cel::vm::FunctionStep's execution logic are primarily driven by the FunctionDescriptor (based on cel::Kind). The registry itself, having accepted only one entry based on Kind, has lost the direct link between the specific overload_id (from the type checking phase) and the runtime function implementation. Modifying FunctionStep to use the external AST type map for dispatch based on overload ID would require significant changes to the evaluation core and the function call mechanism.
Affected Components:
cel::FunctionRegistry (Registration logic using Kind)
cel::FunctionDescriptor::ShapeMatches (Comparison logic based on Kind)
- Runtime evaluation (
cel::runtime::Program, cel::vm::FunctionStep, cel::Value::GetRuntimeType()) and its handling of empty containers (potentially yielding DynType).
- Interaction between Type Checking (
cel::Type, OverloadDecl) and Runtime (cel::Kind, cel::Value).
Request:
We request consideration of this limitation. Ideally, the runtime function registration and dispatch mechanism should allow for distinguishing overloads based on the precise cel::Type of container parameters, possibly by enhancing FunctionDescriptor or the registration/lookup process, and addressing the challenge of dispatching correctly even for empty containers potentially represented with DynType at runtime.
Area: Runtime, Function Overloading, Type System
Description:
Currently, it appears impossible to register function overloads into the
cel::FunctionRegistrythat differ only in the specific parameter types of container arguments (likelist,map,message, oropaquetypes identified bycel::Kind::kList,cel::Kind::kMap,cel::Kind::kStruct,cel::Kind::kOpaque), while having the same function name, receiver style, arity, and argumentcel::Kind. Support for types likeoptionalmight also be affected depending on their internal representation.Example Scenario:
Consider defining two functions in the type checker (
OverloadDecl) with signatures like:my_func(list<int>)my_func(list<string>)Both are non-member functions with one argument.
When attempting to register corresponding implementations using
FunctionRegistry::Register, the second registration fails.Root Cause:
The
FunctionRegistry::Registermethod checks for existing overloads usingFunctionRegistry::DescriptorRegistered. This function, in turn, relies oncel::FunctionDescriptor::ShapeMatchesto compare the new descriptor with existing ones.ShapeMatchescompares:receiver_style()types().size())cel::Kindfor each parameter (e.g.,kList,kMap,kOpaque).In the example above, both
list<int>andlist<string>have thecel::Kind::kList. Therefore,ShapeMatchesconsiders them identical in shape,DescriptorRegisteredreturnstruefor the second registration attempt, andRegisterreturnsabsl::StatusCode::kAlreadyExists.This limitation prevents users from defining and registering runtime function overloads that leverage the more precise type information available in
cel::Type(used during type checking) for container types.Impact:
Users cannot implement fine-grained function overloading based on specific container element/key/value types at the runtime registration level using the standard
FunctionRegistry.The "Empty Container" Problem:
A potential workaround might involve registering a single "dispatcher" function implementation (
overload_x) for the genericKind(e.g.,my_func(list)where the descriptor usesKind::kList). Inside this function, one could inspect the runtimecel::Value's actual type usingvalue->GetRuntimeType()to dispatch to the correct underlying implementation (e.g., the one forlist<int>orlist<string>).However, this approach faces a significant challenge with empty containers:
list<int>is passed,value->GetRuntimeType()would likely return aListTypewhoseelement_type()corresponds toint64, allowing correct dispatch.[]) is passed, the resultingcel::Value's runtime type (value->GetRuntimeType()) might return aListTypewhoseelement_type()isDynType(corresponding toTypeKind::kDyn), as suggested by the default construction ofListTypeand the existence ofDynType. In this case, the dispatcher function lacks the necessary information to determine whether the caller intended this empty list to be notionally alist<int>or alist<string>, making correct dispatch impossible based solely on the runtime value's potentially dynamic type information. A similar issue arises for empty maps where key/value types might resolve toDynType.Potential Solution Idea & Challenges (As discussed):
One theoretical approach to resolve the empty container ambiguity could involve utilizing the type information generated by the type checker, which is often stored in maps like
reference_maportype_mapwithin theAstImpl. This map contains the precisecel::Typeinferred for expressions, including empty literal containers.However, pursuing this faces hurdles:
cel::runtime::Programevaluation interface does not seem to provide a standard mechanism to pass this detailed AST type map information (type_map/reference_map) from the type checking phase into the runtime execution environment (e.g., to thecel::vm::ExecutionFrameorActivationInterface).FunctionRegistry's current lookup and thecel::vm::FunctionStep's execution logic are primarily driven by theFunctionDescriptor(based oncel::Kind). The registry itself, having accepted only one entry based onKind, has lost the direct link between the specificoverload_id(from the type checking phase) and the runtime function implementation. ModifyingFunctionStepto use the external AST type map for dispatch based on overload ID would require significant changes to the evaluation core and the function call mechanism.Affected Components:
cel::FunctionRegistry(Registration logic usingKind)cel::FunctionDescriptor::ShapeMatches(Comparison logic based onKind)cel::runtime::Program,cel::vm::FunctionStep,cel::Value::GetRuntimeType()) and its handling of empty containers (potentially yieldingDynType).cel::Type,OverloadDecl) and Runtime (cel::Kind,cel::Value).Request:
We request consideration of this limitation. Ideally, the runtime function registration and dispatch mechanism should allow for distinguishing overloads based on the precise
cel::Typeof container parameters, possibly by enhancingFunctionDescriptoror the registration/lookup process, and addressing the challenge of dispatching correctly even for empty containers potentially represented withDynTypeat runtime.