Simplify functions and distinguish methods#244
Conversation
srikrsna-buf
left a comment
There was a problem hiding this comment.
Overall I am quite happy with the simplification of removing overloads and fixing the method vs function problem. Left some comments, still reviewing the rest.
| narrowedByName(name: string): FuncRegistry; | ||
| narrowedByArgs(...args: [CelResult[]] | [CelResult, CelResult[]]): FuncRegistry; | ||
| // TODO: narrowedByParams(...args: [CelType[]] | [CelType, CelType[]]): FuncRegistry; |
There was a problem hiding this comment.
This means we will create a new FuncRegistry for each lookup. Maybe we can avoid the allocation if we club the args and name and just return a Callable | undefined?
There was a problem hiding this comment.
I simplified function registry, renamed it back to a dispatcher (to avoid the proto registry confusion), and added a lookup cache.
| find(name: string) { | ||
| return this.functions.get(name); | ||
| } | ||
| withFallback(registry: FuncRegistry): FuncRegistry; |
There was a problem hiding this comment.
User's will also see this I think we can expose a simpler interface.
There was a problem hiding this comment.
I adjusted this to withFallbacks(Callable[]) — let me know what you think. The idea is to make the ordering explicit.
| celFunc(olc.SIZE, [MAP], INT, (x) => BigInt(x.size)), | ||
|
|
||
| celFunc(opc.IN, [DYN, LIST], BOOL, inList), | ||
| celFunc(opc.IN, [DYN, MAP], BOOL, (n, h) => h.has(n as CelMapIndex)), |
|
|
||
| causes(value: unknown, exprId?: bigint | number): CelError; |
There was a problem hiding this comment.
I think we can use a function instead for this that is internal like the deleted celErrorMerge which is not exported from the package.
| } | ||
| } | ||
|
|
||
| function isOfType<T extends CelType>( |
There was a problem hiding this comment.
Maybe we can move this to type.ts?
| export interface Dispatcher { | ||
| find(name: string): CallDispatch | undefined; | ||
| } | ||
| export interface Callable<R extends CelType = CelType> { |
There was a problem hiding this comment.
Because the interface is public via celFunc and celMethod I think it will be useful to add the doc comments back
| export interface Dispatcher { | ||
| find(name: string): CallDispatch | undefined; | ||
| } | ||
| export interface Callable<R extends CelType = CelType> { |
There was a problem hiding this comment.
I am not sure making the result generic is useful. We can simplify if we use CelType directly.
|
|
||
| get parameters() { | ||
| return this._parameters; | ||
| export function celFunc<const P extends TypeTuple, const R extends CelType>( |
There was a problem hiding this comment.
Doc comments for celFunc
|
|
||
| if (errors.length) return errors[0].causes(errors.slice(1)); | ||
|
|
||
| return results as CelValue[]; |
There was a problem hiding this comment.
I think we should return unwrapped here
| const unwrapped = results.reduce((u, r, i) => { | ||
| return u.concat([ | ||
| types ? unwrapResult(r, types[i], i + 1) : unwrapResult(r), | ||
| ]); | ||
| }, [] as CelResult[]); | ||
|
|
||
| const errors = unwrapped.filter((r) => isCelError(r)); |
There was a problem hiding this comment.
Nit: Using a regular loop we can do it in one go
| } | ||
|
|
||
| call(id: number, args: CelResult[]): CelResultFromType<R> { | ||
| const target = unwrapResult(args[0], this._target); |
There was a problem hiding this comment.
There could be a subtle bug here unwrapResult expects a CelResult which can never be undefined but here the args can be an empty list and args[0] will return undefined. I think we should replicate matchArgs to be safe
| readonly #callables: Callable[]; | ||
| readonly #nameCache: Map<string, Dispatcher | undefined> = new Map(); | ||
| readonly #overloadIdCache: Map<string, Callable | undefined> = new Map(); |
There was a problem hiding this comment.
We deliberately don't use JS private fields to support older runtimes. This is anyway an internal class we can just use TS private fields
| } | ||
| eval(ctx: Activation): CelResult { | ||
| const vals = coerceToValues(this.values.map((x) => x.eval(ctx))); | ||
| const vals = unwrapResultTuple(this.values.map((x) => x.eval(ctx))); |
There was a problem hiding this comment.
We don't want to unwrap here because we want the Any unwrap to be lazy
|
Dan Hudlow seems not to be a GitHub user. You need a GitHub account to be able to sign the CLA. If you have already a GitHub account, please add the email address used for this commit to your account. You have signed the CLA already but the status is still pending? Let us recheck it. |
NOTE THIS IS A BREAKING CHANGE AND NEEDS TO BE CONSIDERED CAREFULLY
As I discussed with @timostamm and @srikrsna-buf, this PR eliminates the "overload" abstraction from the
cel-escodebase. Instead, multiple functions (and methods) can have the same name, and each function is responsible for whether it matches a name and/or a set of arguments.This way, the
_&&_and_||_functions can still match on any set of arguments and handle short-circuiting in a conformant way — but other functions rely on standard argument-matching logic.This also lays the groundwork for matching functions via
overload_id(which will be necessary if evaluating a checked expression) and matching functions via parameter types instead of argument values.This PR does introduce unique, generated overload IDs in a different way than #243, but it doesn't attempt to match the IDs to the
cel-goimplementation.Unfortunately, the scope grew larger than I intended. If it's necessary, I can try to decompose the work further.