Skip to content

Commit 8bfda7a

Browse files
Add IMPLIED_BOUNDS_ENTAILMENT lint
1 parent 352eb59 commit 8bfda7a

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
@@ -251,15 +251,15 @@ fn compare_predicate_entailment<'tcx>(
251251

252252
let mut wf_tys = FxIndexSet::default();
253253

254-
let impl_sig = infcx.replace_bound_vars_with_fresh_vars(
254+
let unnormalized_impl_sig = infcx.replace_bound_vars_with_fresh_vars(
255255
impl_m_span,
256256
infer::HigherRankedType,
257257
tcx.fn_sig(impl_m.def_id),
258258
);
259+
let unnormalized_impl_fty = tcx.mk_fn_ptr(ty::Binder::dummy(unnormalized_impl_sig));
259260

260261
let norm_cause = ObligationCause::misc(impl_m_span, impl_m_hir_id);
261-
let impl_sig = ocx.normalize(&norm_cause, param_env, impl_sig);
262-
let impl_fty = tcx.mk_fn_ptr(ty::Binder::dummy(impl_sig));
262+
let impl_fty = ocx.normalize(&norm_cause, param_env, unnormalized_impl_fty);
263263
debug!("compare_impl_method: impl_fty={:?}", impl_fty);
264264

265265
let trait_sig = tcx.bound_fn_sig(trait_m.def_id).subst(tcx, trait_to_placeholder_substs);
@@ -308,21 +308,86 @@ fn compare_predicate_entailment<'tcx>(
308308
return Err(reported);
309309
}
310310

311+
// FIXME(compiler-errors): This can be removed when IMPLIED_BOUNDS_ENTAILMENT
312+
// becomes a hard error.
313+
let lint_infcx = infcx.fork();
314+
311315
// Finally, resolve all regions. This catches wily misuses of
312316
// lifetime parameters.
313317
let outlives_environment = OutlivesEnvironment::with_bounds(
314318
param_env,
315319
Some(infcx),
316-
infcx.implied_bounds_tys(param_env, impl_m_hir_id, wf_tys),
320+
infcx.implied_bounds_tys(param_env, impl_m_hir_id, wf_tys.clone()),
317321
);
318-
infcx.check_region_obligations_and_report_errors(
322+
if let Some(guar) = infcx.check_region_obligations_and_report_errors(
319323
impl_m.def_id.expect_local(),
320324
&outlives_environment,
325+
) {
326+
return Err(guar);
327+
}
328+
329+
// FIXME(compiler-errors): This can be simplified when IMPLIED_BOUNDS_ENTAILMENT
330+
// becomes a hard error (i.e. ideally we'd just register a WF obligation above...)
331+
lint_implied_wf_entailment(
332+
impl_m.def_id.expect_local(),
333+
lint_infcx,
334+
param_env,
335+
unnormalized_impl_fty,
336+
wf_tys,
321337
);
322338

323339
Ok(())
324340
}
325341

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

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
@@ -3957,3 +3957,44 @@ declare_lint! {
39573957
Warn,
39583958
"named arguments in format used positionally"
39593959
}
3960+
3961+
declare_lint! {
3962+
/// The `implied_bounds_entailment` lint detects cases where the arguments of an impl method
3963+
/// have stronger implied bounds than those from the trait method it's implementing.
3964+
///
3965+
/// ### Example
3966+
///
3967+
/// ```rust,compile_fail
3968+
/// #![deny(implied_bounds_entailment)]
3969+
///
3970+
/// trait Trait {
3971+
/// fn get<'s>(s: &'s str, _: &'static &'static ()) -> &'static str;
3972+
/// }
3973+
///
3974+
/// impl Trait for () {
3975+
/// fn get<'s>(s: &'s str, _: &'static &'s ()) -> &'static str {
3976+
/// s
3977+
/// }
3978+
/// }
3979+
///
3980+
/// let val = <() as Trait>::get(&String::from("blah blah blah"), &&());
3981+
/// println!("{}", val);
3982+
/// ```
3983+
///
3984+
/// {{produces}}
3985+
///
3986+
/// ### Explanation
3987+
///
3988+
/// Neither the trait method, which provides no implied bounds about `'s`, nor the impl,
3989+
/// which can't name `'s`, requires the main function to prove that 's: 'static, but the
3990+
/// impl method is able to assume that 's: 'static within its own body.
3991+
///
3992+
/// This can be used to implement an unsound API if used incorrectly.
3993+
pub IMPLIED_BOUNDS_ENTAILMENT,
3994+
Deny,
3995+
"impl method assumes more implied bounds than its corresponding trait method",
3996+
@future_incompatible = FutureIncompatibleInfo {
3997+
reference: "issue #105572 <https://github.com/rust-lang/rust/issues/105572>",
3998+
reason: FutureIncompatibilityReason::FutureReleaseErrorReportNow,
3999+
};
4000+
}
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)