So, how about this (the following code is for scala3, but it is easy to implement in scala2):
scala> @annotation.implicitNotFound("Need (None, Some) or (Some, None), found (${F}, ${S})")
| trait Cons[F, S]
|
| object Cons:
| given Cons[Some[String], None.type] = new Cons {}
| given Cons[None.type, Some[Boolean]] = new Cons {}
|
| case class MyClass[F <: Option[String], S <: Option[Boolean]](first: F, second: S)(using c: Cons[F, S])
scala> val m1 = MyClass(None, Some(true))
val m1: MyClass[None.type, Some[Boolean]] = MyClass(None,Some(true))
scala> val m2 = MyClass(Some(""), None)
val m2: MyClass[Some[String], None.type] = MyClass(Some(),None)
scala> val m3 = MyClass(None, None)
-- Error: --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 |val m3 = MyClass(None, None)
| ^
| Need (None, Some) or (Some, None), found (None.type, None.type)
1 error found
scala> val m4 = MyClass(Some(""), Some(false))
-- Error: --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 |val m4 = MyClass(Some(""), Some(false))
| ^
| Need (None, Some) or (Some, None), found (Some[String], Some[Boolean])
1 error found