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

Incorrect wireing of abstract override methods implementing specialized (narrowed) version of the root trait #9894

Open
scabug opened this issue Aug 17, 2016 · 6 comments
Assignees
Milestone

Comments

@scabug
Copy link

scabug commented Aug 17, 2016

Having a base trait, AbstractOverrideTest, with a specialized version EnhancedAOT, intended to be implemented as a mixin using stacked traits pattern, results in the following code compiling, but throwing ClassCastException as the abstract override method expects
an EnhancedAOT instead of root AbstractOverrideTest. In practice, the following mixin shouldn't compile:

trait AbstractOverrideTest[-I, +O] { self =>

	def flatMap[T](f :O=>Seq[T]) :AbstractOverrideTest[I, T] //=

	override def toString = "AbstractOverrideTest"
}

trait EnhancedAOT[-I, +O] extends AbstractOverrideTest[I, O] { self =>
	override def flatMap[T](f: (O) => Seq[T]): EnhancedAOT[I, T] //= new EnhancedAOT[I, T]{

	override def toString = "EnhancedAOT"
}

trait EnhancedMixin[-I, +O] extends EnhancedAOT[I, O] {
	self =>
//	self :EnhancedAOT[I, O] =>
	override def toString = "EnhancedMixin"

	abstract override def flatMap[T](f: (O) => Seq[T]): EnhancedAOT[I, T] = {
		println(s"EhancedMixin[$self].flatMap{ ...")
		val e = super.flatMap(f)
		new EnhancedAOT[I, T] {
			println(s"... } EnhancedMixin[$self].flatMap($e)")

			override def toString = s"EnhancedMixin[$self].flatMap($e).new"

			override def flatMap[S](g: (T) => Seq[S]): EnhancedAOT[I, S] = {
				println(s"EnhancedMixin[$self].flatMap.new[$this]{")
				self.flatMap(f(_).flatMap(g))
			}
		}
	}
}



class RealAOT[-I, +O] extends AbstractOverrideTest[I, O] {
	override def flatMap[T](f: (O) => Seq[T]): AbstractOverrideTest[I, T] = new RealAOT[I, T]

	override def toString = s"RealAOT"
}

class Mixed[-I, +O] extends RealAOT[I, O] with EnhancedAOT[I, O] with EnhancedMixin[I, O] {
	override def toString = s"Mixed"
}


object AbstractOverrideApp extends App {
	case object MakeSeq extends (Any=>Seq[Any]) {
		override def apply(v1: Any): Seq[Any] = Seq(v1)
	}

	val m = new Mixed[Any, Any]
	println(s"m:\n$m\n")
	//below throws a ClassCast because EnhancedMixin expects an EnhancedAOT result in abstract override instead of just AbstractOverrideTest
	val mf = m.flatMap(MakeSeq)
	println(s"m.flatMap:\n$mf\n")

	val mff = mf.flatMap(MakeSeq)
	println(s"m.flatMap.flatMap:\n$mff\n")
}

And the resulting stack trace:

m:
Exception in thread "main" java.lang.ClassCastException: pl.proplus.station.recombiners.RealAOT cannot be cast to pl.proplus.station.recombiners.EnhancedAOT
Mixed
	at pl.proplus.station.recombiners.Mixed.pl$proplus$station$recombiners$EnhancedMixin$$super$flatMap(AbstractOverrideTest.scala:48)

	at pl.proplus.station.recombiners.EnhancedMixin$class.flatMap(AbstractOverrideTest.scala:26)
EhancedMixin[Mixed].flatMap{ ...
	at pl.proplus.station.recombiners.Mixed.flatMap(AbstractOverrideTest.scala:48)
	at pl.proplus.station.recombiners.AbstractOverrideApp$.delayedEndpoint$pl$proplus$station$recombiners$AbstractOverrideApp$1(AbstractOverrideTest.scala:64)
	at pl.proplus.station.recombiners.AbstractOverrideApp$delayedInit$body.apply(AbstractOverrideTest.scala:53)
	at scala.Function0$class.apply$mcV$sp(Function0.scala:34)
	at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
	at scala.App$$anonfun$main$1.apply(App.scala:76)
	at scala.App$$anonfun$main$1.apply(App.scala:76)
	at scala.collection.immutable.List.foreach(List.scala:381)
	at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
	at scala.App$class.main(App.scala:76)
	at pl.proplus.station.recombiners.AbstractOverrideApp$.main(AbstractOverrideTest.scala:53)
	at pl.proplus.station.recombiners.AbstractOverrideApp.main(AbstractOverrideTest.scala)
	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:497)
	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)

Process finished with exit code 1

Proper implementation with the following change:

trait EnhancedMixin[-I, +O] extends AbstractOverrideTest[I, O] {
	self : EnhancedAOT[I, O] =>

Works as expected, so this is a minor issue which should just report an error during compilation.

@scabug
Copy link
Author

scabug commented Aug 17, 2016

Imported From: https://issues.scala-lang.org/browse/SI-9894?orig=1
Reporter: Marcin Mościcki (mmoscicki)
Affected Versions: 2.11.8, 2.12.0-RC1
Attachments:

@scabug
Copy link
Author

scabug commented Sep 29, 2016

@SethTisue said:
Can this be minimized? That's a lot of code up there.

@scabug
Copy link
Author

scabug commented Sep 29, 2016

Marcin Mościcki (mmoscicki) said (edited by @lrytz on Sep 30, 2016 9:42:11 AM UTC):
Hi,
Here it is with removed all debug information in case it will be easier to process for you:

trait AbstractOverrideTest[-I, +O] { self =>

	def flatMap[T](f :O=>Seq[T]) :AbstractOverrideTest[I, T]
}

trait EnhancedAOT[-I, +O] extends AbstractOverrideTest[I, O] { self =>
	override def flatMap[T](f: (O) => Seq[T]): EnhancedAOT[I, T]
}

trait EnhancedMixin[-I, +O] extends EnhancedAOT[I, O] {
	self =>

	abstract override def flatMap[T](f: (O) => Seq[T]): EnhancedAOT[I, T] = {
		val e = super.flatMap(f)
		//we won't reach here
		???
	}
}



class RealAOT[-I, +O] extends AbstractOverrideTest[I, O] {
	override def flatMap[T](f: (O) => Seq[T]): AbstractOverrideTest[I, T] = new RealAOT[I, T]
}

class Mixed[-I, +O] extends RealAOT[I, O] with EnhancedAOT[I, O] with EnhancedMixin[I, O]


object AbstractOverrideApp extends App {
	//below throws a ClassCast because EnhancedMixin expects an EnhancedAOT result in abstract override instead of just AbstractOverrideTest
	val mf = new Mixed[Any, Any].flatMap(Seq(_))
}

And the stack trace (not very helpful due to all re-wiring by the compiler, but the final like should be obvious:

Exception in thread "main" java.lang.ClassCastException: RealAOT cannot be cast to EnhancedAOT
	at Mixed.EnhancedMixin$$super$flatMap(Playground.scala:28)
	at EnhancedMixin$class.flatMap(Playground.scala:16)
	at Mixed.flatMap(Playground.scala:28)
	at AbstractOverrideApp$.delayedEndpoint$AbstractOverrideApp$1(Playground.scala:33)
	at AbstractOverrideApp$delayedInit$body.apply(Playground.scala:31)
	at scala.Function0$class.apply$mcV$sp(Function0.scala:34)
	at scala.runtime.AbstractFunction0.apply$mcV$sp(AbstractFunction0.scala:12)
	at scala.App$$anonfun$main$1.apply(App.scala:76)
	at scala.App$$anonfun$main$1.apply(App.scala:76)
	at scala.collection.immutable.List.foreach(List.scala:381)
	at scala.collection.generic.TraversableForwarder$class.foreach(TraversableForwarder.scala:35)
	at scala.App$class.main(App.scala:76)
	at AbstractOverrideApp$.main(Playground.scala:31)
	at AbstractOverrideApp.main(Playground.scala)
	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 com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Process finished with exit code 1

I don't think I can simplify it to use fewer classes. The bug is between class linearization and how method overriding works;

  • 1 EnhancedAOT overrides the declaration of flatMap in AbstractOverrideTest, without providing a definition
  • 2 EnhancedMixin declares an abstract override, and as it extends EnhancedAOT, it expects the actual implementation to conform to the narrowed down EnhancedAOT#flatMap signature;
  • 3 RealAOT implements the vanilla signature from root AbstractOverrideTest, as it doesn't extend either of EnhancedXxx;
  • 4 Mixed inherits the only implementation of the method, found in RealAOT;
  • 5 In its linearization, RealAOT is followed by EnhancedAOT which overrides the signature only, without providing implementation, and finally EnhancedMixin which provides only an abstract override implementation.

After itemizing it this way, I believe that my first conclusion was wrong, and in fact the declaration

class Mixed[-I, +O] extends RealAOT[I, O] with EnhancedAOT[I, O] with EnhancedMixin[I, O]

should be illegal, failing compilation at this point, even if Mixed was declared abstract, because there isn't any implementation of EnhancedAOT#flatMap in its linearization beefore EnhancedMixin.

Sorry if I refered to linearization in different order than definition from scala spec, but I find it much more intuitive if it follows the order in which the types are mixed in, rather than the reverse for the sake of being able to call the right method the first.

I hope this explanation will be clear enough.
Cheers,
Marcin

@scabug
Copy link
Author

scabug commented Sep 29, 2016

@SethTisue said:
OK, one more question, is the situation improved in 2.12.0-RC1?

@scabug
Copy link
Author

scabug commented Sep 29, 2016

Marcin Mościcki (mmoscicki) said (edited on Sep 29, 2016 9:26:32 PM UTC):
Unfortunately it seems that it didn't, at least in REPL/IDEA worksheet.sc

@scabug
Copy link
Author

scabug commented Sep 30, 2016

@lrytz said:
Reduced it a bit - not the number of classes, but the cruft around:

trait T {
  def m: T
}

trait U extends T {
  override def m: U
}

trait V extends U {
  abstract override def m: U = super.m
}

class W extends T {
  override def m: T = new W
}

class C extends W with U with V

object Test extends App {
    new C().m // bum
}

@scabug scabug added the quickfix label Apr 7, 2017
@scabug scabug added this to the Backlog milestone Apr 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants