Skip to content

Use overload_id for checked function overload resolution#2115

Open
xjasonli wants to merge 2 commits into
cel-expr:masterfrom
xjasonli:feat/runtime-function-overload-resolution
Open

Use overload_id for checked function overload resolution#2115
xjasonli wants to merge 2 commits into
cel-expr:masterfrom
xjasonli:feat/runtime-function-overload-resolution

Conversation

@xjasonli

Copy link
Copy Markdown

Fixes #1484.

This is a follow-up to #1530, rewritten around the approach suggested in that discussion.

The previous version tried to pass overload information through the runtime function invocation path. That made the overridable function interface harder to keep compatible. This version takes a different route: FlatExprBuilder uses the checker’s overload_id information to narrow the candidate set ahead of time, and the runtime registry records which overload ID each implementation handles.

This PR is a bit large because the registry, planner, runtime dispatch, standard function registrations, and tests need to move together to keep the tree green. If this is too large to review or sync comfortably, I am happy to discuss how to split it into smaller reviewable pieces.

What changed

This PR adds overload_id-aware function overload resolution for checked expressions:

  • FunctionDescriptor can carry an overload_id and full cel::Type information.
  • FunctionRegistry can look up static and lazy overloads by overload ID.
  • ActivationInterface can look up activation-provided overloads by overload ID.
  • FlatExprBuilder reads overload IDs from the checked expression reference_map and prefers the overloads selected by the checker.
  • Runtime matching still validates actual argument values and keeps fallback candidates for dyn/runtime mismatch cases.

This PR also annotates standard library, optional types, and extension runtime function registrations with the overload IDs declared by the checker.

Why

Today, checked expressions can contain more precise overload information than the runtime normally uses during dispatch. For example, the checker may distinguish overloads such as list<int> and list<string>, while the default runtime matching path only sees both arguments as cel::Kind::kList.

This change uses a two-part approach:

  • At plan time, checked-expression overload_id data narrows the candidate set to the overloads selected by the checker.
  • At runtime, enable_type_level_overload can enable type-level verification for cases where precise container element/key/value types matter.

The overload_id path also helps with empty containers, where runtime values may not carry enough element/key/value type information to disambiguate overloads on their own.

Compatibility

The main compatibility note is FunctionDescriptor:

  • FunctionDescriptor::types() now returns const std::vector<cel::Type>&.
  • FunctionDescriptor::kinds() returns the previous kind-level view: const std::vector<cel::Kind>&.

Apart from that API adjustment, the runtime behavior is intended to stay compatible by default:

  • overload_id-based candidate filtering is used for checked expressions when reference_map overload information is available.
  • Kind-level runtime matching remains the default because RuntimeOptions::enable_type_level_overload defaults to false.
  • Type-level runtime matching is only enabled when RuntimeOptions::enable_type_level_overload is set to true.
  • Functions without overload IDs continue to use the existing arity/kind fallback matching path.

xjasonli added 2 commits June 30, 2026 15:27
…nctions

Align cel-cpp function resolution with cel-go and cel-java by introducing:

1. Plan-time overload filtering using overload_id from checked expressions
2. Runtime type-level argument matching for container elements and TypeParam,
   guarded by enable_type_level_overload
3. Parse-only and legacy fallback paths using arity/kind-based matching

Key changes:
- Add overload_id to FunctionDescriptor with Type-based constructors
- Add FindStaticOverloadById/FindLazyOverloadById to FunctionRegistry
- Add FindFunctionOverloadById(name, overload_id) to ActivationInterface
- Add enable_type_level_overload flag to RuntimeOptions
- Prefer checked overload_id candidates while still validating runtime arguments

Compatibility:
- Kind-level matching remains the default runtime behavior
- Functions without overload_id fall back to arity-based matching
Annotate standard library, optional type, and extension function registrations
with their corresponding overload_id constants. This enables plan-time overload
resolution via overload_id lookup from the checker reference_map, replacing
arity-only candidate selection for checked expressions where overload_id data is
available.

- runtime/standard/: standard functions pass StandardOverloadIds constants
  through RegisterHelper and FunctionAdapter interfaces.
- runtime/optional_types.cc: optional functions use overload_id constants
  derived from checker/optional.cc declarations.
- extensions/: extension registrations for strings, math, sets, lists, regex,
  encoders, formatting, and comprehensions_v2 are annotated with their
  checker-declared overload_id constants.
- common/standard_definitions.h: add missing kBoolToInt constant.
- checker/standard_library.cc: add missing bool-to-int overload decl.
- checker/optional.cc: add unwrap/unwrapOpt declarations.

Functions where the checker declaration count does not match the runtime
registration count (e.g. container membership operators, list.sort)
intentionally omit overload_id to preserve runtime dynamic matching.
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>)

1 participant