Skip to content

Commit ce16189

Browse files
add some more comments to GAT where clause check
1 parent d8b49f0 commit ce16189

File tree

1 file changed

+62
-19
lines changed

1 file changed

+62
-19
lines changed

compiler/rustc_typeck/src/check/wfcheck.rs

+62-19
Original file line numberDiff line numberDiff line change
@@ -263,9 +263,24 @@ pub fn check_trait_item(tcx: TyCtxt<'_>, def_id: LocalDefId) {
263263
/// Require that the user writes where clauses on GATs for the implicit
264264
/// outlives bounds involving trait parameters in trait functions and
265265
/// lifetimes passed as GAT substs. See `self-outlives-lint` test.
266+
///
267+
/// We use the following trait as an example throughout this function:
268+
/// ```rust,ignore (this code fails due to this lint)
269+
/// trait IntoIter {
270+
/// type Iter<'a>: Iterator<Item = Self::Item<'a>>;
271+
/// type Item<'a>;
272+
/// fn into_iter<'a>(&'a self) -> Self::Iter<'a>;
273+
/// }
266274
/// ```
267275
fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRef]) {
276+
// Associates every GAT's def_id to a list of possibly missing bounds detected by this lint.
268277
let mut required_bounds_by_item = FxHashMap::default();
278+
279+
// Loop over all GATs together, because if this lint suggests adding a where-clause bound
280+
// to one GAT, it might then require us to an additional bound on another GAT.
281+
// In our `IntoIter` example, we discover a missing `Self: 'a` bound on `Iter<'a>`, which
282+
// then in a second loop adds a `Self: 'a` bound to `Item` due to the relationship between
283+
// those GATs.
269284
loop {
270285
let mut should_continue = false;
271286
for gat_item in associated_items {
@@ -276,14 +291,18 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe
276291
continue;
277292
}
278293
let gat_generics = tcx.generics_of(gat_def_id);
294+
// FIXME(jackh726): we can also warn in the more general case
279295
if gat_generics.params.is_empty() {
280296
continue;
281297
}
282298

299+
// Gather the bounds with which all other items inside of this trait constrain the GAT.
300+
// This is calculated by taking the intersection of the bounds that each item
301+
// constrains the GAT with individually.
283302
let mut new_required_bounds: Option<FxHashSet<ty::Predicate<'_>>> = None;
284303
for item in associated_items {
285304
let item_def_id = item.id.def_id;
286-
// Skip our own GAT, since it would blow away the required bounds
305+
// Skip our own GAT, since it does not constrain itself at all.
287306
if item_def_id == gat_def_id {
288307
continue;
289308
}
@@ -292,7 +311,11 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe
292311
let param_env = tcx.param_env(item_def_id);
293312

294313
let item_required_bounds = match item.kind {
314+
// In our example, this corresponds to `into_iter` method
295315
hir::AssocItemKind::Fn { .. } => {
316+
// For methods, we check the function signature's return type for any GATs
317+
// to constrain. In the `into_iter` case, we see that the return type
318+
// `Self::Iter<'a>` is a GAT we want to gather any potential missing bounds from.
296319
let sig: ty::FnSig<'_> = tcx.liberate_late_bound_regions(
297320
item_def_id.to_def_id(),
298321
tcx.fn_sig(item_def_id),
@@ -302,11 +325,14 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe
302325
param_env,
303326
item_hir_id,
304327
sig.output(),
328+
// We also assume that all of the function signature's parameter types
329+
// are well formed.
305330
&sig.inputs().iter().copied().collect(),
306331
gat_def_id,
307332
gat_generics,
308333
)
309334
}
335+
// In our example, this corresponds to the `Iter` and `Item` associated types
310336
hir::AssocItemKind::Type => {
311337
// If our associated item is a GAT with missing bounds, add them to
312338
// the param-env here. This allows this GAT to propagate missing bounds
@@ -316,6 +342,7 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe
316342
param_env,
317343
required_bounds_by_item.get(&item_def_id),
318344
);
345+
// FIXME(compiler-errors): Do we want to add a assoc ty default to the wf_tys?
319346
gather_gat_bounds(
320347
tcx,
321348
param_env,
@@ -333,9 +360,11 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe
333360
};
334361

335362
if let Some(item_required_bounds) = item_required_bounds {
336-
// Take the intersection of the new_required_bounds and the item_required_bounds
337-
// for this item. This is why we use an Option<_>, since we need to distinguish
338-
// the empty set of bounds from the uninitialized set of bounds.
363+
// Take the intersection of the required bounds for this GAT, and
364+
// the item_required_bounds which are the ones implied by just
365+
// this item alone.
366+
// This is why we use an Option<_>, since we need to distinguish
367+
// the empty set of bounds from the _uninitialized_ set of bounds.
339368
if let Some(new_required_bounds) = &mut new_required_bounds {
340369
new_required_bounds.retain(|b| item_required_bounds.contains(b));
341370
} else {
@@ -346,14 +375,17 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe
346375

347376
if let Some(new_required_bounds) = new_required_bounds {
348377
let required_bounds = required_bounds_by_item.entry(gat_def_id).or_default();
349-
if new_required_bounds != *required_bounds {
350-
*required_bounds = new_required_bounds;
378+
if new_required_bounds.into_iter().any(|p| required_bounds.insert(p)) {
351379
// Iterate until our required_bounds no longer change
352380
// Since they changed here, we should continue the loop
353381
should_continue = true;
354382
}
355383
}
356384
}
385+
// We know that this loop will eventually halt, since we only set `should_continue` if the
386+
// `required_bounds` for this item grows. Since we are not creating any new region or type
387+
// variables, the set of all region and type bounds that we could ever insert are limited
388+
// by the number of unique types and regions we observe in a given item.
357389
if !should_continue {
358390
break;
359391
}
@@ -422,8 +454,7 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, associated_items: &[hir::TraitItemRe
422454
}
423455
}
424456

425-
/// Add a new set of predicates to the caller_bounds of an existing param_env,
426-
/// and normalize the param_env afterwards
457+
/// Add a new set of predicates to the caller_bounds of an existing param_env.
427458
fn augment_param_env<'tcx>(
428459
tcx: TyCtxt<'tcx>,
429460
param_env: ty::ParamEnv<'tcx>,
@@ -444,6 +475,16 @@ fn augment_param_env<'tcx>(
444475
ty::ParamEnv::new(bounds, param_env.reveal(), param_env.constness())
445476
}
446477

478+
/// We use the following trait as an example throughout this function.
479+
/// Specifically, let's assume that `to_check` here is the return type
480+
/// of `into_iter`, and the GAT we are checking this for is `Iter`.
481+
/// ```rust,ignore (this code fails due to this lint)
482+
/// trait IntoIter {
483+
/// type Iter<'a>: Iterator<Item = Self::Item<'a>>;
484+
/// type Item<'a>;
485+
/// fn into_iter<'a>(&'a self) -> Self::Iter<'a>;
486+
/// }
487+
/// ```
447488
fn gather_gat_bounds<'tcx, T: TypeFoldable<'tcx>>(
448489
tcx: TyCtxt<'tcx>,
449490
param_env: ty::ParamEnv<'tcx>,
@@ -453,13 +494,13 @@ fn gather_gat_bounds<'tcx, T: TypeFoldable<'tcx>>(
453494
gat_def_id: LocalDefId,
454495
gat_generics: &'tcx ty::Generics,
455496
) -> Option<FxHashSet<ty::Predicate<'tcx>>> {
456-
// The bounds we that we would require from this function
497+
// The bounds we that we would require from `to_check`
457498
let mut bounds = FxHashSet::default();
458499

459500
let (regions, types) = GATSubstCollector::visit(tcx, gat_def_id.to_def_id(), to_check);
460501

461502
// If both regions and types are empty, then this GAT isn't in the
462-
// return type, and we shouldn't try to do clause analysis
503+
// set of types we are checking, and we shouldn't try to do clause analysis
463504
// (particularly, doing so would end up with an empty set of clauses,
464505
// since the current method would require none, and we take the
465506
// intersection of requirements of all methods)
@@ -474,18 +515,18 @@ fn gather_gat_bounds<'tcx, T: TypeFoldable<'tcx>>(
474515
if let ty::ReStatic = **region_a {
475516
continue;
476517
}
477-
// For each region argument (e.g., 'a in our example), check for a
478-
// relationship to the type arguments (e.g., Self). If there is an
518+
// For each region argument (e.g., `'a` in our example), check for a
519+
// relationship to the type arguments (e.g., `Self`). If there is an
479520
// outlives relationship (`Self: 'a`), then we want to ensure that is
480521
// reflected in a where clause on the GAT itself.
481522
for (ty, ty_idx) in &types {
482-
// In our example, requires that Self: 'a
523+
// In our example, requires that `Self: 'a`
483524
if ty_known_to_outlive(tcx, item_hir, param_env, &wf_tys, *ty, *region_a) {
484525
debug!(?ty_idx, ?region_a_idx);
485526
debug!("required clause: {} must outlive {}", ty, region_a);
486527
// Translate into the generic parameters of the GAT. In
487-
// our example, the type was Self, which will also be
488-
// Self in the GAT.
528+
// our example, the type was `Self`, which will also be
529+
// `Self` in the GAT.
489530
let ty_param = gat_generics.param_at(*ty_idx, tcx);
490531
let ty_param = tcx
491532
.mk_ty(ty::Param(ty::ParamTy { index: ty_param.index, name: ty_param.name }));
@@ -507,11 +548,13 @@ fn gather_gat_bounds<'tcx, T: TypeFoldable<'tcx>>(
507548
}
508549
}
509550

510-
// For each region argument (e.g., 'a in our example), also check for a
511-
// relationship to the other region arguments. If there is an
512-
// outlives relationship, then we want to ensure that is
513-
// reflected in a where clause on the GAT itself.
551+
// For each region argument (e.g., `'a` in our example), also check for a
552+
// relationship to the other region arguments. If there is an outlives
553+
// relationship, then we want to ensure that is reflected in the where clause
554+
// on the GAT itself.
514555
for (region_b, region_b_idx) in &regions {
556+
// Again, skip `'static` because it outlives everything. Also, we trivially
557+
// know that a region outlives itself.
515558
if ty::ReStatic == **region_b || region_a == region_b {
516559
continue;
517560
}

0 commit comments

Comments
 (0)