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

Disallow methods from traits that are not in scope #31908

Merged
merged 2 commits into from
Mar 25, 2016

Conversation

jseyfried
Copy link
Contributor

This PR only allows a trait method to be used if the trait is in scope (fixes #31379).
This is a [breaking-change]. For example, the following would break:

mod foo {
    pub trait T { fn f(&self) {} }
    impl T for () {}
}

mod bar { pub use foo::T; }

fn main() {
    pub use bar::*;
    struct T; // This shadows the trait `T`,
    ().f() // making this an error.
}

r? @nikomatsakis

@jseyfried jseyfried force-pushed the disallow_shadowed_traits branch 2 times, most recently from 200383b to 6dd4548 Compare February 26, 2016 08:47
}
impl T for () {}

fn g<T>() {
Copy link
Contributor

Choose a reason for hiding this comment

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

to be fully consistent, we need to also update E0256 ("import T conflicts with type in this module"), because "use self::T;" will still work in the generic function g, but not in h.

@jseyfried jseyfried force-pushed the disallow_shadowed_traits branch from 6dd4548 to bfa7fc4 Compare February 26, 2016 18:45
@nikomatsakis
Copy link
Contributor

Now that I see the impact, I have some concerns about both backwards compat, and that this might just not be the right rule. I hadn't fully considered that shadowing with any arbitrary type would rule out using the methods from a trait as well.

For example, this change does not impact the iterator naming pattern, because we name the types IntoIter and the trait IntoIterator, but if you adopted a variant that did not abbreviate, this change might well cause a problem for you (that said, this variant would be unergonomic to begin with, since you cannot easily write impl IntoIterator for IntoIterator).

Another example might be fn foo<Iterator>(i: Iterator) where Iterator: Iterator, though again the shadowing there probably means that pattern wouldn't work either.

I've yet to come up with an actual problematic example, but at minimum a crater run seems worthwhile.

cc @rust-lang/lang

@nikomatsakis
Copy link
Contributor

Crater run executing.

@nikomatsakis
Copy link
Contributor

Crater run result: https://gist.github.com/nikomatsakis/23a0ed555ecf83287c25

5 regressions.

@jseyfried
Copy link
Contributor Author

Ok, I believe these are our options:

  1. Write PRs against the offending crates and then land this as is.
  2. Implement warnings and have a warning cycle. This would be a little invasive but is definitely doable.
  3. Compromise and forbid prelude- or glob-imported traits shadowed by a type in the same module but continue allowing all traits from ancestor scopes. I believe this would fix 4 of the 5 regressions but I'd have to look in more detail to be sure.
  4. Do nothing.

@bors
Copy link
Contributor

bors commented Mar 5, 2016

☔ The latest upstream changes (presumably #31726) made this pull request unmergeable. Please resolve the merge conflicts.

@jseyfried jseyfried force-pushed the disallow_shadowed_traits branch from bfa7fc4 to 8774852 Compare March 6, 2016 08:50
@nikomatsakis
Copy link
Contributor

@jseyfried

Compromise and forbid prelude- or glob-imported traits shadowed by a type in the same module but continue allowing all traits from ancestor scopes. I believe this would fix 4 of the 5 regressions but I'd have to look in more detail to be sure.

Having thought about this some more, I feel like this rule may in fact be the one that I find most intuitive. Admittedly, I argued the opposite in the past, but thinking about examples like this sort of changed my mind:

fn foo<Default>(...) -> Default {
    ...
    let x = SomeType::default();
    ...
}

@jseyfried jseyfried force-pushed the disallow_shadowed_traits branch 2 times, most recently from 21ce5cb to d08b0c6 Compare March 8, 2016 02:05
@jseyfried
Copy link
Contributor Author

That example would still work since it uses UFCS, but your point stands.

I updated this PR to use the compromise rule (i.e. allow traits from ancestor scopes). The only remaining regression is matrix-0.21.0, where this use of the method fold of core::iter::Iterator breaks since it is shadowed. We could fix this last regression by always allowing traits from the prelude so that the only newly forbidden traits are shadowed glob-imported re-exports.

Thinking about this some more, I actually prefer this approach since I think we should consider the prelude not as something that gets imported into all* modules' scopes but instead as its own scope that is the parent of all modules' scopes. @nrc suggests this interpretation in his sets of scopes blog post.

This interpretation would mean that we permanently treat the prelude like we currently treat all private imports; for example, we would never allow prelude-"imported" names to be used in a crate relative path or imported into another module.

*except for no_implicit_prelude modules, of course

@nikomatsakis
Copy link
Contributor

@jseyfried

That example would still work since it uses UFCS, but your point stands.

I would not expect my example to work, even though it uses UFCS. The set of traits in scope for a UFCS call like Type::method() (as opposed to Trait::method()) is the same set we would use for method dispatch.

We could fix this last regression by always allowing traits from the prelude so that the only newly forbidden traits are shadowed glob-imported re-exports.

Hmm. I would find this a little surprising...

Thinking about this some more, I actually prefer this approach since I think we should consider the prelude not as something that gets imported into all* modules' scopes but instead as its own scope that is the parent of all modules' scopes.

...but when you put it like that, it kind of makes sense.

cc @rust-lang/lang, thoughts?

This interpretation would mean that we permanently treat the prelude like we currently treat all private imports; for example, we would never allow prelude-"imported" names to be used in a crate relative path or imported into another module.

I think both @nrc and I would like to change "private imports" so that they behave precisely like all other items (e.g., use super::* should also get the use statements from the parent). Not sure how that affects this paragraph. Not much I guess except that prelude things might just be different from explicit use foo::bar imports.

@nikomatsakis
Copy link
Contributor

ok, chatted briefly with @aturon on IRC, conclusion was that we should discuss again in the next @rust-lang/lang meeting :)

@jseyfried
Copy link
Contributor Author

The set of traits in scope for a UFCS call ... is the same set we would use for method dispatch.

Makes sense -- for some reason I thought the trait search only applied to non-UFCS methods.

I think both @nrc and I would like to change "private imports" so that they behave precisely like all other items

As would @petrochenkov and I. I think the prelude should be treated differently from private imports (well, the prelude is already treated differently since it is shadowable by globs) in part because otherwise,

mod foo {
    pub type Result = ();
}

mod bar {
    // use super::*; // uncommenting this line would import `Result` from `super`'s prelude ...
    use foo::*;

    fn f() -> Result { () } // ... making this an ambiguity error.
}

It seems wrong for the prelude to be shadowable but for the names imported from another module's prelude to not be shadowable (and to shadow the original prelude).

@petrochenkov also argues for treating the prelude differently in #31726.

@nikomatsakis
Copy link
Contributor

@jseyfried ok, I'm basically convinced that treating the prelude as a 'level above' in the scope makes sense.

@bors
Copy link
Contributor

bors commented Mar 13, 2016

☔ The latest upstream changes (presumably #32141) made this pull request unmergeable. Please resolve the merge conflicts.

@jseyfried jseyfried force-pushed the disallow_shadowed_traits branch from d08b0c6 to f7cb841 Compare March 14, 2016 02:17
@jseyfried jseyfried force-pushed the disallow_shadowed_traits branch from f7cb841 to ed8a2e2 Compare March 17, 2016 11:57
@jseyfried
Copy link
Contributor Author

@nikomatsakis I amended this PR to treat the prelude as a level above in the scope hierarchy, i.e. to always allow prelude traits and only disallow shadowed glob-imported re-exports.

@jseyfried jseyfried closed this Mar 18, 2016
@jseyfried jseyfried deleted the disallow_shadowed_traits branch March 18, 2016 00:17
@jseyfried jseyfried restored the disallow_shadowed_traits branch March 18, 2016 00:17
@jseyfried jseyfried reopened this Mar 18, 2016
@aturon aturon added the T-lang Relevant to the language team, which will review and decide on the PR/issue. label Mar 24, 2016
@nikomatsakis
Copy link
Contributor

After discussing in @rust-lang/lang, decided to adopt this approach where prelude is a scope above the other scopes. Thanks @jseyfried for your patience as always!

@jseyfried
Copy link
Contributor Author

@nikomatsakis no problem! This is ready to land pending your review (there's not much to review). After this lands, the rebase of #32167 will remove ModuleS's field shadowed_traits.

@nikomatsakis
Copy link
Contributor

@bors r+

@jseyfried heh, when I looked at the code this morning, I was a bit surprised -- I expected a bit more. Anyway, going to work hard on getting to your other PRs now!

@bors
Copy link
Contributor

bors commented Mar 25, 2016

📌 Commit ed8a2e2 has been approved by nikomatsakis

@bors
Copy link
Contributor

bors commented Mar 25, 2016

⌛ Testing commit ed8a2e2 with merge 64a13a4...

bors added a commit that referenced this pull request Mar 25, 2016
…akis

Disallow methods from traits that are not in scope

This PR only allows a trait method to be used if the trait is in scope (fixes #31379).
This is a [breaking-change]. For example, the following would break:
```rust
mod foo {
    pub trait T { fn f(&self) {} }
    impl T for () {}
}

mod bar { pub use foo::T; }

fn main() {
    pub use bar::*;
    struct T; // This shadows the trait `T`,
    ().f() // making this an error.
}
```
r? @nikomatsakis
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Inconsistency in whether methods of shadowed traits are usable
5 participants