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

Incorrect inference of Nothing as type argument through eta expansion #8347

Closed
scabug opened this issue Feb 27, 2014 · 5 comments
Closed

Incorrect inference of Nothing as type argument through eta expansion #8347

scabug opened this issue Feb 27, 2014 · 5 comments
Assignees
Labels

Comments

@scabug
Copy link

scabug commented Feb 27, 2014

Here is a minimized test case:

object O {

	class X[A]

	def id[B](x: X[B]): X[B] = x

	def test[C](f: X[C] => X[C], x: X[C]): X[C] = f(x)

	val x0 = new X[Int]

	test(id, x0)

}

It fails to compile, giving:

/home/clhodapp/test/Test.scala:12: type mismatch;
 found   : O.X[Nothing] => O.X[Nothing]
 required: O.X[Int] => O.X[Int]
	test(id, x0)
	     ^
one error found

It would seem that the compiler is inferring Int to pass as C, but Nothing to pass as B in test(id, x). I would argue that it Int should be passed as both B and C.

Let's look at (a fragment of) the typer-debug output:

test(id, x0) BYVALmode-EXPRmode (site: value <local O> in O) 
 |-- test BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local O> in O) 
 |    [adapt] [C](f: O.X[C] => O.X[C], x: O.X[C])O.X[C] adapted to [C](f: O.X[C] => O.X[C], x: O.X[C])O.X[C]
 |    \-> (f: O.X[C] => O.X[C], x: O.X[C])O.X[C]
 |-- id : pt=O.X[?] => O.X[?] BYVALmode-EXPRmode-POLYmode (site: value <local O> in O) 
 |    |-- { ((x: O.X[B]) => O.this.id[B](x)) } BYVALmode-EXPRmode-POLYmode (site solving: type B: value <local O> in O) 
 |    |    |-- ((x: O.X[B]) => O.this.id[B](x)) BYVALmode-EXPRmode-POLYmode (site: value <local O> in O) 
 |    |    |    |-- O.this.id[B](x) EXPRmode (site: value $anonfun in O) 
 |    |    |    |    |-- O.this.id[B] BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value $anonfun in O) 
 |    |    |    |    |    |-- O.this.id BYVALmode-EXPRmode-FUNmode-POLYmode-TAPPmode (silent: value $anonfun in O) 
 |    |    |    |    |    |    \-> [B](x: O.X[B])O.X[B]
 |    |    |    |    |    \-> (x: O.X[B])O.X[B]
 |    |    |    |    |-- x : pt=O.X[B] BYVALmode-EXPRmode (site: value $anonfun in O) 
 |    |    |    |    |    \-> O.X[B]
 |    |    |    |    \-> O.X[B]
 |    |    |    \-> O.X[B] => O.X[B]
 |    |    \-> O.X[B] => O.X[B]
 |    solving for (B: ?B)
 |    [adapt] [B](x: O.X[B])O.X[B] adapted to { ((x: O.X[Nothing]) => O.this.id[Nothing](x)) } based on pt O.X[?] => O.X[?]
 |    \-> O.X[Nothing] => O.X[Nothing]
 |-- x0 : pt=O.X[?] BYVALmode-EXPRmode-POLYmode (site: value <local O> in O) 
 |    \-> O.X[Int]
 solving for (C: ?C)
 \-> O.X[Int]

We can see that the types for x and id are computed before the type to use as an argument to test is computed. Specifically, the type to be passed as B is computed as Nothing, since, given no other constraints, this is the most specific, and thus the most desirable, type to pass. This way of doing things might be acceptable if these were regarded as tentative types, with the possibility of being recomputed once the type to be passed as C was inferred. However, they are not recomputed, leading to the sorry state of affairs seen here.

I do have one idea as to how to change the logic here, in case it helps anyone. Basically, it would seem to me that the best thing to do would be to infer type arguments from the outside in instead of from the inside out. Specifically, I would create a type range, which started as <: Any >: Nothing, and narrow it with each value parameter that referenced it, based on the restrictions created by the arguments passed as those parameters. For instance, here the type of f references C and id is passed as f. However, the type of id does not narrow the range of possible values for C. The type of x also references C. Since x0 is passed as x, we have to see if the type of x0 restricts the type of C. In fact it does! It restricts C to be >: Int <: Int, meaning that it must just be Int. From here, we can compute expected types for the value arguments of test. Specifically, Int as C gives us an expected type of X[Int] for the argument passed as x, meaning that Int must be passed as B.

One other thing to note: this seems to only be a problem when eta-expansion is involved. I when I changed id to return a custom type similar to Function1:

object O {

	class X[A]

	class ~>[-T, +U]

	def id[B]: X[B] ~> X[B] = ???

	def test[C](f: X[C] ~> X[C], x: X[C]): X[C] = ???

	val x0 = new X[Int]

	test(id, x0)

}
@scabug
Copy link
Author

scabug commented Feb 27, 2014

Imported From: https://issues.scala-lang.org/browse/SI-8347?orig=1
Reporter: @clhodapp
Affected Versions: 2.11.0-M8
See #7641

@scabug
Copy link
Author

scabug commented Feb 27, 2014

@clhodapp said:
I'll add that, if you're going with the narrowing type-range idea, you may not actually need the whole type range. You may be able to just use the bottom, which you repeatedly LUB with parts of the argument types, based on references in the parameter types. This is because you are just going to end up using the bottom of the range, anyway. If ends up not being below the upper limit created by the type of one of the arguments, I think it will end up shaking out after you are done inferring the outer type argument, when you move across the value arguments to compute their inferred types.

@scabug
Copy link
Author

scabug commented Feb 27, 2014

@paulp said:
Good luck on this one. Take my advice and appease the inference gods like so.

def test[C](x: X[C])(f: X[C] => X[C]): X[C] = f(x)

@scabug
Copy link
Author

scabug commented Jun 7, 2015

@retronym said:
This might be related:

scala> trait PPrint[A]
defined trait PPrint

scala> def foo[A: PPrint](a: A) = a
foo: [A](a: A)(implicit evidence$1: PPrint[A])A

scala> def test[A: PPrint](a: A) = Seq(a).foreach(foo(_))
test: [A](a: A)(implicit evidence$1: PPrint[A])Unit

scala> def test[A: PPrint](a: A) = Seq(a).foreach(foo)
<console>:9: error: could not find implicit value for evidence parameter of type PPrint[A]
       def test[A: PPrint](a: A) = Seq(a).foreach(foo)
                                                  ^

@scabug
Copy link
Author

scabug commented Jun 7, 2015

@retronym said:
Nope, the ticket I was looking for is #7641. I don't believe that we can make this work, Paul's suggestion is needed.

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

No branches or pull requests

2 participants