-
Notifications
You must be signed in to change notification settings - Fork 60
Description
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 aListType
whoseelement_type()
corresponds toint64
, allowing correct dispatch. - If an empty list (
[]
) is passed, the resultingcel::Value
's runtime type (value->GetRuntimeType()
) might return aListType
whoseelement_type()
isDynType
(corresponding toTypeKind::kDyn
), as suggested by the default construction ofListType
and 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_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 thecel::vm::ExecutionFrame
orActivationInterface
). - Dispatch Mechanism: Even if this type information were available at runtime, the
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. ModifyingFunctionStep
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 usingKind
)cel::FunctionDescriptor::ShapeMatches
(Comparison logic based onKind
)- Runtime evaluation (
cel::runtime::Program
,cel::vm::FunctionStep
,cel::Value::GetRuntimeType()
) and its handling of empty containers (potentially yieldingDynType
). - 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.