scala / bug

Scala 2 bug reports only. Please, no questions — proper bug reports only.
https://scala-lang.org
232 stars 21 forks source link

Type equivalence failure with nested type component inside a structural type #6904

Open scabug opened 11 years ago

scabug commented 11 years ago
object Test {
  class Bippy[A]

  def foo[Result[_]] = new {
    private type Elem[B] = Result[B]

    def bar: Result[Elem[Int]] = sys.error("TODO")
  }

  // error: Cannot prove that Test.Bippy[Test.Bippy[Int]] =:= Test.Bippy[Result[Int]].
  check[Bippy[Bippy[Int]]](foo[Bippy].bar)

  def check[A] = new {
    def apply[B](b: B)(implicit ev: A =:= B) = ()
  }
}

The "check" function is simply to assert inferred type equivalence without triggering implicit conversion or subtype coercion. If I inline "Elem", this works just fine. Similarly, if I use a named class as the return type of "foo", rather than new {}, there is again no problem.

scabug commented 11 years ago

Imported From: https://issues.scala-lang.org/browse/SI-6904?orig=1 Reporter: @djspiewak Affected Versions: 2.9.2, 2.10.0-RC1 See #4400

scabug commented 11 years ago

@milessabin said: Taking the private modifier off type Elem allows this to compile. I'd say the behaviour is probably correct as-is.

scabug commented 11 years ago

@djspiewak said: I'm not convinced. Private type members behave as true type aliases, with no polymorphic semantics. This precisely describes the behavior we see when I extract the anonymous class into a named class (which works just fine). More importantly, you'll note that the inferred type does not actually involve Elem, it involves Result. The compiler is failing to prove that Result[Int] = Bippy[Int], types which are entirely independent of Elem.

scabug commented 11 years ago

@paulp said: Simplified, with enhanced contrast (not that I object to bringing in Bippy.)

object Test {
  def foo1[A1] = new { private type A2 = A1 ; def bar: A2 = ??? }
  def foo2[A1] = new {         type A2 = A1 ; def bar: A2 = ??? }

  def f1 = check[Int](foo1[Int].bar)
  def f2 = check[Int](foo2[Int].bar)

  def check[A] = new { def apply[B](b: B)(implicit ev: A =:= B) = () }
}

There's no question this is a bug - look at the inferred type of foo1. Not only is A2 private, it is no longer in scope. Two for the price of one.

def foo1[A1 >: Nothing <: Any]: AnyRef{def bar: this.A2}
def foo2[A1 >: Nothing <: Any]: AnyRef{type A2 = A1; def bar: this.A2}
scabug commented 11 years ago

@paulp said: ...though one could see those as manifestation of the same thing.

Here's some code from one of my infinite variety of unpublished branches.

  /** Weaken the type of the symbol's info such that it doesn't contain
   *  symbols with tighter access restriction than the symbol itself.
   *  Since lubs are calculated without considering these requirements,
   *  they tend to include inaccessible types.
   */
  class WeakenToVisible(owner: Symbol) extends TypeMap {
    def isTighter(tp: Type) = (
         (tp.typeSymbol isLessAccessibleThan owner)
      && (tp.typeSymbolDirect isLessAccessibleThan owner)
    )
    def visibleParents(tps: List[Type]): List[Type] = {
      val tps1 = tps flatMap (tp =>
        if (isTighter(tp)) visibleParents(tp.parents)
        else List(tp)
      )
      // "flatMapConserve" to avoid new refined types.
      if ((tps corresponds tps1)(_ eq _)) tps
      else elimSuper(tps1)
    }
    def apply(tp: Type): Type = tp match {
      case rt @ RefinedType(ps, decls)       => copyRefinedType(rt, visibleParents(ps), mapOver(decls))
      case PolyType(_, _)                    => mapOver(tp)
      case MethodType(_, _)                  => mapOver(tp)
      case ExistentialType(_, _)             => mapOver(tp)
      case TypeRef(_, sym, _) if sym.isClass => mapOver(tp)
      case _                                 => tp
    }
  }