Yes, believe me, this is something that we have been discussing for a LONG time, and it has been implemented with every variation at some point.
The big thing is that the compiler does not actually know the type of the expression, it only knows what type is being reported, and that can come from hint type flow which may not actually match the later runtime reality.
A concrete example:
(defn remove [^Collection coll ^Predicate pred]
(.removeIf coll pred)))
Here, the call to removeIf takes a Predicate and expression type of the pred local will be Predicate. But Clojure treats hints as hints, not a static type signature, and you might pass an actual Predicate, or you might pass an IFn. Having the type check in the emitted code allows both to work transparently.
More subtlely, you might even have provided the type hint just on the pred argument: (.removeIf coll ^Predicate pred)
to resolve an overload (not in this case, but there are some like this) even though you know pred is not a Predicate and FI adapting is what you want. One possible way out of the overload case is to use the new param-tags metadata to indicate the preferred overload (^[_ Predicate] Collection/.removeIf coll pred)
but that's getting really clever to understand and apply.
We also looked at different behavior for the invocation and let-binding cases to maybe do this for let, but not for invocation. In the end, the only safe thing to do is to actually check at runtime.
The next question is the cost of that check, and in general in my perf testing, I can't find a detectable performance cost to this - you are usually passing functions to something else that is the real cost driver (passing a Predicate to a .filter or a Function to a .map or something like that). Especially on newer JVMs and CPUs, this is all going to optimized and correctly predicted if you happen to be in a loop.