Skip to content

dealiasing within existentialExtrapolation violates principle of least surprise and degrades type inference #9156

Open
@scabug

Description

@scabug

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    dealiascompiler isn't dealiasing when it should, or vice versatcpolytyper

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions