Without seeing what should the impl be it is hard to say exactly, but you can define MyType as:
type MyType[T] = T => T
or
type MyType[T <: Animal] = T => T
Here is one way possible way to write this so that everything typechecks:
type MyType[T] = T => T
enum Animal:
case Dog(i: Int)
case Cat(a: String)
def impl[T]: MyType[T] = x => x
val cat: Animal.Cat = Animal.Cat("A")
val dog: Animal.Dog = Animal.Dog(1)
val updatedCat: Animal.Cat = impl(cat)
val updatedDog: Animal.Dog = impl(dog)
Note: without explicit type even type of val cat = Animal.Cat("A") is Animal, not Animal.Cat.
If you wonder why the cast is necessary in the match case, is because as far as the compiler is concerned that animal matched Cat at runtime, doesn’t mean that the type was Cat. It could have been Animal or it could have been something like Cat | Dog or it could have been something like Cat & Foo, etc.
It is possible to return the same time as on the input, using generics, two major techniques are type-classes or f-bound polymorphism, also called recursive types.
In your case though it seems you do not have the exact type (Cat or Dog) on the input compile-time, therefore I am not sure what compile-time type of the result do you expect.
Doing the same runtime is trivial, and your code using match already does it.
Perhaps if you show how do you consume the result of impl, so that we can see where is your asInstanceOf now, it might be possible to suggest a way.