Skip to content

Commit 4d629f5

Browse files
committed
lint implied bounds from trait impl
1 parent 6525e6f commit 4d629f5

File tree

15 files changed

+392
-67
lines changed

15 files changed

+392
-67
lines changed

compiler/rustc_hir_analysis/src/check/wfcheck.rs

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -110,8 +110,6 @@ where
110110

111111
let assumed_wf_types = wfcx.ocx.assumed_wf_types_and_report_errors(param_env, body_def_id)?;
112112

113-
let implied_bounds = infcx.implied_bounds_tys_compat(param_env, body_def_id, assumed_wf_types);
114-
115113
let errors = wfcx.select_all_or_error();
116114
if !errors.is_empty() {
117115
let err = infcx.err_ctxt().report_fulfillment_errors(errors);
@@ -126,9 +124,46 @@ where
126124
}
127125
}
128126

127+
let infcx_compat = infcx.fork();
128+
129+
let implied_bounds = infcx.implied_bounds_tys(param_env, &assumed_wf_types);
130+
let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds);
131+
132+
let errors = infcx.resolve_regions(&outlives_env);
133+
if errors.is_empty() {
134+
return Ok(());
135+
}
136+
137+
let implied_bounds =
138+
infcx_compat.implied_bounds_tys_compat(param_env, body_def_id, assumed_wf_types);
129139
let outlives_env = OutlivesEnvironment::with_bounds(param_env, implied_bounds);
140+
let errors_compat = infcx_compat.resolve_regions(&outlives_env);
141+
if !errors_compat.is_empty() {
142+
return Err(infcx_compat.err_ctxt().report_region_errors(body_def_id, &errors_compat));
143+
}
144+
145+
let hir_id = tcx.local_def_id_to_hir_id(body_def_id);
146+
let (lint_level, _) = tcx
147+
.lint_level_at_node(rustc_session::lint::builtin::IMPLIED_BOUNDS_FROM_TRAIT_IMPL, hir_id);
148+
tcx.struct_span_lint_hir(
149+
rustc_session::lint::builtin::IMPLIED_BOUNDS_FROM_TRAIT_IMPL,
150+
hir_id,
151+
tcx.def_span(body_def_id),
152+
format!("{} is missing necessary lifetime bounds", tcx.def_descr(body_def_id.into())),
153+
|lint| {
154+
if !lint_level.is_error() {
155+
lint.note(
156+
"to get more detailed errors, use `#[deny(implied_bounds_from_trait_impl)]`",
157+
)
158+
} else {
159+
lint.note("more concrete lifetime errors are emitted below")
160+
}
161+
},
162+
);
163+
if lint_level.is_error() {
164+
infcx.err_ctxt().report_region_errors(body_def_id, &errors);
165+
}
130166

131-
wfcx.ocx.resolve_regions_and_report_errors(body_def_id, &outlives_env)?;
132167
infcx.tainted_by_errors().error_reported()
133168
}
134169

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3388,6 +3388,7 @@ declare_lint_pass! {
33883388
ILL_FORMED_ATTRIBUTE_INPUT,
33893389
ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
33903390
IMPLIED_BOUNDS_ENTAILMENT,
3391+
IMPLIED_BOUNDS_FROM_TRAIT_IMPL,
33913392
INCOMPLETE_INCLUDE,
33923393
INDIRECT_STRUCTURAL_MATCH,
33933394
INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
@@ -4620,3 +4621,44 @@ declare_lint! {
46204621
reference: "issue #115010 <https://github.com/rust-lang/rust/issues/115010>",
46214622
};
46224623
}
4624+
4625+
declare_lint! {
4626+
/// The `implied_bounds_from_trait_impl` lint detects
4627+
/// a compiler bug allowed some code to assume invalid implied lifetime bounds.
4628+
///
4629+
/// ### Example
4630+
///
4631+
/// ```rust,compile_fail
4632+
/// #![deny(implied_bounds_from_trait_impl)]
4633+
///
4634+
/// trait Trait {
4635+
/// type Assoc;
4636+
/// }
4637+
///
4638+
/// impl<X: 'static> Trait for (X,) {
4639+
/// type Assoc = ();
4640+
/// }
4641+
///
4642+
/// struct Foo<T: Trait>(T)
4643+
/// where
4644+
/// T::Assoc: Clone; // any bound using `T::Assoc`
4645+
///
4646+
/// fn func(foo: Foo<(&str,)>) {
4647+
/// let _: &'static str = foo.0.0;
4648+
/// }
4649+
/// ```
4650+
///
4651+
/// {{produces}}
4652+
///
4653+
/// ### Explanation
4654+
///
4655+
/// FIXME: explanataion
4656+
///
4657+
pub IMPLIED_BOUNDS_FROM_TRAIT_IMPL,
4658+
Warn,
4659+
"item uses illegal implied bounds derived from a trait impl",
4660+
@future_incompatible = FutureIncompatibleInfo {
4661+
reason: FutureIncompatibilityReason::FutureReleaseErrorDontReportInDeps,
4662+
reference: "issue #109628 <https://github.com/rust-lang/rust/issues/109628>",
4663+
};
4664+
}

compiler/rustc_middle/src/query/erase.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ impl<T> EraseType for Result<&'_ T, traits::query::NoSolution> {
7474
type Result = [u8; size_of::<Result<&'static (), traits::query::NoSolution>>()];
7575
}
7676

77+
impl<T> EraseType for Result<&'_ [T], traits::query::NoSolution> {
78+
type Result = [u8; size_of::<Result<&'static [()], traits::query::NoSolution>>()];
79+
}
80+
7781
impl<T> EraseType for Result<&'_ T, rustc_errors::ErrorGuaranteed> {
7882
type Result = [u8; size_of::<Result<&'static (), rustc_errors::ErrorGuaranteed>>()];
7983
}

compiler/rustc_middle/src/query/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1962,6 +1962,15 @@ rustc_queries! {
19621962
desc { "computing implied outlives bounds for `{}`", goal.value.value }
19631963
}
19641964

1965+
query implied_outlives_bounds(
1966+
goal: ty::ParamEnvAnd<'tcx, Ty<'tcx>>
1967+
) -> Result<
1968+
&'tcx [OutlivesBound<'tcx>],
1969+
NoSolution,
1970+
> {
1971+
desc { "computing implied outlives bounds v2 for `{}`", goal.value }
1972+
}
1973+
19651974
/// Do not call this query directly:
19661975
/// invoke `DropckOutlives::new(dropped_ty)).fully_perform(typeck.infcx)` instead.
19671976
query dropck_outlives(

compiler/rustc_middle/src/traits/query.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ pub struct NormalizationResult<'tcx> {
191191
/// case they are called implied bounds). They are fed to the
192192
/// `OutlivesEnv` which in turn is supplied to the region checker and
193193
/// other parts of the inference system.
194-
#[derive(Clone, Debug, TypeFoldable, TypeVisitable, HashStable)]
194+
#[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable, HashStable)]
195195
pub enum OutlivesBound<'tcx> {
196196
RegionSubRegion(ty::Region<'tcx>, ty::Region<'tcx>),
197197
RegionSubParam(ty::Region<'tcx>, ty::ParamTy),

compiler/rustc_trait_selection/src/traits/outlives_bounds.rs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rustc_span::def_id::LocalDefId;
99

1010
pub use rustc_middle::traits::query::OutlivesBound;
1111

12+
pub type BoundsCompat<'a, 'tcx: 'a> = impl Iterator<Item = OutlivesBound<'tcx>> + 'a;
1213
pub type Bounds<'a, 'tcx: 'a> = impl Iterator<Item = OutlivesBound<'tcx>> + 'a;
1314
pub trait InferCtxtExt<'a, 'tcx> {
1415
fn implied_outlives_bounds_compat(
@@ -23,6 +24,12 @@ pub trait InferCtxtExt<'a, 'tcx> {
2324
param_env: ty::ParamEnv<'tcx>,
2425
body_id: LocalDefId,
2526
tys: FxIndexSet<Ty<'tcx>>,
27+
) -> BoundsCompat<'a, 'tcx>;
28+
29+
fn implied_bounds_tys(
30+
&'a self,
31+
param_env: ty::ParamEnv<'tcx>,
32+
tys: &'a FxIndexSet<Ty<'tcx>>,
2633
) -> Bounds<'a, 'tcx>;
2734
}
2835

@@ -126,8 +133,27 @@ impl<'a, 'tcx: 'a> InferCtxtExt<'a, 'tcx> for InferCtxt<'tcx> {
126133
param_env: ParamEnv<'tcx>,
127134
body_id: LocalDefId,
128135
tys: FxIndexSet<Ty<'tcx>>,
129-
) -> Bounds<'a, 'tcx> {
136+
) -> BoundsCompat<'a, 'tcx> {
130137
tys.into_iter()
131138
.flat_map(move |ty| self.implied_outlives_bounds_compat(param_env, body_id, ty))
132139
}
140+
141+
fn implied_bounds_tys(
142+
&'a self,
143+
param_env: ParamEnv<'tcx>,
144+
tys: &'a FxIndexSet<Ty<'tcx>>,
145+
) -> Bounds<'a, 'tcx> {
146+
tys.iter()
147+
.flat_map(move |&ty| {
148+
let ty = self.resolve_vars_if_possible(ty);
149+
let ty = OpportunisticRegionResolver::new(self).fold_ty(ty);
150+
151+
if ty.has_infer() {
152+
return &[] as &[OutlivesBound<'_>];
153+
}
154+
155+
self.tcx.implied_outlives_bounds(param_env.and(ty)).unwrap_or(&[])
156+
})
157+
.copied()
158+
}
133159
}

compiler/rustc_trait_selection/src/traits/query/type_op/implied_outlives_bounds.rs

Lines changed: 146 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ use crate::traits::ObligationCtxt;
55

66
use rustc_infer::infer::canonical::Canonical;
77
use rustc_infer::infer::outlives::components::{push_outlives_components, Component};
8+
use rustc_infer::infer::resolve::OpportunisticRegionResolver;
89
use rustc_infer::traits::query::OutlivesBound;
910
use rustc_middle::infer::canonical::CanonicalQueryResponse;
1011
use rustc_middle::traits::ObligationCause;
11-
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeVisitableExt};
12+
use rustc_middle::ty::{self, ParamEnvAnd, Ty, TyCtxt, TypeFolder, TypeVisitableExt};
13+
use rustc_span::def_id::CRATE_DEF_ID;
14+
use rustc_span::DUMMY_SP;
1215
use smallvec::{smallvec, SmallVec};
1316

1417
#[derive(Copy, Clone, Debug, HashStable, TypeFoldable, TypeVisitable)]
@@ -52,7 +55,7 @@ impl<'tcx> super::QueryTypeOp<'tcx> for ImpliedOutlivesBounds<'tcx> {
5255
ocx: &ObligationCtxt<'_, 'tcx>,
5356
key: ParamEnvAnd<'tcx, Self>,
5457
) -> Result<Self::QueryResponse, NoSolution> {
55-
compute_implied_outlives_bounds_inner(ocx, key.param_env, key.value.ty)
58+
compute_implied_outlives_bounds_compat_inner(ocx, key.param_env, key.value.ty)
5659
}
5760
}
5861

@@ -63,20 +66,15 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
6366
ocx: &ObligationCtxt<'_, 'tcx>,
6467
param_env: ty::ParamEnv<'tcx>,
6568
ty: Ty<'tcx>,
66-
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
67-
let normalize_op = |ty: Ty<'tcx>| {
68-
let ty = if ocx.infcx.next_trait_solver() {
69-
solve::deeply_normalize(ocx.infcx.at(&ObligationCause::dummy(), param_env), ty)
70-
// FIXME(-Ztrait-solver=next)
71-
.unwrap_or_else(|_errs| ty)
72-
} else {
73-
ocx.normalize(&ObligationCause::dummy(), param_env, ty)
74-
};
69+
) -> Result<&'tcx [OutlivesBound<'tcx>], NoSolution> {
70+
let normalize_op = |ty| {
71+
let ty = ocx.normalize(&ObligationCause::dummy(), param_env, ty);
7572
if !ocx.select_all_or_error().is_empty() {
7673
return Err(NoSolution);
7774
}
7875
let ty = ocx.infcx.resolve_vars_if_possible(ty);
79-
assert!(!ty.has_non_region_infer());
76+
let ty = OpportunisticRegionResolver::new(&ocx.infcx).fold_ty(ty);
77+
assert!(!ty.has_infer());
8078
Ok(ty)
8179
};
8280

@@ -141,14 +139,148 @@ pub fn compute_implied_outlives_bounds_inner<'tcx>(
141139
}
142140
}
143141

144-
Ok(outlives_bounds)
142+
Ok(ocx.infcx.tcx.arena.alloc_slice(&outlives_bounds))
143+
}
144+
145+
pub fn compute_implied_outlives_bounds_compat_inner<'tcx>(
146+
ocx: &ObligationCtxt<'_, 'tcx>,
147+
param_env: ty::ParamEnv<'tcx>,
148+
ty: Ty<'tcx>,
149+
) -> Result<Vec<OutlivesBound<'tcx>>, NoSolution> {
150+
let tcx = ocx.infcx.tcx;
151+
152+
// Sometimes when we ask what it takes for T: WF, we get back that
153+
// U: WF is required; in that case, we push U onto this stack and
154+
// process it next. Because the resulting predicates aren't always
155+
// guaranteed to be a subset of the original type, so we need to store the
156+
// WF args we've computed in a set.
157+
let mut checked_wf_args = rustc_data_structures::fx::FxHashSet::default();
158+
let mut wf_args = vec![ty.into()];
159+
160+
let mut outlives_bounds: Vec<ty::OutlivesPredicate<ty::GenericArg<'tcx>, ty::Region<'tcx>>> =
161+
vec![];
162+
163+
while let Some(arg) = wf_args.pop() {
164+
if !checked_wf_args.insert(arg) {
165+
continue;
166+
}
167+
168+
// Compute the obligations for `arg` to be well-formed. If `arg` is
169+
// an unresolved inference variable, just substituted an empty set
170+
// -- because the return type here is going to be things we *add*
171+
// to the environment, it's always ok for this set to be smaller
172+
// than the ultimate set. (Note: normally there won't be
173+
// unresolved inference variables here anyway, but there might be
174+
// during typeck under some circumstances.)
175+
//
176+
// FIXME(@lcnr): It's not really "always fine", having fewer implied
177+
// bounds can be backward incompatible, e.g. #101951 was caused by
178+
// us not dealing with inference vars in `TypeOutlives` predicates.
179+
let obligations = wf::obligations(ocx.infcx, param_env, CRATE_DEF_ID, 0, arg, DUMMY_SP)
180+
.unwrap_or_default();
181+
182+
for obligation in obligations {
183+
debug!(?obligation);
184+
assert!(!obligation.has_escaping_bound_vars());
185+
186+
// While these predicates should all be implied by other parts of
187+
// the program, they are still relevant as they may constrain
188+
// inference variables, which is necessary to add the correct
189+
// implied bounds in some cases, mostly when dealing with projections.
190+
//
191+
// Another important point here: we only register `Projection`
192+
// predicates, since otherwise we might register outlives
193+
// predicates containing inference variables, and we don't
194+
// learn anything new from those.
195+
if obligation.predicate.has_non_region_infer() {
196+
match obligation.predicate.kind().skip_binder() {
197+
ty::PredicateKind::Clause(ty::ClauseKind::Projection(..))
198+
| ty::PredicateKind::AliasRelate(..) => {
199+
ocx.register_obligation(obligation.clone());
200+
}
201+
_ => {}
202+
}
203+
}
204+
205+
let pred = match obligation.predicate.kind().no_bound_vars() {
206+
None => continue,
207+
Some(pred) => pred,
208+
};
209+
match pred {
210+
ty::PredicateKind::Clause(ty::ClauseKind::Trait(..))
211+
// FIXME(const_generics): Make sure that `<'a, 'b, const N: &'a &'b u32>` is sound
212+
// if we ever support that
213+
| ty::PredicateKind::Clause(ty::ClauseKind::ConstArgHasType(..))
214+
| ty::PredicateKind::Subtype(..)
215+
| ty::PredicateKind::Coerce(..)
216+
| ty::PredicateKind::Clause(ty::ClauseKind::Projection(..))
217+
| ty::PredicateKind::ObjectSafe(..)
218+
| ty::PredicateKind::Clause(ty::ClauseKind::ConstEvaluatable(..))
219+
| ty::PredicateKind::ConstEquate(..)
220+
| ty::PredicateKind::Ambiguous
221+
| ty::PredicateKind::AliasRelate(..)
222+
=> {}
223+
224+
// We need to search through *all* WellFormed predicates
225+
ty::PredicateKind::Clause(ty::ClauseKind::WellFormed(arg)) => {
226+
wf_args.push(arg);
227+
}
228+
229+
// We need to register region relationships
230+
ty::PredicateKind::Clause(ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(
231+
r_a,
232+
r_b,
233+
))) => outlives_bounds.push(ty::OutlivesPredicate(r_a.into(), r_b)),
234+
235+
ty::PredicateKind::Clause(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(
236+
ty_a,
237+
r_b,
238+
))) => outlives_bounds.push(ty::OutlivesPredicate(ty_a.into(), r_b)),
239+
}
240+
}
241+
}
242+
243+
// This call to `select_all_or_error` is necessary to constrain inference variables, which we
244+
// use further down when computing the implied bounds.
245+
match ocx.select_all_or_error().as_slice() {
246+
[] => (),
247+
_ => return Err(NoSolution),
248+
}
249+
250+
// We lazily compute the outlives components as
251+
// `select_all_or_error` constrains inference variables.
252+
let mut implied_bounds = Vec::new();
253+
for ty::OutlivesPredicate(a, r_b) in outlives_bounds {
254+
match a.unpack() {
255+
ty::GenericArgKind::Lifetime(r_a) => {
256+
implied_bounds.push(OutlivesBound::RegionSubRegion(r_b, r_a))
257+
}
258+
ty::GenericArgKind::Type(ty_a) => {
259+
let mut ty_a = ocx.infcx.resolve_vars_if_possible(ty_a);
260+
// Need to manually normalize in the new solver as `wf::obligations` does not.
261+
if ocx.infcx.next_trait_solver() {
262+
ty_a = solve::deeply_normalize(
263+
ocx.infcx.at(&ObligationCause::dummy(), param_env),
264+
ty_a,
265+
)
266+
.map_err(|_errs| NoSolution)?;
267+
}
268+
let mut components = smallvec![];
269+
push_outlives_components(tcx, ty_a, &mut components);
270+
implied_bounds.extend(implied_bounds_from_components(r_b, components))
271+
}
272+
ty::GenericArgKind::Const(_) => unreachable!(),
273+
}
274+
}
275+
276+
Ok(implied_bounds)
145277
}
146278

147279
/// When we have an implied bound that `T: 'a`, we can further break
148280
/// this down to determine what relationships would have to hold for
149281
/// `T: 'a` to hold. We get to assume that the caller has validated
150282
/// those relationships.
151-
fn implied_bounds_from_components<'tcx>(
283+
pub fn implied_bounds_from_components<'tcx>(
152284
sub_region: ty::Region<'tcx>,
153285
sup_components: SmallVec<[Component<'tcx>; 4]>,
154286
) -> Vec<OutlivesBound<'tcx>> {

0 commit comments

Comments
 (0)