Expressions
Method dispatch
An instance method call runs the override of the receiver's actual class, whatever static type names the receiver.
since 0.1.0-alpha.1linuxwindowswasm
Description
An unqualified instance method call runs the method of the receiver's runtime class. This holds for receivers typed as an interface and for receivers typed as a class. If Dog extends Animal and overrides speak(), then speak() on any Animal-typed variable, field, parameter or method reference that holds a Dog runs Dog's version, not the version of the type the variable was declared with.
Operators, constructor selection, and calls to static or namespace functions are resolved by the names written in the program and do not dispatch on the runtime class. A base-qualified call (this.Base::m()) is resolved statically to Base's own method when the bare call would be unambiguous: this is how an override calls the method it overrides. Where the bare call would be ambiguous between distinct paths, this.Base::m() selects Base's path and dispatches on the receiver's runtime class to the most-derived body on that path. It becomes a direct call under the same rule as an unqualified call (see lang.distinct).
The compiler may turn a call into a direct call when it can prove that no class in the program overrides the method below the receiver's static type. That is purely a speed optimization: it never changes which method runs.
The override of the actual class runs
class Animal {
string speak() => "...";
}
class Dog : Animal {
string speak() => "Woof";
}
class Cat : Animal {
string speak(string s) => "Meow ${s}";
}
string callSpeak(Animal a) => a.speak();
console.writeln(callSpeak(Dog()));
console.writeln(callSpeak(Animal()));
Animal a = Dog();
console.writeln(a.speak());
console.writeln(Dog().Animal::speak());
Array<Animal> zoo = [Animal(), Dog()];
for (Animal z in zoo) {
console.writeln(z.speak());
}
console.writeln(Cat().speak());
console.writeln(Cat().speak("x"));
Woof
...
Woof
...
...
Woof
...
Meow x
Rules
- Dispatch uses the runtime class of the receiver, found by the method's name and its number of parameters. It does not use the parameter types to choose between overloads at run time.
- Because of that, an overridden method that shares its name and parameter count with another overload on the receiver's static type is a compile error at the call site. Give the overloads different parameter counts or different names, or qualify the call explicitly.
- Overloads that differ in parameter count (the common case) stay legal and dispatch correctly. Only same-count siblings are rejected.
- Interface dispatch has the same rule.
- Path dispatch inherits the name-and-parameter-count limit.
Examples
A qualified call in a class that holds a distinct method on two paths runs the override on that
path, through a receiver typed as the combining class as well:
Path dispatch through a base-typed receiver
class Race { distinct string cry() => "..."; }
class Job { distinct string cry() => "..."; }
class Elf : Race { string cry() => "For the grove!"; }
class Fighter : Job { string cry() => "Hold the line!"; }
class Unit : Race, Job {
string shout() => this.Race::cry() + " " + this.Job::cry();
}
class Archer : Unit, Elf, Fighter { }
Unit u = Archer();
console.writeln(u.shout());
console.writeln(Unit().shout());
For the grove! Hold the line!
... ...
class Animal {
string speak(string s) => "a-str";
string speak(int n) => "a-int";
}
class Dog : Animal {
string speak(int n) => "d-int"; // overrides speak(int)
}
Animal a = Dog();
console.writeln(a.speak(5));
// error: method 'speak' is overridden below 'Animal' but shares its arity with another overload
Notes
A subclass method with the same name but a different parameter count, like Cat.speak(string) above, is a separate overload, not an override: Cat().speak() still reaches the version inherited from Animal.
See also
- Array — An ordered sequence of values of one type,
Array<T>, with value semantics.