Skip to content

Unsound type propagation/refinement *without case classes* #4014

Closed
@Blaisorblade

Description

@Blaisorblade

Due to @odersky — he generalized the first part of #3989 without using case classes.
I reconstructed the example from its core (case b: B[_] => a.i), but that was tricky, hence this ticket.

trait A[+X]; class B[X](val x: X) extends A[X]
def f(a: A[Int]): Int =
  a match {
    case b: B[_] => b.x
  }
class C extends B[String]("") with A[Int]
f(new C())

Transcript:

scala> trait A[+X]; class B[X](val x: X) extends A[X]
// defined trait A
// defined class B
scala> def f(a: A[Int]): Int =
           a match {
             case b: B[_] => b.x
           }
def f(a: A[Int]): Int
scala> class C extends B[String]("") with A[Int]
// defined class C
scala> f(new C())
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
	at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
	at rs$line$29$.f(rs$line$29:3)
	at rs$line$31$.<init>(rs$line$31:1)
	at rs$line$31$.<clinit>(rs$line$31)
	at rs$line$31.res5Show(rs$line$31)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at dotty.tools.repl.Rendering.valueOf(Rendering.scala:58)
	at dotty.tools.repl.Rendering.renderVal(Rendering.scala:79)
	at dotty.tools.repl.ReplDriver.$anonfun$displayDefinitions$8(ReplDriver.scala:281)
	at scala.collection.TraversableLike.$anonfun$map$1(TraversableLike.scala:234)
	at scala.collection.mutable.ResizableArray.foreach(ResizableArray.scala:59)
	at scala.collection.mutable.ResizableArray.foreach$(ResizableArray.scala:52)
	at scala.collection.mutable.ArrayBuffer.foreach(ArrayBuffer.scala:48)
	at scala.collection.TraversableLike.map(TraversableLike.scala:234)
	at scala.collection.TraversableLike.map$(TraversableLike.scala:227)
	at scala.collection.AbstractTraversable.map(Traversable.scala:104)
	at dotty.tools.repl.ReplDriver.displayMembers$1(ReplDriver.scala:281)
	at dotty.tools.repl.ReplDriver.$anonfun$displayDefinitions$15(ReplDriver.scala:308)
	at scala.Option.map(Option.scala:146)
	at dotty.tools.repl.ReplDriver.$anonfun$displayDefinitions$13(ReplDriver.scala:306)
	at dotty.tools.dotc.core.Periods.atPhase(Periods.scala:26)
	at dotty.tools.dotc.core.Phases.atPhase(Phases.scala:36)
	at dotty.tools.dotc.core.Phases.atPhase$(Phases.scala:35)
	at dotty.tools.dotc.core.Contexts$Context.atPhase(Contexts.scala:70)
	at dotty.tools.repl.ReplDriver.displayDefinitions(ReplDriver.scala:301)
	at dotty.tools.repl.ReplDriver.$anonfun$compile$2(ReplDriver.scala:238)
	at scala.util.Either.fold(Either.scala:188)
	at dotty.tools.repl.ReplDriver.compile(ReplDriver.scala:232)
	at dotty.tools.repl.ReplDriver.interpret(ReplDriver.scala:202)
	at dotty.tools.repl.ReplDriver.loop$1(ReplDriver.scala:150)
	at dotty.tools.repl.ReplDriver.$anonfun$runUntilQuit$1(ReplDriver.scala:154)
	at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
	at scala.Console$.withErr(Console.scala:192)
	at dotty.tools.repl.ReplDriver.$anonfun$withRedirectedOutput$1(ReplDriver.scala:163)
	at scala.util.DynamicVariable.withValue(DynamicVariable.scala:58)
	at scala.Console$.withOut(Console.scala:163)
	at dotty.tools.repl.ReplDriver.withRedirectedOutput(ReplDriver.scala:163)
	at dotty.tools.repl.ReplDriver.runUntilQuit(ReplDriver.scala:154)
	at dotty.tools.repl.Main$.main(Main.scala:6)
	at dotty.tools.repl.Main.main(Main.scala)

The basic strategy is similar to #3989 (comment), but it's a bit trickier to exploit it. The compiler assumes that a: A[Int] is also a B[Int] while it can be, in fact, B[String] — or in general, it can be a B[X] for arbitrary X.

Since here no case classes are involved, we can't forbid definitions like C (they're too common), @odersky suggested that we simply deducing unsound constraints about X.

Conversely, for GADTs we can make the opposite tradeoff: forbid definitions like C and allow deducing (sound) constraints on type variables as usual.

I'm a bit concerned by the irregularity, but it's hard to think of anything better.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions