Skip to content

Commit

Permalink
use implied bounds when checking opaque types
Browse files Browse the repository at this point in the history
  • Loading branch information
aliemjay committed May 6, 2023
1 parent 4a18324 commit d548747
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 4 deletions.
17 changes: 14 additions & 3 deletions compiler/rustc_hir_analysis/src/check/check.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use rustc_target::abi::FieldIdx;
use rustc_target::spec::abi::Abi;
use rustc_trait_selection::traits::error_reporting::on_unimplemented::OnUnimplementedDirective;
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt as _;
use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt as _;
use rustc_trait_selection::traits::{self, ObligationCtxt, TraitEngine, TraitEngineExt as _};

use std::ops::ControlFlow;
Expand Down Expand Up @@ -222,7 +223,7 @@ fn check_opaque(tcx: TyCtxt<'_>, id: hir::ItemId) {
if check_opaque_for_cycles(tcx, item.owner_id.def_id, substs, span, &origin).is_err() {
return;
}
check_opaque_meets_bounds(tcx, item.owner_id.def_id, substs, span, &origin);
check_opaque_meets_bounds(tcx, item.owner_id.def_id, span, &origin);
}

/// Checks that an opaque type does not use `Self` or `T::Foo` projections that would result
Expand Down Expand Up @@ -391,7 +392,6 @@ pub(super) fn check_opaque_for_cycles<'tcx>(
fn check_opaque_meets_bounds<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: LocalDefId,
substs: SubstsRef<'tcx>,
span: Span,
origin: &hir::OpaqueTyOrigin,
) {
Expand All @@ -406,6 +406,8 @@ fn check_opaque_meets_bounds<'tcx>(
.with_opaque_type_inference(DefiningAnchor::Bind(defining_use_anchor))
.build();
let ocx = ObligationCtxt::new(&infcx);

let substs = InternalSubsts::identity_for_item(tcx, def_id.to_def_id());
let opaque_ty = tcx.mk_opaque(def_id.to_def_id(), substs);

// `ReErased` regions appear in the "parent_substs" of closures/generators.
Expand Down Expand Up @@ -448,9 +450,18 @@ fn check_opaque_meets_bounds<'tcx>(
match origin {
// Checked when type checking the function containing them.
hir::OpaqueTyOrigin::FnReturn(..) | hir::OpaqueTyOrigin::AsyncFn(..) => {}
// Nested opaque types occur only in associated types:
// ` type Opaque<T> = impl Trait<&'static T, AssocTy = impl Nested>; `
// They can only be referenced as `<Opaque<T> as Trait<&'static T>>::AssocTy`.
// We don't have to check them here because their well-formedness follows from the WF of
// the projection input types in the defining- and use-sites.
hir::OpaqueTyOrigin::TyAlias
if tcx.def_kind(tcx.parent(def_id.to_def_id())) == DefKind::OpaqueTy => {}
// Can have different predicates to their defining use
hir::OpaqueTyOrigin::TyAlias => {
let outlives_env = OutlivesEnvironment::new(param_env);
let wf_tys = ocx.assumed_wf_types(param_env, span, def_id);
let implied_bounds = infcx.implied_bounds_tys(param_env, def_id, wf_tys);
let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds);
let _ = ocx.resolve_regions_and_report_errors(defining_use_anchor, &outlives_env);
}
}
Expand Down
13 changes: 12 additions & 1 deletion compiler/rustc_ty_utils/src/implied_bounds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ fn assumed_wf_types(tcx: TyCtxt<'_>, def_id: DefId) -> &ty::List<Ty<'_>> {
}
}
DefKind::AssocConst | DefKind::AssocTy => tcx.assumed_wf_types(tcx.parent(def_id)),
DefKind::OpaqueTy => match tcx.def_kind(tcx.parent(def_id)) {
DefKind::TyAlias => ty::List::empty(),
DefKind::AssocTy => tcx.assumed_wf_types(tcx.parent(def_id)),
// Nested opaque types only occur in associated types:
// ` type Opaque<T> = impl Trait<&'static T, AssocTy = impl Nested>; `
// assumed_wf_types should include those of `Opaque<T>`, `Opaque<T>` itself
// and `&'static T`.
DefKind::OpaqueTy => bug!("unimplemented implied bounds for neseted opaque types"),
def_kind @ _ => {
bug!("unimplemented implied bounds for opaque types with parent {def_kind:?}")
}
},
DefKind::Mod
| DefKind::Struct
| DefKind::Union
Expand All @@ -51,7 +63,6 @@ fn assumed_wf_types(tcx: TyCtxt<'_>, def_id: DefId) -> &ty::List<Ty<'_>> {
| DefKind::ForeignMod
| DefKind::AnonConst
| DefKind::InlineConst
| DefKind::OpaqueTy
| DefKind::ImplTraitPlaceholder
| DefKind::Field
| DefKind::LifetimeParam
Expand Down
25 changes: 25 additions & 0 deletions tests/ui/type-alias-impl-trait/wf-in-associated-type.fail.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
error[E0309]: the parameter type `T` may not live long enough
--> $DIR/wf-in-associated-type.rs:36:23
|
LL | type Opaque = impl Sized + 'a;
| ^^^^^^^^^^^^^^^ ...so that the type `&'a T` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound...
|
LL | impl<'a, T: 'a> Trait<'a, T> for () {
| ++++

error[E0309]: the parameter type `T` may not live long enough
--> $DIR/wf-in-associated-type.rs:36:23
|
LL | type Opaque = impl Sized + 'a;
| ^^^^^^^^^^^^^^^ ...so that the reference type `&'a T` does not outlive the data it points at
|
help: consider adding an explicit lifetime bound...
|
LL | impl<'a, T: 'a> Trait<'a, T> for () {
| ++++

error: aborting due to 2 previous errors

For more information about this error, try `rustc --explain E0309`.
45 changes: 45 additions & 0 deletions tests/ui/type-alias-impl-trait/wf-in-associated-type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// WF check for impl Trait in associated type position.
//
// revisions: pass fail
// [pass] check-pass
// [fail] check-fail

#![feature(impl_trait_in_assoc_type)]

// The hidden type here (`&'a T`) requires proving `T: 'a`.
// We know it holds because of implied bounds from the impl header.
#[cfg(pass)]
mod pass {
trait Trait<Req> {
type Opaque1;
fn constrain_opaque1(req: Req) -> Self::Opaque1;
}

impl<'a, T> Trait<&'a T> for () {
type Opaque1 = impl IntoIterator<Item = impl Sized + 'a>;
fn constrain_opaque1(req: &'a T) -> Self::Opaque1 {
[req]
}
}
}

// The hidden type here (`&'a T`) requires proving `T: 'a`,
// but that is not known to hold in the impl.
#[cfg(fail)]
mod fail {
trait Trait<'a, T> {
type Opaque;
fn constrain_opaque(req: &'a T) -> Self::Opaque;
}

impl<'a, T> Trait<'a, T> for () {
type Opaque = impl Sized + 'a;
//[fail]~^ ERROR the parameter type `T` may not live long enough
//[fail]~| ERROR the parameter type `T` may not live long enough
fn constrain_opaque(req: &'a T) -> Self::Opaque {
req
}
}
}

fn main() {}
19 changes: 19 additions & 0 deletions tests/ui/type-alias-impl-trait/wf-nested.fail.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
error[E0310]: the parameter type `T` may not live long enough
--> $DIR/wf-nested.rs:55:27
|
LL | type InnerOpaque<T> = impl Sized;
| ^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds...
|
note: ...that is required by this bound
--> $DIR/wf-nested.rs:12:20
|
LL | struct IsStatic<T: 'static>(T);
| ^^^^^^^
help: consider adding an explicit lifetime bound...
|
LL | type InnerOpaque<T: 'static> = impl Sized;
| +++++++++

error: aborting due to previous error

For more information about this error, try `rustc --explain E0310`.
14 changes: 14 additions & 0 deletions tests/ui/type-alias-impl-trait/wf-nested.pass_sound.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
error[E0310]: the parameter type `T` may not live long enough
--> $DIR/wf-nested.rs:46:17
|
LL | let _ = outer.get();
| ^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds
|
help: consider adding an explicit lifetime bound...
|
LL | fn test<T: 'static>() {
| +++++++++

error: aborting due to previous error

For more information about this error, try `rustc --explain E0310`.
60 changes: 60 additions & 0 deletions tests/ui/type-alias-impl-trait/wf-nested.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Well-formedness of nested opaque types, i.e. `impl Sized` in
// `type Outer = impl Trait<Assoc = impl Sized>`.
// See the comments below.
//
// revisions: pass pass_sound fail
// [pass] check-pass
// [pass_sound] check-fail
// [fail] check-fail

#![feature(type_alias_impl_trait)]

struct IsStatic<T: 'static>(T);

trait Trait<In> {
type Out;

fn get(&self) -> Result<Self::Out, ()> {
Err(())
}
}

impl<T> Trait<&'static T> for () {
type Out = IsStatic<T>;
}

// The hidden type for `impl Sized` is `IsStatic<T>`, which requires `T: 'static`.
// We know it is well-formed because it can *only* be referenced as a projection:
// <OuterOpaque<T> as Trait<&'static T>>::Out`.
// So any instantiation of the type already requires proving `T: 'static`.
#[cfg(pass)]
mod pass {
use super::*;
type OuterOpaque<T> = impl Trait<&'static T, Out = impl Sized>;
fn define<T>() -> OuterOpaque<T> {}
}

// Test the soundness of `pass` - We should require `T: 'static` at the use site.
#[cfg(pass_sound)]
mod pass_sound {
use super::*;
type OuterOpaque<T> = impl Trait<&'static T, Out = impl Sized>;
fn define<T>() -> OuterOpaque<T> {}

fn test<T>() {
let outer = define::<T>();
let _ = outer.get(); //[pass_sound]~ ERROR `T` may not live long enough
}
}

// Similar to `pass` but here `impl Sized` can be referenced directly as
// InnerOpaque<T>, so we require an explicit bound `T: 'static`.
#[cfg(fail)]
mod fail {
use super::*;
type InnerOpaque<T> = impl Sized; //[fail]~ ERROR `T` may not live long enough
type OuterOpaque<T> = impl Trait<&'static T, Out = InnerOpaque<T>>;
fn define<T>() -> OuterOpaque<T> {}
}

fn main() {}

0 comments on commit d548747

Please sign in to comment.