Hmm, I think the way that some language does it (c++?) is to encode the parameters into the name of the function. So when compiling doStuff(i:Int,f:Float) you get the symbol doStuff_Int_Float or something, because that is different from doStuff(s:String,d:Double), which would be doStuff_String_Double.
The only limitation would be that you have to get the types exact. If you have doStuff(i:Int,f:Float), doStuff(2, 5.0) would work as it compiles down to doStuff_Int_Float. But say you did doStuff(999999999999, 5.0). That wouldn't work because it would compile down to doStuff_Long_Float, which isn't the same.
That would solve the issue of which function to choose. For the other issues like the Base/Derived example, I think an arbitrary rule would have to be set such as always pick Base or something.
The only limitation would be that you have to get the types exact. If you have doStuff(i:Int,f:Float), doStuff(2, 5.0) would work as it compiles down to doStuff_Int_Float. But say you did doStuff(999999999999, 5.0). That wouldn't work because it would compile down to doStuff_Long_Float, which isn't the same.
That would solve the issue of which function to choose. For the other issues like the Base/Derived example, I think an arbitrary rule would have to be set such as always pick Base or something.