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

unsoundness in many guises #7278

Closed
scabug opened this issue Mar 20, 2013 · 9 comments
Closed

unsoundness in many guises #7278

scabug opened this issue Mar 20, 2013 · 9 comments

Comments

@scabug
Copy link

scabug commented Mar 20, 2013

[Updated to consolidate demonstrations.]

class A { class E }
class B extends A { class E }
trait C { type E = Int }
trait D { type E = String }

object Test {
  // should not compile (?)
  // martin says "I'd argue about that"
  type EE[+X <: { type E }] = X#E

  def fail1() {
    val b = new B
    var x1: EE[A] = null
    var x2: EE[B] = new b.E
    x1 = x2
  }

  def fail2() {
    val b = new B
    var x1: p.E forSome { val p: A } = new b.E // should not compile
    var x2: p.E forSome { val p: B } = new b.E
    x1 = x2 // should not compile
  }

  def fail3() {
    var x1: EE[C] = 5
    var x2: EE[C with D] = ""
    x1 = x2
  }

  def wrap(label: String)(op: => Unit): Unit =
    try { op } catch { case x: ClassCastException => println("%30s %s".format(label, x)) }

  def main(args: Array[String]): Unit = {
    wrap("Variance and inner classes")(fail1())
    wrap("Conformance of existentials")(fail2())
    wrap("Linearization and type aliases")(fail3())
  }
}
//     Variance and inner classes java.lang.ClassCastException: B$E cannot be cast to A$E
//    Conformance of existentials java.lang.ClassCastException: B$E cannot be cast to A$E
// Linearization and type aliases java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
@scabug
Copy link
Author

scabug commented Mar 20, 2013

Imported From: https://issues.scala-lang.org/browse/SI-7278?orig=1
Reporter: @paulp
See #436, #902, #634
Duplicates #1557

@scabug
Copy link
Author

scabug commented Mar 20, 2013

@paulp said (edited on Mar 20, 2013 8:46:26 PM UTC):
Were E an abstract type, the E in B would be required to conform to the E in A. I assume the same requirement should be imposed on classes, which translates to: B#E must extend A#E.

[Update] I retitled this to point the unsoundness finger more vaguely, since martin says: "Same-named inner classes which are in no relationship with each other are explicitly allowed in both Java and Scala. I would like to tighten this - disallow then altogether or demand them to be in a subtype relationship. Unfortunately, this would break existing code [...] I believe they do not pose an unsoundness problem themselves. But they certainly are a trap to fall into in all sorts of feature interactions."

[Update] After finding another soundness hole which doesn't use existentials, I consolidated the code and retitled it even more vaguely.

@scabug
Copy link
Author

scabug commented Mar 21, 2013

@paulp said (edited on Mar 21, 2013 6:31:37 PM UTC):
I updated the code with a third soundness hole, this one using type aliases instead of inner classes. And I ported it so it fails on all scala versions from 2.7 onward.

@scabug
Copy link
Author

scabug commented Mar 22, 2013

@paulp said:
I see fail3() is not news; #1557 is reporting a variation on the problem in 2008. I am closing that as a duplicate, hoping it will receive more attention here. Enclosing the example because it exploits the problem differently.

trait A
trait B extends A

trait C {
  val x : AnyRef
  trait D { type T >: B <: A }
  val y: (D with x.type)#T = new B { }
}

class D extends C {
  trait E
  val x : AnyRef { type T = E} = null
  def frob(arg : E) : E = arg
  frob(y)
}

new D

@scabug
Copy link
Author

scabug commented May 31, 2013

@retronym said (edited on May 31, 2013 9:21:22 PM UTC):
Merging #7535 as a duplicate.

Code that provokes LinkageError

trait Def {
  type Element >: Null
  trait Motor {
    type Element = Def.this.Element
  }
}

trait Def2 extends Def {
  trait Element
  trait Motor extends super.Motor {
    def foo(parent: Element): Any
    foo(null)
  }
}

trait Def3 extends Def2 {
  trait Element // or extends super.Element2, same result.
  new super.Motor {
    def foo(e:Element): Any = null
  }
}

new Def3 {} // java.lang.AbstractMethodError: Main$$anon$3$Def3$$anon$2.foo(LMain$$anon$3$Def2$Element;)Ljava/lang/Object

Suggested:

trait Def {
  type Element >: Null
  trait Motor {
    type Element = Def.this.Element
  }
}

trait Def2 extends Def {
  type Element >: Null <: Element2 
  trait Element2
  trait Motor extends super.Motor {
    def foo(parent: Element): Any
    foo(null)
  }
}

trait Def3 extends Def2 {
  type Element >: Null <: Element3
  trait Element3 extends super.Element2
  new super.Motor {
    def foo(e:Element): Any = null
  }
}

new Def3 {}

@scabug
Copy link
Author

scabug commented Jun 10, 2013

@soc said:
An example which doesn't fail with a bang, but behaves in a way which seems to violate a lot of reasonable expectations:

  class Foo {
    class Bar {
      def foo = 1
    }
  }
 
  class SubFoo extends Foo {
    class Bar {
      def foo = 42
    }
  }

// Let's create some instances:
  val foo = new Foo
  val fooBar = new foo.Bar
  fooBar.foo                                      //> res0: Int = 1
  // ok

  val subFoo = new SubFoo
  val subFooBar = new subFoo.Bar
  subFooBar.foo                                   //> res1: Int = 42
  // ok

  val superFoo: Foo = subFoo
  val superFooBar = new superFoo.Bar
  superFooBar.foo                                 //> res2: Int = 1
  // NOT OK!

The last result is scary, because the syntax looks like things are dynamically bound, but the method is called on the static type instead.

Mailing list: https://groups.google.com/d/topic/scala-language/vQYwywr0LL4/discussion

@SethTisue
Copy link
Member

SethTisue commented Aug 27, 2023

Scala 3 refuses to compile type EE[+X <: { type E }] = X#E with "X is not a legal path since it is not a concrete type"

It also rejects class A { class E }; class B extends A { class E } with "class E cannot have the same name as class E in class A -- class definitions cannot be overridden"

As for the status of these various examples in Scala 2, I think one reason this ticket has stayed open so long is it combines a lot of different stuff. I don't understand what the motivation was to consolidate all this in a single ticket — and then it got bigger as people have piled on with comments. In recent years we have been more consistent about discouraging these sorts of umbrella tickets.

If anyone cares about some individual case here, I invite you to open a fresh ticket on it, so we can discuss it independently, label it independently, attempt to reproduce it in Scala 2.13.x and Scala 3 independently, and so forth.

@SethTisue SethTisue closed this as not planned Won't fix, can't repro, duplicate, stale Aug 27, 2023
@som-snytt
Copy link

Instead of a fresh ticket, I reopened 7535, which now shows green above.

@som-snytt
Copy link

The example from the mailing seems OK to me, but the thread shows that the expectation at the time was virtual classes.

It warns on 2.13 and errors under -Xsource:3:

shadowing a nested class of a parent is deprecated but class Bar shadows class Bar defined in class Foo; rename the class to something else

The paulp coda still crashes.

  import scala.language.existentials
  def pp = {
    var b: foo.Bar forSome { val foo: Foo } = new foo.Bar
    b = new subFoo.Bar
    b
  }

It can be "made to work" by the (probably expected) idiom

class Bar extends super.Bar

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

No branches or pull requests

4 participants