Description
From the mailing list: https://groups.google.com/forum/#!topic/scala-user/6YSTIJuXpKw
import scala.language.higherKinds
object Identity {
type Id[+A] = A
}
import Identity._
case class ConfigItem[F[+_], +A](name: String, value: F[A], expected: Option[A])
abstract class Check[ItemF[+_], Value] {
type CItem = ConfigItem[ItemF, Value]
def check: List[CItem]
}
final case class TestCheck() extends Check[Id, Int] {
def check: List[CItem] = List(ConfigItem[Id, Int]("Test", 1, None))
}
object Main extends App {
def filter[F[+_], A](in: List[ConfigItem[F, A]]) =
in.filter(!_.expected.isDefined)
/*
* Assigning the Check instance to an intermediate variable allows the type inferencer to do its job
* These two rows compile fine
*/
val testInstance = TestCheck()
val filteredWorks = filter(testInstance.check)
/*
* If on the other hand the instance is created at the same time as we call check() on it, the inferencer fails miserably.
*/
val filtered = filter(TestCheck().check)
println(filteredWorks)
}
[info] Compiling 1 Scala source to /tmp/rendererAhc2oLasji/target/classes...
[error] /tmp/rendererAhc2oLasji/src/main/scala/test.scala:39: no type parameters for method filter: (in: List[ConfigItem[F,A]])List[ConfigItem[F,A]] exist so that it can be applied to arguments (List[ConfigItem[[+A]A,Int]])
[error] --- because ---
[error] argument expression's type is not compatible with formal parameter type;
[error] found : List[ConfigItem[[+A]A,Int]]
[error] required: List[ConfigItem[?F,?A]]
[error] val filtered = filter(TestCheck().check)
[error] ^
[error] /tmp/rendererAhc2oLasji/src/main/scala/test.scala:39: type mismatch;
[error] found : List[ConfigItem[[+A(in type Id)]A(in type Id),Int]]
[error] required: List[ConfigItem[F,A(in method filter)]]
[error] val filtered = filter(TestCheck().check)
[error] ^
[error] two errors found
=====================================
The curious difference seems to boil down to a difference in inferred types for the call to the check method depending on whether or not the receiver has a stable type.
import scala.language.higherKinds
class HK[F[B]]
trait TestCheck {
type Id[A] = A
type HKId = HK[Id]
def check: List[HKId]
}
trait Test {
val testInstanceVal: TestCheck
def testInstanceDef: TestCheck
def a /* : List[HKId] */ = testInstanceVal.check
def b /* : List[HK[[a]a]] */ = testInstanceDef.check
}
Warning: Implementation details follow!
This happens when the type of check is computed. Because it contains types that are prefixed with the this type of TestCheck (e.g. TestCheck.this.HKId, the asSeenFrom operation internally uses an existential type in the second case. It is a bit easier to think about this as though there was a synthetic block of code like:
def b = { val _1: TestCheck = b; _1.check : List[_1.HKId] } : HK[[a]A]
When the type of this block is computed, we have to hide the local symbol _1. This is done in existentialAbstraction . Since aaf919859f / Scala 2.8, this internally expands type aliases. I don’t know the motivation of that change. But the overall result seems surprising enough to me to be considered a bug.
In your example, a workaround is to explicitly annotate the result of calling check with a type alias that does not include the TestCheck.this type.
final case class TestCheck() extends Check[Id, Int] {
def check: List[TestCheck.CItem] = List(ConfigItem[Id, Int]("Test", 1, None))
}
object TestCheck {
type CItem = ConfigItem[Id, Int]
}
=====================
I tried changing normalizeAliases
to just dealias, rather than normalize. This has the effect of making some tests fail (e.g. test/files/pos/t7517.scala). So we might not have a way to fix this (if, indeed, it is considered a bug!). But I thought I'd lodge the ticket here for the record in any case.