Question on implicit search

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.

3 Likes