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

Projects
None yet
5 participants
@jseyfried
Copy link
Contributor

jseyfried commented Feb 26, 2016

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 jseyfried:disallow_shadowed_traits branch 2 times, most recently from 200383b to 6dd4548 Feb 26, 2016

}
impl T for () {}

fn g<T>() {

This comment has been minimized.

@oli-obk

oli-obk Feb 26, 2016

Contributor

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 jseyfried:disallow_shadowed_traits branch from 6dd4548 to bfa7fc4 Feb 26, 2016

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 3, 2016

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

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 3, 2016

Crater run executing.

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 4, 2016

@jseyfried

This comment has been minimized.

Copy link
Contributor Author

jseyfried commented Mar 4, 2016

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

This comment has been minimized.

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 jseyfried:disallow_shadowed_traits branch from bfa7fc4 to 8774852 Mar 6, 2016

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 7, 2016

@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 jseyfried:disallow_shadowed_traits branch 2 times, most recently from 21ce5cb to d08b0c6 Mar 7, 2016

@jseyfried

This comment has been minimized.

Copy link
Contributor Author

jseyfried commented Mar 8, 2016

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

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 8, 2016

@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

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 8, 2016

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

@jseyfried

This comment has been minimized.

Copy link
Contributor Author

jseyfried commented Mar 8, 2016

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

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 9, 2016

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

@bors

This comment has been minimized.

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 jseyfried:disallow_shadowed_traits branch from d08b0c6 to f7cb841 Mar 14, 2016

@jseyfried jseyfried force-pushed the jseyfried:disallow_shadowed_traits branch from f7cb841 to ed8a2e2 Mar 17, 2016

@jseyfried

This comment has been minimized.

Copy link
Contributor Author

jseyfried commented Mar 17, 2016

@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 jseyfried:disallow_shadowed_traits branch Mar 18, 2016

@jseyfried jseyfried restored the jseyfried:disallow_shadowed_traits branch Mar 18, 2016

@jseyfried jseyfried reopened this Mar 18, 2016

@aturon aturon added the T-lang label Mar 24, 2016

@nikomatsakis

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 24, 2016

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

This comment has been minimized.

Copy link
Contributor Author

jseyfried commented Mar 25, 2016

@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

This comment has been minimized.

Copy link
Contributor

nikomatsakis commented Mar 25, 2016

@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

This comment has been minimized.

Copy link
Contributor

bors commented Mar 25, 2016

📌 Commit ed8a2e2 has been approved by nikomatsakis

@bors

This comment has been minimized.

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

Auto merge of #31908 - jseyfried:disallow_shadowed_traits, r=nikomats…
…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

@bors bors merged commit ed8a2e2 into rust-lang:master Mar 25, 2016

2 checks passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
homu Test successful
Details

@jseyfried jseyfried deleted the jseyfried:disallow_shadowed_traits branch Mar 25, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.