There actually is an optimization which does this, it’s known as “world splitting” (which is different from union splitting!). What world splitting does is it looks at a call like f(::Any)
and then it looks at the methods of f
. If f
does not have very many methods, and they all return the same (or similar) types, then the optimizer will use that information to optimize the call.
Here’s a demo of it in action:
f(::Int) = 1;
f(::Float64) = 2;
f(::Complex) = 3;
and then
julia> Core.Compiler.return_type(f, Tuple{Any})
Int64
Voila! Unfortunately, now with the benefit of hindsight, this optimization is often seen as a kinda bad idea. The problem is that the result of this optimization depends strongly on non-local information, and can suddenly fail and de-optimize if someone somewhere else defines a method.
Basically, suppose someone has code that does something like string(::Any)
, and the compiler is able to infer that into string(::Any)::String
. Now suppose you compiled code that relies on this result.
Problems can then occur if you load a package which adds a method to string
that doesn’t return a string. Suppose it was a symbolic package and they defined some lazy-symbolic thing such that string(::Sym)::Sym
or something. Now suddenly your compiled code is invalid and needs to be re-compiled before it can be used.
Situations like this may seem rare, but it’s actually been a quite big source of hard-to-fix latency bugs out there in a lot of julia packages.
However, there has recently been some talk about creating a way to statically guarentee that a function like string
always returns a String
or that constructors FloatXX
always return an object of that type, this’d be nice, but doesn’t exist yet.