The problem is that the following should all be considered identical:
pub struct Data {...}
pub type Error = String;
pub fn foo() -> Result<Data, String> {...}
pub struct Data {...}
pub type Error = String;
pub fn foo() -> Result<Data, Error> {...}
mod detail {
pub struct Data {...}
pub type Error = String;
pub fn foo() -> Result<Data, Error> {...}
}
pub use detail::*;
Particularly, that last one requires that the path you compare, say, in foo's return type, is not detail::Data but rather just Data because that’s what is exported, i.e. the canonicalization for all of them is:
pub struct Data {...}
pub type Error = std::string::String;
pub fn foo() -> std::result::Result<Data, std::string::String>;
Now this is a deterministic algorithm that requires name resolution (so an AST isn’t enough), but there’s another hitch: what if you make detail public? Or what if it was from the start? Or you add a third way to access those 3 items.
The only solution, IMO, is to use an arbitrary choice (e.g. shortest past and lexicographic so rather Data than foo::Data than aaaaa::Data than detail::Data), for the older version, then if any of the exported items in the new version has the same export path as an item from the old version, it uses the old canonical path, otherwise it’s arbitrary.
You can get away with doing only some equivalences (i.e. not substituting type aliases, although this feels like it’d have a high false positive rate), but you still need name resolution to have completed to handle any shape of exports.