So I was talking to @jseyfried today about fallback, because indeed it seems like fallback is one of the "lynchpins" about which this decision rests. It's worth highlighting that there are two kinds of fallback. Let's say we're resolving ::foo::bar
in the new epoch:
- "Fallback to new semantics": If there is a plausible
foo
resolution in the root module, use that but issue a warning/error. If not, look for a crate.
- "Fallback to old semantics": If there is a
foo
crate available, use that. Otherwise, check in the root module for something. If found, issue a warning/error.
The second version is quite clean: the set of available crates is fixed, either by the Cargo.toml or the file system. This makes the fallback simple to implement, because the first test can be done anytime.
However, simply implementing "fallback to new semantics" is not compatible with the "hard constraints" section of the epoch RFC, which states:
There are only two things a new epoch can do that a normal release cannot:
- Change an existing deprecation into a hard error
- This option is only available when the deprecation is expected to hit a relatively small percentage of code.
- Change an existing deprecation to deny by default, and leverage the corresponding lint setting to produce error messages as if the feature were removed entirely.
There might though be some way to make a variant that complies, wherein pre-epoch we resolve through the root module but also check for an available crate. If a crate is available, but we found something in the root module that is not the corresponding extern crate
, then we report a warning. This means we warn only if the path would change semantics.
In the newer epoch, then, we would resolve to the crate, but make it an error if there is any item in the root module (i.e., any plausible resolution) and there is a crate by the same name, unless that root item is an extern crate
. This avoids some of the concerns that @josh had about ambiguity, since there is only one possible meaning of ::foo
. It does mean that -- at least initially -- you cannot have a root module named foo
and an external crate named foo
.
I don't know though that this is strictly compatible. I think there could be e.g. a macro expansion in the root module that used to resolve to something from within the crate but which now resolves to something from an external crate. In the older semantics, that macro might have generated items at the root level which would have shadowed an external crate -- but now that the macro is resolving via an external crate, it does not. (But, I guess for that to happen, the path to the macro itself must be ambiguous in the new semantics, so maybe there is an induction argument here?)
Sorry if I'm retreading ground here. It's hard to remember all the territory we covered. Maybe we need to make a kind of "fallback summary" post as well, covering the various ways we can do fallback, and the conditions to be concerned about? (I remember @rpjohnst in particular making a comment earlier referencing the detailed examination of fallback cases from the original RFC thread.)