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

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

Open
scabug opened this issue Feb 17, 2015 · 1 comment
Labels
dealias compiler isn't dealiasing when it should, or vice versa tcpoly typer
Milestone

Comments

@scabug
Copy link

scabug commented Feb 17, 2015

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.

@scabug
Copy link
Author

scabug commented Feb 17, 2015

Imported From: https://issues.scala-lang.org/browse/SI-9156?orig=1
Reporter: @retronym
Affected Versions: 2.11.5

@scabug scabug added this to the Backlog milestone Apr 7, 2017
@SethTisue SethTisue added the dealias compiler isn't dealiasing when it should, or vice versa label Nov 11, 2020
@SethTisue SethTisue changed the title dealising within existentialExtrapolation violates principle of least surprise and degrades type inference dealiasing within existentialExtrapolation violates principle of least surprise and degrades type inference Jul 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dealias compiler isn't dealiasing when it should, or vice versa tcpoly typer
Projects
None yet
Development

No branches or pull requests

3 participants