The actual algorithm is much more efficient than this, but conceptually the compiler does this:
Assign every intermediate value a unique type name: A, B, C, ...
For each of these, keep a list of all the allowable types:
If the user specifed the type explicitly, that’s the only type in the list.
Otherwise, the initial list contains all the types known to the compiler.
Look at a subexpression, like _:A = _:B.into(). Cross off all of the possibilites for A that don’t match any possible Bs, and vice versa.
Repeat step 3 for various subexpressions, until you can’t cross anything else off of a list.
At this point, for compilation to continue, every list needs to contain exactly one item.
If a list is empty, the programmer has asked for something impossible, like storing a function result into a variable with an incompatible type.
If a list has more than one allowable type in it, there’s more than one program that matches the code— The compiler doesn’t know which one was intended.
Both of your examples fall into this last case: there are lots of different types that can be converted into a u128, and the compiler doesn’t have enough information to know which of those types you want adjusted_fee to be.
Right; I see what you mean now. I suspect it’s something to do with the UniqueSaturatedInto<u128> bound in the FixedPointOperand trait, but I’m not sure.
There must be only one solution for the intermediate value here, which seems unusual:
let _:BalanceOf<T> = <BalanceOf<T>>::saturated_into().saturated_into()