import scala.language.reflectiveCalls

trait A {
  trait Concrete { def conco: Int = 1 }
  type Foo <: { def bippy: Int }
  type Bar <: { def barry: Int }

  implicit def barTag: scala.reflect.ClassTag[Bar]

  def f1(x: Any) = x match {
    case x: Foo with Concrete => x.bippy + x.conco
    case _                    => -1
  }
  def f2(x: Any) = x match {
    case x: Concrete with Foo => x.bippy + x.conco
    case _                    => -1
  }
  def f3(x: Any) = x match {
    case x: Foo with Bar => x.bippy + x.barry
    case _               => -1
  }
  def f4(x: Any) = x match {
    case x: (Foo @unchecked) => x.bippy  // warns, suppressed
    case _                   => -1
  }
  def f5(x: Any) = x match {
    case x: (Bar @unchecked) => x.barry // warns (but about the "outer reference"), suppressed
    case _      => -1
  }
}

trait B extends A {
  type Foo <: { def bippy: Int ; def dingo: Int }
  type Bar <: { def barry: Int ; def bongo: Int }

  override implicit def barTag: scala.reflect.ClassTag[Bar]

  override def f1(x: Any) = x match {
    case x: Foo with Concrete => x.bippy + x.dingo + x.conco
    case _                    => -1
  }
  override def f2(x: Any) = x match {
    case x: Concrete with Foo => x.bippy + x.dingo + x.conco
    case _                    => -1
  }
  override def f3(x: Any) = x match {
    case x: Foo with Bar with Concrete => x.bippy + x.barry + x.dingo + x.conco + x.bongo
    case _                             => -1
  }
  override def f4(x: Any) = x match {
    case x: (Foo @unchecked) => x.bippy + x.dingo // warns, suppressed
    case _                   => -1
  }
  override def f5(x: Any) = x match {
    case x: (Bar @unchecked) => x.barry + x.bongo // warns (but about the "outer reference"), suppressed
    case _                   => -1
  }
}

object Test {
  abstract class Base extends A {
    trait Foo {
      def bippy = 2
      def dingo = 3
    }
    trait Bar {
      def barry = 2
      def bongo = 3
    }
    implicit def barTag: scala.reflect.ClassTag[Bar] = scala.reflect.ClassTag(classOf[Bar])

    def run(): Unit = {
      println("f1")
      wrap(f1(new Concrete {}))
      wrap(f1(new Foo {}))
      wrap(f1(new Bar {}))
      wrap(f1(new Foo with Concrete {}))
      wrap(f1(new Concrete with Foo {}))

      println("\nf2")
      wrap(f2(new Concrete {}))
      wrap(f2(new Foo {}))
      wrap(f2(new Bar {}))
      wrap(f2(new Foo with Concrete {}))
      wrap(f2(new Concrete with Foo {}))
      wrap(f2(new Bar with Concrete {}))
      wrap(f2(new Concrete with Bar {}))
      wrap(f2(new Concrete with Foo with Bar {}))
      wrap(f2(new Foo with Bar with Concrete {}))

      println("\nf3")
      wrap(f3(new Concrete {}))
      wrap(f3(new Foo {}))
      wrap(f3(new Bar {}))
      wrap(f3(new Foo with Concrete {}))
      wrap(f3(new Concrete with Foo {}))
      wrap(f3(new Bar with Concrete {}))
      wrap(f3(new Concrete with Bar {}))
      wrap(f3(new Concrete with Foo with Bar {}))
      wrap(f3(new Foo with Bar with Concrete {}))

      println("\nf4")
      wrap(f4(new Concrete {}))
      wrap(f4(new Foo {}))
      wrap(f4(new Bar {}))
      wrap(f4(new Foo with Concrete {}))
      wrap(f4(new Concrete with Foo {}))
      wrap(f4(new Bar with Concrete {}))
      wrap(f4(new Concrete with Bar {}))
      wrap(f4(new Concrete with Foo with Bar {}))
      wrap(f4(new Foo with Bar with Concrete {}))

      println("\nf5")
      wrap(f5(new Concrete {}))
      wrap(f5(new Foo {}))
      wrap(f5(new Bar {}))
      wrap(f5(new Foo with Concrete {}))
      wrap(f5(new Concrete with Foo {}))
      wrap(f5(new Bar with Concrete {}))
      wrap(f5(new Concrete with Bar {}))
      wrap(f5(new Concrete with Foo with Bar {}))
      wrap(f5(new Foo with Bar with Concrete {}))
    }
  }

  object ao extends Base
  object bo extends Base with B

  private def wrap(body: => Any): Unit = {
    try println(body)
    catch { case ex: NoSuchMethodException => println(ex) }
  }

  def main(args: Array[String]): Unit = {
    ao.run()
    bo.run()
  }
}

// java.lang.NoSuchMethodException: Test$$anon$1.bippy()