LEVIATHAN v962456e · 962456eee1

Declarations

Argument binding

How a call's arguments are matched to parameters, filled by defaults or injection, and how overloads are ranked.

since 0.1.0-alpha.1linuxwindowswasm

Description

A call to a function, method or constructor is resolved as one set of bindings from arguments to parameters. The steps are:

  1. Positional arguments bind to the parameters in order, starting with the first.
  2. Named arguments (name: value) bind to the parameter with that name. A name that matches no parameter, or a parameter that is already bound, is an error. Positional arguments must come before named ones; the named ones may be in any order.
  3. Each parameter that is still unbound is filled by its declared = constant default; if it has none, by a matching bind that is in scope (see lang.dependency-injection). A default takes precedence over injection.
  4. The supplied arguments are checked against the types of the parameters they were mapped to. When several overloads fit, the most specific one wins. If two overloads score the same on argument types, the one that needs fewer defaults and injections wins. If they are still tied, the one declared first wins, except that when the tie is between different numeric parameter types the call is ambiguous and you must convert the argument explicitly.

Defaults must be compile-time constants and cannot refer to another parameter or to this. Defaults on parameters whose type is a type variable are not supported. After a call is resolved it behaves as a call with every argument given positionally, so default and named arguments cost nothing extra at run time.

Named arguments, defaults and overload ranking

void show(int a = 1, string s = "x", bool b = false) {
    console.writeln(a.toString() + ":" + s + ":" + b.toString());
}
show();
show(9);
show(b: true);
show(9, b: true);
show(s: "y", a: 3);
int choose(int x) => 10;
int choose(int x, int y = 0) => 20;
console.writeln(choose(1));
console.writeln(choose(1, 2));
string selected(string text) => "text:" + text;
string selected(int number) => "number:" + number.toString();
console.writeln(selected(text: "named"));
console.writeln(selected(number: 4));
void pick(float x) { console.writeln("float"); }
void pick(int x) { console.writeln("int"); }
pick(1);
pick(1.5);
void folded(int seconds = 60 * 60) { console.writeln(seconds); }
folded();
1:x:false
9:x:false
1:x:true
9:x:true
3:y:false
10
20
text:named
number:4
int
float
3600

choose(1) fits both overloads equally on its argument types; the first needs no default, so it wins. selected is chosen by the parameter name alone.

Rules

  • Positional arguments come first and bind in order. A positional argument after a named one is an error.
  • A named argument must name an existing parameter and must not repeat one that is already bound.
  • A parameter with no argument takes its default; otherwise it is filled by injection; otherwise the call is an error ("missing required argument").
  • A default value is always used in preference to an injected one.
  • A default value must be a compile-time constant and cannot refer to another parameter or this.
  • A default may be written on a parameter that is followed by one without a default (void gap(int a = 7, int b)); the later parameter must then be supplied.
  • When several overloads are candidates, the one with the most specific parameter types wins; then the one that uses fewer defaults and injections; then the one declared first.
  • Two overloads that differ only in distinct numeric parameter types (float and byte, for example) and an integer literal argument are ambiguous. Convert the argument explicitly to choose.
  • The same rules apply to constructors and attribute arguments.

Examples

A default beats an injected value:

Default versus injection

interface ILog { string tag(); }
class A : ILog { string tag() => "A"; }
string show(ILog? log = None) {
    if (log != None) { return log.tag(); }
    return "none";
}
string needs(ILog log) => log.tag();
bind ILog => A();
console.writeln(needs());
console.writeln(show());
console.writeln(show(A()));
A
none
A

needs has no default, so the bind fills it. show has the default None, so the bind is ignored.

Binding errors:

Rejected calls

void f(int a, int b = 2) { }
f(c: 1);        // error: no parameter named 'c'
f(1, a: 1);     // error: parameter 'a' is bound both positionally and by name
f(a: 1, 2);     // error: positional argument after named argument
void g(int a, int b) { }
g(1);           // error: missing required argument 'b'
void pick(float x) { }
void pick(byte x) { }
pick(1);        // error: ambiguous numeric argument ... convert it explicitly
not run — a compile error on purpose

See also

  • Methods and functions — Declare callables with block or single-statement bodies, overloads, default parameters, named arguments and type parameters.
  • Constructors — Declare constructors with new, select among them by label and argument types, call base constructors, and construct classes that declare none.
  • Dependency injection: bind and inject — Declare lexically scoped factory bindings that fill unbound parameters automatically, and select a binding explicitly with inject.