-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Type parameters should be instantiated before doing an implicit search #739
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
scalac does not generally instantiate parameters before an implicit search. If it did, the whole concept of CanBuildFrom would not work, to give an example. But parameter instantiation is different between the two systems, and it seems this example shows where dotty's way is problematic. Maybe instantiate when there are ambiguity errors otherwise? |
FYI, here's how to get a bit of logging from scalac about type param inference. % cat sandbox/test.scala
class Foo[A, B]
object Test {
def foo[T, U](x: T)(implicit ev: Foo[T, U]): U = ???
def test: Unit = {
implicit val evidence1: Foo[Int, String] = ???
implicit val evidence2: Foo[String, Int] = ???
foo(1)
}
}
% scalac -Dscalac.debug.tvar -Xprint:typer -Ytyper-debug sandbox/test.scala
| | | |-- foo(1) : pt=Unit EXPRmode (site: method test in Test)
| | | | |-- foo BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method test in Test)
| | | | | [adapt] [T, U](x: T)(implicit ev: Foo[T,U])U adapted to [T, U](x: T)(implicit ev: Foo[T,U])U
| | | | | \-> (x: T)(implicit ev: Foo[T,U])U
[ create] ?T ( In Test#foo[T,U] )
[ create] ?U ( In Test#foo[T,U] )
| | | | |-- 1 BYVALmode-EXPRmode-POLYmode (site: method test in Test)
| | | | | \-> Int(1)
[ create] ?T ( In Test#foo[T,U] )
[ create] ?U ( In Test#foo[T,U] )
| | | | solving for (T: ?T, U: ?U)
[ setInst] Int ( In Test#foo[T,U], T=Int )
[ setInst] Nothing ( In Test#foo[T,U], U=Nothing )
[ create] ?U ( In Test#foo[T,U] )
| | | | solving for (U: ?U)
[ setInst] Nothing ( In Test#foo[T,U], U=Nothing )
[ create] ?U ( In Test#foo[T,U] )
| | | | solving for (U: ?U)
[ setInst] String ( In Test#foo[T,U], U=String )
| | | | |-- [T, U](x: T)(implicit ev: Foo[T,U])U : pt=Unit EXPRmode (silent: method test in Test)
| | | | | |-- { Test.this.foo[Int, String](1)(evidence1); () } : pt=Unit EXPRmode (silent: method test in Test)
| | | | | | \-> Unit
| | | | | [adapt] [T, U](x: T)(implicit ev: Foo[T,U])U adapted to { Test.this.foo[Int, String](1)(evidence1); () } based on pt Unit
| | | | | \-> Unit
| | | | [adapt] [T, U](x: T)(implicit ev: Foo[T,U])U adapted to { Test.this.foo[Int, String](1)(evidence1); () } based on pt Unit
| | | | \-> Unit
| | | \-> Unit
| | \-> [def test] => Unit
| \-> [object Test] Test.type
[[syntax trees at end of typer]] // test.scala
package <empty> {
class Foo[A, B] extends scala.AnyRef {
def <init>(): Foo[A,B] = {
Foo.super.<init>();
()
}
};
object Test extends scala.AnyRef {
def <init>(): Test.type = {
Test.super.<init>();
()
};
def foo[T, U](x: T)(implicit ev: Foo[T,U]): U = scala.this.Predef.???;
def test: Unit = {
implicit val evidence1: Foo[Int,String] = scala.this.Predef.???;
implicit val evidence2: Foo[String,Int] = scala.this.Predef.???;
{
Test.this.foo[Int, String](1)(evidence1);
()
}
}
}
}
|
@odersky : Yes, that description is not exactly right: in Scala 2, type parameters are instantiated after the first parameter list in which they appear, for example: class Foo
class Bar extends Foo
object Test {
def one[T](x: T, y: T): T = x
def two[T](x: T)(y: T): T = x
def twob[T](x: T)(y: T): Nothing = ???
def two2[T,S](x: T)(y: S): T = x
def test = {
one(new Bar, new Foo) // scala and dotty: T=Foo
two(new Bar)(new Foo) // scala: T=Bar, type mismatch; dotty: T=Foo
twob(new Bar)(new Foo) // scala: T=Bar, type mismatch; dotty: T=Any
two2(new Bar)(new Foo) // scala: T=Bar, S=Foo; dotty: T=Bar, S=Any
}
} The behavior of scalac with implicits is just a special case of that: type parameters which appear in a parameter list before an implicit parameter list will be instantiated, others won't so |
I think it is more accurate to say that the compiler attempts to instantiate them all, but retracts ones that solve to |
One more note: Applying this diff: git diff -- '**/Implicits.scala'
diff --git a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
index 7ec9cd7..1b5783f 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Implicits.scala
@@ -325,7 +325,7 @@ trait Implicits {
class ImplicitSearch(tree: Tree, pt: Type, isView: Boolean, context0: Context, pos0: Position = NoPosition) extends Typer(context0) with ImplicitsContextErrors {
val searchId = implicitSearchId()
private def typingLog(what: String, msg: => String) =
- typingStack.printTyping(tree, f"[search #$searchId] $what $msg")
+ typingStack.printTyping(f"[search #$searchId] $what $msg")
import infer._
if (Statistics.canEnable) Statistics.incCounter(implicitSearchCount) Shows the interaction between implicit search and inference more explicitly: | | | |-- foo(1) : pt=Unit EXPRmode (site: method test in Test)
| | | | |-- foo BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method test in Test)
| | | | | [adapt] [T, U](x: T)(implicit ev: Foo[T,U])U adapted to [T, U](x: T)(implicit ev: Foo[T,U])U
| | | | | \-> (x: T)(implicit ev: Foo[T,U])U
[ create] ?T ( In Test#foo[T,U] )
[ create] ?U ( In Test#foo[T,U] )
| | | | |-- 1 BYVALmode-EXPRmode-POLYmode (site: method test in Test)
| | | | | \-> Int(1)
[ create] ?T ( In Test#foo[T,U] )
[ create] ?U ( In Test#foo[T,U] )
| | | | solving for (T: ?T, U: ?U)
[ setInst] Int ( In Test#foo[T,U], T=Int )
[ setInst] Nothing ( In Test#foo[T,U], U=Nothing )
[ create] ?U ( In Test#foo[T,U] )
| | | | solving for (U: ?U)
[ setInst] Nothing ( In Test#foo[T,U], U=Nothing )
| | | | [search #1] start `[T, U](x: T)(implicit ev: Foo[T,U])U` inferring type U, searching for adaptation to pt=Foo[Int,U] (silent: method test in Test) implicits disabled
| | | | [search #1] considering evidence1
[ create] ?U ( In Test#foo[T,U] )
| | | | [search #1] solve tvars=?U, tvars.constr= >: String <: String
| | | | solving for (U: ?U)
[ setInst] String ( In Test#foo[T,U], U=String )
| | | | [search #1] success inferred value of type Foo[Int,=?String] is SearchResult(evidence1, TreeTypeSubstituter(List(type U),List(String)))
| | | | |-- [T, U](x: T)(implicit ev: Foo[T,U])U : pt=Unit EXPRmode (silent: method test in Test)
| | | | | |-- { Test.this.foo[Int, String](1)(evidence1); () } : pt=Unit EXPRmode (silent: method test in Test)
| | | | | | \-> Unit
| | | | | [adapt] [T, U](x: T)(implicit ev: Foo[T,U])U adapted to { Test.this.foo[Int, String](1)(evidence1); () } based on pt Unit
| | | | | \-> Unit
| | | | [adapt] [T, U](x: T)(implicit ev: Foo[T,U])U adapted to { Test.this.foo[Int, String](1)(evidence1); () } based on pt Unit
| | | | \-> Unit |
Here's an test case that shows the difference between the "try to instantiate but retract nothing" and "instantiate when referred to in a param list" description: class Foo[A, B]
class Test {
implicit val f: Foo[Int, String] = ???
def t[A, B >: A](a: A)(implicit f: Foo[A, B]) = ???
t(1)
} This fails with | |-- t(1) BYVALmode-EXPRmode (site: value <local Test> in Test)
| | |-- t BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local Test> in Test)
| | | [adapt] [A, B >: A](a: A)(implicit f: Foo[A,B])Nothing adapted to [A, B >: A](a: A)(implicit f: Foo[A,B])Nothing
| | | \-> (a: A)(implicit f: Foo[A,B])Nothing
| | |-- 1 BYVALmode-EXPRmode-POLYmode (site: value <local Test> in Test)
| | | \-> Int(1)
| | solving for (A: ?A, B: ?B)
| | [search #1] start `[A, B >: A](a: A)(implicit f: Foo[A,B])Nothing`, searching for adaptation to pt=Foo[Int,Int] (silent: value <local Test> in Test) implicits disabled
sandbox/instantiate.scala:5: error: could not find implicit value for parameter f: Foo[Int,Int]
t(1)
^
| | |-- [A, B >: A](a: A)(implicit f: Foo[A,B])Nothing BYVALmode-EXPRmode (site: value <local Test> in Test)
| | | [search #2] start `()`, searching for adaptation to pt=Unit => Foo[Int,Int] (silent: value <local Test> in Test) implicits disabled
| | | [search #3] start `()`, searching for adaptation to pt=(=> Unit) => Foo[Int,Int] (silent: value <local Test> in Test) implicits disabled
| | | \-> <error>
| | [adapt] [A, B >: A](a: A)(implicit f: Foo[A,B])Nothing adapted to [A, B >: A](a: A)(implicit f: Foo[A,B])Nothing
| | \-> <error> |
@retronym Ah indeed, I didn't know that! I think that for dotty we want to avoid special-casing class Foo
class Bar extends Foo
object Test {
def a[T >: Foo](x: T): T = x
def b[T >: Foo]()(y: T): T = y
def test = {
a(new Bar) // scalac: T=Bar
b()(new Bar) // scalac: T=Foo
}
} |
(FWIW, I have a hacky implementation of my proposed scheme at https://github.com/smarter/dotty/commits/fix/tp-instantiation-scheme but it uncovered some other issues in dotty I need to fix first before opening a PR for it, it also breaks inference for some scala2 library methods like |
On Wed, Aug 5, 2015 at 7:43 PM, Jason Zaugg [email protected]
—
Martin Odersky |
On Wed, Aug 5, 2015 at 7:56 PM, Guillaume Martres [email protected]
But how do you define that precisely? Does the argument need a type which
Martin Odersky |
"A type which contains the typevar" looks like a good rule to me since it's easy to visually identify, this is what I implemented in my prototype: https://github.com/smarter/dotty/commits/fix/tp-instantiation-scheme Your proposal could work too, but note that some methods like |
@smarter We could say: "Before typing an implicit parameter list of a method
|
No, I don't special case implicit parameter lists, here's an example of how my prototype works: def foo[T, S](x: Thing[T])(y: OtherThing[S]) = ???
foo(arg1)(arg2)
Interesting, I did not know that. That covers the most common usecase for multiple parameter lists, but there are other cases where people rely on eager instantiation, for example: http://stackoverflow.com/questions/31177695/shapeless-hlist-type-checking/31192042#31192042 So we need to decide if it's worth breaking compatibility here. |
But I think we should special case them. The philosophy of type inference in dotty and scalac is just different. In dotty, we leave type variables uninstantiated by default, and instantiate in a small number of well-specifed cases. In scalac it's the reverse, type variables are by default instantiated except in a number of circumstances where they are left uninstantiated. Scalac's scheme is more problematic. In particular, it relies crucially on not inferring Nothing and in doing so it confounds Nothing as "unconstrained" and Nothing as a valid instance type. Mixing the two schemes will make things more complicated than necessary, IMO. I implemented the scheme I outlined and which is close to your idea. A PR will come shortly. |
This is a separate issue from #738 but I think that they will need to be fixed together to get something that makes sense.
Consider the following code:
It currently fails with:
When we do the implicit search,
T
is not instantiated yet, the only thing we know is that its lower bound isFoo
as the error message says:... match type (T? >: Foo) of parameter ev of method foo ...
. I don't know howscalac
deals with this exactly but it seems like it simply instantiate the type parameter before doing the implicit search, we should consider doing the same thing.Note that this is also needed to properly fix #553, if we don't instantiate
CC
toList
, thenCC
is only lower-bounded byList
, and lower bounds of abstract types are not part of the implicit scope, this means that the implicitList.canBuildFrom
will not be in the scope.The text was updated successfully, but these errors were encountered: