Skip to content

Conversation

xjasonli
Copy link

@xjasonli xjasonli commented Jun 5, 2025

Motivation

Currently, CEL-C++ only supports Type-level function overload resolution during the type-checking phase, while runtime function dispatch is limited to Kind-level resolution. This limitation prevents runtime selection of the most appropriate function overload when dealing with complex type hierarchies or when type information is available but not fully determined during static analysis.

As described in issue #1484, the FunctionRegistry cannot distinguish overloads differing only by container parameter types (e.g., list<int> vs list<string>) because the current implementation only compares cel::Kind rather than precise cel::Type information during function registration and dispatch.

Objective

Enable runtime function overload resolution based on precise Type information by propagating overload IDs from the type-checking phase to the runtime execution phase. This enhancement allows the runtime to make more informed decisions about which function overload to invoke, improving both correctness and performance in scenarios where multiple overloads are available.

Implementation

Core Changes

  1. Enhanced Function Interface

    • Extended Function::Invoke() method signature to accept an optional overload_id parameter (absl::Span<const std::string>) with default empty value
    • Updated all function adapter classes (Nullary, Unary, Binary, Ternary, Quaternary) to propagate overload ID information
    • Modified CelFunction implementation to support the new interface
  2. FlatExpr Builder Integration

    • Added reference_map_ field to FlatExprVisitor to access type-checking reference information during expression compilation
    • Implemented FindReference() helper method to retrieve overload IDs associated with specific expressions
    • Updated CreateFunctionStep() and CreateDirectFunctionStep() calls to pass overload ID information from the reference map
    • Added default parameter values to maintain backward compatibility
  3. Function Step Enhancement

    • Extended AbstractFunctionStep constructor to accept overload IDs with move semantics
    • Updated both eager (EagerFunctionStep) and lazy (LazyFunctionStep) function step implementations to store overload ID information
    • Modified direct execution steps (DirectFunctionStepImpl) to store and utilize overload ID information
    • Enhanced the Invoke() helper function to pass overload IDs to the underlying function implementation

Technical Details

  • Backward Compatibility: All function creation methods provide default empty overload ID parameters, ensuring existing code continues to work without modification

Benefits

  1. Enhanced Precision: Runtime can select optimal function overloads based on complete type information rather than just value kinds
  2. Better Performance: Reduced need for runtime type checks and fallback mechanisms when precise overload information is available
  3. Improved Extensibility: Framework for future enhancements requiring type-aware runtime behavior
  4. Maintained Compatibility: All existing functionality preserved while adding new capabilities
  5. Resolves Container Type Disambiguation: Enables proper handling of function overloads that differ only in container element types, addressing the "empty container" problem described in the issue

Testing

This change maintains full API and ABI compatibility through default parameter values. All existing tests should continue to pass without modification, and new tests can be added to verify type-aware overload resolution behavior.

Closes #1484

Copy link

google-cla bot commented Jun 5, 2025

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@xjasonli xjasonli force-pushed the runtime-function-with-overload-id branch from f020409 to 3540ea5 Compare July 9, 2025 09:26
@xjasonli xjasonli force-pushed the runtime-function-with-overload-id branch 3 times, most recently from a34ef8b to 2b41197 Compare July 22, 2025 08:11
@xjasonli xjasonli force-pushed the runtime-function-with-overload-id branch from 2b41197 to b761a2e Compare August 14, 2025 06:14
@xjasonli
Copy link
Author

Hi maintainers,

I wanted to follow up on my pull request #1530 regarding runtime function overload resolution enhancement. It's been about two months since submission, and I wanted to check on the current status.

Current Status

What This PR Does

Enables runtime function overload resolution based on precise Type information rather than just Kind-level resolution, resolving the limitation where FunctionRegistry cannot distinguish overloads differing only by container parameter types.

Potential Concern

This PR modifies an abstract class's virtual function definition by adding an optional overload_id parameter. While I've maintained backward compatibility through default parameters, this change could potentially impact external API users who inherit from these classes.

Questions

  1. Is the delay due to concerns about API compatibility changes requiring design discussion?
  2. Are there specific compatibility considerations I should address?
  3. Should I consider alternative approaches that don't modify the abstract interface?
  4. What additional steps do I need to take to move this forward?

I'm committed to finding the right approach and ensuring this enhancement meets the project's compatibility standards.

Thank you for your consideration.

Best regards

@jnthntatum
Copy link
Collaborator

Hi there, sorry for the delay in responding.

We won't be able to accept this change as is for a couple of reasons, but you are right that the way the C++ library handles function overload resolution is not ideal or consistent with the other runtime implementations.

Issues here:

  • While we recommend against it, directly overriding the Evaluate function is a supported way to introduce new extension functions, so this is a breaking change for client code.

  • We use the piper (google internal) mirror as source of truth so we are limited in what we can accept from Github. This is pretty cross cutting so we would have to do more manual work to actually sync this correctly.

  • Preferred approach would be to update the function resolution logic at plan time to filter the function overloads as much as possible ahead of time. This would require an update to the function registry to tag the function implementations with information about what overload ids they satisfy or specific types they expect. I expect this would change behavior of stored expressions in some cases so would need to be guarded by an interpreter option.

We'd be open to taking a contribution but we'd need to coordinate a bit up front to break the change down into syncable chunks. If you'd like to create an Issue we can coordinate there. We are pretty busy on internal work so I can't guarantee we'll be able to devote much time to this, but can at least keep the issue updated.

Tagging @jcking if he has any additional input.

Currently, CEL-C++ only supports Type-level function overload resolution
during the type-checking phase, while runtime function dispatch is limited
to Kind-level resolution. This limitation prevents runtime selection of
the most appropriate function overload when dealing with complex type
hierarchies or when type information is available but not fully determined
during static analysis.

As described in issue google#1484, the FunctionRegistry cannot distinguish overloads
differing only by container parameter types (e.g., `list<int>` vs `list<string>`)
because the current implementation only compares `cel::Kind` rather than precise
`cel::Type` information during function registration and dispatch.

Enable runtime function overload resolution based on precise Type information
by propagating overload IDs from the type-checking phase to the runtime
execution phase. This enhancement allows the runtime to make more informed
decisions about which function overload to invoke, improving both correctness
and performance in scenarios where multiple overloads are available.

1. **Enhanced Function Interface**
   - Extended `Function::Invoke()` method signature to accept an optional
     `overload_id` parameter (`absl::Span<const std::string>`) with default empty value
   - Updated all function adapter classes (Nullary, Unary, Binary, Ternary, Quaternary)
     to propagate overload ID information
   - Modified `CelFunction` implementation to support the new interface

2. **FlatExpr Builder Integration**
   - Added `reference_map_` field to `FlatExprVisitor` to access type-checking
     reference information during expression compilation
   - Implemented `FindReference()` helper method to retrieve overload IDs
     associated with specific expressions
   - Updated `CreateFunctionStep()` and `CreateDirectFunctionStep()` calls
     to pass overload ID information from the reference map
   - Added default parameter values to maintain backward compatibility

3. **Function Step Enhancement**
   - Extended `AbstractFunctionStep` constructor to accept overload IDs with move semantics
   - Updated both eager (`EagerFunctionStep`) and lazy (`LazyFunctionStep`)
     function step implementations to store overload ID information
   - Modified direct execution steps (`DirectFunctionStepImpl`) to store
     and utilize overload ID information
   - Enhanced the `Invoke()` helper function to pass overload IDs to the
     underlying function implementation

- **Backward Compatibility**: All function creation methods provide default
  empty overload ID parameters, ensuring existing code continues to work
  without modification

1. **Enhanced Precision**: Runtime can select optimal function overloads
   based on complete type information rather than just value kinds
2. **Better Performance**: Reduced need for runtime type checks and
   fallback mechanisms when precise overload information is available
3. **Improved Extensibility**: Framework for future enhancements requiring
   type-aware runtime behavior
4. **Maintained Compatibility**: All existing functionality preserved
   while adding new capabilities
5. **Resolves Container Type Disambiguation**: Enables proper handling of
   function overloads that differ only in container element types, addressing
   the "empty container" problem described in the issue

This change maintains full API and ABI compatibility through default parameter
values. All existing tests should continue to pass without modification, and
new tests can be added to verify type-aware overload resolution behavior.

Closes google#1484
@xjasonli xjasonli force-pushed the runtime-function-with-overload-id branch from b761a2e to 1f85b20 Compare September 12, 2025 09:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

FunctionRegistry cannot distinguish overloads differing only by container parameter types (e.g., list<int> vs list<string>)
2 participants