import scala.collection.mutable.ListBuffer

class A
class B extends A
class C extends B

trait FF1[T, +R]
trait FF2[-T, R]

class Inv[T]
class InvA extends Inv[A]
class InvB extends Inv[B]
class InvC extends Inv[C]

class Multi[+Cov, Inv, -Con]
class MultiCov[+T <: A] extends Multi[T, B, C]
class MultiInv[T] extends Multi[A, T, C]
class MultiCon[-T >: C] extends Multi[A, B, T]

object Test {
  def f1 = Set[Inv[A]]() + new Inv[A]
  def f2 = Set[Inv[A]]() + new Inv[B]
  def f3 = Set[Inv[A]]() + new Inv[C]
  def f4 = Set[Inv[B]]() + new Inv[A]
  def f5 = Set[Inv[B]]() + new Inv[B]
  def f6 = Set[Inv[B]]() + new Inv[C]
  def f7 = Set[Inv[C]]() + new Inv[A]
  def f8 = Set[Inv[C]]() + new Inv[B]
  def f9 = Set[Inv[C]]() + new Inv[C]
  
  def g1 = Set[Multi[A, B, C]]() + new MultiCov[A]
  def g2 = Set[Multi[A, B, C]]() + new MultiCov[B]
  def g3 = Set[Multi[A, B, C]]() + new MultiCov[C]
  def g4 = Set[Multi[A, B, C]]() + new MultiInv[A]
  def g5 = Set[Multi[A, B, C]]() + new MultiInv[B]
  def g6 = Set[Multi[A, B, C]]() + new MultiInv[C]
  def g7 = Set[Multi[A, B, C]]() + new MultiCon[A]
  def g8 = Set[Multi[A, B, C]]() + new MultiCon[B]
  def g9 = Set[Multi[A, B, C]]() + new MultiCon[C]
}

object Functions {
  object Set1 {
    def f[T, R](x: FF1[T, R]) = ()
    def h[T, R] : FF1[T, R] = sys.error("")
    
    def ff1 = f[B, B](h[A, A]) // fail
    def ff2 = f[B, B](h[B, A]) // fail
    def ff3 = f[B, B](h[C, A]) // fail
    def ff4 = f[B, B](h[A, B]) // suggest
    def ff5 = f[B, B](h[B, B]) // ok 
    def ff6 = f[B, B](h[C, B]) // suggest
    def ff7 = f[B, B](h[A, C]) // suggest
    def ff8 = f[B, B](h[B, C]) // ok
    def ff9 = f[B, B](h[C, C]) // suggest
  }
  object Set2 {
    def f[T, R](x: FF2[T, R]) = ()
    def h[T, R] : FF2[T, R] = sys.error("")
    
    def ff1 = f[B, B](h[A, A]) // suggest
    def ff2 = f[B, B](h[B, A]) // suggest
    def ff3 = f[B, B](h[C, A]) // fail
    def ff4 = f[B, B](h[A, B]) // ok
    def ff5 = f[B, B](h[B, B]) // ok
    def ff6 = f[B, B](h[C, B]) // fail
    def ff7 = f[B, B](h[A, C]) // suggest
    def ff8 = f[B, B](h[B, C]) // suggest
    def ff9 = f[B, B](h[C, C]) // fail
  }
}

// TODO
// object TypeAlias {
//   type LL[T] = List[T]
//   val LL = List
//   
//   def f1 = Set[LL[B]]() + LL[A](new A)
//   def f2 = Set[LL[B]]() + LL[C](new C)
// }

object Javas {
  def f[T](x: java.util.List[T]) = ()
  def g[T](x: java.util.Comparator[T]) = ()
  
  def g1 = f[AnyRef](new java.util.ArrayList[String] { })
  def g2 = g[String](Ordering.fromLessThan[AnyRef](_.toString < _.toString))
}

object Misc {  
  // original motivation
  class Data[A <: AnyVal]
  class MyData extends Data[Int] { }
  def f1 = Set[Data[AnyVal]]() + new MyData

  // from stackoverflow
  def foo(s: Set[CharSequence]): Unit = ()
  def f4 = {
    val s: Set[String] = Set("Hello", "World");
    foo(s)
  }

  class Trippy[+T1, T2, +T3]
  def g1 = Set[Trippy[AnyRef, AnyRef, AnyRef]]() + new Trippy[String, String, String]
  def g2 = Set[Map[String, String]]() + Map[AnyRef, String]()
}