From 26c2dc99a0de35bb8daa0af15c00e373949aebeb Mon Sep 17 00:00:00 2001 From: Jimmy Cai Date: Fri, 5 Jun 2026 19:02:44 +0200 Subject: [PATCH 1/3] Create PartialFunctionCallIterator to centralize the logic of partial application --- ...iltinPartialApplicationRewriteVisitor.java | 124 ------------ .../compiler/ExecutionModeVisitor.java | 4 +- .../rumbledb/compiler/InferTypeVisitor.java | 8 - .../compiler/RuntimeIteratorVisitor.java | 13 +- .../org/rumbledb/compiler/VisitorHelpers.java | 9 - .../org/rumbledb/context/NamedFunctions.java | 24 ++- .../functions/FunctionItemCallIterator.java | 191 ++---------------- .../PartialFunctionCallIterator.java | 182 +++++++++++++++++ 8 files changed, 225 insertions(+), 330 deletions(-) delete mode 100644 src/main/java/org/rumbledb/compiler/BuiltinPartialApplicationRewriteVisitor.java create mode 100644 src/main/java/org/rumbledb/runtime/functions/PartialFunctionCallIterator.java diff --git a/src/main/java/org/rumbledb/compiler/BuiltinPartialApplicationRewriteVisitor.java b/src/main/java/org/rumbledb/compiler/BuiltinPartialApplicationRewriteVisitor.java deleted file mode 100644 index 20cccbbbc7..0000000000 --- a/src/main/java/org/rumbledb/compiler/BuiltinPartialApplicationRewriteVisitor.java +++ /dev/null @@ -1,124 +0,0 @@ -package org.rumbledb.compiler; - -import org.rumbledb.context.BuiltinFunction; -import org.rumbledb.context.BuiltinFunctionCatalogue; -import org.rumbledb.context.Name; -import org.rumbledb.expressions.Expression; -import org.rumbledb.expressions.Node; -import org.rumbledb.expressions.postfix.DynamicFunctionCallExpression; -import org.rumbledb.expressions.primary.FunctionCallExpression; -import org.rumbledb.expressions.primary.InlineFunctionExpression; -import org.rumbledb.expressions.primary.NamedFunctionReferenceExpression; -import org.rumbledb.expressions.primary.VariableReferenceExpression; -import org.rumbledb.expressions.scripting.statement.StatementsAndOptionalExpr; -import org.rumbledb.types.SequenceType; - -import java.util.*; -import java.util.stream.Collectors; - -/** - * Rewrites direct partial application of builtins, e.g. {@code fn:max(0, ?)}, - * into - * an equivalent inline function, e.g. {@code function($x) { fn:max(0, $x) }}. - *

- * This keeps builtin partial application on the same code path as ordinary - * inline functions. - */ -public class BuiltinPartialApplicationRewriteVisitor extends CloneVisitor { - - private InlineFunctionExpression rewriteBuiltinPartialApplication( - Name functionName, - BuiltinFunction builtin, - List arguments, - Expression sourceExpression - ) { - List parameterTypes = builtin.getSignature().getParameterTypes(); - Map params = new LinkedHashMap<>(); - - /// We will create a new function call to builtin function, but this time - /// replace ? with real parameters - List fullArguments = new ArrayList<>(arguments.size()); - - for (int i = 0; i < arguments.size(); i++) { - Expression currentArgument = arguments.get(i); - if (currentArgument != null) { - /// It was not a ? - fullArguments.add(currentArgument); - continue; - } - - Name parameterName = Name.createVariableInNoNamespace(String.format("param%s", i)); - params.put(parameterName, parameterTypes.get(i)); - VariableReferenceExpression variableReference = new VariableReferenceExpression( - parameterName, - sourceExpression.getMetadata() - ); - variableReference.setActualType(parameterTypes.get(i)); - fullArguments.add(variableReference); - } - - FunctionCallExpression bodyCall = new FunctionCallExpression( - functionName, - fullArguments, - sourceExpression.getMetadata() - ); - StatementsAndOptionalExpr body = new StatementsAndOptionalExpr( - Collections.emptyList(), - bodyCall, - sourceExpression.getMetadata() - ); - return new InlineFunctionExpression( - Collections.emptyList(), - null, - params, - builtin.getSignature().getReturnType(), - body, - sourceExpression.getMetadata() - ); - } - - @Override - public Node visitFunctionCall(FunctionCallExpression expression, Node argument) { - BuiltinFunction builtin = BuiltinFunctionCatalogue.getBuiltinFunction(expression.getFunctionIdentifier()); - - if (!expression.isPartialApplication() || builtin == null) { - /// In case of non-partial application or non-builtin function, we still need to - /// keep descending - /// Because a partial builtin function might be in the nested level - /// See qt3 test hof-041 - return super.visitFunctionCall(expression, argument); - } - - List arguments = expression.getArguments() - .stream() - .map(expr -> expr != null ? (Expression) visit(expr, argument) : null) - .collect(Collectors.toList()); - return rewriteBuiltinPartialApplication(expression.getFunctionName(), builtin, arguments, expression); - } - - @Override - public Node visitDynamicFunctionCallExpression(DynamicFunctionCallExpression expression, Node argument) { - List arguments = expression.getArguments() - .stream() - .map(expr -> expr != null ? (Expression) visit(expr, argument) : null) - .collect(Collectors.toList()); - Expression rewrittenMainExpression = (Expression) visit(expression.getMainExpression(), argument); - - if (!(rewrittenMainExpression instanceof NamedFunctionReferenceExpression namedFunctionReference)) { - return super.visitDynamicFunctionCallExpression(expression, argument); - } - - BuiltinFunction builtin = BuiltinFunctionCatalogue.getBuiltinFunction(namedFunctionReference.getIdentifier()); - boolean isPartialApplication = arguments.stream().anyMatch(arg -> arg == null); - if (!isPartialApplication || builtin == null) { - return super.visitDynamicFunctionCallExpression(expression, argument); - } - - return rewriteBuiltinPartialApplication( - namedFunctionReference.getIdentifier().getName(), - builtin, - arguments, - expression - ); - } -} diff --git a/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java b/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java index b2baba266a..db9a830d3d 100644 --- a/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java +++ b/src/main/java/org/rumbledb/compiler/ExecutionModeVisitor.java @@ -267,7 +267,9 @@ public StaticContext visitFunctionCall(FunctionCallExpression expression, Static expression.getMetadata() ); } - if (BuiltinFunctionCatalogue.exists(expression.getFunctionIdentifier())) { + if (expression.isPartialApplication()) { + expression.setHighestExecutionMode(ExecutionMode.LOCAL); + } else if (BuiltinFunctionCatalogue.exists(expression.getFunctionIdentifier())) { BuiltinFunction builtinFunction = BuiltinFunctionCatalogue.getBuiltinFunction( expression.getFunctionIdentifier() ); diff --git a/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java b/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java index 9427d465a3..b2332681cd 100644 --- a/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java +++ b/src/main/java/org/rumbledb/compiler/InferTypeVisitor.java @@ -22,7 +22,6 @@ import org.rumbledb.exceptions.OurBadException; import org.rumbledb.exceptions.UnexpectedStaticTypeException; import org.rumbledb.exceptions.UnknownFunctionCallException; -import org.rumbledb.exceptions.UnsupportedFeatureException; import org.rumbledb.expressions.AbstractNodeVisitor; import org.rumbledb.expressions.CommaExpression; import org.rumbledb.expressions.Expression; @@ -766,13 +765,6 @@ public StaticContext visitFunctionCall(FunctionCallExpression expression, Static visitDescendants(expression, argument); if (BuiltinFunctionCatalogue.exists(expression.getFunctionIdentifier())) { - if (expression.isPartialApplication()) { - /// This should never be reached because partial application on built-in functions should have been rewritten before - throw new UnsupportedFeatureException( - "Partial application on built-in functions are not supported.", - expression.getMetadata() - ); - } BuiltinFunction builtinFunction = BuiltinFunctionCatalogue.getBuiltinFunction( expression.getFunctionIdentifier() ); diff --git a/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java b/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java index 9e4674799c..a599bcbc19 100644 --- a/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java +++ b/src/main/java/org/rumbledb/compiler/RuntimeIteratorVisitor.java @@ -1323,7 +1323,18 @@ public RuntimeIterator visitFunctionCall(FunctionCallExpression expression, Runt FunctionIdentifier identifier = new FunctionIdentifier(fnName, arity); RuntimeIterator runtimeIterator = null; - if (BuiltinFunctionCatalogue.exists(identifier)) { + if (expression.isPartialApplication()) { + runtimeIterator = new DynamicFunctionCallIterator( + new NamedFunctionRefRuntimeIterator( + identifier, + expression.getStaticContextForRuntime(this.config, this.visitorConfig) + ), + arguments, + expression.getStaticContextForRuntime(this.config, this.visitorConfig) + ); + } + + else if (BuiltinFunctionCatalogue.exists(identifier)) { runtimeIterator = NamedFunctions.getBuiltInFunctionIterator( identifier, arguments, diff --git a/src/main/java/org/rumbledb/compiler/VisitorHelpers.java b/src/main/java/org/rumbledb/compiler/VisitorHelpers.java index aa1ea2f1e7..10460c0901 100644 --- a/src/main/java/org/rumbledb/compiler/VisitorHelpers.java +++ b/src/main/java/org/rumbledb/compiler/VisitorHelpers.java @@ -65,15 +65,6 @@ private static void inferTypes(Module module, RumbleRuntimeConfiguration conf) { private static MainModule applyTypeIndependentOptimizations(MainModule module, RumbleRuntimeConfiguration conf) { MainModule result = module; - if (conf.debug()) { - System.err.println("***************************************"); - System.err.println("Builtin Partial Application Rewrite Visitor"); - System.err.println("***************************************"); - } - result = (MainModule) new BuiltinPartialApplicationRewriteVisitor().visit(result, null); - if (conf.debug()) { - printTree(result, conf); - } // Annotate recursive functions as such if (conf.debug()) { System.err.println("***************************************"); diff --git a/src/main/java/org/rumbledb/context/NamedFunctions.java b/src/main/java/org/rumbledb/context/NamedFunctions.java index a8957937ec..3573c49cd4 100644 --- a/src/main/java/org/rumbledb/context/NamedFunctions.java +++ b/src/main/java/org/rumbledb/context/NamedFunctions.java @@ -29,13 +29,13 @@ import org.rumbledb.exceptions.DuplicateFunctionIdentifierException; import org.rumbledb.exceptions.ExceptionMetadata; import org.rumbledb.exceptions.OurBadException; -import org.rumbledb.exceptions.UnsupportedFeatureException; import org.rumbledb.exceptions.UnknownFunctionCallException; import org.rumbledb.expressions.ExecutionMode; import org.rumbledb.items.FunctionItem; import org.rumbledb.runtime.RuntimeIterator; import org.rumbledb.runtime.functions.BuiltinFunctionItemCallIterator; import org.rumbledb.runtime.functions.FunctionItemCallIterator; +import org.rumbledb.runtime.functions.PartialFunctionCallIterator; import org.rumbledb.runtime.functions.sequences.general.DataFunctionIterator; import org.rumbledb.runtime.typing.AtMostOneItemTypePromotionIterator; import org.rumbledb.runtime.typing.TypePromotionIterator; @@ -104,7 +104,21 @@ public static RuntimeIterator buildFunctionItemCallIterator( List arguments, boolean isTailOptimization ) { - ExceptionMetadata metadata = callerRuntimeContext.getMetadata(); + if (isTailOptimization) { + return new PartialFunctionCallIterator( + functionItem, + arguments, + callerRuntimeContext.withExecutionMode(ExecutionMode.LOCAL), + Name.TAIL_CALL_OPTIMIZATION + ); + } + if (arguments.stream().anyMatch(a -> a == null)) { + return new PartialFunctionCallIterator( + functionItem, + arguments, + callerRuntimeContext.withExecutionMode(ExecutionMode.LOCAL) + ); + } SequenceType sequenceType = functionItem.getSignature().getReturnType(); SequenceType innerSequenceType = functionItem.getBodyIterator().getStaticType(); RuntimeStaticContext outerStaticContext = callerRuntimeContext.withStaticType( @@ -118,12 +132,6 @@ public static RuntimeIterator buildFunctionItemCallIterator( ).withExecutionMode(executionModeForFunctionCall); RuntimeIterator functionCallIterator; if (functionItem.isBuiltinFunction()) { - if (arguments.stream().anyMatch(a -> a == null)) { - throw new UnsupportedFeatureException( - "Partial application of builtin named function references is not supported yet.", - metadata - ); - } functionCallIterator = new BuiltinFunctionItemCallIterator( functionItem, arguments, diff --git a/src/main/java/org/rumbledb/runtime/functions/FunctionItemCallIterator.java b/src/main/java/org/rumbledb/runtime/functions/FunctionItemCallIterator.java index 246ead3fdd..9a9247fcf7 100644 --- a/src/main/java/org/rumbledb/runtime/functions/FunctionItemCallIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/FunctionItemCallIterator.java @@ -20,7 +20,6 @@ package org.rumbledb.runtime.functions; -import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -28,24 +27,13 @@ import org.apache.spark.api.java.JavaRDD; import org.rumbledb.api.Item; import org.rumbledb.context.DynamicContext; -import org.rumbledb.context.FunctionIdentifier; import org.rumbledb.context.Name; import org.rumbledb.context.RuntimeStaticContext; import org.rumbledb.exceptions.IteratorFlowException; -import org.rumbledb.exceptions.OurBadException; -import org.rumbledb.exceptions.UnexpectedTypeException; -import org.rumbledb.expressions.ExecutionMode; -import org.rumbledb.items.FunctionItem; import org.rumbledb.items.structured.JSoundDataFrame; -import org.rumbledb.runtime.ConstantRuntimeIterator; import org.rumbledb.runtime.HybridRuntimeIterator; import org.rumbledb.runtime.RuntimeIterator; -import org.rumbledb.runtime.typing.AtMostOneItemTypePromotionIterator; -import org.rumbledb.runtime.typing.TypePromotionIterator; import org.rumbledb.runtime.update.PendingUpdateList; -import org.rumbledb.types.FunctionSignature; -import org.rumbledb.types.SequenceType; -import org.rumbledb.types.SequenceType.Arity; public class FunctionItemCallIterator extends HybridRuntimeIterator { @@ -55,8 +43,6 @@ public class FunctionItemCallIterator extends HybridRuntimeIterator { private List functionArguments; // calculated fields - private boolean isPartialApplication; - private boolean isTailOptimization; private RuntimeIterator functionBodyIterator; private Item nextResult; private transient DynamicContext dynamicContextForCalls; @@ -70,23 +56,19 @@ public FunctionItemCallIterator( ) { super(null, staticContext); for (RuntimeIterator arg : functionArguments) { - if (arg == null) { - this.isPartialApplication = true; - } else { - this.children.add(arg); - } - } - if (isTailOptimization) { - this.isPartialApplication = true; - this.isTailOptimization = true; + this.children.add(arg); } this.functionItem = functionItem; this.functionArguments = functionArguments; this.functionBodyIterator = null; this.isUpdating = functionItem.getSignature().isUpdating(); - this.validateNumberOfArguments(); - this.wrapArgumentIteratorsWithTypeCheckingIterators(); + FunctionCallArgumentCoercion.validateArity(functionItem, this.functionArguments, getMetadata()); + FunctionCallArgumentCoercion.wrapAccordingToSignature( + functionItem, + this.functionArguments, + staticContext + ); // Prepopulation of the dynamic context (without the parameters) Map> localArgumentValues = new LinkedHashMap<>( @@ -107,155 +89,18 @@ public FunctionItemCallIterator( ); } - private void validateNumberOfArguments() { - if (this.functionItem.getParameterNames().size() != this.functionArguments.size()) { - throw new UnexpectedTypeException( - "Dynamic function " - + this.functionItem.getIdentifier().getName() - + " invoked with incorrect number of arguments. Expected: " - + this.functionItem.getParameterNames().size() - + ", Found: " - + this.functionArguments.size(), - getMetadata() - ); - } - } - - private void wrapArgumentIteratorsWithTypeCheckingIterators() { - if (this.functionItem.getSignature().getParameterTypes() != null) { - for (int i = 0; i < this.functionArguments.size(); i++) { - if ( - this.functionArguments.get(i) != null - && !this.functionItem.getSignature() - .getParameterTypes() - .get(i) - .equals(SequenceType.createSequenceType("item*")) - ) { - SequenceType sequenceType = this.functionItem.getSignature().getParameterTypes().get(i); - ExecutionMode executionMode = this.functionArguments.get(i).getHighestExecutionMode(); - if ( - sequenceType.isEmptySequence() - || sequenceType.getArity().equals(Arity.One) - || sequenceType.getArity().equals(Arity.OneOrZero) - ) { - executionMode = ExecutionMode.LOCAL; - } - RuntimeStaticContext runtimeStaticContext = getRuntimeStaticContext().withStaticType(sequenceType) - .withExecutionMode(executionMode) - .withMetadata(this.functionArguments.get(i).getMetadata()); - if ( - sequenceType.isEmptySequence() - || sequenceType.getArity().equals(Arity.One) - || sequenceType.getArity().equals(Arity.OneOrZero) - ) { - RuntimeIterator typePromotionIterator = new AtMostOneItemTypePromotionIterator( - this.functionArguments.get(i), - sequenceType, - "Invalid argument for " + this.functionItem.getIdentifier().getName() + " function. ", - runtimeStaticContext - ); - this.functionArguments.set(i, typePromotionIterator); - } else { - RuntimeIterator typePromotionIterator = new TypePromotionIterator( - this.functionArguments.get(i), - sequenceType, - "Invalid argument for " + this.functionItem.getIdentifier().getName() + " function. ", - runtimeStaticContext - ); - this.functionArguments.set(i, typePromotionIterator); - } - } - } - } - } - @Override public void openLocal() { - if (this.isPartialApplication) { - this.functionBodyIterator = generatePartiallyAppliedFunction(this.currentDynamicContextForLocalExecution); - } else { - if (this.functionBodyIterator == null) { - this.functionBodyIterator = this.functionItem.getBodyIterator().deepCopy(); - } - this.populateDynamicContextWithArguments( - this.currentDynamicContextForLocalExecution - ); + if (this.functionBodyIterator == null) { + this.functionBodyIterator = this.functionItem.getBodyIterator().deepCopy(); } + this.populateDynamicContextWithArguments( + this.currentDynamicContextForLocalExecution + ); this.functionBodyIterator.open(this.dynamicContextForCalls); setNextResult(); } - /** - * Partial application generates a new function: - * - Supplied parameters are set as NonLocalVariables - * - Argument placeholders form the parameters - * - * @return FunctionRuntimeIterator that contains the newly generated FunctionItem - */ - private RuntimeIterator generatePartiallyAppliedFunction(DynamicContext context) { - Name argName; - RuntimeIterator argIterator; - - Map> localArgumentValues = new LinkedHashMap<>( - this.functionItem.getLocalVariablesInClosure() - ); - Map> RDDArgumentValues = new LinkedHashMap<>( - this.functionItem.getRDDVariablesInClosure() - ); - Map DFArgumentValues = new LinkedHashMap<>( - this.functionItem.getDFVariablesInClosure() - ); - - List partialApplicationParamNames = new ArrayList<>(); - List partialApplicationParamTypes = new ArrayList<>(); - - for (int i = 0; i < this.functionArguments.size(); i++) { - argName = this.functionItem.getParameterNames().get(i); - argIterator = this.functionArguments.get(i); - - if (argIterator == null) { // == ArgumentPlaceholder - partialApplicationParamNames.add(argName); - partialApplicationParamTypes.add(this.functionItem.getSignature().getParameterTypes().get(i)); - } else { - if (argIterator.isDataFrame()) { - DFArgumentValues.put(argName, argIterator.getDataFrame(context)); - } else if (argIterator.isRDDOrDataFrame()) { - RDDArgumentValues.put(argName, argIterator.getRDD(context)); - } else { - localArgumentValues.put(argName, argIterator.materialize(context)); - } - } - } - - Name functionItemName = this.functionItem.getIdentifier().getName(); - if (this.isTailOptimization) { - functionItemName = Name.TAIL_CALL_OPTIMIZATION; - } - FunctionItem partiallyAppliedFunction = new FunctionItem( - new FunctionIdentifier( - functionItemName, - partialApplicationParamNames.size() - ), - partialApplicationParamNames, - new FunctionSignature( - partialApplicationParamTypes, - this.functionItem.getSignature().getReturnType(), - this.functionItem.getSignature().isUpdating() - ), - this.functionItem.getModuleDynamicContext(), - this.functionItem.getBodyIterator(), - localArgumentValues, - RDDArgumentValues, - DFArgumentValues - ); - return new ConstantRuntimeIterator( - partiallyAppliedFunction, - this.staticContext.withStaticType( - SequenceType.createSequenceType("function(*)") - ).withExecutionMode(ExecutionMode.LOCAL).withMetadata(getMetadata()) - ); - } - private void populateDynamicContextWithArguments(DynamicContext context) { Name argName; RuntimeIterator argIterator; @@ -328,12 +173,6 @@ public void setNextResult() { @Override public JavaRDD getRDDAux(DynamicContext dynamicContext) { - if (this.isPartialApplication) { - throw new OurBadException( - "Unexpected program state reached. Partially applied function calls must be evaluated locally." - ); - } - this.populateDynamicContextWithArguments(dynamicContext); this.functionBodyIterator = this.functionItem.getBodyIterator(); return this.functionBodyIterator.getRDD(this.dynamicContextForCalls); @@ -346,12 +185,6 @@ protected boolean implementsDataFrames() { @Override public JSoundDataFrame getDataFrame(DynamicContext dynamicContext) { - if (this.isPartialApplication) { - throw new OurBadException( - "Unexpected program state reached. Partially applied function calls must be evaluated locally." - ); - } - populateDynamicContextWithArguments(dynamicContext); this.functionBodyIterator = this.functionItem.getBodyIterator(); return this.functionBodyIterator.getDataFrame(this.dynamicContextForCalls); diff --git a/src/main/java/org/rumbledb/runtime/functions/PartialFunctionCallIterator.java b/src/main/java/org/rumbledb/runtime/functions/PartialFunctionCallIterator.java new file mode 100644 index 0000000000..1de31edb20 --- /dev/null +++ b/src/main/java/org/rumbledb/runtime/functions/PartialFunctionCallIterator.java @@ -0,0 +1,182 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.rumbledb.runtime.functions; + +import org.apache.spark.api.java.JavaRDD; +import org.rumbledb.api.Item; +import org.rumbledb.context.DynamicContext; +import org.rumbledb.context.FunctionIdentifier; +import org.rumbledb.context.Name; +import org.rumbledb.context.RuntimeStaticContext; +import org.rumbledb.expressions.ExecutionMode; +import org.rumbledb.items.FunctionItem; +import org.rumbledb.items.structured.JSoundDataFrame; +import org.rumbledb.runtime.AtMostOneItemLocalRuntimeIterator; +import org.rumbledb.runtime.RuntimeIterator; +import org.rumbledb.runtime.primary.VariableReferenceIterator; +import org.rumbledb.types.FunctionSignature; +import org.rumbledb.types.SequenceType; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Creates a partially-applied function item by capturing supplied arguments in the closure and + * exposing each placeholder as a parameter of the returned function item. + */ +public class PartialFunctionCallIterator extends AtMostOneItemLocalRuntimeIterator { + + private static final long serialVersionUID = 1L; + private static final Name PARTIAL_FUNCTION_TARGET_NAME = Name.createVariableInNoNamespace( + "$4deff1a0-0d7f-43d1-bf96-09934cc6a539" + ); + + private final Item functionItem; + private final List functionArguments; + private final Name functionNameOverride; + + public PartialFunctionCallIterator( + Item functionItem, + List functionArguments, + RuntimeStaticContext staticContext + ) { + this(functionItem, functionArguments, staticContext, null); + } + + public PartialFunctionCallIterator( + Item functionItem, + List functionArguments, + RuntimeStaticContext staticContext, + Name functionNameOverride + ) { + super(null, staticContext); + for (RuntimeIterator arg : functionArguments) { + if (arg != null) { + this.children.add(arg); + } + } + this.functionItem = functionItem; + this.functionArguments = functionArguments; + this.functionNameOverride = functionNameOverride; + + FunctionCallArgumentCoercion.validateArity(functionItem, this.functionArguments, getMetadata()); + FunctionCallArgumentCoercion.wrapAccordingToSignature( + functionItem, + this.functionArguments, + staticContext + ); + } + + @Override + public Item materializeFirstItemOrNull(DynamicContext context) { + Map> localArgumentValues = new LinkedHashMap<>( + this.functionItem.getLocalVariablesInClosure() + ); + Map> rddArgumentValues = new LinkedHashMap<>( + this.functionItem.getRDDVariablesInClosure() + ); + Map dfArgumentValues = new LinkedHashMap<>( + this.functionItem.getDFVariablesInClosure() + ); + + List partialApplicationParamNames = new ArrayList<>(); + List partialApplicationParamTypes = new ArrayList<>(); + + for (int i = 0; i < this.functionArguments.size(); i++) { + Name parameterName = this.functionItem.getParameterNames().get(i); + RuntimeIterator argumentIterator = this.functionArguments.get(i); + + if (argumentIterator == null) { + partialApplicationParamNames.add(parameterName); + partialApplicationParamTypes.add(this.functionItem.getSignature().getParameterTypes().get(i)); + } else if (argumentIterator.isDataFrame()) { + dfArgumentValues.put(parameterName, argumentIterator.getDataFrame(context)); + } else if (argumentIterator.isRDDOrDataFrame()) { + rddArgumentValues.put(parameterName, argumentIterator.getRDD(context)); + } else { + localArgumentValues.put(parameterName, argumentIterator.materialize(context)); + } + } + + RuntimeIterator functionBodyIterator = this.functionItem.getBodyIterator(); + boolean isBuiltinFunctionItem = this.functionItem.isBuiltinFunction(); + if (isBuiltinFunctionItem) { + localArgumentValues.put(PARTIAL_FUNCTION_TARGET_NAME, List.of(this.functionItem)); + functionBodyIterator = createPartialBuiltinBodyIterator(); + } + + return new FunctionItem( + new FunctionIdentifier( + this.functionNameOverride == null + ? this.functionItem.getIdentifier().getName() + : this.functionNameOverride, + partialApplicationParamNames.size() + ), + partialApplicationParamNames, + new FunctionSignature( + partialApplicationParamTypes, + this.functionItem.getSignature().getReturnType(), + this.functionItem.getSignature().isUpdating() + ), + this.functionItem.getModuleDynamicContext(), + functionBodyIterator, + localArgumentValues, + rddArgumentValues, + dfArgumentValues, + false + ); + } + + private RuntimeIterator createPartialBuiltinBodyIterator() { + RuntimeStaticContext targetStaticContext = getRuntimeStaticContext() + .withStaticType(SequenceType.createSequenceType("function(*)")) + .withExecutionMode(ExecutionMode.LOCAL); + + RuntimeIterator targetIterator = new VariableReferenceIterator( + PARTIAL_FUNCTION_TARGET_NAME, + targetStaticContext + ); + + List callArguments = new ArrayList<>(); + for (int i = 0; i < this.functionArguments.size(); i++) { + RuntimeStaticContext argumentStaticContext; + if (this.functionArguments.get(i) == null) { + argumentStaticContext = getRuntimeStaticContext() + .withStaticType(this.functionItem.getSignature().getParameterTypes().get(i)) + .withExecutionMode(ExecutionMode.LOCAL); + } else { + argumentStaticContext = this.functionArguments.get(i).getRuntimeStaticContext(); + } + callArguments.add( + new VariableReferenceIterator( + this.functionItem.getParameterNames().get(i), + argumentStaticContext + ) + ); + } + + return new DynamicFunctionCallIterator( + targetIterator, + callArguments, + getRuntimeStaticContext() + .withStaticType(this.functionItem.getSignature().getReturnType()) + .withExecutionMode(ExecutionMode.LOCAL) + ); + } +} From fbc2d5c0341361c115778e342a43f3217b664ba9 Mon Sep 17 00:00:00 2001 From: Jimmy Cai Date: Wed, 17 Jun 2026 00:15:57 +0200 Subject: [PATCH 2/3] Return null function name in case of partial function --- .../runtime/functions/PartialFunctionCallIterator.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/org/rumbledb/runtime/functions/PartialFunctionCallIterator.java b/src/main/java/org/rumbledb/runtime/functions/PartialFunctionCallIterator.java index 1de31edb20..4b722b4308 100644 --- a/src/main/java/org/rumbledb/runtime/functions/PartialFunctionCallIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/PartialFunctionCallIterator.java @@ -123,9 +123,7 @@ public Item materializeFirstItemOrNull(DynamicContext context) { return new FunctionItem( new FunctionIdentifier( - this.functionNameOverride == null - ? this.functionItem.getIdentifier().getName() - : this.functionNameOverride, + this.functionNameOverride, partialApplicationParamNames.size() ), partialApplicationParamNames, From f82f2bd9a4f981826a1f32fee908842a1a151cbd Mon Sep 17 00:00:00 2001 From: Jimmy Cai Date: Wed, 17 Jun 2026 00:23:41 +0200 Subject: [PATCH 3/3] Remove function-name debug output --- .../runtime/functions/typing/FunctionNameFunctionIterator.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/org/rumbledb/runtime/functions/typing/FunctionNameFunctionIterator.java b/src/main/java/org/rumbledb/runtime/functions/typing/FunctionNameFunctionIterator.java index b7fdbd85a3..797c9db91c 100644 --- a/src/main/java/org/rumbledb/runtime/functions/typing/FunctionNameFunctionIterator.java +++ b/src/main/java/org/rumbledb/runtime/functions/typing/FunctionNameFunctionIterator.java @@ -37,7 +37,6 @@ public Item materializeFirstItemOrNull(DynamicContext context) { getMetadata() ); } - System.err.println("Item is of type function"); Item functionItem = functionIterator.materializeFirstItemOrNull(context); if (functionItem == null || !(functionItem instanceof FunctionItem)) { throw new OurBadException("Expected argument to be of type function and not be null");