@@ -45,8 +45,7 @@ public Expression.Lambda lambda(List<Type> paramTypes, Function<Scope, Expressio
4545 Type .Struct params = Type .Struct .builder ().nullable (false ).addAllFields (paramTypes ).build ();
4646 pushLambdaContext (params );
4747 try {
48- int index = lambdaContext .size () - 1 ;
49- Scope scope = new Scope (index );
48+ Scope scope = new Scope (params );
5049 Expression body = bodyFn .apply (scope );
5150 return ImmutableExpression .Lambda .builder ().parameters (params ).body (body ).build ();
5251 } finally {
@@ -83,14 +82,14 @@ public Expression.Lambda lambdaFromStruct(
8382 * @throws IllegalArgumentException if stepsOut exceeds the current nesting depth
8483 */
8584 public Type .Struct resolveParams (int stepsOut ) {
86- int index = lambdaContext .size () - 1 - stepsOut ;
87- if (index < 0 || index >= lambdaContext .size ()) {
85+ int targetDepth = lambdaContext .size () - stepsOut ;
86+ if (targetDepth <= 0 || targetDepth > lambdaContext .size ()) {
8887 throw new IllegalArgumentException (
8988 String .format (
9089 "Lambda parameter reference with stepsOut=%d is invalid (current depth: %d)" ,
9190 stepsOut , lambdaContext .size ()));
9291 }
93- return lambdaContext .get (index );
92+ return lambdaContext .get (targetDepth - 1 );
9493 }
9594
9695 /**
@@ -113,26 +112,39 @@ private void popLambdaContext() {
113112 /**
114113 * A handle to a particular lambda's parameter scope. Use {@link #ref} to create validated
115114 * parameter references.
115+ *
116+ * <p>Each Scope captures the depth of the lambdaContext stack at the time it was created. When
117+ * {@link #ref} is called, the Substrait {@code stepsOut} value is computed as the difference
118+ * between the current stack depth and the captured depth. This means the same Scope produces
119+ * different stepsOut values depending on the nesting level at the time of the call, which is what
120+ * allows outer.ref(0) to produce stepsOut=1 when called inside a nested lambda.
116121 */
117122 public class Scope {
118- private final int index ;
123+ private final Type .Struct params ;
124+ private final int depth ;
125+
126+ private Scope (Type .Struct params ) {
127+ this .params = params ;
128+ this .depth = lambdaContext .size ();
129+ }
119130
120- private Scope (int index ) {
121- this .index = index ;
131+ /**
132+ * Computes the number of lambda boundaries between this scope and the current innermost scope.
133+ * This value changes dynamically as nested lambdas are built.
134+ */
135+ private int stepsOut () {
136+ return lambdaContext .size () - depth ;
122137 }
123138
124139 /**
125- * Creates a validated reference to a parameter of this lambda. The correct {@code stepsOut}
126- * value is computed automatically.
140+ * Creates a validated reference to a parameter of this lambda.
127141 *
128142 * @param paramIndex index of the parameter within this lambda's parameter struct
129143 * @return a {@link FieldReference} pointing to the specified parameter
130144 * @throws IndexOutOfBoundsException if paramIndex is out of bounds
131145 */
132146 public FieldReference ref (int paramIndex ) {
133- int stepsOut = lambdaContext .size () - 1 - index ;
134- return FieldReference .newLambdaParameterReference (
135- paramIndex , lambdaContext .get (index ), stepsOut );
147+ return FieldReference .newLambdaParameterReference (paramIndex , params , stepsOut ());
136148 }
137149 }
138150}
0 commit comments