Skip to content

Commit

Permalink
clean up ADT sized constraint computation
Browse files Browse the repository at this point in the history
  • Loading branch information
Lukas Markeffsky committed Mar 14, 2024
1 parent d1ec4eb commit 495807a
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 92 deletions.
4 changes: 2 additions & 2 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -703,8 +703,8 @@ rustc_queries! {
separate_provide_extern
}

query adt_sized_constraint(key: DefId) -> ty::EarlyBinder<&'tcx ty::List<Ty<'tcx>>> {
desc { |tcx| "computing `Sized` constraints for `{}`", tcx.def_path_str(key) }
query adt_sized_constraint(key: DefId) -> Option<ty::EarlyBinder<Ty<'tcx>>> {
desc { |tcx| "computing `Sized` constraint for `{}`", tcx.def_path_str(key) }
}

query adt_dtorck_constraint(
Expand Down
8 changes: 4 additions & 4 deletions compiler/rustc_middle/src/ty/adt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,10 +590,10 @@ impl<'tcx> AdtDef<'tcx> {
tcx.adt_destructor(self.did())
}

/// Returns a list of types such that `Self: Sized` if and only if that
/// type is `Sized`, or `ty::Error` if this type has a recursive layout.
pub fn sized_constraint(self, tcx: TyCtxt<'tcx>) -> ty::EarlyBinder<&'tcx ty::List<Ty<'tcx>>> {
tcx.adt_sized_constraint(self.did())
/// Returns a type such that `Self: Sized` if and only if that type is `Sized`, or `None`
/// if the type is always sized, or `ty::Error` if this type has a recursive layout.
pub fn sized_constraint(self, tcx: TyCtxt<'tcx>) -> Option<ty::EarlyBinder<Ty<'tcx>>> {
if self.is_struct() { tcx.adt_sized_constraint(self.did()) } else { None }
}
}

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/ty/sty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2489,7 +2489,7 @@ impl<'tcx> Ty<'tcx> {

ty::Tuple(tys) => tys.iter().all(|ty| ty.is_trivially_sized(tcx)),

ty::Adt(def, _args) => def.sized_constraint(tcx).skip_binder().is_empty(),
ty::Adt(def, _args) => def.sized_constraint(tcx).is_none(),

ty::Alias(..) | ty::Param(_) | ty::Placeholder(..) | ty::Bound(..) => false,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,9 @@ pub(in crate::solve) fn instantiate_constituent_tys_for_sized_trait<'tcx>(

ty::Adt(def, args) => {
let sized_crit = def.sized_constraint(ecx.tcx());
Ok(sized_crit.iter_instantiated(ecx.tcx(), args).map(ty::Binder::dummy).collect())
Ok(sized_crit.map_or_else(Vec::new, |ty| {
vec![ty::Binder::dummy(ty.instantiate(ecx.tcx(), args))]
}))
}
}
}
Expand Down
8 changes: 3 additions & 5 deletions compiler/rustc_trait_selection/src/traits/select/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2120,11 +2120,9 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
ty::Adt(def, args) => {
let sized_crit = def.sized_constraint(self.tcx());
// (*) binder moved here
Where(
obligation
.predicate
.rebind(sized_crit.iter_instantiated(self.tcx(), args).collect()),
)
Where(obligation.predicate.rebind(
sized_crit.map_or_else(Vec::new, |ty| vec![ty.instantiate(self.tcx(), args)]),
))
}

ty::Alias(..) | ty::Param(_) | ty::Placeholder(..) => None,
Expand Down
139 changes: 71 additions & 68 deletions compiler/rustc_ty_utils/src/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,76 +3,56 @@ use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_index::bit_set::BitSet;
use rustc_middle::query::Providers;
use rustc_middle::ty::{self, EarlyBinder, Ty, TyCtxt, TypeVisitor};
use rustc_middle::ty::{self, EarlyBinder, Ty, TyCtxt, TypeVisitableExt, TypeVisitor};
use rustc_middle::ty::{ToPredicate, TypeSuperVisitable, TypeVisitable};
use rustc_span::def_id::{DefId, LocalDefId, CRATE_DEF_ID};
use rustc_span::DUMMY_SP;
use rustc_trait_selection::traits;

fn sized_constraint_for_ty<'tcx>(
tcx: TyCtxt<'tcx>,
adtdef: ty::AdtDef<'tcx>,
ty: Ty<'tcx>,
) -> Vec<Ty<'tcx>> {
#[instrument(level = "debug", skip(tcx), ret)]
fn sized_constraint_for_ty<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Ty<'tcx>> {
use rustc_type_ir::TyKind::*;

let result = match ty.kind() {
Bool | Char | Int(..) | Uint(..) | Float(..) | RawPtr(..) | Ref(..) | FnDef(..)
| FnPtr(_) | Array(..) | Closure(..) | CoroutineClosure(..) | Coroutine(..) | Never => {
vec![]
}

Str | Dynamic(..) | Slice(_) | Foreign(..) | Error(_) | CoroutineWitness(..) => {
// these are never sized - return the target type
vec![ty]
}

Tuple(tys) => match tys.last() {
None => vec![],
Some(&ty) => sized_constraint_for_ty(tcx, adtdef, ty),
},

match ty.kind() {
// these are always sized
Bool
| Char
| Int(..)
| Uint(..)
| Float(..)
| RawPtr(..)
| Ref(..)
| FnDef(..)
| FnPtr(..)
| Array(..)
| Closure(..)
| CoroutineClosure(..)
| Coroutine(..)
| CoroutineWitness(..)
| Never
| Dynamic(_, _, ty::DynStar) => None,

// these are never sized
Str | Slice(..) | Dynamic(_, _, ty::Dyn) | Foreign(..) => Some(ty),

Tuple(tys) => tys.last().and_then(|&ty| sized_constraint_for_ty(tcx, ty)),

// recursive case
Adt(adt, args) => {
// recursive case
let adt_tys = adt.sized_constraint(tcx);
debug!("sized_constraint_for_ty({:?}) intermediate = {:?}", ty, adt_tys);
adt_tys
.iter_instantiated(tcx, args)
.flat_map(|ty| sized_constraint_for_ty(tcx, adtdef, ty))
.collect()
}

Alias(..) => {
// must calculate explicitly.
// FIXME: consider special-casing always-Sized projections
vec![ty]
let intermediate = adt.sized_constraint(tcx);
intermediate.and_then(|intermediate| {
let ty = intermediate.instantiate(tcx, args);
sized_constraint_for_ty(tcx, ty)
})
}

Param(..) => {
// perf hack: if there is a `T: Sized` bound, then
// we know that `T` is Sized and do not need to check
// it on the impl.

let Some(sized_trait_def_id) = tcx.lang_items().sized_trait() else { return vec![ty] };
let predicates = tcx.predicates_of(adtdef.did()).predicates;
if predicates.iter().any(|(p, _)| {
p.as_trait_clause().is_some_and(|trait_pred| {
trait_pred.def_id() == sized_trait_def_id
&& trait_pred.self_ty().skip_binder() == ty
})
}) {
vec![]
} else {
vec![ty]
}
}
// these can be sized or unsized
Param(..) | Alias(..) | Error(_) => Some(ty),

Placeholder(..) | Bound(..) | Infer(..) => {
bug!("unexpected type `{:?}` in sized_constraint_for_ty", ty)
bug!("unexpected type `{ty:?}` in sized_constraint_for_ty")
}
};
debug!("sized_constraint_for_ty({:?}) = {:?}", ty, result);
result
}
}

fn defaultness(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::Defaultness {
Expand All @@ -90,29 +70,52 @@ fn defaultness(tcx: TyCtxt<'_>, def_id: LocalDefId) -> hir::Defaultness {
///
/// In fact, there are only a few options for the types in the constraint:
/// - an obviously-unsized type
/// - a type parameter or projection whose Sizedness can't be known
/// - a tuple of type parameters or projections, if there are multiple
/// such.
/// - a type parameter or projection whose sizedness can't be known
/// - an Error, if a type is infinitely sized
#[instrument(level = "debug", skip(tcx), ret)]
fn adt_sized_constraint<'tcx>(
tcx: TyCtxt<'tcx>,
def_id: DefId,
) -> ty::EarlyBinder<&'tcx ty::List<Ty<'tcx>>> {
) -> Option<ty::EarlyBinder<Ty<'tcx>>> {
if let Some(def_id) = def_id.as_local() {
if let ty::Representability::Infinite(guar) = tcx.representability(def_id) {
return ty::EarlyBinder::bind(tcx.mk_type_list(&[Ty::new_error(tcx, guar)]));
return Some(ty::EarlyBinder::bind(Ty::new_error(tcx, guar)));
}
}
let def = tcx.adt_def(def_id);

let result =
tcx.mk_type_list_from_iter(def.variants().iter().filter_map(|v| v.tail_opt()).flat_map(
|f| sized_constraint_for_ty(tcx, def, tcx.type_of(f.did).instantiate_identity()),
));
if !def.is_struct() {
bug!("`adt_sized_constraint` called on non-struct type: {def:?}");
}

let tail_def = def.non_enum_variant().tail_opt()?;
let tail_ty = tcx.type_of(tail_def.did).instantiate_identity();

let result = sized_constraint_for_ty(tcx, tail_ty);

let result = result.filter(|&ty| {
// perf hack: if there is a `ty: Sized` bound, then we know that
// the type is sized and do not need to check it on the impl.

if ty.references_error() {
return true;
}

let Some(sized_trait_def_id) = tcx.lang_items().sized_trait() else {
return true;
};

let predicates = tcx.predicates_of(def.did()).predicates;

debug!("adt_sized_constraint: {:?} => {:?}", def, result);
!predicates.iter().any(|(p, _)| {
p.as_trait_clause().is_some_and(|trait_pred| {
trait_pred.def_id() == sized_trait_def_id
&& trait_pred.self_ty().skip_binder() == ty
})
})
});

ty::EarlyBinder::bind(result)
result.map(ty::EarlyBinder::bind)
}

/// See `ParamEnv` struct definition for details.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,5 @@ where

fn main() {
let mut list = RcNode::<i32>::new();
//~^ ERROR the size for values of type `Node<i32, RcFamily>` cannot be known at compilation time
//~^ ERROR trait bounds were not satisfied
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,15 @@ help: consider relaxing the implicit `Sized` restriction
LL | type Pointer<T>: Deref<Target = T> + ?Sized;
| ++++++++

error[E0599]: the size for values of type `Node<i32, RcFamily>` cannot be known at compilation time
error[E0599]: the variant or associated item `new` exists for enum `Node<i32, RcFamily>`, but its trait bounds were not satisfied
--> $DIR/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.rs:31:35
|
LL | enum Node<T, P: PointerFamily> {
| ------------------------------ variant or associated item `new` not found for this enum because it doesn't satisfy `Node<i32, RcFamily>: Sized`
| ------------------------------ variant or associated item `new` not found for this enum
...
LL | let mut list = RcNode::<i32>::new();
| ^^^ doesn't have a size known at compile-time
| ^^^ variant or associated item cannot be called on `Node<i32, RcFamily>` due to unsatisfied trait bounds
|
note: trait bound `Node<i32, RcFamily>: Sized` was not satisfied
--> $DIR/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.rs:4:18
|
LL | type Pointer<T>: Deref<Target = T>;
| ------- ^ unsatisfied trait bound introduced here
note: trait bound `(dyn Deref<Target = Node<i32, RcFamily>> + 'static): Sized` was not satisfied
--> $DIR/issue-119942-unsatisified-gat-bound-during-assoc-ty-selection.rs:23:29
|
Expand All @@ -37,8 +32,6 @@ LL | impl<T, P: PointerFamily> Node<T, P>
LL | where
LL | P::Pointer<Node<T, P>>: Sized,
| ^^^^^ unsatisfied trait bound introduced here
note: the trait `Sized` must be implemented
--> $SRC_DIR/core/src/marker.rs:LL:COL

error: aborting due to 2 previous errors

Expand Down

0 comments on commit 495807a

Please sign in to comment.