In Scala 2
trait Supe { self: Singleton =>
  type T1 <: Singleton
  def default: T1
  def process(v: Any): T1 =
    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
}
object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
}
object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 1
To overcome type erasure, in Scala 2 we use scala.reflect.ClassTag. Either (approach 1)
trait Supe { self: Singleton =>
  type T1 <: Singleton
  def default: T1
  def process(v: Any)(implicit ct: ClassTag[T1]): T1 =
    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
}
object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
}
object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
or (approach 2)
trait Supe { self: Singleton =>
  type T1 <: Singleton
  def default: T1
  implicit def ctag: ClassTag[T1]
  def process(v: Any): T1 =
    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
}
object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
  override implicit def ctag: ClassTag[T1] = {
    val ctag = null // hiding implicit by name, otherwise implicitly[...] returns the above ctag
    implicitly[ClassTag[T1]]
  }
}
object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
  override implicit def ctag: ClassTag[T1] = {
    val ctag = null // hiding implicit by name, otherwise implicitly[...] returns the above ctag
    implicitly[ClassTag[T1]]
  }
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
Similarly, in Scala 3
trait Supe { self: Singleton =>
  type T1 <: Singleton
  lazy val default: T1
  def process(v: Any): T1 =
    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
}
object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
}
object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 1
In Scala 3 scala.reflect.Typeable (scala.reflect.TypeTest) is used instead of ClassTag. The approach 1 works
trait Supe { self: Singleton =>
  type T1 <: Singleton
  lazy val default: T1
  def process(v: Any)(using Typeable[T1]): T1 =
    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
}
object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
}
object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
Regarding the approach 2, in Scala 3 there is no way to hide implicit by name, so let's put it into a local scope so that it will not be found in object implicit scope
trait Supe { self: Singleton =>
  type T1 <: Singleton
  lazy val default: T1
  def tt: Typeable[T1]
  def process(v: Any): T1 = {
    given Typeable[T1] = tt
    v match {
      case vv: T1 => println(1); vv
      case _      => println(2); default
    }
  }
}
object Impl1 extends Supe {
  override type T1 = Impl1.type
  override lazy val default: T1 = this
  override def tt: Typeable[T1] = summon
}
object Impl2 extends Supe {
  override type T1 = Impl2.type
  override lazy val default: T1 = this
  override def tt: Typeable[T1] = summon
}
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
I still think given or using could be avoided in all instances. Either method requires a lot of boilerplate, particularly if the pattern matching contains a lot of union or intersection types
In your last example, adding every new case using types like T1 with Int or T1 {def v: Int} necessitates a Typeable. This is infeasible in many cases.
ClassTag, TypeTag, shapeless.Typeable/TypeCase (Scala 2), TypeTest/Typeable, Shapeless-3 Typeable (Scala 3) are standard tools to overcome type erasure. Matching types at runtime is not typical for pattern matching. If your business logic is based on types then maybe you don't need pattern matching at all, maybe type classes would be a better choice
trait Supe:
  self: Singleton =>
  type T1 <: Singleton
  lazy val default: T1
  trait Process[A]:
    def process(a: A): T1
  trait LowPriorityProcess:
    given low[A]: Process[A] with
      def process(a: A): T1 = { println(2); default }
  object Process extends LowPriorityProcess:
    given [A <: T1]: Process[A] with
      def process(a: A): T1 = { println(1); a }
  def process[A: Process](v: A): T1 =
    summon[Process[A]].process(v)
object Impl1 extends Supe:
  override type T1 = Impl1.type
  override lazy val default: T1 = this
object Impl2 extends Supe:
  override type T1 = Impl2.type
  override lazy val default: T1 = this
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
or
trait Process[A, T1 <: Singleton]:
  def process(a: A, default: T1): T1
trait LowPriorityProcess:
  given low[A, T1 <: Singleton]: Process[A, T1] with
    def process(a: A, default: T1): T1 =
      {println(2); default}
object Process extends LowPriorityProcess:
  given[A <: T1, T1 <: Singleton]: Process[A, T1] with
    def process(a: A, default: T1): T1 =
      {println(1); a }
trait Supe:
  self: Singleton =>
  type T1 <: Singleton
  lazy val default: T1
  def process[A](v: A)(using p: Process[A, T1]): T1 =
    p.process(v, default)
object Impl1 extends Supe:
  override type T1 = Impl1.type
  override lazy val default: T1 = this
object Impl2 extends Supe:
  override type T1 = Impl2.type
  override lazy val default: T1 = this
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2
adding every new case using types like T1 with Int or T1 {def v: Int} necessitates a Typeable. This is infeasible in many cases.
trait Supe:
  self: Singleton =>
  type T1 <: Singleton
  lazy val default: T1
  def tt[S <: T1]: Typeable[S]
  def process(v: Any): T1 =
    given Typeable[T1] = tt
    given tt1: Typeable[T1 with SomeTrait] = tt
    given tt2: Typeable[T1 {def v: Int}] = tt
    //...
    v match
      case vv: T1 => {println(1); vv}
      case _      => {println(2); default}
trait SomeTrait
object Impl1 extends Supe:
  override type T1 = Impl1.type with SomeTrait
  override lazy val default: T1 = this.asInstanceOf[T1]
  override def tt[S <: T1]: Typeable[S] = summon[Typeable[S @unchecked]]
object Impl2 extends Supe:
  override type T1 = Impl2.type {def v: Int}
  override lazy val default: T1 = this.asInstanceOf[T1]
  override def tt[S <: T1]: Typeable[S] = summon[Typeable[S @unchecked]]
Impl1.process(Impl1) // 1
Impl1.process(Impl2) // 2