... unless the reason you renamed 'foo' was so you could introduce another function called 'foo' which does foo properly/differently.
For a realistic example, suppose you decided that 'foo' should acquire a lock. So you rename all existing 'foo' to 'foo_nolock', and add a new wrapper 'foo' which takes the lock and called 'foo_nolock'.
If your other branch called the original 'foo', it should probably now be calling 'foo_nolock', but instead it'll be calling the lock function after the merge, and your compile (or even tests) may not be able to find that error.
This is why the round trip between source-code and parse tree is so great. Say branch A adds a call to foo(), and branch B swaps out foo() for foo_nolock(). You can tell from the round trip on branch A that there was a new reference to foo(). Then in branch B you can tell that the implementation of foo() has changed.
I'm not sure how you would represent such a conflict. A valid way to resolve it would be to tell the DVCS, "You dummy, this isn't a conflict, the author of branch B obviously wanted to change foo() for every call-site, even those he didn't know about." The normal diff-file syntax of "this branch added these lines, that branch removed those lines" wouldn't work.
However I don't think semantic parsing helps here. For example, suppose I'd told you (the feature branch developer) that I was going to change 'foo' so that it had locking semantics, and you had deliberately used 'foo' because of this. Now when we merge you definitely don't want your 'foo' to be changed to 'foo_nolock'. Alternately you can think of a case where I don't change all 'foo' to 'foo_nolock', so the VCS has no idea what the "rule" is.
I don't think it is appropriate to change a reference, like you say. If two people modify the same code, then you should signify a conflict and have someone resolve the issue by hand. There's no machine on earth that can tell whether you meant to call foo() or foo_nolock(). The point is to prevent a false positive (the worst thing by far when merging). If you modify the foo() function and I add a new reference to it, current line-based merge strategies will silently resolve that because our edits appear to be far apart, even though they are semantically conflicting. With some semantic analysis you can determine that manual resolution is much better. The point is to throw a conflict, not change a reference silently.
The point of better merge tools shouldn't be to automate merging with 100% correctness, that's an impossible task. Instead, the point should be to have a high level of accuracy in doing safe merges and in alerting a human being to an unsafe merge that requires resolution.