Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala: How can an import prevent finding an implicit value?

I could use suggestions debugging an implicit:

I want to use the implicit, x:

type T
trait HasT {
  implicit def x: T = ...
}

But I also need a wildcard import from some package foo. I've tried two different ways of introducing both:

class UseT extends HasT {
  import foo._
  implicitly[T] // fails! "could not find implicit value"
  // use foo stuff
}

and

class UseT {
  object hasT extends HasT
  import hasT.x
  import foo._
  implicitly[T] // fails! "could not find implicit value"
}

Both fail with "could not find" (not "ambiguous implicits values").

This happens while implicit identifier x: T is accessible at the point of method call via either inheritance or importing.

My workaround is to rebind x to an implicit val before the import. Both of the following work:

implicit val x2: T = implicitly[T]
import foo._
implicitly[T] // works!

and

implicit val x2: T = x
import foo._
implicitly[T] // works!

What value could possibly be in foo to cause this behavior?

My first guess is that there is some competing implicit in foo, but if it were higher priority, the following implicitly would still work, and if it were an ambiguous implicit, I'd be getting a different a different error.

edit: Miles Sabin's guess was correct! I found the shadowing implicit: timeColumnType. I still don't completely understand how this works given Som Snytt's observation that the shadowing implicit was wildcard imported (lower priority) while the shadowed was inherited (part of highest priority), so I'll leave the whole post here for posterity.

retracted: A second guess, offered by miles sabin, is implicit shadowing. I've since clarified my post to exclude that possibility. That case would be consistent with my errors if I had tried package hasT extends HasT; import hasT._, but As som-snytt points out, those two cases would not result in shadowing.

In my specific case, this can be confirmed by changing the name of the implicit I'm trying to use.
(This is false. I likely missed a publishLocal or reload while using this test to verify.)

context: I'm actually trying to use slick. The implicit T above is actually a column type mapping:

import slick.driver.JdbcProfile

class Custom { ... } // stored as `Long` in postgres

trait ColumnTypes {
  val profile: JdbcProfile
  import profile.api._ // this is `foo` above
  type T = profile.BaseColumnType[Custom]
  implicit def customColumnType: T = 
    MappedColumnType.base[Custom, Long](_.toLong, Custom.fromLong)
}

class DatabaseSchema(val profile: JdbcProfile) extends ColumnTypes {
  // `implicitly[T]` does not fail here.
  import profile.api._ // this is also `foo` above
  // `implicitly[T]` fails here, but it's needed for the following:
  class CustomTable(tag: Tag) extends Table[Custom](tag, "CUSTOMS") {
    // following fails unless I rebind customColumnType to a local implicit
    def custom = column[Custom]("CUSTOM")
    def * = custom
  }
}

The type of api/foo is JdbcProfile.API. The offending implicit is probably here, but I can't tell why. I'll try blocking some of those from being imported and see if I can narrow it down.

like image 277
stewSquared Avatar asked Nov 20 '16 21:11

stewSquared


2 Answers

I think it's most likely that foo contains a definition named x. When (wildcard) imported from foo it shadows the local definition,

scala> object foo { val x: Boolean = true }
defined object foo

scala> implicit val x: Int = 23
x: Int = 23

scala> implicitly[Int]
res0: Int = 23

scala> import foo._
import foo._

scala> implicitly[Int]
<console>:17: error: could not find implicit value for parameter e: Int
       implicitly[Int]
                 ^
like image 189
Miles Sabin Avatar answered Nov 14 '22 04:11

Miles Sabin


This is clearly a bug in implicit search.

First, eligible are all identifiers x that can be accessed at the point of the method call without a prefix and that denote an implicit definition or an implicit parameter. An eligible identifier may thus be a local name, or a member of an enclosing template, or it may be have been made accessible without a prefix through an import clause.

In the example, unprefixed x refers to the inherited symbol. X.x is not accessible without a prefix.

Implicit search is fumbling the import.

$ scala
Welcome to Scala 2.12.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.

scala> :pa
// Entering paste mode (ctrl-D to finish)

object X { def x: Int = 42 }

trait T { def x: Int = 17 }

object Y extends T {
  import X._
  def f = x
}

// Exiting paste mode, now interpreting.

defined object X
defined trait T
defined object Y

scala> Y.f
res0: Int = 17

scala> :pa
// Entering paste mode (ctrl-D to finish)

object X { implicit def x: Int = 42 }

trait T { implicit def x: Int = 17 }

object Y extends T {
  import X._
  def f: Int = implicitly[Int]
}

// Exiting paste mode, now interpreting.

<pastie>:19: error: could not find implicit value for parameter e: Int
         def f: Int = implicitly[Int]
                                ^

scala> :pa
// Entering paste mode (ctrl-D to finish)

object X { implicit def x: Int = 42 }

trait T { implicit def x: Int = 17 }

object Y extends T {
  import X.{x => _, _}          // avoids bug, but is redundant
  def f: Int = implicitly[Int]
}

// Exiting paste mode, now interpreting.

defined object X
defined trait T
defined object Y

scala> 

The other example using the REPL is scoped this way:

scala> :pa
// Entering paste mode (ctrl-D to finish)

object X { def x: Int = 42 }
object Y { implicit def x: Int = 17 }
object Z {
  import Y.x
  def f = {
    import X._
    x
  }
}

// Exiting paste mode, now interpreting.

<pastie>:19: error: reference to x is ambiguous;
it is imported twice in the same scope by
import X._
and import Y.x
           x
           ^

Where x is not available at all, and the implicit is correctly excluded.

Just to confirm:

$ scala -Xlog-implicits
Welcome to Scala 2.12.0 (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_111).
Type in expressions for evaluation. Or try :help.

scala> :pa
// Entering paste mode (ctrl-D to finish)

object X { implicit def x: Int = 42 }

trait T { implicit def x: Int = 17 }

object Y extends T {
  import X._
  def f: Int = implicitly[Int]
}

// Exiting paste mode, now interpreting.

<console>:17: x is not a valid implicit value for Int because:
candidate implicit method x in object X is shadowed by method x in trait T
         def f: Int = implicitly[Int]
                                ^
<console>:17: x is not a valid implicit value for Int because:
candidate implicit method x in object X is shadowed by method x in trait T
         def f: Int = implicitly[Int]
                                ^
<console>:17: error: could not find implicit value for parameter e: Int
         def f: Int = implicitly[Int]
                                ^

scala> 

Probably https://issues.scala-lang.org/browse/SI-9208

like image 2
som-snytt Avatar answered Nov 14 '22 04:11

som-snytt