Rust: Glob Time Travel

Created on 20 Jul 2020  路  13Comments  路  Source: rust-lang/rust

I tried this code:

mod foo {
    pub mod bar {
        pub mod bar {
            pub fn foobar() {}
        }
    }
}

use foo::*;
use bar::bar;
use bar::foobar;



fn main() {
    bar::foobar();
}

I expected to see this happen: Compilation error. This use bar::bar; shadows one (glob-imported) bar with another bar. As far as I know, this should be forbidden.

Instead, this happened: this code successfully compiled.

Meta

rustc --version --verbose:

rustc 1.47.0-nightly (d7f945163 2020-07-19)
binary: rustc
commit-hash: d7f94516345a36ddfcd68cbdf1df835d356795c3
commit-date: 2020-07-19
host: x86_64-unknown-linux-gnu
release: 1.47.0-nightly
LLVM version: 10.0

c.c. @matklad
c.c. @petrochenkov

A-resolve C-bug P-high T-compiler T-lang regression-from-stable-to-stable

Most helpful comment

Sigh, the issue already affects three stable releases - from 1.44 to 1.46.
I'll try to prioritize it.

All 13 comments

This use bar::bar; shadows one (glob-imported) bar with another bar. As far as I know, this should be forbidden.

Shadowing a glob import with a specific item is allowed and will unambiguously resolve to the specific one. This is also how the prelude works. The following compiles fine as well:

// implicit: use std::prelude::v1::*; containing Vec
struct Vec;

fn a() -> Vec { unimplemented!() }

playground.

To clarify, the problem is not shadowing, but time travel. We shadow identifier which have been already used to resolve another import, so we end up with bar meaning two things at the same time in the same scope.

This is a change from Rust 1.44, in Rust 1.43 this is an ICE "inconsistent resolution for an import".
Possibly a consequence of https://github.com/rust-lang/rust/pull/70236.

I'm not sure this is a bug.

When resolving use bar::bar; we are looking at all names in scope except for the names introduced by use bar::bar; itself. Otherwise we'd have cycles even in trivial cases like use my_crate; where my_crate is a name from extern prelude.

If you take this detail into consideration, then all paths seem to be resolved correctly.

I think, the difference with use my_crate; is that use my_crate; doesn't replace one resolution with another. We already have a name my_crate in the scope that is resolved to a particular extern crate.

In my example, use bar::bar; uses bar name from the scope, and then rebinds this bar name to another item.

I can complicate my example a bit.

mod foo {
    pub mod bar {
        pub mod bar {
            pub fn foobar() { println!("111") }
        }
        pub fn foobar() { println!("222") }
    }
}

use foo::*;
use bar::bar;
use bar::foobar;

fn main() {
    foobar();
}

What this program prints, "111" or "222"? It'd say, it depends on imports resolution order, but looks like it always prints "111".

It'd say, it depends on imports resolution order

The intent for the resolution results is to never depend on internal resolution order.
(cc https://github.com/rust-lang/rust/pull/53778#issuecomment-419224049, order-dependence can probably happen in practice due to the current not very principled implementation, but hopefully still can be eliminated in backward-compatible-in-practice way by rewriting the main resolution/expansion loop more carefully.)

but looks like it always prints "111"

That's what I'd expect.

  • foobar resolves to use bar::foobar
  • bar in bar::foobar resolves to use bar::bar (because non-globs shadow globs)
  • first bar in bar::bar resolves to use foo::* (which the only bar in scope after excluding use bar::bar itself)

I'd still be ok with replacing the former ICE with an error though, since that's a more conservative choice.

Actually enabling the property "name resolve to the same thing whether some items were excluded during its resolution to avoid cycles or not" may be useful for using something like "inference variables" for unresolved imports and making import resolution more like unification in type inference.

That's an idea I had for quite some, but I hadn't realized that merging https://github.com/rust-lang/rust/pull/70236 went against it.

Sigh, the issue already affects three stable releases - from 1.44 to 1.46.
I'll try to prioritize it.

Assigning P-high so this isn't lost. See the relevant discussion.

The fix caused some regressions - https://github.com/rust-lang/rust/issues/77586.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nikomatsakis picture nikomatsakis  路  412Comments

cramertj picture cramertj  路  512Comments

nikomatsakis picture nikomatsakis  路  274Comments

nikomatsakis picture nikomatsakis  路  340Comments

Leo1003 picture Leo1003  路  898Comments