Skip to content

Align partial application of native functions with jsonata-js (#95) - #132

Open
aeberhart wants to merge 3 commits into
mainfrom
95-partial-native
Open

aeberhart wants to merge 3 commits into
mainfrom
95-partial-native

Conversation

@aeberhart

Copy link
Copy Markdown
Contributor

Second of three PRs split out of #130. Builds on #131 (already merged).

Fixes #79, #86

Root cause

jsonata-js partially applies a native function by building function($a, $b){ _ } and setting the body to the native function itself (bodyAST.body = native). When the partial is invoked, applyProcedure calls applyNativeFunction, which does not validate the signature. The Java port instead generated a trampoline expression that called the builtin by name ($each($a, $b)). So the signature was checked again, and the function had to be looked up by name.

Changes (following the JS code)

  • partialApplyNativeFunction sets bodyAST.body = _native (Symbol.body is now Object). Implemented the commented-out applyNativeFunction / getNativeFunctionArguments and the native branch in applyProcedure.
  • partialApplyProcedure: only ? leaves a parameter unbound. Missing args are bound to null, the way JS binds undefined (incorrect result when $map($, $append(?)) #86).
  • ? comparison: the token value is a Character, so arg.value.equals("?") was always false.
  • No lookup by name, so custom / unnamed JFunctions with a signature can be partially applied.
  • Fixed the inverted T1007 check (proc != null vs JS typeof proc === 'undefined').
  • Arrays now reach $each / $merge through partials such as [5,6] ~> $each(?, …). Those functions iterate arrays like JS for..in since $each / $merge iterate arrays like JS for..in #131 (Incorrect each function signature validation #79).

Notes for reviewers

  • The arity of a JFunction is taken from its implementing method or its signature. Partially applying custom functions without a signature, Fn* lambdas, and lambdas bound via Frame.bind is left for the third PR (Custom function pipe error #83, together with the JLambda API change). The two Issues-83: Custom function pipe error  #85 tests that need it are not included here.
  • Calling $each([5,6], …) directly is still rejected with T0410, because the signature is still checked on direct calls.
  • Conflict with Port hot-path optimizations from #102 #129 in the trampoline loop: kept "variable".equals(...) and added the (Symbol) cast for body.

Testing

🤖 Generated with Claude Code

aeberhart and others added 3 commits November 5, 2026 10:36
Partially applying a native function now wraps the function itself as the
lambda body (bodyAST.body = native) instead of generating a trampoline
expression that calls the builtin by name. As in JS, the signature of the
native function is not validated when the partial is invoked.

- implement applyNativeFunction / getNativeFunctionArguments
- only '?' leaves a parameter unbound, missing args are bound to null (#86)
- fix '?' comparison (token value is a Character)
- no lookup by name, so unnamed / custom JFunctions work (#83)
- arity of a JFunctionCallable is taken from its signature
- arrays now reach $each / $merge, which iterate them like JS for..in since #131 (#79)
- fix inverted T1007 check

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Partial application of custom functions without signature (#85 pipe
cases) is left for the follow-up on #83.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

Test Results

1 839 tests  +14   1 837 ✅ +14   9s ⏱️ ±0s
  126 suites + 2       2 💤 ± 0 
  126 files   + 2       0 ❌ ± 0 

Results for commit a1f2a78. ± Comparison against base commit 6bb65b9.

@aeberhart
aeberhart requested a review from uw4 November 5, 2026 09:50
for(var ii = 0; ii < expr.arguments.size(); ii++) {
var arg = expr.arguments.get(ii);
if (arg.type.equals("operator") && (arg.value.equals("?"))) {
if (arg.type.equals("operator") && (""+arg.value).equals("?")) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixes char - string equals which use to be false always

// lookup the procedure
var proc = /* await */ evaluate(expr.procedure, input, environment);
if (proc != null && expr.procedure.type.equals("path") && environment.lookup((String)expr.procedure.steps.get(0).value)!=null) {
if (proc == null && expr.procedure.type.equals("path") && environment.lookup((String)expr.procedure.steps.get(0).value)!=null) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

original:

    // lookup the procedure
    var proc = await evaluate(expr.procedure, input, environment);
    if (typeof proc === 'undefined' && expr.procedure.type === 'path' && environment.lookup(expr.procedure.steps[0].value)) {

}
if (proc.body instanceof Symbol) {
result = evaluate(proc.body, proc.input, env);
if (proc.body instanceof JFunction) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moves commented out code from line 1979 on the left

// proc.arguments.forEach(Object (param, index) {
Object arg = index<args.size() ? args.get(index) : null;
if ((arg==null) || (arg instanceof Symbol && ("operator".equals(((Symbol)arg).type) && "?".equals(((Symbol)arg).value)))) {
if (arg instanceof Symbol && ("operator".equals(((Symbol)arg).type) && "?".equals(""+((Symbol)arg).value))) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

original:

if (arg && arg.type === 'operator' && arg.value === '?') {

// return "$" + sigArg;
// }).toList();
// var body = "function(" + String.join(", ", sigArgs) + "){ _ }";
var sigArgs = getNativeFunctionArguments(_native);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move commented out code from line 2042 on the left


var bodyAST = parser.parse(body);
//bodyAST.body = _native;
bodyAST.body = _native;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

point to parent of this partial fn

if (method != null)
return method.getParameterTypes().length;
// Java: a JFunctionCallable has no declared parameters, use the signature (if any)
return signature != null ? signature.getNumberOfArgs() : 0;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the third part will address this

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.

Incorrect each function signature validation

1 participant