Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#[feature(uniform_paths)]: allow use x::y; to resolve through self::x, not just ::x. #52923

Merged
merged 6 commits into from
Aug 14, 2018

Conversation

eddyb
Copy link
Member

@eddyb eddyb commented Jul 31, 2018

Branch originally by @cramertj, based on @petrochenkov's description on the internals forum.
(note, however, that the approach has significantly changed since)

Implements #[feature(uniform_paths)] from #53130, by treating unqualified use paths as maybe-relative. That is, use x::y;, where x is a plain identifier (not a keyword), is no longer the same as use ::x::y;, and before picking an external crate named x, it first looks for an item named x in the same module (i.e. self::x) and prefers that local item instead.

Such a "maybe-relative" x can only resolve to an external crate if it's listed in "extern_prelude" (i.e. core / std and all the crates passed to --extern; the latter includes Cargo dependencies) - this is the same condition as being able to refer to the external crate from an unqualified, non-use path.
All other crates must be explicitly imported with an absolute path, e.g. use ::x::y;

To detect an ambiguity between the external crate and the local item with the same name, a "canary" import (e.g. use self::x as _;), tagged with the is_uniform_paths_canary flag, is injected. As the initial implementation is not sophisticated enough to handle all possible ways in which self::x could appear (e.g. from macro expansion), this also guards against accidentally picking the external crate, when it might actually get "shadowed" later.
Also, more canaries are injected for each block scope around the use, as self::x cannot resolve to any items named x in those scopes, but non-use paths can, and that could be confusing or even backwards-incompatible.

Errors are emitted only if the main "canary" import succeeds while an external crate exists (or if any of the block-scoped ones succeed at all), and ambiguities have custom error reporting, e.g.:

#![feature(uniform_paths)]
pub mod foo {
    use std::io;
    pub mod std { pub mod io {} }
}
error: import from `std` is ambiguous
 --> test.rs:3:9
  |
3 |     use std::io;
  |         ^^^ could refer to external crate `::std`
4 |     pub mod std { pub mod io {} }
  |     ----------------------------- could also refer to `self::std`
  |
  = help: write `::std` or `self::std` explicitly instead
  = note: relative `use` paths enabled by `#![feature(uniform_paths)]`

Another example, this time with a block-scoped item shadowing a module-scoped one:

#![feature(uniform_paths)]
enum Foo { A, B }
fn main() {
    enum Foo {}
    use Foo::*;
}
error: import from `Foo` is ambiguous
 --> test.rs:5:9
  |
4 |     enum Foo {}
  |     ----------- shadowed by block-scoped `Foo`
5 |     use Foo::*;
  |         ^^^
  |
  = help: write `::Foo` or `self::Foo` explicitly instead
  = note: relative `use` paths enabled by `#![feature(uniform_paths)]`

Additionally, this PR, because replacing "the finalize_import hack" was a blocker:

cc @aturon @joshtriplett

@rust-highfive
Copy link
Collaborator

r? @varkor

(rust_highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 31, 2018
@petrochenkov

This comment has been minimized.

@eddyb

This comment has been minimized.

@eddyb

This comment has been minimized.

@petrochenkov

This comment has been minimized.

@petrochenkov petrochenkov self-assigned this Aug 1, 2018
@eddyb

This comment has been minimized.

@varkor
Copy link
Member

varkor commented Aug 2, 2018

r? @petrochenkov

I had a look at the changes: everything looks good to me (apart from needing more tests), but I'm not familiar with this part of the code.

@eddyb

This comment has been minimized.

@joshtriplett

This comment has been minimized.

self.session.features_untracked().relative_imports &&
if let Some(second_segment) = module_path.get(1) {
module_path[0].name == keywords::CrateRoot.name() &&
self.extern_prelude.contains(&second_segment.name)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Talking to @joshtriplett, turns out this condition is wrong, we should always fork paths that don't start with :: or a keyword.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to be more forward-compatible with "1path", then crates outside of extern_prelude (not passed with ---extern) should not be accessible with non-disambiguated paths.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(So the condition needs to stay in some form.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@petrochenkov I guess with relative_imports, we'd have to separate relative use paths (which would be the default?) from allowing to resolve through an extern crate (which would be gated by extern_prelude), but preferably still detect ambiguities when a crate could be loaded?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean something like this?

mod rustc {}

// refers to the local module rustc as a relative path
// however, there's an extern crate `rustc` that can be loaded even if it's not in extern prelude
use rustc;

We can certainly detect this "ambiguity" in a post-processing step if rustc was successfully resolved locally, and possibly report an error.

let self_ident = Ident { name: keywords::SelfValue.name(), span: use_tree.span };
let extern_ident = Ident { name: keywords::Extern.name(), span: use_tree.span };
self_module_path[0] = self_ident;
extern_module_path[0] = extern_ident;
Copy link
Member Author

@eddyb eddyb Aug 2, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't need to use Extern, it could rely on CrateRoot which has same effect.

@eddyb
Copy link
Member Author

eddyb commented Aug 3, 2018

(reminder to self to incorporate these)

Tricky expansion-related tests: https://internals.rust-lang.org/t/relative-paths-in-rust-2018/7883/105

An intricate testcase from @joshtriplett
Expected output:
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2
0 1 2
V1: 10
V1: 10
V1: 10

main.rs:

pub const A: usize = 0;

pub mod foo {
    pub const B: usize = 1;

    pub mod bar {
        pub const C: usize = 2;

        pub enum E {
            V1(usize),
            V2(String),
        }

        pub fn test() {
            println!("{} {} {}", crate::A, crate::foo::B, C);
        }

        pub fn test_use() {
            use crate::A;
            use crate::foo::B;

            println!("{} {} {}", A, B, C);
        }

        pub fn test_enum() {
            use E::*;
            match E::V1(10) {
                V1(i) => { println!("V1: {}", i); }
                V2(s) => { println!("V2: {}", s); }
            }
        }
    }

    pub fn test() {
        println!("{} {} {}", crate::A, B, bar::C);
    }

    pub fn test_use() {
        use crate::A;
        use bar::C;

        println!("{} {} {}", A, B, C);
    }

    pub fn test_enum() {
        use bar::E::*;
        match bar::E::V1(10) {
            V1(i) => { println!("V1: {}", i); }
            V2(s) => { println!("V2: {}", s); }
        }
    }
}

pub fn test() {
    println!("{} {} {}", A, foo::B, foo::bar::C);
}

pub fn test_use() {
    use foo::B;
    use foo::bar::C;

    println!("{} {} {}", A, B, C);
}

pub fn test_enum() {
    use foo::bar::E::*;
    match foo::bar::E::V1(10) {
        V1(i) => { println!("V1: {}", i); }
        V2(s) => { println!("V2: {}", s); }
    }
}

fn main() {
    test();
    foo::test();
    foo::bar::test();
    test_use();
    foo::test_use();
    foo::bar::test_use();
    test_enum();
    foo::test_enum();
    foo::bar::test_enum();
}

EDIT: more testcases, on Discord

@joshtriplett
Copy link
Member

Updated version of the edition guide for these changes: rust-lang/edition-guide#74

@joshtriplett

This comment has been minimized.

@petrochenkov

This comment has been minimized.

@petrochenkov

This comment has been minimized.

@petrochenkov

This comment has been minimized.

{
return Ok(());
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean "both self::path and extern::path exist, but they actually refer to the same non-import entity in the end"?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Looks like this should be unnecessary if the "backtracking" is not supported.)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to remember if I added this or @cramertj but I think it was here when I started hacking on it. IMO this should probably be an ambiguity error to stay on the safe side.

@petrochenkov

This comment has been minimized.

@petrochenkov petrochenkov added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 5, 2018
@eddyb

This comment has been minimized.

@aturon
Copy link
Member

aturon commented Aug 6, 2018

@bors: p=100

I'm going to go ahead and set high priority on this, since it's blocking the Preview 2 announcement.

@bors bors added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Aug 14, 2018
@eddyb
Copy link
Member Author

eddyb commented Aug 14, 2018

Huh, we don't run those two accursed tests on the PR Travis checks?!

@bors r=petrochenkov

@bors
Copy link
Contributor

bors commented Aug 14, 2018

📌 Commit 13bc0b5 has been approved by petrochenkov

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 14, 2018
@bors
Copy link
Contributor

bors commented Aug 14, 2018

⌛ Testing commit 13bc0b5 with merge 3e05f80...

bors added a commit that referenced this pull request Aug 14, 2018
#[feature(uniform_paths)]: allow `use x::y;` to resolve through `self::x`, not just `::x`.

_Branch originally by @cramertj, based on @petrochenkov's [description on the internals forum](https://internals.rust-lang.org/t/relative-paths-in-rust-2018/7883/30?u=petrochenkov)._
_(note, however, that the approach has significantly changed since)_

Implements `#[feature(uniform_paths)]` from #53130, by treating unqualified `use` paths as maybe-relative. That is, `use x::y;`, where `x` is a plain identifier (not a keyword), is no longer the same as `use ::x::y;`, and before picking an external crate named `x`, it first looks for an item named `x` in the same module (i.e. `self::x`) and prefers that local item instead.

Such a "maybe-relative" `x` can only resolve to an external crate if it's listed in "`extern_prelude`" (i.e. `core` / `std` and all the crates passed to `--extern`; the latter includes Cargo dependencies) - this is the same condition as being able to refer to the external crate from an unqualified, non-`use` path.
All other crates must be explicitly imported with an absolute path, e.g. `use ::x::y;`

To detect an ambiguity between the external crate and the local item with the same name, a "canary" import (e.g. `use self::x as _;`), tagged with the `is_uniform_paths_canary` flag, is injected. As the initial implementation is not sophisticated enough to handle all possible ways in which `self::x` could appear (e.g. from macro expansion), this also guards against accidentally picking the external crate, when it might actually get "shadowed" later.
Also, more canaries are injected for each block scope around the `use`, as `self::x` cannot resolve to any items named `x` in those scopes, but non-`use` paths can, and that could be confusing or even backwards-incompatible.

Errors are emitted only if the main "canary" import succeeds while an external crate exists (or if any of the block-scoped ones succeed at all), and ambiguities have custom error reporting, e.g.:
```rust
#![feature(uniform_paths)]
pub mod foo {
    use std::io;
    pub mod std { pub mod io {} }
}
```
```rust
error: import from `std` is ambiguous
 --> test.rs:3:9
  |
3 |     use std::io;
  |         ^^^ could refer to external crate `::std`
4 |     pub mod std { pub mod io {} }
  |     ----------------------------- could also refer to `self::std`
  |
  = help: write `::std` or `self::std` explicitly instead
  = note: relative `use` paths enabled by `#![feature(uniform_paths)]`
```
Another example, this time with a block-scoped item shadowing a module-scoped one:
```rust
#![feature(uniform_paths)]
enum Foo { A, B }
fn main() {
    enum Foo {}
    use Foo::*;
}
```
```rust
error: import from `Foo` is ambiguous
 --> test.rs:5:9
  |
4 |     enum Foo {}
  |     ----------- shadowed by block-scoped `Foo`
5 |     use Foo::*;
  |         ^^^
  |
  = help: write `::Foo` or `self::Foo` explicitly instead
  = note: relative `use` paths enabled by `#![feature(uniform_paths)]`
```

Additionally, this PR, because replacing "the `finalize_import` hack" was a blocker:
* fixes #52140
* fixes #52141
* fixes #52705

cc @aturon @joshtriplett
@bors
Copy link
Contributor

bors commented Aug 14, 2018

☀️ Test successful - status-appveyor, status-travis
Approved by: petrochenkov
Pushing 3e05f80 to master...

@bors bors merged commit 13bc0b5 into rust-lang:master Aug 14, 2018
@rust-highfive
Copy link
Collaborator

📣 Toolstate changed by #52923!

Tested on commit 3e05f80.
Direct link to PR: #52923

💔 clippy-driver on windows: test-pass → build-fail (cc @Manishearth @llogiq @mcarton @oli-obk, @rust-lang/infra).
💔 clippy-driver on linux: test-pass → build-fail (cc @Manishearth @llogiq @mcarton @oli-obk, @rust-lang/infra).
💔 rls on windows: test-pass → build-fail (cc @nrc, @rust-lang/infra).
💔 rls on linux: test-pass → build-fail (cc @nrc, @rust-lang/infra).

rust-highfive added a commit to rust-lang-nursery/rust-toolstate that referenced this pull request Aug 14, 2018
Tested on commit rust-lang/rust@3e05f80.
Direct link to PR: <rust-lang/rust#52923>

💔 clippy-driver on windows: test-pass → build-fail (cc @Manishearth @llogiq @mcarton @oli-obk, @rust-lang/infra).
💔 clippy-driver on linux: test-pass → build-fail (cc @Manishearth @llogiq @mcarton @oli-obk, @rust-lang/infra).
💔 rls on windows: test-pass → build-fail (cc @nrc, @rust-lang/infra).
💔 rls on linux: test-pass → build-fail (cc @nrc, @rust-lang/infra).
@Manishearth
Copy link
Member

Manishearth commented Aug 14, 2018

Edit: removed bug report for nonexistent bug caused by rustup falling back to an oudated cargo.

This hits an ICE on clippy_lints

   Compiling clippy_lints v0.0.212 (file:///home/manishearth/mozilla/Wall/rust-clippy/clippy_lints)
thread 'main' panicked at 'librustc_resolve/lib.rs:2853: unexpected definition for an identifier in pattern: Mod(DefId(10/0:0))', librustc/util/bug.rs:47:26
note: Run with `RUST_BACKTRACE=1` for a backtrace.

error: internal compiler error: unexpected panic

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/blob/master/CONTRIBUTING.md#bug-reports

note: rustc 1.30.0-nightly (3e05f80a3 2018-08-14) running on x86_64-unknown-linux-gnu

note: compiler flags: -C debuginfo=2 -C incremental --crate-type lib

note: some of the compiler flags provided by cargo are hidden

@eddyb eddyb deleted the relative-imports branch August 14, 2018 06:55
@eddyb
Copy link
Member Author

eddyb commented Aug 14, 2018

@Manishearth Oh right I fixed a bug where paths not starting with :: could load arbitrary extern crates, so that's where the first error comes from - is ansi_term not in build-dependencies?
(EDIT: looking again at the code, I didn't actually do that after all, the behavior only changes with #![feature(uniform_paths)] - I would've had to change too many tests otherwise)

I'll have to look into the ICE, that's unexpected! (EDIT: see #53333).

@Manishearth

This comment has been minimized.

@Manishearth

This comment has been minimized.

bors added a commit that referenced this pull request Aug 14, 2018
rustc_resolve: crates only exist in the type namespace.

Fixes #53333 by resolving `::crate_name` in `TypeNS` alone, which was overlooked in #52923 and didn't break tests, since having `use crate_name;` and a `crate_name` value in the same scope is rare.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Projects
None yet
10 participants