Skip to content

Commit 96154d7

Browse files
Add IMPLIED_BOUNDS_ENTAILMENT lint
1 parent 4653c93 commit 96154d7

File tree

8 files changed

+203
-7
lines changed

8 files changed

+203
-7
lines changed

compiler/rustc_hir_analysis/src/check/compare_method.rs

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -255,15 +255,15 @@ fn compare_predicate_entailment<'tcx>(
255255

256256
let mut wf_tys = FxIndexSet::default();
257257

258-
let impl_sig = infcx.replace_bound_vars_with_fresh_vars(
258+
let unnormalized_impl_sig = infcx.replace_bound_vars_with_fresh_vars(
259259
impl_m_span,
260260
infer::HigherRankedType,
261261
tcx.fn_sig(impl_m.def_id),
262262
);
263+
let unnormalized_impl_fty = tcx.mk_fn_ptr(ty::Binder::dummy(unnormalized_impl_sig));
263264

264265
let norm_cause = ObligationCause::misc(impl_m_span, impl_m_hir_id);
265-
let impl_sig = ocx.normalize(&norm_cause, param_env, impl_sig);
266-
let impl_fty = tcx.mk_fn_ptr(ty::Binder::dummy(impl_sig));
266+
let impl_fty = ocx.normalize(&norm_cause, param_env, unnormalized_impl_fty);
267267
debug!("compare_impl_method: impl_fty={:?}", impl_fty);
268268

269269
let trait_sig = tcx.bound_fn_sig(trait_m.def_id).subst(tcx, trait_to_placeholder_substs);
@@ -312,21 +312,86 @@ fn compare_predicate_entailment<'tcx>(
312312
return Err(reported);
313313
}
314314

315+
// FIXME(compiler-errors): This can be removed when IMPLIED_BOUNDS_ENTAILMENT
316+
// becomes a hard error.
317+
let lint_infcx = infcx.fork();
318+
315319
// Finally, resolve all regions. This catches wily misuses of
316320
// lifetime parameters.
317321
let outlives_environment = OutlivesEnvironment::with_bounds(
318322
param_env,
319323
Some(infcx),
320-
infcx.implied_bounds_tys(param_env, impl_m_hir_id, wf_tys),
324+
infcx.implied_bounds_tys(param_env, impl_m_hir_id, wf_tys.clone()),
321325
);
322-
infcx.check_region_obligations_and_report_errors(
326+
if let Some(guar) = infcx.check_region_obligations_and_report_errors(
323327
impl_m.def_id.expect_local(),
324328
&outlives_environment,
329+
) {
330+
return Err(guar);
331+
}
332+
333+
// FIXME(compiler-errors): This can be simplified when IMPLIED_BOUNDS_ENTAILMENT
334+
// becomes a hard error (i.e. ideally we'd just register a WF obligation above...)
335+
lint_implied_wf_entailment(
336+
impl_m.def_id.expect_local(),
337+
lint_infcx,
338+
param_env,
339+
unnormalized_impl_fty,
340+
wf_tys,
325341
);
326342

327343
Ok(())
328344
}
329345

346+
fn lint_implied_wf_entailment<'tcx>(
347+
impl_m_def_id: LocalDefId,
348+
infcx: InferCtxt<'tcx>,
349+
param_env: ty::ParamEnv<'tcx>,
350+
unnormalized_impl_fty: Ty<'tcx>,
351+
wf_tys: FxIndexSet<Ty<'tcx>>,
352+
) {
353+
let ocx = ObligationCtxt::new(&infcx);
354+
355+
// We need to check that the impl's args are well-formed given
356+
// the hybrid param-env (impl + trait method where-clauses).
357+
ocx.register_obligation(traits::Obligation::new(
358+
infcx.tcx,
359+
ObligationCause::dummy(),
360+
param_env,
361+
ty::Binder::dummy(ty::PredicateKind::WellFormed(unnormalized_impl_fty.into())),
362+
));
363+
364+
let hir_id = infcx.tcx.hir().local_def_id_to_hir_id(impl_m_def_id);
365+
let lint = || {
366+
infcx.tcx.struct_span_lint_hir(
367+
rustc_session::lint::builtin::IMPLIED_BOUNDS_ENTAILMENT,
368+
hir_id,
369+
infcx.tcx.def_span(impl_m_def_id),
370+
"impl method assumes more implied bounds than the corresponding trait method",
371+
|lint| lint,
372+
);
373+
};
374+
375+
let errors = ocx.select_all_or_error();
376+
if !errors.is_empty() {
377+
lint();
378+
}
379+
380+
let outlives_environment = OutlivesEnvironment::with_bounds(
381+
param_env,
382+
Some(&infcx),
383+
infcx.implied_bounds_tys(param_env, hir_id, wf_tys.clone()),
384+
);
385+
infcx.process_registered_region_obligations(
386+
outlives_environment.region_bound_pairs(),
387+
param_env,
388+
);
389+
390+
if !infcx.resolve_regions(&outlives_environment).is_empty() {
391+
lint();
392+
}
393+
}
394+
330395
fn compare_asyncness<'tcx>(
331396
tcx: TyCtxt<'tcx>,
332397
impl_m: &ty::AssocItem,

compiler/rustc_infer/src/infer/mod.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1693,7 +1693,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
16931693
&self,
16941694
generic_param_scope: LocalDefId,
16951695
outlives_env: &OutlivesEnvironment<'tcx>,
1696-
) {
1696+
) -> Option<ErrorGuaranteed> {
16971697
let errors = self.resolve_regions(outlives_env);
16981698

16991699
if let None = self.tainted_by_errors() {
@@ -1704,6 +1704,10 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
17041704
// errors from silly ones.
17051705
self.report_region_errors(generic_param_scope, &errors);
17061706
}
1707+
1708+
(!errors.is_empty()).then(|| {
1709+
self.tcx.sess.delay_span_bug(rustc_span::DUMMY_SP, "error should have been emitted")
1710+
})
17071711
}
17081712

17091713
// [Note-Type-error-reporting]

compiler/rustc_infer/src/infer/outlives/obligations.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ use crate::infer::{
6868
};
6969
use crate::traits::{ObligationCause, ObligationCauseCode};
7070
use rustc_data_structures::undo_log::UndoLogs;
71+
use rustc_errors::ErrorGuaranteed;
7172
use rustc_hir::def_id::DefId;
7273
use rustc_hir::def_id::LocalDefId;
7374
use rustc_middle::mir::ConstraintCategory;
@@ -177,7 +178,7 @@ impl<'tcx> InferCtxt<'tcx> {
177178
&self,
178179
generic_param_scope: LocalDefId,
179180
outlives_env: &OutlivesEnvironment<'tcx>,
180-
) {
181+
) -> Option<ErrorGuaranteed> {
181182
self.process_registered_region_obligations(
182183
outlives_env.region_bound_pairs(),
183184
outlives_env.param_env,

compiler/rustc_lint_defs/src/builtin.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3998,3 +3998,44 @@ declare_lint! {
39983998
Warn,
39993999
"named arguments in format used positionally"
40004000
}
4001+
4002+
declare_lint! {
4003+
/// The `implied_bounds_entailment` lint detects cases where the arguments of an impl method
4004+
/// have stronger implied bounds than those from the trait method it's implementing.
4005+
///
4006+
/// ### Example
4007+
///
4008+
/// ```rust,compile_fail
4009+
/// #![deny(implied_bounds_entailment)]
4010+
///
4011+
/// trait Trait {
4012+
/// fn get<'s>(s: &'s str, _: &'static &'static ()) -> &'static str;
4013+
/// }
4014+
///
4015+
/// impl Trait for () {
4016+
/// fn get<'s>(s: &'s str, _: &'static &'s ()) -> &'static str {
4017+
/// s
4018+
/// }
4019+
/// }
4020+
///
4021+
/// let val = <() as Trait>::get(&String::from("blah blah blah"), &&());
4022+
/// println!("{}", val);
4023+
/// ```
4024+
///
4025+
/// {{produces}}
4026+
///
4027+
/// ### Explanation
4028+
///
4029+
/// Neither the trait method, which provides no implied bounds about `'s`, nor the impl,
4030+
/// which can't name `'s`, requires the main function to prove that 's: 'static, but the
4031+
/// impl method is able to assume that 's: 'static within its own body.
4032+
///
4033+
/// This can be used to implement an unsound API if used incorrectly.
4034+
pub IMPLIED_BOUNDS_ENTAILMENT,
4035+
Deny,
4036+
"impl method assumes more implied bounds than its corresponding trait method",
4037+
@future_incompatible = FutureIncompatibleInfo {
4038+
reference: "issue #105572 <https://github.com/rust-lang/rust/issues/105572>",
4039+
reason: FutureIncompatibilityReason::FutureReleaseErrorReportNow,
4040+
};
4041+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
trait Project {
2+
type Ty;
3+
}
4+
impl Project for &'_ &'_ () {
5+
type Ty = ();
6+
}
7+
trait Trait {
8+
fn get<'s>(s: &'s str, _: ()) -> &'static str;
9+
}
10+
impl Trait for () {
11+
fn get<'s>(s: &'s str, _: <&'static &'s () as Project>::Ty) -> &'static str {
12+
//~^ ERROR impl method assumes more implied bounds than the corresponding trait method
13+
//~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
14+
s
15+
}
16+
}
17+
fn main() {
18+
let val = <() as Trait>::get(&String::from("blah blah blah"), ());
19+
println!("{}", val);
20+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: impl method assumes more implied bounds than the corresponding trait method
2+
--> $DIR/impl-implied-bounds-compatibility-unnormalized.rs:11:5
3+
|
4+
LL | fn get<'s>(s: &'s str, _: <&'static &'s () as Project>::Ty) -> &'static str {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #105572 <https://github.com/rust-lang/rust/issues/105572>
9+
= note: `#[deny(implied_bounds_entailment)]` on by default
10+
11+
error: aborting due to previous error
12+
13+
Future incompatibility report: Future breakage diagnostic:
14+
error: impl method assumes more implied bounds than the corresponding trait method
15+
--> $DIR/impl-implied-bounds-compatibility-unnormalized.rs:11:5
16+
|
17+
LL | fn get<'s>(s: &'s str, _: <&'static &'s () as Project>::Ty) -> &'static str {
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
19+
|
20+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
21+
= note: for more information, see issue #105572 <https://github.com/rust-lang/rust/issues/105572>
22+
= note: `#[deny(implied_bounds_entailment)]` on by default
23+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use std::cell::RefCell;
2+
3+
pub struct MessageListeners<'a> {
4+
listeners: RefCell<Vec<Box<dyn FnMut(()) + 'a>>>,
5+
}
6+
7+
pub trait MessageListenersInterface {
8+
fn listeners<'c>(&'c self) -> &'c MessageListeners<'c>;
9+
}
10+
11+
impl<'a> MessageListenersInterface for MessageListeners<'a> {
12+
fn listeners<'b>(&'b self) -> &'a MessageListeners<'b> {
13+
//~^ ERROR impl method assumes more implied bounds than the corresponding trait method
14+
//~| WARN this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
15+
self
16+
}
17+
}
18+
19+
fn main() {}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
error: impl method assumes more implied bounds than the corresponding trait method
2+
--> $DIR/impl-implied-bounds-compatibility.rs:12:5
3+
|
4+
LL | fn listeners<'b>(&'b self) -> &'a MessageListeners<'b> {
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
8+
= note: for more information, see issue #105572 <https://github.com/rust-lang/rust/issues/105572>
9+
= note: `#[deny(implied_bounds_entailment)]` on by default
10+
11+
error: aborting due to previous error
12+
13+
Future incompatibility report: Future breakage diagnostic:
14+
error: impl method assumes more implied bounds than the corresponding trait method
15+
--> $DIR/impl-implied-bounds-compatibility.rs:12:5
16+
|
17+
LL | fn listeners<'b>(&'b self) -> &'a MessageListeners<'b> {
18+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
19+
|
20+
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
21+
= note: for more information, see issue #105572 <https://github.com/rust-lang/rust/issues/105572>
22+
= note: `#[deny(implied_bounds_entailment)]` on by default
23+

0 commit comments

Comments
 (0)