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

destructuring binds, implicit resolution, and declaration order #9130

Open
scabug opened this issue Feb 2, 2015 · 9 comments
Open

destructuring binds, implicit resolution, and declaration order #9130

scabug opened this issue Feb 2, 2015 · 9 comments
Assignees

Comments

@scabug
Copy link

scabug commented Feb 2, 2015

This caused me quite the literal headache today:

class S[_]
class T[_]

case class B(x: Int)
object B {
  def makeST = (new S[B], new T[B])
  implicit val (s,t): (S[B],T[B]) = makeST
}

object A { implicitly[T[B]] }

The code above compiles, but if you swap the order of A and B, it no longer will:

class S[_]
class T[_]

object A { implicitly[T[B]] }

case class B(x: Int)
object B {
  def makeST = (new S[B], new T[B])
  implicit val (s,t): (S[B],T[B]) = makeST
}
<console>:11: error: could not find implicit value for parameter e: T[B]
         implicitly[T[B]]
                   ^

Very sad indeed.

The tuple has something to with it; this is ok:

class S[_]
class T[_]

object A { implicitly[T[B]] }

case class B(x: Int)
object B {
  def makeST = (new S[B], new T[B])
  implicit val s: S[B] = makeST._1
  implicit val t: T[B] = makeST._2
}
@scabug
Copy link
Author

scabug commented Feb 2, 2015

Imported From: https://issues.scala-lang.org/browse/SI-9130?orig=1
Reporter: @refried
Affected Versions: 2.11.5

@scabug
Copy link
Author

scabug commented Feb 2, 2015

@retronym said:
Implicits must be explicitly type annotated, otherwise the typechecker may ignore them from preceding parts of the same source file. This is done to avoid triggering spurious cycles in type inference.

Unfortunately, there is no way to explicitly annotated the type of an implicit val with a pattern on the LHS of the =.

% qscala -Xprint:parser -e 'implicit val (a, b): (Int, Int) = (1, 2)'
[[syntax trees at end of                    parser]] // scalacmd6461168732762336959.scala
package <empty> {
  object Main extends scala.AnyRef {
    def <init>() = {
      super.<init>();
      ()
    };
    def main(args: Array[String]): scala.Unit = {
      final class $anon extends scala.AnyRef {
        def <init>() = {
          super.<init>();
          ()
        };
        <synthetic> <artifact> private[this] val x$1 = (scala.Tuple2(1, 2): @scala.unchecked: scala.Tuple2[Int, Int]) match {
          case scala.Tuple2((a @ _), (b @ _)) => scala.Tuple2(a, b)
        };
        implicit val a = x$1._1;
        implicit val b = x$1._2
      };
      new $anon()
    }
  }
}

We should add a style checking rule to https://github.com/scala/scala-abide/blob/master/README.md to help find such implicits. It is probably that a future version of Scala will actually enforce that implicits are type annotated.

@scabug
Copy link
Author

scabug commented Feb 2, 2015

@refried said:
Ah ok. It's tricky though, because I thought I /had/ annotated them. But I guess my annotation didn't count.

My goal was to produce two implicit instances with a single call and minimal boilerplate.

@scabug
Copy link
Author

scabug commented Feb 2, 2015

@refried said:
Looks like implicit val (a:Int, b:Int) = (1,2) does what I wanted. Is that fine, or is that a bug?

@scabug
Copy link
Author

scabug commented Feb 2, 2015

@refried said:
Although, I can get an "unchecked" warning now. :-\

Is there a ticket for getting rid of the "explicitly-annotated types" requirement? Declarations like these remind me of Java:

implicit def encode[A](implicit A: EncodeJson[A]): EncodeJson[Success[A]] = EncodeJson[Success[A]](...)

OTOH, I guess it's not too hard to work around if the rule you mentioned is the only one, "the typechecker may ignore [implicits] from preceding parts of the same source file". I just never knew this rule, and it felt very Heisenbuggy.

Thanks again.

@scabug
Copy link
Author

scabug commented Feb 3, 2015

@retronym said:
You're not going to like this, but you can get rid of the unchecked warnings with:

object Test {
  def foo = implicitly[Int]
  implicit val (a:Int, b:String) : (Int, String) = (1,"")
}
% qscalac -Xprint:parser sandbox/test.scala
[[syntax trees at end of                    parser]] // test.scala
package <empty> {
  object Test extends scala.AnyRef {
    def <init>() = {
      super.<init>();
      ()
    };
    def foo = implicitly[Int];
    <synthetic> <artifact> private[this] val x$1 = (scala.Tuple2(1, ""): @scala.unchecked: scala.Tuple2[Int, String]) match {
      case scala.Tuple2((a @ (_: Int)), (b @ (_: String))) => scala.Tuple2(a, b)
    };
    implicit val a: Int = x$1._1;
    implicit val b: String = x$1._2
  }
}

@scabug
Copy link
Author

scabug commented Feb 3, 2015

@refried said:
laugh ok, good to know. For now I'm going to leave off the ascriptions and just be mindful of declaration order within a given file.

Thanks for the help & insights & for the -Xprint:parser trick.

@scabug
Copy link
Author

scabug commented Feb 3, 2015

@retronym said:
I've just been playing with an approach to issuing a warning for discarded implicits. After we finish typechecking the file, we can see if any discarded candidates turned out to be viable for the search. We can safely check this after the entire file has been typechecked.

Here's a prototype: https://github.com/retronym/scala/compare/topic/warn-discarded-implicit?expand=1

However, this does not take into account the way that implicit search participates in type inference. If the expected type of the implicit search contains an undetermined type variable, the discarded implicit might not appear viable after inference has occurred.

See also #7486, in which a previous attempt at highlighting this sort of issue introduced a regression.

@scabug
Copy link
Author

scabug commented Feb 4, 2015

@refried said:
Awesome. I don't think there's such a thing as too much explanatory info or hints when something goes wrong.

How does implicit search participate in type inference? Is it the thing where
{code}def foo[A:Bar](a: A) = a{code} infers type {code}A=Foo{code} if the only {code}Bar[x]{code} available is a {code}Bar[Foo]{code}? That could/should probably go away if it simplifies any other aspect of anything :-P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants