-
Notifications
You must be signed in to change notification settings - Fork 3
Simplify functions and distinguish methods #244
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
srikrsna-buf
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
packages/cel/src/func.ts
Outdated
| narrowedByName(name: string): FuncRegistry; | ||
| narrowedByArgs(...args: [CelResult[]] | [CelResult, CelResult[]]): FuncRegistry; | ||
| // TODO: narrowedByParams(...args: [CelType[]] | [CelType, CelType[]]): FuncRegistry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I simplified function registry, renamed it back to a dispatcher (to avoid the proto registry confusion), and added a lookup cache.
packages/cel/src/func.ts
Outdated
| find(name: string) { | ||
| return this.functions.get(name); | ||
| } | ||
| withFallback(registry: FuncRegistry): FuncRegistry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
User's will also see this I think we can expose a simpler interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I adjusted this to withFallbacks(Callable[]) — let me know what you think. The idea is to make the ordering explicit.
517d0f1 to
f0d0d2f
Compare
f0d0d2f to
922890e
Compare
| 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)), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@srikrsna-buf CelMapIndex is used here.
|
|
||
| causes(value: unknown, exprId?: bigint | number): CelError; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't want to unwrap here because we want the Any unwrap to be lazy
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.