Skip to content

Commit

Permalink
Autotrait bounds on dyn-safe trait methods
Browse files Browse the repository at this point in the history
  • Loading branch information
dtolnay committed Jan 19, 2023
1 parent a247952 commit ba9b196
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 10 deletions.
22 changes: 22 additions & 0 deletions compiler/rustc_hir_analysis/src/coherence/orphan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ fn do_orphan_check_impl<'tcx>(
// impl MyAuto for dyn Trait {} // NOT OKAY
// impl<T: ?Sized> MyAuto for T {} // NOT OKAY
//
// With this restriction, it's guaranteed that an auto-trait is
// implemented for a trait object if and only if the auto-trait is
// one of the trait object's trait bounds (or a supertrait of a
// bound). In other words `dyn Trait + AutoTrait` always implements
// AutoTrait, while `dyn Trait` never implements AutoTrait.
//
// This is necessary in order for autotrait bounds on methods of
// trait objects to be sound.
//
// auto trait AutoTrait {}
//
// trait ObjectSafeTrait {
// fn f(&self) where Self: AutoTrait;
// }
//
// We can allow f to be called on `dyn ObjectSafeTrait + AutoTrait`.
//
// If we didn't deny `impl AutoTrait for dyn Trait`, it would be
// unsound for the ObjectSafeTrait shown above to be object safe
// because someone could take some type implementing ObjectSafeTrait
// but not AutoTrait, unsize it to `dyn ObjectSafeTrait`, and call
// .f() which has no concrete implementation (issue #50781).
let problematic_kind = match self_ty_kind {
ty::Dynamic(..) => Some("trait object"),
ty::Param(..) if !self_ty.is_sized(tcx, tcx.param_env(def_id)) => {
Expand Down
52 changes: 42 additions & 10 deletions compiler/rustc_trait_selection/src/traits/object_safety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -529,16 +529,48 @@ fn virtual_call_violation_for_method<'tcx>(

// NOTE: This check happens last, because it results in a lint, and not a
// hard error.
if tcx
.predicates_of(method.def_id)
.predicates
.iter()
// A trait object can't claim to live more than the concrete type,
// so outlives predicates will always hold.
.cloned()
.filter(|(p, _)| p.to_opt_type_outlives().is_none())
.any(|pred| contains_illegal_self_type_reference(tcx, trait_def_id, pred))
{
if tcx.predicates_of(method.def_id).predicates.iter().any(|(pred, _span)| {
// dyn Trait is okay:
//
// trait Trait {
// fn f(&self) where Self: 'static;
// }
//
// because a trait object can't claim to live longer than the concrete
// type. If the lifetime bound holds on dyn Trait then it's guaranteed
// to hold as well on the concrete type.
if pred.to_opt_type_outlives().is_some() {
return false;
}

// dyn Trait is okay:
//
// auto trait AutoTrait {}
//
// trait Trait {
// fn f(&self) where Self: AutoTrait;
// }
//
// because `impl AutoTrait for dyn Trait` is disallowed by coherence.
// Traits with a default impl are implemented for a trait object if and
// only if the autotrait is one of the trait object's trait bounds, like
// in `dyn Trait + AutoTrait`.
if let ty::PredicateKind::Clause(ty::Clause::Trait(ty::TraitPredicate {
trait_ref: pred_trait_ref,
constness: ty::BoundConstness::NotConst,
polarity: ty::ImplPolarity::Positive,
})) = pred.kind().skip_binder()
&& pred_trait_ref.self_ty() == tcx.types.self_param
&& tcx.trait_is_auto(pred_trait_ref.def_id)
{
// Only check the rest of the bound's parameters. So `Self: Bound<Self>`
// is still considered illegal.
let rest_of_substs = &pred_trait_ref.substs[1..];
return contains_illegal_self_type_reference(tcx, trait_def_id, rest_of_substs);
}

contains_illegal_self_type_reference(tcx, trait_def_id, pred.clone())
}) {
return Some(MethodViolationCode::WhereClauseReferencesSelf);
}

Expand Down
23 changes: 23 additions & 0 deletions tests/ui/where-clauses/self-in-where-clause-allowed.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// check-fail

#![feature(auto_traits)]
#![deny(where_clauses_object_safety)]

auto trait AutoTrait {}

trait Trait {
fn static_lifetime_bound(&self) where Self: 'static {}

fn arg_lifetime_bound<'a>(&self, _arg: &'a ()) where Self: 'a {}

fn autotrait_bound(&self) where Self: AutoTrait {}
}

impl Trait for () {}

fn main() {
let trait_object = &() as &dyn Trait;
trait_object.static_lifetime_bound();
trait_object.arg_lifetime_bound(&());
trait_object.autotrait_bound(); //~ ERROR: the trait bound `dyn Trait: AutoTrait` is not satisfied
}
15 changes: 15 additions & 0 deletions tests/ui/where-clauses/self-in-where-clause-allowed.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
error[E0277]: the trait bound `dyn Trait: AutoTrait` is not satisfied
--> $DIR/self-in-where-clause-allowed.rs:22:18
|
LL | trait_object.autotrait_bound();
| ^^^^^^^^^^^^^^^ the trait `AutoTrait` is not implemented for `dyn Trait`
|
note: required by a bound in `Trait::autotrait_bound`
--> $DIR/self-in-where-clause-allowed.rs:13:43
|
LL | fn autotrait_bound(&self) where Self: AutoTrait {}
| ^^^^^^^^^ required by this bound in `Trait::autotrait_bound`

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

0 comments on commit ba9b196

Please sign in to comment.