I think that this is one of the cases, where Julia complexity is hidden behind very intuitive usage design. Take what following with the grain of salt, since I never wrote compiler in my life and maybe very far away from the real reason why your idea can’t be implemented, but we can try and reason about it.
Assume that you have function like
function f(x1, x2)
y1 = g1(x1, x2)
y2 = g2(x1, x2)
z = h(y1, y2)
return z
end
and you want to constraint it’s output with annotation f::Float64
and want for compiler to take this information into account and avoid unnecessary boxing.
Now, since Julia support multiple dispatch, you may have multiple versions of g1
and g2
depending on the types of its arguments, so you have to infer arguments types in order to choose appropriate functions. When you are moving top-down (from the types of arguments x1
and x2
) this task is more or less straightforward - you recursively go inside functions g1
, g2
until you hit some basic functions which return type you can infer and combining this information you can infer return types of these functions. As a result you get types of y1
and y2
so you know which version of h
to choose and so you finally can write final llvm code (of course procedure is way more complicated than that, but I think it is main idea).
Now, what happens if you try to take into account fact that the return type of f
is Float64
? You can infer that type of z
is Float64
, because it is the return value, but what are the types of y1
and y2
? You can’t just go inside the function and infer types from basic functions, because input types cardinality can become huge very quickly. For example, what if h(y1, y2) = y1 + y2
? y1
and y2
can have any types that gives you Float64
in sum. It can be Float64
and Float64
or Float64
and Float32
or Float64
and Int
or Float64
and MyAwesomeType
if you define plus
operation for these types.
At this point you have huge number of possible types of y1
and y2
and for each combination compiler should infer possible types of x1
and x2
in order to choose appropriate versions of functions g1
and g2
. So this procedure should be repeated for g1
and g2
and you can see how this task quickly explode out of control. Very soon you just give up and say that everything is Any
and box all variables, because there is nothing you can say about their types, so you have to choose most common of all.
So, I think backward type propagation is breaking very fast and as such it has no meaning. If you can’t infer types more than one or two step backwards what’s the reason to implement this machinery at all? It’s better to have forward type propagation and let user annotate the type of the result - which is happening now in Julia.
Maybe there are ways to solve this problem (other than annotate everything - this would return us to the land of static compiling), but it looks like it is not going to be simple, or at least it will be way more complicated than current implementation of the compiler with its limitations.