I thought I understood implicit search, but this confuses me:
trait Foo:
trait A
def foo(using a: A) = a
object Bar extends Foo:
given A with {}
object Test:
Bar.foo
How is the given A
found in the call to Bar.foo
?
I thought I understood implicit search, but this confuses me:
trait Foo:
trait A
def foo(using a: A) = a
object Bar extends Foo:
given A with {}
object Test:
Bar.foo
How is the given A
found in the call to Bar.foo
?
Note that the same thing works in Scala 2, so we don’t need to consider Scala 3 changes in order to explain this:
trait Foo {
trait A
def foo(implicit a: A) = a
}
object Bar extends Foo {
implicit val a: A = new A { }
}
object Test {
Bar.foo
}
It’s crucial to this example that A
is nested inside Foo
. If we move A
to the top level, it doesn’t work.
Because A
is inside Foo
, when we write Bar.foo
, the type of the missing implicit parameter isn’t merely A
, it’s Bar.A
. Because Bar
is part of this type, Bar
’s implicit scope is searched and the implicit is found.
Donning my language-lawyer wig to unpack that at greater length:
The chapter of the Scala 2 spec that covers implicit lookup is Implicits | Scala 2.13
There, we read:
The implicit scope of a type T consists of all companion modules of classes that are associated with the implicit parameter’s type. Here, we say a class C is associated with a type T if it is a base class of some part of T.
What does “part” mean here? The spec goes on to define that via a series of clauses, and the relevant clause here says that:
if T is a type projection S#U, the parts of S as well as T itself
are parts.
It may not be obvious that Bar.A
is a type projection, but in SLS 3.2.5 (Types | Scala 2.13) we read:
A qualified type designator has the form p.t [and] is equivalent to the type projection
p.type#t
.
So Bar.A
is shorthand for Bar.type#A
and the rule about type projections applies.