Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implicit Lookup fails for implicit view on nested type. #4225

Closed
scabug opened this issue Feb 8, 2011 · 13 comments · Fixed by scala/scala#5999
Closed

Implicit Lookup fails for implicit view on nested type. #4225

scabug opened this issue Feb 8, 2011 · 13 comments · Fixed by scala/scala#5999
Assignees
Milestone

Comments

@scabug
Copy link

scabug commented Feb 8, 2011

trait Foo {
 val name : String
 trait Bar {
   def doSomething : String
   override def toString = "Foo(" + name + ").Bar -> " + doSomething
 }
 object Bar {
   implicit def fromFunction(func : Function0[String]) = new Bar {
     def doSomething = func()
   }
 }
 def andThen(b : Bar) = b
}

object Foo {
  def foo(n : String) = new Foo {
    override val name = n
  }
}

And then run in the REPL

Welcome to Scala version 2.8.1.final (OpenJDK 64-Bit Server VM, Java 1.6.0_20).
Type in expressions to have them evaluated.
Type :help for more information.

scala> Foo.foo("foo") andThen (() => "Bar")
java.lang.AssertionError: assertion failed
	at scala.Predef$$.assert(Predef.scala:77)
	at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:82)
	at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:45)
	at scala.tools.nsc.ast.TreeGen.mkAttributedRef(TreeGen.scala:107)
	at scala.tools.nsc.ast.TreeGen.mkAttributedStableRef(TreeGen.scala:149)
	at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:60)
	at scala.tools.nsc.ast.TreeGen.mkAttributedQualifier(TreeGen.scala:45)
	at scala.tools.nsc.typechecker.Implicits$$ImplicitSearch.typedImplicit0(Implicits.scala:441)
	at scala.tools.nsc.typechecker.Implicits$$ImplicitSearch.typedImplicit(Implicits.scala:359)

=== What is the expected behavior? ===

I expect the implicit lookup to succeed in finding an implicit view for foo.Bar.

=== What do you see instead? ===

A stack trace

=== Additional information ===
(for instance, a link to a relevant mailing list discussion)

=== What versions of the following are you using? ===

  • Scala: 2.8.1
  • Java: OpenJDK 1.6.0_20
  • Operating system: Ubuntu
@scabug
Copy link
Author

scabug commented Feb 8, 2011

Imported From: https://issues.scala-lang.org/browse/SI-4225?orig=1
Reporter: Josh Suereth (joshua.suereth)
See #5946

@scabug
Copy link
Author

scabug commented Feb 8, 2011

@axel22 said:
With trunk, this does not fail, and reports a type mismatch instead:

scala> Foo.foo("foo") andThen (() => "Bar")
<console>:8: error: type mismatch;
 found   : () => java.lang.String
 required: _1.Bar where val _1: java.lang.Object with Foo
       Foo.foo("foo") andThen (() => "Bar"

@scabug
Copy link
Author

scabug commented Feb 22, 2011

@axel22 said:
It was stated on the meeting that the implicit should not be triggered. Perhaps I'm missing something with this ticket, but I do not think so - according to the spec:

7.3 Views
...
Views are applied in two situations:
1. If an expression e is of type T , and T does not conform to the expression’s
expected type pt. In this case an implicit v is searched which is applicable to
e and whose result type conforms to pt. The search proceeds as in the case of
implicit parameters, where the implicit scope is the one of T => pt. If such a
view is found, the expression e is converted to v (e ).

In our case, the T => pt is Function0[String] => _1.Bar, that is Function1[Function0[String], _1.Bar], where _1: Foo. Now, in section on implicit parameters, about how the search proceeds:

The implicit scope of a type T consists of all companion modules (�5.4) of classes
that are associated with the implicit parameter’s type. Here, we say a class C is asso-
ciated with a type T , if it is a base class (�5.1.2) of some part of T . The parts of a type
T are:
...
* if T is a parameterized type S [T1 , . . . , Tn ], the union of the parts of S and
T1 , . . . , Tn ,
...

The implicit scope T above is Function1[Function0[String], _1.Bar], so T2 = _1.Bar, making its companion module eligible for the implicit search.

I've shown this and talked to two more people from the lab, and they think this should be put back to the meeting.

@scabug
Copy link
Author

scabug commented Jun 15, 2011

@adriaanm said:
I'm having a look, but in the meantime -- work around, don't make the compiler invent an existential for the Foo created by Foo.foo("foo"):

trait Foo {
 val name : String
 trait Bar {
   def doSomething : String
   override def toString = "Foo(" + name + ").Bar -> " + doSomething
 }
 object Bar {
   implicit def fromFunction(func : Function0[String]) = new Bar {
     def doSomething = func()
   }
 }
 def andThen(b : Bar) = b
}

object Foo {
  def foo(n : String) = new Foo {
    override val name = n
  }
}

object Test {
   val f = Foo.foo("foo") 
   f andThen (() => "Bar")
}

@scabug
Copy link
Author

scabug commented Jun 15, 2011

@adriaanm said:
simplified test case (it's also probably more of a problem with existentials rather than implicits)

class Foo {
  class Bar
  object Bar {
    implicit def fromString(a: String) = new Bar
  }
  def andThen(b : Bar) = b
}

object Test {
  (new Foo) andThen ("Bar")
}

@scabug
Copy link
Author

scabug commented Jun 15, 2011

@adriaanm said:
the compiler certainly shouldn't crash, but I don't think it's feasible to make this work

the problem is that the target of the andThen call is not stable, and thus, nor is the expected type of andThen's argument. In the "val f" variant above, the type of the argument is f.Bar, but in the original example it's X.Bar forSome {val X : Foo}

before the search for the implicit conversion that converts "Bar" into a suitable argument for andThen has even begun, it is doomed: since we've essentially opened the existential in the expected type, but not in the return type of the fromFunction implicit, those types have become hopelessly incompatible. fromFunction's return type must now be thought of as Y.Bar forSome {val Y : Foo} (renaming only for emphasis), which is not compatible with X.Bar forSome {val X : Foo}

conceptually, it would be reasonable to expect the skolems (existentials that have been opened) X and Y in X.Bar (expected type of andThen's argument) and Y.Bar (return type of the fromFunction implicit) to refer to the same "value", and thus for the type selections to refer to the same type, but this is not how it's implemented (and I'm pretty sure it would be hard to change that)

@scabug
Copy link
Author

scabug commented Aug 16, 2011

@okomok said:
If stable id is ok, this should compile..?
For some reason, type annotation is needed. (In 2.9.1.RC3 and some 2.10.0-SNAPSHOT)

class DependentImplicitTezt {

    trait Bridge

    class Outer {
        class Inner extends Bridge

        object Inner {
            implicit def fromOther(b: Bridge): Inner = throw new Error("todo")
        }

        def run(x: Inner) = throw new Error("todo")
    }

    val o1 = new Outer
    val o2 = new Outer
    val i1 = new o1.Inner
    val i2 = new o2.Inner

    def doesntCompile {
        o1.run(i2) // no
    }

    def workaround1 {
        o1.run(i2: Bridge) // ok
    }

    def workaround2 {
        import o1.Inner.fromOther
        o1.run(i2) // ok
    }
}

@scabug
Copy link
Author

scabug commented Jun 17, 2012

@paulp said:
With the universes and path-dependence running around in reflection, I think this has become a significantly larger problem. I ran into it immediately:

object Test {
  implicit class Ops(val g: scala.reflect.api.JavaUniverse) {
    def op[T: g.TypeTag] = ()
  }
  
  scala.reflect.runtime.universe.op[Int]
}

// uncaught exception during compilation: java.lang.AssertionError
// error: java.lang.AssertionError: assertion failed: mkAttributedQualifier(_1.type, <none>)//
//
// at scala.Predef$.assert(Predef.scala:162)
// at scala.reflect.internal.TreeGen.mkAttributedQualifier(TreeGen.scala:87)
// at scala.reflect.internal.TreeGen.mkAttributedQualifier(TreeGen.scala:60)
// at scala.reflect.internal.TreeGen.mkAttributedRef(TreeGen.scala:120)
// at scala.tools.nsc.typechecker.Implicits$ImplicitSearch.tagOfType(Implicits.scala:1176)
// at scala.tools.nsc.typechecker.Implicits$ImplicitSearch.implicitTagOrOfExpectedType(Implicits.scala:1339)
// at scala.tools.nsc.typechecker.Implicits$ImplicitSearch.bestImplicit(Implicits.scala:1373)

@scabug
Copy link
Author

scabug commented Jun 17, 2012

@retronym said:
That's not going through a normal implicit search; it's a type tag specific code path.

  // [Eugene to Martin] this is the crux of the interaction between
  // implicits and reifiers here we need to turn a (supposedly
  // path-dependent) type into a tree that will be used as a prefix I'm
  // not sure if I've done this right - please, review
  case SingleType(prePre, preSym) =>
    gen.mkAttributedRef(prePre, preSym) setType pre

@scabug
Copy link
Author

scabug commented Jun 17, 2012

@retronym said:
I haven't thought this through fully, but this looks like a workaround:

object Test {
  class Ops[U <: scala.reflect.api.JavaUniverse](val g: U) {
    def op[T: U#TypeTag] = ()
  }
  implicit def Ops(g: scala.reflect.api.JavaUniverse): Ops[g.type] = new Ops(g)
  Ops(scala.reflect.runtime.universe).op[Int]
  scala.reflect.runtime.universe.op[Int]
}

@scabug
Copy link
Author

scabug commented Jun 18, 2012

@retronym said:
Extracted the typeTag crasher to #5946

@scabug
Copy link
Author

scabug commented Jan 28, 2013

@adriaanm said:
since this has a workaround and is a tricky existential issue, deferring to 2.10.2

@milessabin
Copy link

PR: scala/scala#5999

milessabin added a commit to milessabin/scala that referenced this issue Jan 18, 2018
val for the unstable LHS of an implicit method application where the
method type depends on the LHS.

As applied to this example, taken from scala/bug#4225,

```scala
class Foo {
  class Bar
  object Bar {
    implicit def mkBar: Bar = new Bar
  }
}

object f extends Foo

implicit class Ops[F <: Foo](val f0: F) {
  def op(i: Int)(implicit b: f0.Bar): f0.Bar = b
}

f.op1(23)
```

this transformation produces,

```scala
{
  val stabilizer$1 = Ops[f.type](f)
  stabilizer$1.op1(23)
}
```

Because `stabilizer$1` is stable we retain the singleton type `F` and
the implicit argument of type `f0.Bar` can be resolved against the
implicit scope of `f.type`.

A real world example of this is the macro API issue reported in
scala/bug#5946 ... following this PR the usage below compiles as
expected,

```scala
class Ops(val g: scala.reflect.api.JavaUniverse) {
  def op[T: g.TypeTag] = ()
}
implicit def Ops(g: scala.reflect.api.JavaUniverse): Ops = new Ops(g)
scala.reflect.runtime.universe.op[Int]
```

In principle this transformation could also be applied for explicit
applications, however there would no gain: to be able to write the
explicit argument the user would already have had to explicitly
introduce the necessary val.
milessabin added a commit to milessabin/scala that referenced this issue Jan 18, 2018
val for the unstable LHS of an implicit method application where the
method type depends on the LHS.

As applied to this example, taken from scala/bug#4225,

```scala
class Foo {
  class Bar
  object Bar {
    implicit def mkBar: Bar = new Bar
  }
}

object f extends Foo

implicit class Ops[F <: Foo](val f0: F) {
  def op(i: Int)(implicit b: f0.Bar): f0.Bar = b
}

f.op1(23)
```

this transformation produces,

```scala
{
  val stabilizer$1 = Ops[f.type](f)
  stabilizer$1.op1(23)
}
```

Because `stabilizer$1` is stable we retain the singleton type `F` and
the implicit argument of type `f0.Bar` can be resolved against the
implicit scope of `f.type`.

A real world example of this is the macro API issue reported in
scala/bug#5946 ... following this PR the usage below compiles as
expected,

```scala
class Ops(val g: scala.reflect.api.JavaUniverse) {
  def op[T: g.TypeTag] = ()
}
implicit def Ops(g: scala.reflect.api.JavaUniverse): Ops = new Ops(g)
scala.reflect.runtime.universe.op[Int]
```

In principle this transformation could also be applied for explicit
applications, however there would no gain: to be able to write the
explicit argument the user would already have had to explicitly
introduce the necessary val.
milessabin added a commit to milessabin/scala that referenced this issue Jan 19, 2018
This fixes scala/bug#4225 and scala/bug#5946 by introducing a synthetic
val for the unstable LHS of an implicit method application where the
method type depends on the LHS.

As applied to this example, taken from scala/bug#4225,

```scala
class Foo {
  class Bar
  object Bar {
    implicit def mkBar: Bar = new Bar
  }
}

object f extends Foo

implicit class Ops[F <: Foo](val f0: F) {
  def op(i: Int)(implicit b: f0.Bar): f0.Bar = b
}

f.op1(23)
```

this transformation produces,

```scala
{
  val stabilizer$1 = Ops[f.type](f)
  stabilizer$1.op1(23)
}
```

Because `stabilizer$1` is stable we retain the singleton type `F` and
the implicit argument of type `f0.Bar` can be resolved against the
implicit scope of `f.type`.

A real world example of this is the macro API issue reported in
scala/bug#5946 ... following this PR the usage below compiles as
expected,

```scala
class Ops(val g: scala.reflect.api.JavaUniverse) {
  def op[T: g.TypeTag] = ()
}
implicit def Ops(g: scala.reflect.api.JavaUniverse): Ops = new Ops(g)
scala.reflect.runtime.universe.op[Int]
```

In principle this transformation could also be applied for explicit
applications, however there would no gain: to be able to write the
explicit argument the user would already have had to explicitly
introduce the necessary val.
@SethTisue SethTisue modified the milestones: Backlog, 2.13.0-M3 Jan 19, 2018
@scala scala deleted a comment from scabug Jan 19, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment