Solution (source code)

= Solution

Because `Set` is invariant in the specified <Scala> library, even returning `Set[U]` would place the class's <covariant type parameter> in an invariant position. Introduce a fresh widened type for each lifting.
``
class CoPartialFnExt[T,+U](data: List[(T,U)])
    extends CoPartialFn[T,U](data) {

  def forwardLift[V >: U](s: Set[T]): Set[V] = {
    val ys: List[V] = this.data.collect {
      case (x,y) if s.contains(x) => y
    }
    ys.toSet
  }

  def backwardLift[V >: U](s: Set[V]): Set[T] =
    this.data.collect {
      case (x,y) if s.contains(y) => x
    }.toSet
}
``
The <lower type bound> gives $U<:V$, so stored output values can be widened into `List[V]` or passed to `s.contains`. Both exposed `Set` types use invariant method <type parameters>, not the class's `U`. Calling `forwardLift[U]` or `backwardLift[U]` when the concrete output type is known recovers the exact output universe; a broader admissible universe is also safe.

For a <partial function>, the forward formula is understood only for inputs in its <domain of a function>. The implementations therefore compute
$$
\boxed{F(S)=\{y\mid(x,y)\in\mathrm{data},\ x\in S\},\qquad B(R)=\{x\mid(x,y)\in\mathrm{data},\ y\in R\}.}
$$
The conversion to a <set> removes repeated images. Inputs not in the <domain of a function> contribute nothing to either lifting.