Skip to content

Commit b6bca0d

Browse files
committed
Auto merge of #150662 - adwinwhite:check-item-bounds, r=<try>
Check item bounds for projection clause wellformedness
2 parents 08cd08f + 1249bd0 commit b6bca0d

9 files changed

Lines changed: 458 additions & 4 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4755,6 +4755,7 @@ dependencies = [
47554755
"rustc_errors",
47564756
"rustc_hir",
47574757
"rustc_infer",
4758+
"rustc_lint_defs",
47584759
"rustc_macros",
47594760
"rustc_middle",
47604761
"rustc_next_trait_solver",

compiler/rustc_hir_analysis/src/collect/predicates_of.rs

Lines changed: 224 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
use std::assert_matches;
22

33
use hir::Node;
4-
use rustc_data_structures::fx::FxIndexSet;
4+
use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
55
use rustc_hir as hir;
66
use rustc_hir::def::DefKind;
77
use rustc_hir::def_id::{DefId, LocalDefId};
88
use rustc_hir::find_attr;
9+
use rustc_lint_defs;
910
use rustc_middle::ty::{
10-
self, GenericPredicates, ImplTraitInTraitData, Ty, TyCtxt, TypeVisitable, TypeVisitor, Upcast,
11+
self, Binder, Clause, GenericArgKind, GenericArgs, GenericPredicates, ImplTraitInTraitData, Ty,
12+
TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitable, TypeVisitor, Upcast, UpcastFrom,
1113
};
1214
use rustc_middle::{bug, span_bug};
1315
use rustc_span::{DUMMY_SP, Ident, Span};
@@ -30,6 +32,16 @@ pub(super) fn predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericPredic
3032
let mut result = tcx.explicit_predicates_of(def_id);
3133
debug!("predicates_of: explicit_predicates_of({:?}) = {:?}", def_id, result);
3234

35+
// Check lint cap level to avoid triggering this flag on deps.
36+
if tcx.sess.opts.lint_cap != Some(rustc_lint_defs::Level::Allow)
37+
&& tcx.sess.opts.unstable_opts.strict_projection_item_bounds
38+
{
39+
let implied_item_bounds = elaborate_projection_predicates(tcx, result.predicates.to_vec());
40+
result.predicates = tcx
41+
.arena
42+
.alloc_from_iter(result.predicates.into_iter().copied().chain(implied_item_bounds));
43+
}
44+
3345
let inferred_outlives = tcx.inferred_outlives_of(def_id);
3446
if !inferred_outlives.is_empty() {
3547
debug!("predicates_of: inferred_outlives_of({:?}) = {:?}", def_id, inferred_outlives,);
@@ -76,6 +88,216 @@ pub(super) fn predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericPredic
7688
debug!("predicates_of({:?}) = {:?}", def_id, result);
7789
result
7890
}
91+
// If the term of projection is a generic param, we try to add implied item bounds to predicates.
92+
fn elaborate_projection_predicates<'tcx>(
93+
tcx: TyCtxt<'tcx>,
94+
predicates: Vec<(Clause<'tcx>, Span)>,
95+
) -> Vec<(ty::Clause<'tcx>, Span)> {
96+
// We use a folder rather than instantiation because we need to map `Self::Assoc` to the rhs of
97+
// the projection. Instantiation doesn't do that.
98+
// We need to support higher ranked region and friends in the item bounds.
99+
// However, we can't bind item bounds predicates twice. So we just append the bound vars of each item
100+
// bound to the projection predicate binder and shift bound var indices in this folder.
101+
// See `tests/ui/wf/wf-item-bounds-on-projection-with-binder.rs`.
102+
struct PredicateArgFolder<'tcx> {
103+
tcx: TyCtxt<'tcx>,
104+
ty_mapping: FxHashMap<Ty<'tcx>, Ty<'tcx>>,
105+
region_mapping: FxHashMap<ty::Region<'tcx>, ty::Region<'tcx>>,
106+
const_mapping: FxHashMap<ty::Const<'tcx>, ty::Const<'tcx>>,
107+
bound_num: usize,
108+
current_index: ty::DebruijnIndex,
109+
}
110+
impl<'tcx> PredicateArgFolder<'tcx> {
111+
fn new(tcx: TyCtxt<'tcx>, projection: Binder<'tcx, ty::ProjectionPredicate<'tcx>>) -> Self {
112+
let bound_num = projection.bound_vars().len();
113+
let projection = projection.skip_binder();
114+
let mut ty_mapping = FxHashMap::default();
115+
let mut region_mapping = FxHashMap::default();
116+
let mut const_mapping = FxHashMap::default();
117+
let assoc_ty = Ty::new_alias(
118+
tcx,
119+
ty::AliasTyKind::Projection,
120+
ty::AliasTy::new(
121+
tcx,
122+
projection.projection_term.def_id,
123+
GenericArgs::identity_for_item(tcx, projection.projection_term.def_id),
124+
),
125+
);
126+
ty_mapping.insert(assoc_ty, projection.term.expect_type());
127+
let target_assoc_args =
128+
GenericArgs::identity_for_item(tcx, projection.projection_term.def_id);
129+
for (target_arg, proj_arg) in
130+
target_assoc_args.into_iter().zip(projection.projection_term.args)
131+
{
132+
match (target_arg.kind(), proj_arg.kind()) {
133+
(GenericArgKind::Lifetime(r1), GenericArgKind::Lifetime(r2)) => {
134+
region_mapping.insert(r1, r2);
135+
}
136+
(GenericArgKind::Type(t1), GenericArgKind::Type(t2)) => {
137+
ty_mapping.insert(t1, t2);
138+
}
139+
(GenericArgKind::Const(c1), GenericArgKind::Const(c2)) => {
140+
const_mapping.insert(c1, c2);
141+
}
142+
_ => bug!("mismatched generic arg kinds in projection predicate"),
143+
}
144+
}
145+
debug!(
146+
"elaborate_projection_predicates: ty_mapping = {:?}, region_mapping = {:?}, const_mapping = {:?}",
147+
ty_mapping, region_mapping, const_mapping
148+
);
149+
Self {
150+
tcx,
151+
ty_mapping,
152+
region_mapping,
153+
const_mapping,
154+
bound_num,
155+
current_index: ty::INNERMOST,
156+
}
157+
}
158+
}
159+
impl<'tcx> ty::TypeFolder<TyCtxt<'tcx>> for PredicateArgFolder<'tcx> {
160+
fn cx(&self) -> TyCtxt<'tcx> {
161+
self.tcx
162+
}
163+
164+
fn fold_ty(&mut self, t: Ty<'tcx>) -> Ty<'tcx> {
165+
if let ty::Bound(ty::BoundVarIndexKind::Bound(debruijn), bound_ty) = t.kind()
166+
&& *debruijn == self.current_index
167+
{
168+
let shifted_bound_var =
169+
ty::BoundVar::from_usize(bound_ty.var.index() + self.bound_num);
170+
return Ty::new_bound(
171+
self.tcx,
172+
*debruijn,
173+
ty::BoundTy { var: shifted_bound_var, kind: bound_ty.kind },
174+
);
175+
}
176+
if let Some(replacement) = self.ty_mapping.get(&t) {
177+
*replacement
178+
} else {
179+
t.super_fold_with(self)
180+
}
181+
}
182+
183+
fn fold_region(&mut self, r: ty::Region<'tcx>) -> ty::Region<'tcx> {
184+
if let ty::RegionKind::ReBound(ty::BoundVarIndexKind::Bound(debruijn), bound_re) =
185+
r.kind()
186+
&& debruijn == self.current_index
187+
{
188+
let shifted_bound_var =
189+
ty::BoundVar::from_usize(bound_re.var.index() + self.bound_num);
190+
return ty::Region::new_bound(
191+
self.tcx,
192+
debruijn,
193+
ty::BoundRegion { var: shifted_bound_var, kind: bound_re.kind },
194+
);
195+
}
196+
if let Some(replacement) = self.region_mapping.get(&r) { *replacement } else { r }
197+
}
198+
199+
fn fold_const(&mut self, c: ty::Const<'tcx>) -> ty::Const<'tcx> {
200+
if let ty::ConstKind::Bound(ty::BoundVarIndexKind::Bound(debruijn), bound_ct) = c.kind()
201+
&& debruijn == self.current_index
202+
{
203+
let shifted_bound_var =
204+
ty::BoundVar::from_usize(bound_ct.var.index() + self.bound_num);
205+
return ty::Const::new_bound(
206+
self.tcx,
207+
debruijn,
208+
ty::BoundConst::new(shifted_bound_var),
209+
);
210+
}
211+
if let Some(replacement) = self.const_mapping.get(&c) { *replacement } else { c }
212+
}
213+
214+
fn fold_binder<T: TypeFoldable<TyCtxt<'tcx>>>(
215+
&mut self,
216+
t: Binder<'tcx, T>,
217+
) -> Binder<'tcx, T> {
218+
self.current_index.shift_in(1);
219+
let folded = t.super_fold_with(self);
220+
self.current_index.shift_out(1);
221+
folded
222+
}
223+
}
224+
225+
let mut new_preds = Vec::new();
226+
for (pred, _span) in &predicates {
227+
if let ty::ClauseKind::Projection(proj_pred) = pred.kind().skip_binder()
228+
&& let Some(proj_ty) = proj_pred.term.as_type()
229+
{
230+
// We should minimize this to allow the where clause check to be useful.
231+
fn should_add_clause(t: Ty<'_>) -> bool {
232+
match t.kind() {
233+
ty::Param(_) => true,
234+
ty::Alias(ty::Projection, alias) => alias.args.types().any(should_add_clause),
235+
_ => false,
236+
}
237+
}
238+
if !should_add_clause(proj_ty) {
239+
continue;
240+
}
241+
debug!("elaborate_projection_predicates: projection predicate = {:?}", pred);
242+
let assoc_bounds = tcx.explicit_item_bounds(proj_pred.projection_term.def_id);
243+
debug!("elaborate_projection_predicates: original assoc_bounds = {:?}", assoc_bounds);
244+
let mut folder = PredicateArgFolder::new(tcx, pred.kind().rebind(proj_pred));
245+
// FIXME: optimize allocation later.
246+
let assoc_bounds: Vec<_> = assoc_bounds
247+
.skip_binder()
248+
.iter()
249+
.map(|(c, span)| {
250+
let concated_bound_vars: Vec<_> =
251+
pred.kind().bound_vars().iter().chain(c.kind().bound_vars()).collect();
252+
debug!(
253+
"elaborate_projection_predicates: concated_bound_vars = {:?}",
254+
concated_bound_vars
255+
);
256+
(
257+
Binder::bind_with_vars(
258+
c.kind().skip_binder().fold_with(&mut folder),
259+
tcx.mk_bound_variable_kinds(&concated_bound_vars),
260+
),
261+
*span,
262+
)
263+
})
264+
.filter(|(c, _)| {
265+
if let ty::ClauseKind::Projection(p) = c.skip_binder() {
266+
if p.projection_term.to_term(tcx) == p.term {
267+
// No need to add identity projection.
268+
// They cause cycles later.
269+
return false;
270+
}
271+
// We shouldn't add opposite projection of existing ones.
272+
// They will be normalized to identity projection by each other.
273+
// We also filter out projections which have the same lhs with existing
274+
// projections.
275+
// They cause ambiguity in normalization.
276+
if predicates.iter().any(|(existing_c, _)| {
277+
if let ty::ClauseKind::Projection(existing_p) =
278+
existing_c.kind().skip_binder()
279+
{
280+
return p.projection_term == existing_p.projection_term
281+
|| (existing_p.projection_term.to_term(tcx) == p.term
282+
&& existing_p.term == p.projection_term.to_term(tcx));
283+
}
284+
false
285+
}) {
286+
return false;
287+
}
288+
}
289+
true
290+
})
291+
.collect();
292+
debug!("elaborate_projection_predicates: replaced assoc_bounds = {:?}", assoc_bounds);
293+
let assoc_bounds: Vec<_> =
294+
assoc_bounds.into_iter().map(|(c, s)| (Clause::upcast_from(c, tcx), s)).collect();
295+
debug!("elaborate_projection_predicates: upcasted assoc_bounds = {:?}", assoc_bounds);
296+
new_preds.extend(assoc_bounds);
297+
}
298+
}
299+
new_preds
300+
}
79301

80302
/// Returns a list of user-specified type predicates for the definition with ID `def_id`.
81303
/// N.B., this does not include any implied/inferred constraints.

compiler/rustc_session/src/options.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2699,6 +2699,8 @@ written to standard error output)"),
26992699
"prefer dynamic linking to static linking for staticlibs (default: no)"),
27002700
strict_init_checks: bool = (false, parse_bool, [TRACKED],
27012701
"control if mem::uninitialized and mem::zeroed panic on more UB"),
2702+
strict_projection_item_bounds: bool = (false, parse_bool, [TRACKED],
2703+
"check item bounds on projection rhs for wellformedness"),
27022704
#[rustc_lint_opt_deny_field_access("use `Session::teach` instead of this field")]
27032705
teach: bool = (false, parse_bool, [TRACKED],
27042706
"show extended diagnostic help (default: no)"),

compiler/rustc_trait_selection/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ rustc_data_structures = { path = "../rustc_data_structures" }
1212
rustc_errors = { path = "../rustc_errors" }
1313
rustc_hir = { path = "../rustc_hir" }
1414
rustc_infer = { path = "../rustc_infer" }
15+
rustc_lint_defs = { path = "../rustc_lint_defs" }
1516
rustc_macros = { path = "../rustc_macros" }
1617
rustc_middle = { path = "../rustc_middle" }
1718
rustc_next_trait_solver = { path = "../rustc_next_trait_solver" }

compiler/rustc_trait_selection/src/traits/wf.rs

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rustc_hir as hir;
99
use rustc_hir::def::DefKind;
1010
use rustc_hir::lang_items::LangItem;
1111
use rustc_infer::traits::{ObligationCauseCode, PredicateObligations};
12+
use rustc_lint_defs;
1213
use rustc_middle::bug;
1314
use rustc_middle::ty::{
1415
self, GenericArgsRef, Term, TermKind, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable,
@@ -183,8 +184,7 @@ pub fn clause_obligations<'tcx>(
183184
wf.add_wf_preds_for_term(ty.into());
184185
}
185186
ty::ClauseKind::Projection(t) => {
186-
wf.add_wf_preds_for_alias_term(t.projection_term);
187-
wf.add_wf_preds_for_term(t.term);
187+
wf.add_wf_preds_for_projection_pred(t);
188188
}
189189
ty::ClauseKind::ConstArgHasType(ct, ty) => {
190190
wf.add_wf_preds_for_term(ct.into());
@@ -455,6 +455,60 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
455455
}
456456
}
457457

458+
fn add_wf_preds_for_projection_pred(&mut self, projection_pred: ty::ProjectionPredicate<'tcx>) {
459+
let tcx = self.tcx();
460+
let cause = self.cause(ObligationCauseCode::WellFormed(None));
461+
debug!("add_wf_preds_for_projection_pred {:?}", projection_pred);
462+
463+
// FIXME: share this function with `elaborate_projection_predicates`.
464+
fn is_total_generic(t: Ty<'_>) -> bool {
465+
match t.kind() {
466+
ty::Param(_) => true,
467+
ty::Alias(ty::Projection, alias) => alias.args.types().all(is_total_generic),
468+
_ => false,
469+
}
470+
}
471+
// Check lint cap level to avoid triggering this flag on deps.
472+
if tcx.sess.opts.lint_cap != Some(rustc_lint_defs::Level::Allow)
473+
&& tcx.sess.opts.unstable_opts.strict_projection_item_bounds
474+
{
475+
// Add item bounds to the predicate term.
476+
// If the term is generic, we can skip these item bounds as they're implied by
477+
// `elaborate_projection_predicates`.
478+
if let Some(normalizes_to_ty) = projection_pred.term.as_type()
479+
&& !is_total_generic(normalizes_to_ty)
480+
{
481+
let bounds = tcx.item_bounds(projection_pred.def_id());
482+
debug!("add_wf_preds_for_projection_pred item_bounds={:?}", bounds);
483+
let bound_obligations: Vec<_> = bounds
484+
.instantiate(tcx, projection_pred.projection_term.args)
485+
.iter()
486+
.filter_map(|bound| {
487+
if !bound.has_escaping_bound_vars() {
488+
Some(traits::Obligation::with_depth(
489+
tcx,
490+
cause.clone(),
491+
self.recursion_depth,
492+
self.param_env,
493+
bound,
494+
))
495+
} else {
496+
None
497+
}
498+
})
499+
.collect();
500+
debug!(
501+
"add_wf_preds_for_projection_pred bound_obligations={:?}",
502+
bound_obligations
503+
);
504+
self.out.extend(bound_obligations);
505+
}
506+
}
507+
508+
self.add_wf_preds_for_alias_term(projection_pred.projection_term);
509+
self.add_wf_preds_for_term(projection_pred.term);
510+
}
511+
458512
/// Pushes the obligations required for an alias (except inherent) to be WF
459513
/// into `self.out`.
460514
fn add_wf_preds_for_alias_term(&mut self, data: ty::AliasTerm<'tcx>) {
@@ -480,6 +534,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
480534
// `i32: Copy`
481535
// ]
482536
let obligations = self.nominal_obligations(data.def_id, data.args);
537+
debug!("add_wf_preds_for_alias_term nominal_obligations={:?}", obligations);
483538
self.out.extend(obligations);
484539

485540
self.add_wf_preds_for_projection_args(data.args);
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//@ compile-flags: -Zstrict-projection-item-bounds
2+
3+
// Projection predicate's WFedness requires that the rhs term satisfy all item bounds defined on the
4+
// associated type.
5+
// Generic types have those bounds implied.
6+
7+
trait Required {}
8+
9+
trait AssocHasBound {
10+
type Assoc: Required;
11+
}
12+
13+
trait Trait<T> {
14+
type Assoc1: AssocHasBound<Assoc = i32>;
15+
//~^ ERROR: the trait bound `i32: Required` is not satisfied [E0277]
16+
type Assoc2: AssocHasBound<Assoc = T>;
17+
type Assoc3: AssocHasBound<Assoc = Self::DummyAssoc>;
18+
type DummyAssoc;
19+
}
20+
21+
fn some_func<T1, T2, U>()
22+
where
23+
T1: AssocHasBound<Assoc = i32>,
24+
//~^ ERROR: type annotations needed [E0284]
25+
T1: AssocHasBound<Assoc = U>,
26+
{}
27+
28+
fn opaque_with_concrete_assoc(_: impl AssocHasBound<Assoc = i32>) {}
29+
//~^ ERROR: the trait bound `i32: Required` is not satisfied [E0277]
30+
31+
fn opaque_with_generic_assoc<T>(_: impl AssocHasBound<Assoc = T>) {}
32+
33+
fn main() {}

0 commit comments

Comments
 (0)