5.3.1.11 Procedure Invocation Argument Processing
Note
This section describes the implementation of MS-VBAL §5.3.1.11 Procedure Invocation Argument Processing.
Procedures are invoked through
IProcedureInvoker.
RDCore.Runtime.Execution.RuntimeProcedureInvoker is the IProcedureInvoker implementation.
IProcedureInvoker and CallableBindingHandle
are the call contract: given a procedure symbol, a resolver, and arguments, run the procedure.
RuntimeProcedureInvoker implements the call: frame setup, ByVal/ByRef parameter binding,
function result values, and the call-depth guard. See
RD-VBAL §3.5.5 Placement and Licensing.
Invocation Sites
Each of these invokes a procedure through IProcedureInvoker:
| Site | Described in |
|---|---|
A Call statement, or a bare call statement |
RD-VBAL §5.4.2.1 Call Statement |
A bare reference to a Sub, Function or Property Get |
RD-VBAL §5.6.10 Simple Name Expressions |
An IndexExpressionNode whose Callee is a bare name resolving to a Sub, Function or Property Get |
RD-VBAL §5.6.13 Index Expressions |
An index expression whose Callee is a bare name is checked for a procedure before the general recursive
Evaluate(Callee). An index expression with a procedure Callee is the only shape that recurses (e.g.
Foo(n - 1), even from within Foo's own body).
Static Semantics
Omitting an argument with a comma (Foo(1, , 3)) is legal MS-VBA for a parameter of any declared type.
MS-VBA defers argument-type validation to run time: it never rejects an omitted argument at compile time.
Note
Not implemented. No diagnostic flags IsMissing used on a non-Variant parameter, for which MS-VBA's
IsMissing always returns False. RD-VBA's IsMissing has no runtime implementation; see
RD-VBAL §6.1.2.7 Information.
Runtime Semantics
A procedure invocation runs these steps:
RuntimeExpressionEvaluator.MapArgumentsmaps the arguments to the parameters (see Argument Mapping).- The argument-evaluation loop of
RuntimeExpressionEvaluatorproduces one argument per parameter: aParamArraycollection, anOptionalparameter's default, aByRefreference, or a Let-coerced copy (see Optional Parameters, ParamArray and ByVal and ByRef Binding). RuntimeProcedureInvokerlooks up the callee's own lowered body by symbol.RuntimeProcedureInvokerpushes a fresh ICallStackFrame for the callee. Exceeding the call-depth limit raises run-time error 28.- For a
FunctionorProperty Get,ICallStackFrame.ReturnValueis seeded to the default value of the declared return type. - Each parameter is bound on the callee's frame: by reference through
CallStackFrame.PushByRef, or to a fresh value binding. RuntimeProcedureInvoker.HoistLocalscreates the procedure'sDimandStaticlocals.- The callee's body runs through the same
ProcedureExecutor, withRuntimeEvaluationContext.Scopeset to the procedure's ownUri. RuntimeProcedureInvokerreports the callee's outcome back to the caller (see Return Value).
Argument Mapping
RuntimeExpressionEvaluator.MapArguments maps named arguments and Optional parameters by the two-pass
argument-mapping algorithm of MS-VBAL §5.3.1.11:
- Each argument is mapped to a parameter:
- Positional arguments map to parameters left to right.
- A NamedArgumentNode maps to its parameter by name.
- A MissingArgumentNode mapped to a
non-
Optionalparameter raises error 448. Error 448 is checked during argument mapping, per MS-VBAL §5.3.1.11; it is not part of the general error-449 sweep that follows. - An extra positional argument beyond the parameter count raises error 450.
- Positional arguments from a trailing
ParamArrayparameter's position onward are collected by that parameter (see ParamArray).
- A general sweep follows argument mapping, and raises error 449 for a non-
Optionalparameter that has no argument mapped to it.
The errors are listed in Run-time Errors.
Optional Parameters
An unmapped Optional parameter uses
VBParameterSymbol.DefaultValue directly.
VBParameterSymbol.DefaultValue holds an Optional parameter's default value, and is described in
RD-VBAL §5.3.1.5 Parameter Lists.
VBParameterSymbol.DefaultValue |
Value bound to the unmapped Optional parameter |
|---|---|
The pre-computed default value (the declaration has an = ... default-value clause) |
That default value. |
null (the declaration has no = ... default-value clause) |
The declared type's default value. |
An unmapped Optional parameter's default is bound with no Let-coercion and no reference binding: an unmapped
Optional parameter has no caller expression to coerce from or alias.
Omitted Arguments and IsMissing
In MS-VBA, IsMissing
(MS-VBAL §6.1.2.7.1.6 IsMissing)
depends on an omitted argument's parameter being Variant (see
RD-VBAL §6.1.2.7 Information):
- The value of an omitted argument is a
VT_ERRORVariantcarryingDISP_E_PARAMNOTFOUND. IsMissingalways returnsFalsefor a non-Variantparameter, without raising an error. Only aVariantcan hold theDISP_E_PARAMNOTFOUNDsentinel.
ParamArray
The arguments passed to a ParamArray parameter are collected by
RuntimeExpressionEvaluator.CollectParamArrayArguments.
- A trailing ParamArrayParameterSymbol collects every positional argument from its own position onward (MS-VBAL §5.3.1.11), instead of mapping 1:1.
RuntimeExpressionEvaluator.CollectParamArrayArgumentscollects the arguments into a fresh, 0-based array ofVariant.- Each argument collected into a
ParamArrayis Let-coerced toVariant, the same way any otherByValargument is Let-coerced to its parameter's declared type (see RD-VBAL §5.5.1.2 Runtime semantics). - A
ParamArrayparameter is never targetable by name. - A
ParamArrayparameter is always considered satisfied, even with nothing collected: it receives an empty array, not error 449.
Call Callee(100) against a ParamArray rest() parameter with no arguments left over collects an empty array
whose Size is 0. An empty ParamArray array has a non-positive storage size, and takes the
SessionStorage.TryAllocate non-positive-size path, like Nothing, Null, Empty and an uninitialized array.
A ParamArray call with nothing left over is the most common ParamArray call shape, which is why zero-size
storage is supported. See RD-VBAL §2.3.1.2 Session Services.
ByVal and ByRef Binding
ByRef parameter binding follows MS-VBAL §5.3.1.11. A parameter is bound ByVal unless both of these hold:
- the parameter is declared
ByRef; and - the argument-evaluation loop of
RuntimeExpressionEvaluatorresolves the argument to an addressable, writable variable whose declared type exactly matches the parameter's declared type, or the parameter's declared type isVariant.
These two shapes (an exact declared-type match, or a Variant parameter) are the two that MS-VBAL §5.3.1.11
allows a plain reference binding for without a class/Object copy-back.
| Parameter | Argument | Binding |
|---|---|---|
ByVal |
Any | A fresh, Let-coerced ValueBindingHandle, which never aliases the caller's storage. |
ByRef |
An addressable, writable variable whose declared type exactly matches the parameter's: a name (x), or a public variable of an object (obj.Count, .Count) |
A reference binding. |
ByRef, declared Variant |
An addressable, writable variable, of either of those | A reference binding. |
ByRef, declared as a class or Object |
A variable of a different declared type | A ByVal-style copy: the class/Object copy-back is not modeled. |
ByRef |
Not recognized as aliasable: an expression, a literal, a variable of mismatched declared type, a read-only target | The same ByVal-style Let-coerced copy: MS-VBAL §5.3.1.11's "otherwise" case. Never an error. |
Optional |
None (unmapped) | The default value; see Optional Parameters. |
ParamArray |
The remaining positional arguments | A fresh, 0-based Variant array; see ParamArray. |
A reference binding is made as follows:
- When a
ByRefargument is aliasable, the argument is passed as a VBRuntimeReference, which carries the variable's address itself. RuntimeProcedureInvokerbinds the aliasedByRefparameter throughCallStackFrame.PushByRef.CallStackFrame.PushByRefcreates a name-aliasing binding onto the same address as the caller's variable, not a copy.- ISymbolResolver
.TryGetAddressandICallStackFrame.TryGetAddressresolve theByRef-aliased parameter to the aliased address. See RD-VBAL §2.3.1.3 Name Resolution.
A write to a ByRef-aliased parameter inside the callee is immediately visible to the caller.
CallStackFrame.ReleaseAll never deallocates the address of a ByRef alias. The address belongs to its
original allocator; the callee only borrows it.
The ByVal and ByRef-fallback copy is made by a direct Let-coercion call, since there is no addressable
symbol to Let-assign through. A Let-assignment to the function result variable makes the same lower-level call,
for the same reason; see RD-VBAL §5.4.3.8 Let Statement.
Note
Not implemented. The MS-VBAL §5.3.1.11 class/Object copy-back for ByRef arguments is not modeled.
A ByRef parameter declared as a class or Object, whose argument is a variable of a different declared
type, falls through to a ByVal-style copy.
👉 UDT values must be passed by reference (
ByRef). See RD-VBAL §2.5.2.1.3 User-Defined Type (UDT) Values.
Standard Library Calls
- The evaluator coerces every argument of an external (standard library) call to the parameter's declared type on the way in.
- A standard library member's
Variantparameter accepts its argument.
👉 An external call carries runtime values rather than typed ones, so the declared type a member was called with is recovered when the call is dispatched. See RD-VBAL §6.0 Standard Library and RD-VBAL §6.1.2.11 Strings (
Len/LenB).
Frame Setup
RuntimeProcedureInvokerlooks up the callee's own lowered body by symbol, and pushes a freshICallStackFramefor the callee.RuntimeCallStackenforces a call-depth limit, inOnBeforeTryPush. Exceeding the call-depth limit raises run-time error 28, "Out of stack space".- The
Mevalue is pushed to the stack frame of an instance member call as any parameter is. See RD-VBAL §5.6.11 Instance Expressions. - Each invocation of a
FunctionorProperty Getgets a fresh function result variable (MS-VBAL §5.3.1 Procedure Declarations), modeled asICallStackFrame.ReturnValue, a single per-activation slot.ReturnValueis seeded to the default value of the procedure's declared return type before the procedure body runs. See RD-VBAL §5.3.1.6 Subroutine and Function Declarations. - MS-VBAL procedure invocation step 4 reads: "create the function result variable and any procedure extent
local variables declared within the procedure". RD-VBA implements it for
DimandStaticlocals as well as for the function result variable:RuntimeProcedureInvoker.HoistLocalswalks the procedure'sLocalsimmediately after parameter binding, before the body runs. See RD-VBAL §5.4.3.1 Local Variable Declarations. RuntimeProcedureInvokeralways setsRuntimeEvaluationContext.Scopeto the invoked procedure's ownUrifor the whole activation.RuntimeProcedureInvokerruns the callee body through the sameProcedureExecutoras the caller. See RD-VBAL §3.5.4 Execution.
Return Value
RuntimeProcedureInvoker reports the callee's outcome back to the caller:
| Callee outcome | Invocation result |
|---|---|
ExitProcedure, from a Function or Property Get |
A successful result: the value of its own function result variable. |
ExitProcedure, from a Sub |
A successful result: VBVoidValue. |
Error |
A RuntimeSemanticsEvaluationResult error. |
A Function or Property Get return value is held in ICallStackFrame.ReturnValue, which models the function
result variable. RuntimeProcedureInvoker reads frame.ReturnValue back once ExitProcedure is reached, and
reports it as the call's result.
The read-back happens however ExitProcedure was reached: an explicit
Exit Function/Exit Property, or falling off the end of the body. See
RD-VBAL §5.4.2.18 Exit Function Statement and
RD-VBAL §5.4.2.19 Exit Property Statement.
The caller's own ExecuteCall turns a callee's RuntimeSemanticsEvaluationResult error back into an Error
outcome. A nested call's runtime error therefore propagates as any other runtime error does; see
RD-VBAL §5.4.4.1 On Error Statement.
Run-time Errors
| Condition | Run-time error |
|---|---|
A MissingArgumentNode (an argument omitted with a comma) is mapped to a non-Optional parameter. Checked during argument mapping. |
448 — Named argument not found |
After argument mapping, a non-Optional parameter other than a ParamArray has no argument mapped to it. |
449 — Argument not optional |
An extra positional argument goes beyond the parameter count, and there is no trailing ParamArray parameter. |
450 — Wrong number of arguments or invalid property assignment |
Pushing the callee's frame exceeds the call-depth limit (RuntimeCallStack.OnBeforeTryPush). |
28 — Out of stack space |
Implementation
| Type or member | Role |
|---|---|
IProcedureInvoker, CallableBindingHandle |
The call contract (RDCore.SDK). |
RDCore.Runtime.Execution.RuntimeProcedureInvoker |
The IProcedureInvoker implementation (RDCore.Runtime). |
RuntimeExpressionEvaluator.MapArguments |
Argument mapping; errors 448, 449 and 450. |
RuntimeExpressionEvaluator.CollectParamArrayArguments |
ParamArray collection. |
RuntimeExpressionEvaluator.ProcedureInvoker, RuntimeExpressionEvaluator.LetCoercionProvider |
Settable properties, not constructor parameters. |
CallStackFrame.PushByRef |
Binds an aliased ByRef parameter. |
CallStackFrame.ReleaseAll |
Frees the frame's storage, never a ByRef alias's address. |
RuntimeCallStack.OnBeforeTryPush |
Enforces the call-depth limit; error 28. |
ICallStackFrame.ReturnValue |
The function result variable. |
ICallStackFrame.TryGetAddress, ISymbolResolver.TryGetAddress |
Resolve a ByRef-aliased parameter to the aliased address. |
VBParameterSymbol.DefaultValue |
An Optional parameter's pre-computed default value. |
ParamArrayParameterSymbol |
A trailing ParamArray parameter. |
VBRuntimeReference |
An aliasable ByRef argument: the variable's address itself. |
ValueBindingHandle |
A ByVal (or ByRef-fallback) parameter's fresh binding. |
RuntimeExpressionEvaluator.ProcedureInvoker is settable because RuntimeProcedureInvoker needs a
ProcedureExecutor built from a StatementRuntimeSemanticsProvider built from the same
RuntimeExpressionEvaluator: the evaluator must exist before its invoker can be built. See
RD-VBAL §2.3.1 Composition Root.
⏮️ RD-VBAL §5.3.1.10 Lifecycle Handler Declarations | ⏭️ RD-VBAL §5.4 Procedure Bodies and Statements