You may want to consider the approach Haskell uses to solve this problem, which is to say via typeclasses. A typeclass is a set of functions that can be implemented by many types. When a polymorphic function needs to be able to call such a function on a type that is unknown, it is silently modified to add an extra parameter, the "dictionary" for the type's instance of the type class (which is a structure containing the functions for that type's instance). Then call sites where the actual type is known are silently modified to pass the correct instance, and so on.
Then you just need a type class "Typeable" that is automatically defined for all types and which contains an appropriate "typeOf" for each defined type.
This may not be the easiest way of achieving what you want, but it leaves you with extremely useful general mechanism for solving many kinds of polymorphism problems, so it might be the most useful way of achieving what you want.
Simplifying the way this works in Haskell slightly (because AIUI Haskell's implementation involves an optimization that is conceptually unnecessary):
The typeclass Typeable is translated to a record type containing a function forall a . a -> Type. 904
The function typeOf is defined with type Typeable a => a -> Type which means that there is an implicit parameter of type Typeable. typeOf is defined to simply unwrap the function from the record and call it.
A polymorphic function that calls typeOf needs to get a dictionary for its type argument, so any such function is inferred to have type Typeable a => [whatever the type would have been originally] for any type variable a that it needs to call typeOf for. This also means that there is an implicit argument to this function, too.
A function that calls typeOf for a known type has to provide an argument for the implicit arguments. This is calculated during the type checking phase, and it picks an appropriate predefined instance for the type of the term (which it obviously knows, because it has just type-checked it).
So in your example from the comments, the type checker knows that the term typeOf is applied to has type [Number] so it picks an instance for that type and adds that as a parameter.
So far, this is just the usual implementation of typeclasses. The twist here is that the type checker treats Typeable as a special case and generates the instances itself on demand so there will always be an instance available for any type that exists in the program.
It may also be worth noting that the instance for [a] can potentially delegate to the instance for a and wrap/otherwise modify the result. This may reduce the number of instances that are required. 904