-
-
Notifications
You must be signed in to change notification settings - Fork 14.3k
Apply stricter orphan rules for traits with defaulted impls #23038
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
Changes from 3 commits
dbec033
2e21689
4e789e0
17358d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,10 +37,13 @@ impl<'cx, 'tcx> OrphanChecker<'cx, 'tcx> { | |
| a trait or new type instead"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<'cx, 'tcx,'v> visit::Visitor<'v> for OrphanChecker<'cx, 'tcx> { | ||
| fn visit_item(&mut self, item: &ast::Item) { | ||
| /// Checks exactly one impl for orphan rules and other such | ||
| /// restrictions. In this fn, it can happen that multiple errors | ||
| /// apply to a specific impl, so just return after reporting one | ||
| /// to prevent inundating the user with a bunch of similar error | ||
| /// reports. | ||
| fn check_item(&self, item: &ast::Item) { | ||
| let def_id = ast_util::local_def(item.id); | ||
| match item.node { | ||
| ast::ItemImpl(_, _, _, None, _, _) => { | ||
|
|
@@ -63,13 +66,15 @@ impl<'cx, 'tcx,'v> visit::Visitor<'v> for OrphanChecker<'cx, 'tcx> { | |
| span_err!(self.tcx.sess, item.span, E0118, | ||
| "no base type found for inherent implementation; \ | ||
| implement a trait or new type instead"); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
| ast::ItemImpl(_, _, _, Some(_), _, _) => { | ||
| // "Trait" impl | ||
| debug!("coherence2::orphan check: trait impl {}", item.repr(self.tcx)); | ||
| let trait_def_id = ty::impl_trait_ref(self.tcx, def_id).unwrap().def_id; | ||
| let trait_ref = ty::impl_trait_ref(self.tcx, def_id).unwrap(); | ||
| let trait_def_id = trait_ref.def_id; | ||
| match traits::orphan_check(self.tcx, def_id) { | ||
| Ok(()) => { } | ||
| Err(traits::OrphanCheckErr::NoLocalInputType) => { | ||
|
|
@@ -80,6 +85,7 @@ impl<'cx, 'tcx,'v> visit::Visitor<'v> for OrphanChecker<'cx, 'tcx> { | |
| types defined in this crate; \ | ||
| only traits defined in the current crate can be \ | ||
| implemented for arbitrary types"); | ||
| return; | ||
| } | ||
| } | ||
| Err(traits::OrphanCheckErr::UncoveredTy(param_ty)) => { | ||
|
|
@@ -89,9 +95,100 @@ impl<'cx, 'tcx,'v> visit::Visitor<'v> for OrphanChecker<'cx, 'tcx> { | |
| some local type (e.g. `MyStruct<T>`); only traits defined in \ | ||
| the current crate can be implemented for a type parameter", | ||
| param_ty.user_string(self.tcx)); | ||
| return; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // In addition to the above rules, we restrict impls of defaulted traits | ||
| // so that they can only be implemented on structs/enums. To see why this | ||
| // restriction exists, consider the following example (#22978). Imagine | ||
| // that crate A defines a defaulted trait `Foo` and a fn that operates | ||
| // on pairs of types: | ||
| // | ||
| // ``` | ||
| // // Crate A | ||
| // trait Foo { } | ||
| // impl Foo for .. { } | ||
| // fn two_foos<A:Foo,B:Foo>(..) { | ||
| // one_foo::<(A,B)>(..) | ||
| // } | ||
| // fn one_foo<T:Foo>(..) { .. } | ||
| // ``` | ||
| // | ||
| // This type-checks fine; in particular the fn | ||
| // `two_foos` is able to conclude that `(A,B):Foo` | ||
| // because `A:Foo` and `B:Foo`. | ||
| // | ||
| // Now imagine that crate B comes along and does the following: | ||
| // | ||
| // ``` | ||
| // struct A { } | ||
| // struct B { } | ||
| // impl Foo for A { } | ||
| // impl Foo for B { } | ||
| // impl !Send for (A, B) { } | ||
| // ``` | ||
| // | ||
| // This final impl is legal according to the orpan | ||
| // rules, but it invalidates the reasoning from | ||
| // `two_foos` above. | ||
| debug!("trait_ref={} trait_def_id={} trait_has_default_impl={}", | ||
| trait_ref.repr(self.tcx), | ||
| trait_def_id.repr(self.tcx), | ||
| ty::trait_has_default_impl(self.tcx, trait_def_id)); | ||
| if | ||
| ty::trait_has_default_impl(self.tcx, trait_def_id) && | ||
| trait_def_id.krate != ast::LOCAL_CRATE | ||
| { | ||
| let self_ty = trait_ref.self_ty(); | ||
| let opt_self_def_id = match self_ty.sty { | ||
| ty::ty_struct(self_def_id, _) | ty::ty_enum(self_def_id, _) => | ||
| Some(self_def_id), | ||
| ty::ty_uniq(..) => | ||
| self.tcx.lang_items.owned_box(), | ||
| _ => | ||
| None | ||
| }; | ||
|
|
||
| let msg = match opt_self_def_id { | ||
| // We only want to permit structs/enums, but not *all* structs/enums. | ||
| // They must be local to the current crate, so that people | ||
| // can't do `unsafe impl Send for Rc<SomethingLocal>` or | ||
| // `unsafe impl !Send for Box<SomethingLocalAndSend>`. | ||
| Some(self_def_id) => { | ||
| if self_def_id.krate == ast::LOCAL_CRATE { | ||
| None | ||
| } else { | ||
| Some(format!( | ||
| "cross-crate traits with a default impl, like `{}`, \ | ||
| can only be implemented for a struct/enum type \ | ||
| defined in the current crate", | ||
| ty::item_path_str(self.tcx, trait_def_id))) | ||
| } | ||
| } | ||
| _ => { | ||
| Some(format!( | ||
| "cross-crate traits with a default impl, like `{}`, \ | ||
| can only be implemented for a struct/enum type, \ | ||
| not `{}`", | ||
| ty::item_path_str(self.tcx, trait_def_id), | ||
| self_ty.user_string(self.tcx))) | ||
| } | ||
| }; | ||
|
|
||
| if let Some(msg) = msg { | ||
| span_err!(self.tcx.sess, item.span, E0321, "{}", msg); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it ok to have the same error code raising 2 different errors? The base error is the same because it's related to cross-crate traits w/ default implementation but the final message is different. I think it'd be better to have 1 error code per error message. It makes it easier to track them.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I felt the opposite :) I'm not sure what's the consensus here. But I figured if the purpose of these error codes is to aid googling and so that you can get a good detailed explanation, you'd want the same detailed explanation for both of these cases. |
||
| return; | ||
| } | ||
| } | ||
|
|
||
| // Disallow *all* explicit impls of `Sized` for now. | ||
| if Some(trait_def_id) == self.tcx.lang_items.sized_trait() { | ||
| span_err!(self.tcx.sess, item.span, E0322, | ||
| "explicit impls for the `Sized` trait are not permitted"); | ||
| return; | ||
| } | ||
| } | ||
| ast::ItemDefaultImpl(..) => { | ||
| // "Trait" impl | ||
|
|
@@ -100,14 +197,20 @@ impl<'cx, 'tcx,'v> visit::Visitor<'v> for OrphanChecker<'cx, 'tcx> { | |
| if trait_ref.def_id.krate != ast::LOCAL_CRATE { | ||
| span_err!(self.tcx.sess, item.span, E0318, | ||
| "cannot create default implementations for traits outside the \ | ||
| crate they're defined in; define a new trait instead."); | ||
| crate they're defined in; define a new trait instead"); | ||
| return; | ||
| } | ||
| } | ||
| _ => { | ||
| // Not an impl | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<'cx, 'tcx,'v> visit::Visitor<'v> for OrphanChecker<'cx, 'tcx> { | ||
| fn visit_item(&mut self, item: &ast::Item) { | ||
| self.check_item(item); | ||
| visit::walk_item(self, item); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: negative impls aren't unsafe