How many AREAS the argument at index covers — 1 for an ordinary reference, more for a
union ((A1:A5,B1:B2)), and 0 for anything that is not a reference at all.
The PARAMETER index — the position in the body's own signature.
getKindAt deliberately cannot answer this: a union IS a reference and reports
ValueKind.Reference, the same as A1:A5. This is the question a body asks when the
union is the thing it must decide about, and the three possible answers all come from asking
it — flatten every area, refuse, or #REF!.
Only a IFunction.ParameterTypeJSON.reportType slot can see a union at all; at every
other slot the marshaller has already answered #VALUE!.
The KIND of the argument at index — the total discriminator over getRawAt's value.
The PARAMETER index — the position in the body's own signature.
Every raw argument is exactly one of a closed set of kinds (ValueKind), and this
answers without materializing anything — a reference stays coords underneath. The scalar
kinds ARE ScalarType values, so getKindAt(i) === ScalarType.Boolean reads naturally.
Under a lift this reports THIS invocation's value, like getRawAt — except the
ValueKind.Omitted answer, which is a fact about the CALL (the caller wrote an empty
comma) and does not change per broadcast position. At a REPEATING parameter it answers for
the group's FIRST argument; per-argument kinds are one level down —
getRepeatingArgsAt(i).getKindAt(j).
There are THREE argument positions:
| form | position | answer |
|---|---|---|
CUMIPMT(0.01,120,100000,13,24) |
never wrote a 6th | Omitted here — the POSITION fact is getLength (index >= getLength()), not a kind |
CUMIPMT(,120,100000,13,24,0) |
the empty comma | ValueKind.Omitted |
CUMIPMT(…,E1), E1 blank |
a blank CELL | ValueKind.Reference — blankness is the cell's value, not the argument |
The kind is the argument's own and it survives what it passes through:
VLOOKUP(10,10,1,TRUE) is #N/A where VLOOKUP(10,{10},1,TRUE) is 10 — scalar vs 1x1
array; TYPE(IF(TRUE,10)) is 1 where TYPE(IF(TRUE,{10})) is 64 — a selector carries the
kind through in both directions.
How many PARAMETERS the caller supplied.
Indexing throughout matches the BODY's own signature, never the raw call positions
(see getRawAt). index >= getLength() is an ABSENT argument — one the caller never
wrote, whose own JS initializer supplied the value. That is a different thing from an OMITTED
one; see isOmittedAt.
This invocation's value at index, uncoerced.
The PARAMETER index — the position in the body's own signature.
A Scalar means the argument IS a value; an IRange means a container whose members
the body reads itself (a reference materializes on demand); a Lambda means a callable.
At a REPEATING parameter (Repeating<T>, or a rest parameter) this is the ARRAY of that
group's values — one entry per argument the caller wrote — and getRepeatingArgsAt is the
per-argument view of the same thing. Indexing by parameter rather than by call position is
what lets a body ask its questions in the vocabulary it declared:
// IFS(condition: Repeating<[logicalTest: boolean, logicalTrue: AcceptError<any>]>)
args.getRawAt(0); // [test1, value1, test2, value2, …]
args.getRepeatingArgsAt(0).getRawAt(0); // test1
Under a lift the call broadcasts and the body runs once per output cell; this reports the value for THIS invocation — the member at the current broadcast position for a lifted argument, the whole argument for one that is not lifting. That is what every identified caller wants: a body checking for a boolean must see the member, not the array around it.
One boundary is worth knowing: under a lift at a parameter that declares a CONCRETE scalar
type, the member has already been converted by that declaration, so the raw and coerced answers
coincide there. At the Scalar slots this interface exists for, nothing coerces and the value
is the member exactly as the caller's range holds it.
A reference UNION ((A1:A3,C1:C3)) is an array too — one IReferenceRange per area — which
is how a body whose slot took the areas apart can still tell one argument's union from two
arguments (SUMPRODUCT refuses the first and accepts the second).
A nested view over one REPEATING parameter's arguments, so a body can ask the same questions
one level down — getRepeatingArgsAt(0).isOmittedAt(1).
The PARAMETER index of the repeating group (or rest parameter).
The nested view is FLAT over the group's arguments: a period-2 group repeated three times reads as six, in call order. That is where "which one was omitted" has an answer at all — the group as a whole cannot be omitted, only one of its arguments can.
At a non-repeating parameter this returns an EMPTY view (getLength() of 0) rather than
throwing, so a body that folds over its groups needs no special case.
True when the argument at index is DIRECT — Excel's "typed into the list of arguments":
the evaluated value IS a value, not a container or a callable.
The PARAMETER index — the position in the body's own signature.
The derived predicate over getKindAt: true exactly when the kind is one of the
ScalarType members. A blank (ScalarType.Null) IS a value and is direct; an omitted
argument, a reference, an array and a callable are not. Directness is what the
direct-vs-member coercion rules key on (SUM(1,TRUE) is 2 where SUM({1,TRUE}) is 1), so
a body speaking that vocabulary can ask in it.
The current call's arguments, as the body sees them — argument reflection.
Remarks
A declaration answers "what does this parameter mean?", and the marshaller coerces every argument to it. That is right for the overwhelming majority of parameters. What it cannot answer is anything about this particular call:
Excel has behaviors that turn on exactly those questions, and they cluster at the parameters Excel declares
any— where there is nothing to express as a type, because Excel declined to express one.CUMIPMTrefuses a boolean at every slot whileFVcoerces one at every slot; the difference is not the values, it is thatCUMIPMT's slots areanyand its body decides.It is REFLECTION and nothing else — two questions, and no third:
Coercion is deliberately absent. A body that wants a number calls IFormulaContext.parseAsNumber on the value this hands it — that already takes a value, already propagates an incoming error with its identity, and already answers
#VALUE!for a boolean, which IS the rule ananyslot needs. There is no coercion an index can do that a value cannot.Obtained from
FormulaContext.getArgs(), and valid ONLY while the body that asked for it is running. A body must not retain it across anawait, a callback, or a nested evaluation — the views are pooled by dispatch depth, so the one at your depth is handed to the next call at that depth.See
IFormulaContext.getArgs