When a method is invoked by name with prepared argument values, which value-to-parameter mismatches does the runtime accept?
answer
- binding is an assignment check
- widening binds, narrowing does not
- absence needs a slot that allows it
- argument count must match the declaration
- pack a variadic tail yourself
basics
~20 sBinding is an assignment check, not a conversion service: a value of a narrower type goes into a wider slot, boxing crosses the primitive-object boundary where a runtime has one, and everything else — narrowing, absence in a slot that forbids it, a wrong argument count — is rejected at the call.
solid answer
~40 sThe invocation path checks each supplied value against the declared parameter type and performs only the conversions the runtime performs on an ordinary assignment. A subtype instance binds to a supertype slot; a narrower numeric value binds to a wider numeric slot; where a runtime separates primitive values from objects, boxing and unboxing happen at the boundary. Narrowing does not bind, an absent value cannot fill a slot that forbids absence, and the argument count must match the declaration exactly — the argument-filling a compiler does at a source call site does not happen here. The trap is a **variadic tail**: where it is represented as a single collection parameter, the dynamic path must pass one packed collection in that slot, not the elements spread out.
code
pseudocode · 6 linesfunction log(level, ...parts) // two parameters: parts is one collection
log("warn", "disk", "full") // source call site: the tail is packed for you
invoke("log", ["warn", ["disk", "full"]]) // 2 arguments -> binds
invoke("log", ["warn", "disk", "full"]) // 3 arguments -> no member matchesgo deeper
Remember that a call built at run time has to supply every argument itself, with values already of the right types, and that nothing along the way converts text into the type a parameter declares.
Explain the assignment rule underneath: widening and boxing bind, narrowing and absence in a forbidding slot do not, and a variadic tail is a single collection parameter rather than a licence to spread values.
Diagnose the confusing form of this failure: a spread variadic tail reports as no matching member, which looks like a discovery bug. Build argument lists from the chosen member's parameter list instead.
Decide whether your platform's manifests carry typed values or text that some layer must parse, and own the consequence: whichever layer parses is the layer that must report a useful error.
## Binding is a check, not a conversion service Once a member has been chosen, its parameters are slots with declared types, and the invocation path has to put the caller's values into them. It does not parse, coerce or repair anything. It applies the same rule an ordinary assignment applies: is this value assignable to that declared type? If yes, the value goes in. If no, the call is rejected before the body runs, and the failure names a type mismatch rather than anything about the target's logic. That single rule explains almost every binding surprise a wiring container produces at startup. ## Which mismatches bind and which fail | Supplied value | Declared slot | Outcome | |---|---|---| | an instance of a subtype | a supertype | binds | | an instance of a supertype | a subtype | rejected | | a narrower numeric value | a wider numeric slot | binds where the runtime widens | | a wider numeric value | a narrower numeric slot | rejected — nothing narrows silently | | a boxed number | a primitive-valued slot | binds where the runtime unboxes | | absence | a slot that cannot hold absence | rejected | | three tail values, spread | a variadic tail parameter | rejected — wrong argument count | | three tail values, packed | a variadic tail parameter | binds | The direction of the numeric row is the one candidates reverse. **Widening binds** — a value from a narrower range always fits a wider slot, so no information is lost and the runtime performs it. **Narrowing does not** — it would have to discard range, and a silent discard is exactly what an assignment rule exists to prevent. ## Arity is exact The dynamic path takes a list of arguments and it must be the same length as the parameter list. This catches people out because source call sites get help the dynamic path never sees: - Where a language lets a declaration carry **default values**, it is very often the *call site* that supplies the missing arguments as the code is built. A dynamic call has no call site, so omitting them supplies too few arguments and matches nothing. Runtimes differ here: some record declared defaults in metadata so a caller can read and supply them, but the call itself still takes the full list. - Where a method is declared to receive an implicit receiver, that receiver is a separate input to the invocation, not one of the arguments — mixing the two is a classic off-by-one. ## The variadic tail is one parameter A variadic tail is convenience at the call site. Where it is represented as a single collection parameter, the packing that a source call site gets for free simply does not happen on the dynamic path, and there are two symmetrical mistakes: 1. **Spreading the elements.** Handing over three tail values as three arguments to a member that declares two parameters produces a count mismatch, and the error says "no matching member" — which reads like a discovery problem and sends people looking in the wrong place. 2. **Packing something that was already one value.** When the single intended tail element is itself a collection, an invoker that helpfully spreads collections turns one argument into several. The same code then behaves differently depending on the runtime class of one value. The defence is to build the argument list from the chosen member's declared parameter list rather than from the shape of the data you happen to hold. ## Field slots bind by the same rule A dynamic write to a named field runs the same assignment check against the field's declared type. Two things differ, and both simplify matters: a field has exactly one type, so there is no selection to get wrong, and there is no argument count. What does not change is when the check happens — at the write, at run time, where a mismatch is an exception rather than a build error. A dynamic **read** inverts the problem. The value comes back described only as "some object", because the reading code named the field with text and the type system had nothing to work with. The caller must narrow it before use, and where a runtime separates primitive values from objects, the read hands back a boxed value even though the slot held a primitive one. ## What this means in practice - Treat a binding failure and a member-not-found failure as different diagnoses; a spread variadic tail produces the second while being a case of the first. - Convert values to the declared parameter types *before* calling, using the member's own parameter list as the specification. - Never rely on a value "looking like" the right type — textual configuration must be parsed by your code, because the invocation path will not do it for you. - Expect an optional, absent collaborator to be the most common startup binding failure, and check what the slot allows before you pass nothing.
- A dynamic write to a named field fails with a type error. Which rule rejected it?The same assignment check the parameters use, applied to the field's declared type. A subtype instance and a widening numeric value go in; a supertype instance, a narrowing value, or absence in a slot that forbids it do not. The difference from a parameter is only that a field has one type and no argument count, so there is no selection step to blame.
- Why must a caller narrow the result of a dynamic field read?Because the field was named by text, the type system had nothing to reason about, and the read is described as returning some object. Where a runtime separates primitive values from objects, the read also hands back a boxed value, so the caller narrows twice: to the expected type and, in effect, out of the box.
saying these in an interview costs you the question
- Says the invocation path will coerce a textual value into the declared parameter type.
- Believes a value of a wider type binds to a narrower declared parameter.
- Expects declared default values to fill in missing arguments on a dynamic call.
- Passes variadic tail values as separate arguments and blames member discovery.
- Assumes an absent value is acceptable for any parameter whatsoever.