Repository navigation
use dep1::foo as dep1 is considered ambiguous #77586
Description
Activity
searched nightlies: from nightly-2020-10-03 to nightly-2020-10-04
regressed nightly: nightly-2020-10-04
searched commits: from 8876ffc to 25c8c53
regressed commit: 6ebad43bisected with cargo-bisect-rustc v0.5.2
Host triple: x86_64-unknown-linux-gnu
Reproduce with:cargo bisect-rustc --start=2020-10-03 --end=2020-10-04 --preserve
Out of the prs in that roll up, I suspect #77421 could be related. @petrochenkov
Yes, #77421 should be the reason.
#77421 makes some code compiling on Rust 1.44-1.46 an error to address #74556.Strictly speaking, #77421 is not a bug fix, but rather an implementation of a slightly more conservative language rule giving us some more freedom in the future.
If it causes a noticeable amount of regressions in practice, then we can return to the 1.44-1.46 rules and declare #74556 as not-an-issue.Interesting. It appears I must have threaded the needle on versions here: 1.43 emits the same error as nightly, as do earlier versions.
For reference: the crate was converted to
edition = "2018"recently during the 1.44-1.46 time frame.edition = "2015"is totally fine with this import style, which is where this code originated.While a bit annoying, it should be straight forward to fix for me. Just need to add some
::prefixes.@rustbot prioritize
- addedI-prioritizeIssue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}Issue needs a team member to assess the impact. Will be replaced by P-{low,medium,high,critical}
on Oct 6, 2020 - addedrequires-nightlyThis issue requires a nightly compiler in some way. When possible, use a F-* label instead.This issue requires a nightly compiler in some way. When possible, use a F-* label instead.
on Oct 6, 2020 - addedregression-from-stable-to-nightlyPerformance or correctness regression from stable to nightly.Performance or correctness regression from stable to nightly.A-inferenceArea: Type inferenceArea: Type inferenceand removedrequires-nightlyThis issue requires a nightly compiler in some way. When possible, use a F-* label instead.This issue requires a nightly compiler in some way. When possible, use a F-* label instead.
on Oct 7, 2020 24 remaining items
there is a self-cycle here, but there is also only one reasonable way to resolve the cycle at present.
Namely, causality.
The current resolve's logic is that auseitem is not planted into its module until its input (thedep1::foopart) is determined, so the input cannot refer to the output (theas dep1part) in any way because the output doesn't yet exist when the input is determined.I was reading a bit into #74556 and especially this comment of yours which seems to be the best summary of what we are "giving up" by accepting this pattern. I'm having a bit of trouble parsing it though, I admit.
The current causality-based approach is different from my "import resolution as inference" idea (this comment of mine) where the output is planted into the module first as a fresh inference variable, and then participates in the input's resolution creating an inference failure (
dep1is required to be equal to two different things in the end).
EDIT: I think it's safe to say that I'll never have time to try implementing this myself.use foo::*importsbarascrate::foo::barbut it is "from a glob". We then see theuse bar::barwhich is resolved based on that import but then shadows it. This is precisely the "time traveling" we were trying to avoid, where the namebarhas multiple meanings across different paths.A name can have different meaning in different places because due to the causality logic different names are effectively resolved in different environments.
- Names in input of import
Aare resolved in environmenteverything - outputs(A). - Names in input of import
Bare resolved in environmenteverything - outputs(B).
So the resolution of a namedep1may be different depending on whether it's resolved in an import producingdep1as one of its outputs or not.
This is a deterministic rule, it doesn't change if we randomize some item resolution order or macro expansion order, so I'm not sure whether it's directly related to time traveling or not.
It seems important to me to reject this case, but perhaps it is not so easy to reject this case while also permitting cases like the one in the OP?
Perhaps it is not, since it's the same feature in action.
- Names in input of import
The causality rule do not apply to glob imports because they don't have an easily obtainable set of output names, and people are actually complaining about this (#62769)!
If we excluded
Foo-the-variant from the environment when resolvingFooin the import because it's an output of the import, the example wouldn't produce an error.This is a deterministic rule, it doesn't change if we randomize some item resolution order or macro expansion order, so I'm not sure whether it's directly related to time traveling or not.
Yeah, I think there's a secondary constraint that I had in mind, which is basically that, in the end, we can come up with a single "resolution result" for each module which is a mapping of
name -> full-pathand that all of our resolutions are consistent with that final result. That is the property I think you are going for with your "inference variable" idea as well, and it is precisely the property that we lose here, sincedep1::foois resolved in a context wheredep1is not bound, butdep1does appear in the final mapping, and hence if we "re-resolved"dep1::foowith that final set of names, we would get an ambiguity.I guess that we can think of
use dep1::fooas being a kind of inference itself, that is elaborated touse ::dep1::foo(oruse self::dep1::foo, depending), and then we can say that these final elaborated forms are consistent with the resolution result I named above. (Do you understand what I mean here?)One option would be to allow overlap between "pub use" and "use", but not allow overlap within the two.
This would allow having
barin scope for a module whilst exporting a differentbar, but doesn't introduce too much ambiguity.Assigning to self for a revert of #77421.
We discussed this in the language meeting today. We thought that we may want the more constrained behavior, but we were not at this point inclined to land breakage. It is possible that in a future edition, we may want to move to a different resolution story (perhaps @petrochenkov's inference idea), and in which case we would lint on such patterns (and presumably others?).
@pnkfelix brought up that we may want to lint on this pattern regardless (since it is rather confusing in most cases), but that should likely go through the standard dance of a proposal dedicated to such a lint which T-lang can FCP (or start in Clippy, perhaps).
So here is a brain puzzler. Which version of
foobarruns in the following code?mod foo { pub mod bar { pub fn foobar() -> u32 { 1 } pub mod bar { pub fn foobar() -> u32 { 2 } } } } use foo:*; use bar::bar; use bar::foobar;
Answer: version 2, and I imagine this is because we refuse to resolve
use bar::foobaruntil all possible sources ofbarhave been resolved. Is that correct, @petrochenkov?(In any case, @joshtriplett, to answer your question in the meeting, what we are giving up is that we are accepting the current interpretation of cases like this, but these are the sorts of corner cases where different models or evaluation orders or what have you might give slightly different answers.)
@nikomatsakis Interesting. That does seem like it should be rejected (specifically, at the point of
use bar::bar), and it's unfortunate we didn't do so from the start. But I'd be hesitant to cause widespread breakage.Our current error messages seem painfully confusing; "ambiguity" isn't the issue here, that does indeed feel wrong because of causality. Ideally we'd give a clear error about a name conflict, instead.
A lint to gently steer people away from this kind of name conflict (and to take advantage of cap-lints to avoid causing breakage in the process) seems reasonable though.
- added a commit that references this issue
on Nov 7, 2020 So here is a brain puzzler. Which version of
foobarruns in the following code?Here's how to solve this step-by step.
foobaris unambiguously introduced byuse bar::foobar;, next we need to determine wherebar(1) comes from.bar(1) is introduced byuse bar::bar;, it could also be introduced by the glob or some prelude, but it doesn't matter because explicit imports shadow anything else, next we need to determine wherebar(2) comes from.bar(2) is unambiguously introduced byuse foo::*;because the otherbaris excluded as an output ofuse bar::bar;and therefore "not in the environment", next we see thatfoois a real item and not an import, chain completed.
Another case where people explicitly wanted disambiguation based on causality - #56414 (comment) ("An import should never be ambiguous with itself.").
src/lib.rs:dep1/src/lib.rs:I expected to see this happen: build succeeds.
Instead, this happened:
Meta
working beta:
rustc --version --verbose:non-working nightly:
rustc +nightly --version --verboseThis broke
rust-systemd's build (https://travis-ci.com/github/jmesmon/rust-systemd/jobs/395293939) (broken versions arev0.6.0andv0.5.0which useedition = "2018").I'm not sure if there is a simpler single-crate break (there could be).