Skip to content

Commit

Permalink
Orphanck: Consider opaque types to never cover type parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
fmease committed Jun 4, 2024
1 parent 5e6c2b6 commit 8746703
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 36 deletions.
59 changes: 24 additions & 35 deletions compiler/rustc_trait_selection/src/traits/coherence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -924,11 +924,12 @@ where
}
}

ty::Alias(kind @ (ty::Projection | ty::Inherent | ty::Weak), ..) => {
if ty.has_type_flags(ty::TypeFlags::HAS_TY_PARAM) {
bug!("unexpected ty param in alias ty");
}

// A rigid alias may normalize to anything.
// * If it references an infer var, placeholder or bound ty, it may
// normalize to that, so we have to treat it as an uncovered ty param.
// * Otherwise it may normalize to any non-type-generic type
// be it local or non-local.
ty::Alias(kind, _) => {
if ty.has_type_flags(
ty::TypeFlags::HAS_TY_PLACEHOLDER
| ty::TypeFlags::HAS_TY_BOUND
Expand All @@ -948,7 +949,24 @@ where
}
}
} else {
ControlFlow::Continue(())
// Regarding *opaque types* specifically, we choose to treat them as non-local,
// even those that appear within the same crate. This seems somewhat surprising
// at first, but makes sense when you consider that opaque types are supposed
// to hide the underlying type *within the same crate*. When an opaque type is
// used from outside the module where it is declared, it should be impossible to
// observe anything about it other than the traits that it implements.
//
// The alternative would be to look at the underlying type to determine whether
// or not the opaque type itself should be considered local.
//
// However, this could make it a breaking change to switch the underlying hidden
// type from a local type to a remote type. This would violate the rule that
// opaque types should be completely opaque apart from the traits that they
// implement, so we don't use this behavior.
// Addendum: Moreover, revealing the underlying type is likely to cause cycle
// errors as we rely on coherence / the specialization graph during typeck.

self.found_non_local_ty(ty)
}
}

Expand Down Expand Up @@ -990,35 +1008,6 @@ where
// auto trait impl applies. There will never be multiple impls, so we can just
// act as if it were a local type here.
ty::CoroutineWitness(..) => ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)),
ty::Alias(ty::Opaque, ..) => {
// This merits some explanation.
// Normally, opaque types are not involved when performing
// coherence checking, since it is illegal to directly
// implement a trait on an opaque type. However, we might
// end up looking at an opaque type during coherence checking
// if an opaque type gets used within another type (e.g. as
// the type of a field) when checking for auto trait or `Sized`
// impls. This requires us to decide whether or not an opaque
// type should be considered 'local' or not.
//
// We choose to treat all opaque types as non-local, even
// those that appear within the same crate. This seems
// somewhat surprising at first, but makes sense when
// you consider that opaque types are supposed to hide
// the underlying type *within the same crate*. When an
// opaque type is used from outside the module
// where it is declared, it should be impossible to observe
// anything about it other than the traits that it implements.
//
// The alternative would be to look at the underlying type
// to determine whether or not the opaque type itself should
// be considered local. However, this could make it a breaking change
// to switch the underlying ('defining') type from a local type
// to a remote type. This would violate the rule that opaque
// types should be completely opaque apart from the traits
// that they implement, so we don't use this behavior.
self.found_non_local_ty(ty)
}
};
// A bit of a hack, the `OrphanChecker` is only used to visit a `TraitRef`, so
// the first type we visit is always the self type.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
error[E0210]: type parameter `T` must be covered by another type when it appears before the first local type (`Local`)
--> $DIR/orphan-check-opaque-types-not-covering.rs:17:6
|
LL | impl<T> foreign::Trait0<Local, T, ()> for Identity<T> {}
| ^ type parameter `T` must be covered by another type when it appears before the first local type (`Local`)
|
= note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local, and no uncovered type parameters appear before that first local type
= note: in this case, 'before' refers to the following order: `impl<..> ForeignTrait<T1, ..., Tn> for T0`, where `T0` is the first and `Tn` is the last

error[E0210]: type parameter `T` must be covered by another type when it appears before the first local type (`Local`)
--> $DIR/orphan-check-opaque-types-not-covering.rs:26:6
|
LL | impl<T> foreign::Trait1<Local, T> for Opaque<T> {}
| ^ type parameter `T` must be covered by another type when it appears before the first local type (`Local`)
|
= note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local, and no uncovered type parameters appear before that first local type
= note: in this case, 'before' refers to the following order: `impl<..> ForeignTrait<T1, ..., Tn> for T0`, where `T0` is the first and `Tn` is the last

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0210`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
error[E0210]: type parameter `T` must be covered by another type when it appears before the first local type (`Local`)
--> $DIR/orphan-check-opaque-types-not-covering.rs:17:6
|
LL | impl<T> foreign::Trait0<Local, T, ()> for Identity<T> {}
| ^ type parameter `T` must be covered by another type when it appears before the first local type (`Local`)
|
= note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local, and no uncovered type parameters appear before that first local type
= note: in this case, 'before' refers to the following order: `impl<..> ForeignTrait<T1, ..., Tn> for T0`, where `T0` is the first and `Tn` is the last

error[E0210]: type parameter `T` must be covered by another type when it appears before the first local type (`Local`)
--> $DIR/orphan-check-opaque-types-not-covering.rs:26:6
|
LL | impl<T> foreign::Trait1<Local, T> for Opaque<T> {}
| ^ type parameter `T` must be covered by another type when it appears before the first local type (`Local`)
|
= note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local, and no uncovered type parameters appear before that first local type
= note: in this case, 'before' refers to the following order: `impl<..> ForeignTrait<T1, ..., Tn> for T0`, where `T0` is the first and `Tn` is the last

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0210`.
31 changes: 31 additions & 0 deletions tests/ui/coherence/orphan-check-opaque-types-not-covering.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Opaque types never cover type parameters.

//@ revisions: classic next
//@[next] compile-flags: -Znext-solver

//@ aux-crate:foreign=parametrized-trait.rs
//@ edition:2021

#![feature(type_alias_impl_trait)]

type Identity<T> = impl Sized;

fn define_identity<T>(x: T) -> Identity<T> {
x
}

impl<T> foreign::Trait0<Local, T, ()> for Identity<T> {}
//~^ ERROR type parameter `T` must be covered by another type

type Opaque<T> = impl Sized;

fn define_local<T>() -> Opaque<T> {
Local
}

impl<T> foreign::Trait1<Local, T> for Opaque<T> {}
//~^ ERROR type parameter `T` must be covered by another type

struct Local;

fn main() {}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
--> $DIR/coherence.rs:14:1
--> $DIR/coherence.rs:16:1
|
LL | impl foreign_crate::ForeignTrait for AliasOfForeignType<()> {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------------
Expand Down
14 changes: 14 additions & 0 deletions tests/ui/type-alias-impl-trait/coherence.next.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0117]: only traits defined in the current crate can be implemented for arbitrary types
--> $DIR/coherence.rs:16:1
|
LL | impl foreign_crate::ForeignTrait for AliasOfForeignType<()> {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------------
| | |
| | `AliasOfForeignType<()>` is not defined in the current crate
| impl doesn't use only types from inside the current crate
|
= note: define and implement a trait or new type instead

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0117`.
2 changes: 2 additions & 0 deletions tests/ui/type-alias-impl-trait/coherence.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//@ aux-build:foreign-crate.rs
//@ revisions: classic next
//@[next] compile-flags: -Znext-solver
#![feature(type_alias_impl_trait)]

extern crate foreign_crate;
Expand Down

0 comments on commit 8746703

Please sign in to comment.