Skip to content

Commit ff873fb

Browse files
committed
check item bounds for projection predicate if the rhs is not totally generic
1 parent 3645249 commit ff873fb

7 files changed

Lines changed: 448 additions & 4 deletions

File tree

compiler/rustc_hir_analysis/src/collect/predicates_of.rs

Lines changed: 220 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
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;
99
use rustc_middle::ty::{
10-
self, GenericPredicates, ImplTraitInTraitData, Ty, TyCtxt, TypeVisitable, TypeVisitor, Upcast,
10+
self, Binder, Clause, GenericArgKind, GenericArgs, GenericPredicates, ImplTraitInTraitData, Ty,
11+
TyCtxt, TypeFoldable, TypeSuperFoldable, TypeVisitable, TypeVisitor, Upcast, UpcastFrom,
1112
};
1213
use rustc_middle::{bug, span_bug};
1314
use rustc_span::{DUMMY_SP, Ident, Span};
@@ -30,6 +31,13 @@ pub(super) fn predicates_of(tcx: TyCtxt<'_>, def_id: DefId) -> ty::GenericPredic
3031
let mut result = tcx.explicit_predicates_of(def_id);
3132
debug!("predicates_of: explicit_predicates_of({:?}) = {:?}", def_id, result);
3233

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

80298
/// Returns a list of user-specified type predicates for the definition with ID `def_id`.
81299
/// 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
@@ -2669,6 +2669,8 @@ written to standard error output)"),
26692669
"prefer dynamic linking to static linking for staticlibs (default: no)"),
26702670
strict_init_checks: bool = (false, parse_bool, [TRACKED],
26712671
"control if mem::uninitialized and mem::zeroed panic on more UB"),
2672+
strict_projection_item_bounds: bool = (false, parse_bool, [TRACKED],
2673+
"check item bounds on projection rhs for wellformedness"),
26722674
#[rustc_lint_opt_deny_field_access("use `Session::teach` instead of this field")]
26732675
teach: bool = (false, parse_bool, [TRACKED],
26742676
"show extended diagnostic help (default: no)"),

compiler/rustc_trait_selection/src/traits/wf.rs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,7 @@ pub fn clause_obligations<'tcx>(
183183
wf.add_wf_preds_for_term(ty.into());
184184
}
185185
ty::ClauseKind::Projection(t) => {
186-
wf.add_wf_preds_for_alias_term(t.projection_term);
187-
wf.add_wf_preds_for_term(t.term);
186+
wf.add_wf_preds_for_projection_pred(t);
188187
}
189188
ty::ClauseKind::ConstArgHasType(ct, ty) => {
190189
wf.add_wf_preds_for_term(ct.into());
@@ -455,6 +454,57 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
455454
}
456455
}
457456

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

485536
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)