Skip to content

Commit

Permalink
Rollup merge of rust-lang#119766 - oli-obk:split_tait_and_atpit, r=co…
Browse files Browse the repository at this point in the history
…mpiler-errors

Split tait and impl trait in assoc items logic

And simplify the assoc item logic where applicable.

This separation shows that it is easier to reason about impl trait in assoc items compared with TAITs. See https://rust-lang.zulipchat.com/#narrow/stream/315482-t-compiler.2Fetc.2Fopaque-types/topic/impl.20trait.20in.20associated.20type for some discussion.

The current plan is to try to stabilize impl trait in associated items before TAIT, as they do not have any issues with their defining scopes (see rust-lang#107645 for why this is not a trivial or uncontroversial topic).
  • Loading branch information
matthiaskrgr committed Jan 22, 2024
2 parents c5f2c10 + 5e5d135 commit 4512fa4
Show file tree
Hide file tree
Showing 21 changed files with 615 additions and 73 deletions.
6 changes: 5 additions & 1 deletion compiler/rustc_hir_analysis/src/collect/type_of.rs
Original file line number Diff line number Diff line change
Expand Up @@ -530,9 +530,13 @@ pub(super) fn type_of_opaque(
Ok(ty::EarlyBinder::bind(match tcx.hir_node_by_def_id(def_id) {
Node::Item(item) => match item.kind {
ItemKind::OpaqueTy(OpaqueTy {
origin: hir::OpaqueTyOrigin::TyAlias { .. },
origin: hir::OpaqueTyOrigin::TyAlias { in_assoc_ty: false },
..
}) => opaque::find_opaque_ty_constraints_for_tait(tcx, def_id),
ItemKind::OpaqueTy(OpaqueTy {
origin: hir::OpaqueTyOrigin::TyAlias { in_assoc_ty: true },
..
}) => opaque::find_opaque_ty_constraints_for_impl_trait_in_assoc_type(tcx, def_id),
// Opaque types desugared from `impl Trait`.
ItemKind::OpaqueTy(&OpaqueTy {
origin:
Expand Down
78 changes: 72 additions & 6 deletions compiler/rustc_hir_analysis/src/collect/type_of/opaque.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,60 @@ pub fn test_opaque_hidden_types(tcx: TyCtxt<'_>) -> Result<(), ErrorGuaranteed>
res
}

/// Checks "defining uses" of opaque `impl Trait` in associated types.
/// These can only be defined by associated items of the same trait.
#[instrument(skip(tcx), level = "debug")]
pub(super) fn find_opaque_ty_constraints_for_impl_trait_in_assoc_type(
tcx: TyCtxt<'_>,
def_id: LocalDefId,
) -> Ty<'_> {
let mut parent_def_id = def_id;
while tcx.def_kind(parent_def_id) == def::DefKind::OpaqueTy {
// Account for `type Alias = impl Trait<Foo = impl Trait>;` (#116031)
parent_def_id = tcx.local_parent(parent_def_id);
}
let impl_def_id = tcx.local_parent(parent_def_id);
match tcx.def_kind(impl_def_id) {
DefKind::Impl { .. } => {}
other => bug!("invalid impl trait in assoc type parent: {other:?}"),
}

let mut locator = TaitConstraintLocator { def_id, tcx, found: None, typeck_types: vec![] };

for &assoc_id in tcx.associated_item_def_ids(impl_def_id) {
let assoc = tcx.associated_item(assoc_id);
match assoc.kind {
ty::AssocKind::Const | ty::AssocKind::Fn => {
locator.check(assoc_id.expect_local(), ImplTraitSource::AssocTy)
}
// Associated types don't have bodies, so they can't constrain hidden types
ty::AssocKind::Type => {}
}
}

if let Some(hidden) = locator.found {
// Only check against typeck if we didn't already error
if !hidden.ty.references_error() {
for concrete_type in locator.typeck_types {
if concrete_type.ty != tcx.erase_regions(hidden.ty)
&& !(concrete_type, hidden).references_error()
{
hidden.report_mismatch(&concrete_type, def_id, tcx).emit();
}
}
}

hidden.ty
} else {
let reported = tcx.dcx().emit_err(UnconstrainedOpaqueType {
span: tcx.def_span(def_id),
name: tcx.item_name(parent_def_id.to_def_id()),
what: "impl",
});
Ty::new_error(tcx, reported)
}
}

/// Checks "defining uses" of opaque `impl Trait` types to ensure that they meet the restrictions
/// laid for "higher-order pattern unification".
/// This ensures that inference is tractable.
Expand Down Expand Up @@ -128,9 +182,15 @@ struct TaitConstraintLocator<'tcx> {
typeck_types: Vec<ty::OpaqueHiddenType<'tcx>>,
}

#[derive(Debug)]
enum ImplTraitSource {
AssocTy,
TyAlias,
}

impl TaitConstraintLocator<'_> {
#[instrument(skip(self), level = "debug")]
fn check(&mut self, item_def_id: LocalDefId) {
fn check(&mut self, item_def_id: LocalDefId, source: ImplTraitSource) {
// Don't try to check items that cannot possibly constrain the type.
if !self.tcx.has_typeck_results(item_def_id) {
debug!("no constraint: no typeck results");
Expand Down Expand Up @@ -182,7 +242,13 @@ impl TaitConstraintLocator<'_> {
continue;
}
constrained = true;
if !self.tcx.opaque_types_defined_by(item_def_id).contains(&self.def_id) {
let opaque_types_defined_by = match source {
ImplTraitSource::AssocTy => {
self.tcx.impl_trait_in_assoc_types_defined_by(item_def_id)
}
ImplTraitSource::TyAlias => self.tcx.opaque_types_defined_by(item_def_id),
};
if !opaque_types_defined_by.contains(&self.def_id) {
self.tcx.dcx().emit_err(TaitForwardCompat {
span: hidden_type.span,
item_span: self
Expand Down Expand Up @@ -240,29 +306,29 @@ impl<'tcx> intravisit::Visitor<'tcx> for TaitConstraintLocator<'tcx> {
}
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
if let hir::ExprKind::Closure(closure) = ex.kind {
self.check(closure.def_id);
self.check(closure.def_id, ImplTraitSource::TyAlias);
}
intravisit::walk_expr(self, ex);
}
fn visit_item(&mut self, it: &'tcx Item<'tcx>) {
trace!(?it.owner_id);
// The opaque type itself or its children are not within its reveal scope.
if it.owner_id.def_id != self.def_id {
self.check(it.owner_id.def_id);
self.check(it.owner_id.def_id, ImplTraitSource::TyAlias);
intravisit::walk_item(self, it);
}
}
fn visit_impl_item(&mut self, it: &'tcx ImplItem<'tcx>) {
trace!(?it.owner_id);
// The opaque type itself or its children are not within its reveal scope.
if it.owner_id.def_id != self.def_id {
self.check(it.owner_id.def_id);
self.check(it.owner_id.def_id, ImplTraitSource::TyAlias);
intravisit::walk_impl_item(self, it);
}
}
fn visit_trait_item(&mut self, it: &'tcx TraitItem<'tcx>) {
trace!(?it.owner_id);
self.check(it.owner_id.def_id);
self.check(it.owner_id.def_id, ImplTraitSource::TyAlias);
intravisit::walk_trait_item(self, it);
}
fn visit_foreign_item(&mut self, it: &'tcx hir::ForeignItem<'tcx>) {
Expand Down
9 changes: 9 additions & 0 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,15 @@ rustc_queries! {
}
}

query impl_trait_in_assoc_types_defined_by(
key: LocalDefId
) -> &'tcx ty::List<LocalDefId> {
desc {
|tcx| "computing the opaque types defined by `{}`",
tcx.def_path_str(key.to_def_id())
}
}

/// Returns the list of bounds that can be used for
/// `SelectionCandidate::ProjectionCandidate(_)` and
/// `ProjectionTyCandidate::TraitDef`.
Expand Down
213 changes: 151 additions & 62 deletions compiler/rustc_ty_utils/src/opaque_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,69 @@ impl<'tcx> OpaqueTypeCollector<'tcx> {
}
TaitInBodyFinder { collector: self }.visit_expr(body);
}

fn visit_opaque_ty(&mut self, alias_ty: &ty::AliasTy<'tcx>) {
if !self.seen.insert(alias_ty.def_id.expect_local()) {
return;
}

// TAITs outside their defining scopes are ignored.
let origin = self.tcx.opaque_type_origin(alias_ty.def_id.expect_local());
trace!(?origin);
match origin {
rustc_hir::OpaqueTyOrigin::FnReturn(_) | rustc_hir::OpaqueTyOrigin::AsyncFn(_) => {}
rustc_hir::OpaqueTyOrigin::TyAlias { in_assoc_ty } => {
if !in_assoc_ty {
if !self.check_tait_defining_scope(alias_ty.def_id.expect_local()) {
return;
}
}
}
}

self.opaques.push(alias_ty.def_id.expect_local());

let parent_count = self.tcx.generics_of(alias_ty.def_id).parent_count;
// Only check that the parent generics of the TAIT/RPIT are unique.
// the args owned by the opaque are going to always be duplicate
// lifetime params for RPITs, and empty for TAITs.
match self
.tcx
.uses_unique_generic_params(&alias_ty.args[..parent_count], CheckRegions::FromFunction)
{
Ok(()) => {
// FIXME: implement higher kinded lifetime bounds on nested opaque types. They are not
// supported at all, so this is sound to do, but once we want to support them, you'll
// start seeing the error below.

// Collect opaque types nested within the associated type bounds of this opaque type.
// We use identity args here, because we already know that the opaque type uses
// only generic parameters, and thus substituting would not give us more information.
for (pred, span) in self
.tcx
.explicit_item_bounds(alias_ty.def_id)
.instantiate_identity_iter_copied()
{
trace!(?pred);
self.visit_spanned(span, pred);
}
}
Err(NotUniqueParam::NotParam(arg)) => {
self.tcx.dcx().emit_err(NotParam {
arg,
span: self.span(),
opaque_span: self.tcx.def_span(alias_ty.def_id),
});
}
Err(NotUniqueParam::DuplicateParam(arg)) => {
self.tcx.dcx().emit_err(DuplicateArg {
arg,
span: self.span(),
opaque_span: self.tcx.def_span(alias_ty.def_id),
});
}
}
}
}

impl<'tcx> super::sig_types::SpannedTypeVisitor<'tcx> for OpaqueTypeCollector<'tcx> {
Expand All @@ -134,67 +197,7 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for OpaqueTypeCollector<'tcx> {
t.super_visit_with(self)?;
match t.kind() {
ty::Alias(ty::Opaque, alias_ty) if alias_ty.def_id.is_local() => {
if !self.seen.insert(alias_ty.def_id.expect_local()) {
return ControlFlow::Continue(());
}

// TAITs outside their defining scopes are ignored.
let origin = self.tcx.opaque_type_origin(alias_ty.def_id.expect_local());
trace!(?origin);
match origin {
rustc_hir::OpaqueTyOrigin::FnReturn(_)
| rustc_hir::OpaqueTyOrigin::AsyncFn(_) => {}
rustc_hir::OpaqueTyOrigin::TyAlias { in_assoc_ty } => {
if !in_assoc_ty {
if !self.check_tait_defining_scope(alias_ty.def_id.expect_local()) {
return ControlFlow::Continue(());
}
}
}
}

self.opaques.push(alias_ty.def_id.expect_local());

let parent_count = self.tcx.generics_of(alias_ty.def_id).parent_count;
// Only check that the parent generics of the TAIT/RPIT are unique.
// the args owned by the opaque are going to always be duplicate
// lifetime params for RPITs, and empty for TAITs.
match self.tcx.uses_unique_generic_params(
&alias_ty.args[..parent_count],
CheckRegions::FromFunction,
) {
Ok(()) => {
// FIXME: implement higher kinded lifetime bounds on nested opaque types. They are not
// supported at all, so this is sound to do, but once we want to support them, you'll
// start seeing the error below.

// Collect opaque types nested within the associated type bounds of this opaque type.
// We use identity args here, because we already know that the opaque type uses
// only generic parameters, and thus substituting would not give us more information.
for (pred, span) in self
.tcx
.explicit_item_bounds(alias_ty.def_id)
.instantiate_identity_iter_copied()
{
trace!(?pred);
self.visit_spanned(span, pred);
}
}
Err(NotUniqueParam::NotParam(arg)) => {
self.tcx.dcx().emit_err(NotParam {
arg,
span: self.span(),
opaque_span: self.tcx.def_span(alias_ty.def_id),
});
}
Err(NotUniqueParam::DuplicateParam(arg)) => {
self.tcx.dcx().emit_err(DuplicateArg {
arg,
span: self.span(),
opaque_span: self.tcx.def_span(alias_ty.def_id),
});
}
}
self.visit_opaque_ty(alias_ty);
}
ty::Alias(ty::Weak, alias_ty) if alias_ty.def_id.is_local() => {
self.tcx
Expand Down Expand Up @@ -272,6 +275,91 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for OpaqueTypeCollector<'tcx> {
}
}

struct ImplTraitInAssocTypeCollector<'tcx>(OpaqueTypeCollector<'tcx>);

impl<'tcx> super::sig_types::SpannedTypeVisitor<'tcx> for ImplTraitInAssocTypeCollector<'tcx> {
#[instrument(skip(self), ret, level = "trace")]
fn visit(&mut self, span: Span, value: impl TypeVisitable<TyCtxt<'tcx>>) -> ControlFlow<!> {
let old = self.0.span;
self.0.span = Some(span);
value.visit_with(self);
self.0.span = old;

ControlFlow::Continue(())
}
}

impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ImplTraitInAssocTypeCollector<'tcx> {
#[instrument(skip(self), ret, level = "trace")]
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<!> {
t.super_visit_with(self)?;
match t.kind() {
ty::Alias(ty::Opaque, alias_ty) if alias_ty.def_id.is_local() => {
self.0.visit_opaque_ty(alias_ty);
}
ty::Alias(ty::Projection, alias_ty) => {
// This avoids having to do normalization of `Self::AssocTy` by only
// supporting the case of a method defining opaque types from assoc types
// in the same impl block.
let parent_trait_ref = self
.0
.parent_trait_ref()
.expect("impl trait in assoc type collector used on non-assoc item");
// If the trait ref of the associated item and the impl differs,
// then we can't use the impl's identity substitutions below, so
// just skip.
if alias_ty.trait_ref(self.0.tcx) == parent_trait_ref {
let parent = self.0.parent().expect("we should have a parent here");

for &assoc in self.0.tcx.associated_items(parent).in_definition_order() {
trace!(?assoc);
if assoc.trait_item_def_id != Some(alias_ty.def_id) {
continue;
}

// If the type is further specializable, then the type_of
// is not actually correct below.
if !assoc.defaultness(self.0.tcx).is_final() {
continue;
}

let impl_args = alias_ty.args.rebase_onto(
self.0.tcx,
parent_trait_ref.def_id,
ty::GenericArgs::identity_for_item(self.0.tcx, parent),
);

if check_args_compatible(self.0.tcx, assoc, impl_args) {
return self
.0
.tcx
.type_of(assoc.def_id)
.instantiate(self.0.tcx, impl_args)
.visit_with(self);
} else {
self.0.tcx.dcx().span_delayed_bug(
self.0.tcx.def_span(assoc.def_id),
"item had incorrect args",
);
}
}
}
}
_ => trace!(kind=?t.kind()),
}
ControlFlow::Continue(())
}
}

fn impl_trait_in_assoc_types_defined_by<'tcx>(
tcx: TyCtxt<'tcx>,
item: LocalDefId,
) -> &'tcx ty::List<LocalDefId> {
let mut collector = ImplTraitInAssocTypeCollector(OpaqueTypeCollector::new(tcx, item));
super::sig_types::walk_types(tcx, item, &mut collector);
tcx.mk_local_def_ids(&collector.0.opaques)
}

fn opaque_types_defined_by<'tcx>(
tcx: TyCtxt<'tcx>,
item: LocalDefId,
Expand Down Expand Up @@ -321,5 +409,6 @@ fn opaque_types_defined_by<'tcx>(
}

pub(super) fn provide(providers: &mut Providers) {
*providers = Providers { opaque_types_defined_by, ..*providers };
*providers =
Providers { opaque_types_defined_by, impl_trait_in_assoc_types_defined_by, ..*providers };
}

0 comments on commit 4512fa4

Please sign in to comment.