Numeric widening 2026-10-31
A language-defined conversion from a numeric type to another admitted numeric type, such as Scala's conversion of an
Int argument to Double. It can make an overloaded method applicable even when the argument's static type differs from the parameter type. It does not imply a subtyping relationship between every pair of numeric types or exact representation of every source value; sufficiently large integers can lose precision in a floating-point target. [ref]Scala 2.11 expressions specification. Numeric Widening
Apply overload selection followed by dynamic dispatch: method overloading chooses a signature from the receiver's static type and argument static types, then dynamic dispatch chooses an override of that signature using the receiver's runtime type. A
super call selects the inherited implementation; a call on the companion object B uses that object's own methods.[ref]Scala 2.11 expressions specification. Static overload resolution; this and super
The output, in order, isFor
f(x:X) in B
f(y:Y) in A
f(x:X) in A, g(x:X) in B
f(x:X) in ObjB, g(y:Y) in ObjB, h(y:Y) in B, h(z:Z) in C
f(y:Y) in C, m(a:A) in Y, k(x:X) in Ca.f(x), the argument's static type is X, selecting f(X); the receiver's runtime type B selects B.f(X). For a.f(z), the receiver's static type A exposes only f(X) and f(Y). The Z argument fits both, and f(Y) is more specific. B does not override that signature, so A.f(Y) runs. The additional overload B.f(Z) is invisible through a.For
c.g(z), the inherited instance B.g(X) calls super.f(x), which is A.f(X). For c.h(z), method overloading selects C.h(Z). Its super.h(z) calls B.h(Y), where the local parameter has static type Y. Consequently B.g(y) selects g(Y) on the companion object, whose only f signature is f(X).Finally,
c.k(x) calls x.m(this). The receiver x has runtime type Y, so dynamic dispatch enters Y.m(A). Inside that method, this has static type Y, selecting a.f(Y); the actual argument object is c, so dynamic dispatch reaches C.f(Y). ThereforeThe five printed lines, including the comma and space after every fragment, are:Scala dynamic dispatch uses the receiver's runtime software class, even though
f1 in C,
f1 in C, f2 in B,
f1 in C, f2 in A, f3 in B,
f1 in A, f2 in A, f3 in ObjB, f4 in B,
f1 in ObjC, f2 in A, f3 in B, f4 in ObjC, f5 in C, a has the static type A. Thus a.f1 uses C.f1, and a.f2 uses B.f2 whose unqualified f1 still dispatches on the new C receiver.For
a.f3, B.f3 explicitly selects super.f2, which is A.f2. Inside A.f2, the unqualified call to f1 still dispatches to C.f1; super does not replace the receiver. For a.f4, B.f3 in the body of class B means a call on the singleton object B, not on this. That object's f3 calls A.f2, which calls A.f1.Finally
a.f5 calls C.f4 on the singleton object C. That object extends class B, overrides f1 and f4, and inherits class B's f3. Its f4 calls this inherited f3; that selects A.f2, whose f1 dispatches to the object's own override. These distinctions follow member selection and super semantics.[ref]Scala 2.11 expressions specification. This and Super; Designators, member selection
The output is
BADDCD, without a newline. Method overloading chooses a signature using static types, including the instantiated class type parameter; numeric widening can make a Double parameter applicable to an Int argument.a.m(o1)choosesm(Int), which is more specific than the polymorphic method:B.a.m(o2)usesm[Double](Double):A.b.m(o1)comparesm(Any)andm(Double).Intcan widen toDouble, andDoubleis more specific thanAny:D.b.m(o2)choosesm(Double)overm(Any):D.c.m(o1)compares the instantiatedm(Int)withm(Double)and choosesm(Int):C.c.m(o2)can use onlym(Double):D.
The third call is the subtle one: the existence of
m(Any) does not prevent the more specific numeric overload from winning. These are Scala 2 method overloading and numeric widening rules. [ref]