-
Notifications
You must be signed in to change notification settings - Fork 21
implicit resolution of F[A] for introduced F[_] and A #10753
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
Comments
(I closed this because I had a typo, then I fixed it and reopened...) |
FYI, works in dotty |
The error I'm getting for scalac
|
@soronpo that's interesting about dotty! Where is the diverging implicit error coming from? Is this some additional debugging you've got? |
The full error:
I'm using TLS 2.12.4 (literal types) with |
If |
In dotty it compiles in either case |
Hmm, looks like that implicit not found error was rather because EDIT: I think this is actually the problem here: implicit scope (or implicit priorities?). If you import the implicits it starts working:
Still doesn't definitively explain why adding or removing |
This hack of importing the To make the minimisation more realistic, let's rename |
changing to
I still get
|
Strange. It works for me. |
@Jasper-M I'm on scala 2.12.4, what are you using? |
Same. Using REPL |
@Jasper-M any special flags? I'm just using
|
@fommil No flags. Enabling |
maybe playing around with the parameter ordering might help in that case. |
removing partial-unification PLUS adding the explicit |
Indeed. That's why it works in dotty 😄 . I think I also ran into this in my own library, but didn't try to minimize and have such implicits in an import. |
ok... I can get it to compile with the
ordering of the type parameters does not matter. We're still back to "why is the |
Can you guess why it is diverging? Well we want an implicit Edit: For me the order of the type parameters does matter ( |
@joroKr21 ah! Thanks for the explanation. However, we still need the |
Ah yes, I convinced myself that it's a bug. It looks like the expected type |
ok, so I think I understand the restriction... it seems implicit def deriv[F[_], A](implicit B: Bar[F], F: F[A]): F[Problem[A]] = ??? solving for |
Well it looks in the immediate scope first, which doesn't include |
right, so it can't solve for |
Excellent investigation, but I disagree that
When running with additional tracing (patch below), and under
Specifically, we only add an upper bound for Patch: diff --git i/src/reflect/scala/reflect/internal/Types.scala w/src/reflect/scala/reflect/internal/Types.scala
index bec839b..9508b97 100644
--- i/src/reflect/scala/reflect/internal/Types.scala
+++ w/src/reflect/scala/reflect/internal/Types.scala
@@ -2887,11 +2887,11 @@ trait Types
@inline final def trace[T](action: String, msg: => String)(value: T): T = {
// Uncomment the following for a compiler that has some diagnostics about type inference
// I doubt this is ever useful in the wild, so a recompile will be needed
-// val s = msg match {
-// case "" => ""
-// case str => "( " + str + " )"
-// }
-// Console.err.println("[%10s] %-25s%s".format(action, value, s))
+ val s = msg match {
+ case "" => ""
+ case str => "( " + str + " )"
+ }
+ Console.err.println("[%10s] %-25s%s".format(action, value, s))
value
}
@@ -3088,6 +3088,7 @@ trait Types
def addLoBound(tp: Type, isNumericBound: Boolean = false) {
assert(tp != this, tp) // implies there is a cycle somewhere (?)
//println("addLoBound: "+(safeToString, debugString(tp))) //DEBUG
+ TypeVar.trace("addLoBound", s"For $originLocation's $originName / sharesConstraints=${sharesConstraints(tp)}")(tp)
if (!sharesConstraints(tp)) {
undoLog record this
constr.addLoBound(tp, isNumericBound)
@@ -3097,6 +3098,7 @@ trait Types
def addHiBound(tp: Type, isNumericBound: Boolean = false) {
// assert(tp != this)
//println("addHiBound: "+(safeToString, debugString(tp))) //DEBUG
+ TypeVar.trace("addHiBound", s"For $originLocation's $originName / sharesConstraints=${sharesConstraints(tp)}")(tp)
if (!sharesConstraints(tp)) {
undoLog record this
constr.addHiBound(tp, isNumericBound)
diff --git i/src/reflect/scala/reflect/internal/tpe/TypeConstraints.scala w/src/reflect/scala/reflect/internal/tpe/TypeConstraints.scala
index 2697824..bccedff 100644
--- i/src/reflect/scala/reflect/internal/tpe/TypeConstraints.scala
+++ w/src/reflect/scala/reflect/internal/tpe/TypeConstraints.scala
@@ -196,7 +196,7 @@ private[internal] trait TypeConstraints {
val up = if (variance.isContravariant) !upper else upper
tvar.constr.inst = null
val bound: Type = if (up) tparam.info.bounds.hi else tparam.info.bounds.lo
- //Console.println("solveOne0(tv, tp, v, b)="+(tvar, tparam, variance, bound))
+ Console.println("solveOne0(tv, tp, v, b)="+(tvar, tparam, variance, bound))
var cyclic = bound contains tparam
foreach3(tvars, tparams, variances)((tvar2, tparam2, variance2) => {
val ok = (tparam2 != tparam) && (
@@ -238,7 +238,7 @@ private[internal] trait TypeConstraints {
}
tvar.constr.inst = NoType // necessary because hibounds/lobounds may contain tvar
- //println("solving "+tvar+" "+up+" "+(if (up) (tvar.constr.hiBounds) else tvar.constr.loBounds)+((if (up) (tvar.constr.hiBounds) else tvar.constr.loBounds) map (_.widen)))
+ println("solving "+tvar+" "+up+" "+(if (up) (tvar.constr.hiBounds) else tvar.constr.loBounds)+((if (up) (tvar.constr.hiBounds) else tvar.constr.loBounds) map (_.widen)))
val newInst = (
if (up) {
if (depth.isAnyDepth) glb(tvar.constr.hiBounds)
@@ -252,11 +252,11 @@ private[internal] trait TypeConstraints {
debuglog(s"$tvar setInst $newInst")
tvar setInst newInst
- //Console.println("solving "+tvar+" "+up+" "+(if (up) (tvar.constr.hiBounds) else tvar.constr.loBounds)+((if (up) (tvar.constr.hiBounds) else tvar.constr.loBounds) map (_.widen))+" = "+tvar.constr.inst)//@MDEBUG
+ Console.println("solving "+tvar+" "+up+" "+(if (up) (tvar.constr.hiBounds) else tvar.constr.loBounds)+((if (up) (tvar.constr.hiBounds) else tvar.constr.loBounds) map (_.widen))+" = "+tvar.constr.inst)//@MDEBUG
}
}
- // println("solving "+tvars+"/"+tparams+"/"+(tparams map (_.info)))
+ println("solving "+tvars+"/"+tparams+"/"+(tparams map (_.info)))
foreach3(tvars, tparams, variances)(solveOne)
def logBounds(tv: TypeVar) = log { |
@adriaanm thanks for that. What is the consequence, is there a workaround to force the lower bound or are you saying this is just not something scala compiler can do? |
It's not so much "can't" as "won't" -- a return type is in a covariant position, which is fundamental to OO (since a method is allowed to return a value of any subtype of its result type, we can only constrain from above the type variable that represents that unknown type). This applies equally for regular method calls as well as the method that produce implicit values -- there needs to be subtype slack, so that they can provide an instance of a subtype of the expected type. It could be possible to refactor your application so that there are more constraints in the mix (such as what happens when you import the implicit values, which means their availability determines the result of type inference). I played around a bit with the code, but it really depends on the greater context. |
Then why does it compile in Dotty? |
@adriaanm greater context is exactly this usecase i.e. we want to get automatic typeclass derivation for We use a lot of |
I don't know, does it still work after scala/scala3#4080 was merged? |
@adriaanm The example compiles on dotty master, but your argument is compelling. Keeping subtyping in mind, we can modify it in in a way to make it compile with scalac and fail with dotty: trait Bar[F[_]]
trait Foo[A]
trait Qux[A] extends Foo[A]
object Qux {
implicit def string: Qux[String] = ???
implicit def bar: Bar[Qux] = ???
}
case class Problem[A](a: A)
object Problem {
import Qux._
implicit def deriv[A, F[_]](implicit B: Bar[F], F: F[A]): F[Problem[A]] = ???
implicitly[Foo[Problem[String]]]
} Import still necessary ofc. |
I hope the meta-takeaway is also coming across, but, for the casual reader: relying on fancy type inference is bound to cause work at some point (even within the 2.x series, although we are generally very resistant to touching type inference for this very reason). It would be great to have tooling that can insert the minimal set of annotations / type arguments to ensure type checking on different versions has the same result. Some kind of fancy cross-compiler AST diffing and patch generation. |
@adriaanm I assume you want to close the ticket then? It would be good if there was some other workaround that we could use to avoid enforcing downstream users to import their contravariant / monaderror instances. |
Yes, I'm afraid this is a won't-fix for the 2.x series, unless there's a feasible carry-over from Dotty that allows us to improve this. I agree inferring Nothing is such a downer. Before closing, let me ping the other side of the street in case they have any suggestions /cc @smarter, @OlivierBlanvillain :-) |
scalaz has a |
that's a nope on |
I expected the following code to compile
but I get
This is minimised, if you remove the
B: Bar[F]
constraint, it compiles (but then I don't have everything I need)Is there a workaround?
The text was updated successfully, but these errors were encountered: