Motivation
Range.getShapes and RaggedRange.getShapes resolve argument value-numbers through a per-positional-count branch structure (numPosArgs == 0/1/2/3), with each branch handling both:
- Raw arg resolution: walking positional + keyword args to get each formal's value-number.
- TF convention application: the
start↔limit swap when only one of them was given (TF treats range(5) as limit=5, and range(starts=5) as limits=5 since limits is the mandatory parameter).
These two concerns are intertwined per-branch, which makes the logic hard to reason about. The bug fixed incidentally in ponder-lab/ML#332 was a direct symptom: the keyword-only swap range(starts=5) → limits=5 was firing BEFORE the keyword-fallback block, which then re-fetched starts and undid the swap. The fix moved the swap to fire AFTER the fallback. Pre-fix this was invisible only because both pre- and post-swap states landed at (1, RaggedDim); once NumericDim specialisation was added, the bug surfaced.
Proposed Refactor
A two-phase design:
// Phase 1: resolve raw arg name → value-number.
ArgResolution args = resolveArgs(builder, numPosArgs);
// args.starts, args.limits, args.deltas are either VN or UNDEFINED.
// Phase 2: apply TF's `tf.range`/`tf.ragged.range` argument-swap conventions.
applyRangeConventions(args);
// Phase 3: compute shape from resolved + convention-applied args.
return computeShape(args);
Phase 1 walks the invoke's positional + keyword params once and produces a clean record (e.g., a record RangeArgs(int startsVn, int limitsVn, int deltasVn)). Phase 2 applies the TF semantic quirks in a single place. Phase 3 is the actual shape computation.
Benefits:
- No branching on
numPosArgs for arg resolution—positional and keyword treated uniformly.
- TF conventions documented in one place, not scattered across
if/else branches.
- Easier to extend (e.g., adding
tf.range variants or new ragged constructors) without expanding the branch matrix.
- Eliminates the class of bug fixed in ponder-lab/ML#332.
Scope
Range.java (lines 71-220 approx)—the main per-positional-count branching plus the keyword-fallback re-fetch block at the end.
RaggedRange.java—inherits the same structural shape from its parent, so the refactor should be applied to both together.
Out of Scope
- Other generators with positional/keyword arg handling that don't use the range-style
start/limit swap. They follow different conventions and the refactor here may or may not apply (each generator's API quirks are different).
- The
tf.range vs tf.ragged.range divergence in shape computation (1D vs 2D ragged)—Phase 3 still differs between subclasses; the refactor only unifies Phases 1-2.
Related
Motivation
Range.getShapesandRaggedRange.getShapesresolve argument value-numbers through a per-positional-count branch structure (numPosArgs == 0/1/2/3), with each branch handling both:start↔limitswap when only one of them was given (TF treatsrange(5)aslimit=5, andrange(starts=5)aslimits=5sincelimitsis the mandatory parameter).These two concerns are intertwined per-branch, which makes the logic hard to reason about. The bug fixed incidentally in ponder-lab/ML#332 was a direct symptom: the keyword-only swap
range(starts=5)→limits=5was firing BEFORE the keyword-fallback block, which then re-fetchedstartsand undid the swap. The fix moved the swap to fire AFTER the fallback. Pre-fix this was invisible only because both pre- and post-swap states landed at(1, RaggedDim); onceNumericDimspecialisation was added, the bug surfaced.Proposed Refactor
A two-phase design:
Phase 1 walks the invoke's positional + keyword params once and produces a clean record (e.g., a
record RangeArgs(int startsVn, int limitsVn, int deltasVn)). Phase 2 applies the TF semantic quirks in a single place. Phase 3 is the actual shape computation.Benefits:
numPosArgsfor arg resolution—positional and keyword treated uniformly.if/elsebranches.tf.rangevariants or new ragged constructors) without expanding the branch matrix.Scope
Range.java(lines 71-220 approx)—the main per-positional-count branching plus the keyword-fallback re-fetch block at the end.RaggedRange.java—inherits the same structural shape from its parent, so the refactor should be applied to both together.Out of Scope
start/limitswap. They follow different conventions and the refactor here may or may not apply (each generator's API quirks are different).tf.rangevstf.ragged.rangedivergence in shape computation (1D vs 2D ragged)—Phase 3 still differs between subclasses; the refactor only unifies Phases 1-2.Related
NumericDimfor statically-known integer-literal args #546—broadertf.ragged.rangespecialisation roadmap; this refactor is orthogonal but reduces friction for follow-up slices.