You can express this with a shapeless type inequality that requires proof that a type argument does not conform with a supertype.
scala> import shapeless._
import shapeless._
scala> trait Animal; trait Veg; class Foo extends Animal; class Bar extends Animal with Veg
defined trait Animal
defined trait Veg
defined class Foo
defined class Bar
scala> def blah[A <: Animal](a: A)(implicit ev: A <:!< Veg): A = a
blah: [A <: Animal](a: A)(implicit ev: shapeless.<:!<[A,Veg])A
scala> blah(new Foo)
res3: Foo = Foo@29eef106
scala> blah(new Bar)
<console>:20: error: ambiguous implicit values:
both method nsubAmbig1 in package shapeless of type [A, B >: A]=> shapeless.package.<:!<[A,B]
and method nsubAmbig2 in package shapeless of type [A, B >: A]=> shapeless.package.<:!<[A,B]
match expected type shapeless.<:!<[Bar,Veg]
blah(new Bar)
^
Of course, you can just upcast to Animal and bypass the constraint…
By way of demonstration, continuing @tpolecat’s example:
scala> blah(new Bar)
:17: error: ambiguous implicit values:
both method nsubAmbig1 in package shapeless of type [A, B >: A]=> A <:!< B
and method nsubAmbig2 in package shapeless of type [A, B >: A]=> A <:!< B
match expected type Bar <:!< Veg
blah(new Bar)
^
scala> blah(new Bar: Animal)
res2: Animal = Bar@7c33e49a
So with respect to classes of values, use of <:!< should be considered
advisory, or an aid to [disambiguating] implicit resolution, not a
guarantee. Of course it’s entirely correct when considered on the level
it’s trying to operate on, types not classes: the /type/ Animal indeed
is not <: Veg, and so the second call compiles.
There is a good theoretical reason why this /must/ be so, @fhalde, which
I have elided here.
Of course, you can just upcast to Animal and bypass the constraint…
I think it would only be useful in cases where the type variable appears in the return type, so you would be forced to give something up by upcasting. But I can’t think of a use case offhand … my point was simply that it is already expressible.