Skip to content

Commit 7f42e58

Browse files
committed
Auto merge of #105575 - compiler-errors:impl-wf-lint, r=oli-obk
Add `IMPLIED_BOUNDS_ENTAILMENT` lint Implements a lint (#105572) version of the hard-error introduced in #105483. Context is in that PR. r? `@lcnr` cc `@oli-obk` who had asked for this to be a lint first Not sure if this needs to be an FCP, since it's a lint for now.
2 parents c43bc13 + 8c86773 commit 7f42e58

File tree

9 files changed

+223
-15
lines changed

9 files changed

+223
-15
lines changed

compiler/rustc_hir_analysis/src/check/compare_method.rs

+98-12
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,14 @@ pub(crate) fn compare_impl_method<'tcx>(
7171
return;
7272
}
7373

74-
if let Err(_) = compare_predicate_entailment(tcx, impl_m, impl_m_span, trait_m, impl_trait_ref)
75-
{
74+
if let Err(_) = compare_predicate_entailment(
75+
tcx,
76+
impl_m,
77+
impl_m_span,
78+
trait_m,
79+
impl_trait_ref,
80+
CheckImpliedWfMode::Check,
81+
) {
7682
return;
7783
}
7884
}
@@ -150,6 +156,7 @@ fn compare_predicate_entailment<'tcx>(
150156
impl_m_span: Span,
151157
trait_m: &ty::AssocItem,
152158
impl_trait_ref: ty::TraitRef<'tcx>,
159+
check_implied_wf: CheckImpliedWfMode,
153160
) -> Result<(), ErrorGuaranteed> {
154161
let trait_to_impl_substs = impl_trait_ref.substs;
155162

@@ -255,15 +262,15 @@ fn compare_predicate_entailment<'tcx>(
255262

256263
let mut wf_tys = FxIndexSet::default();
257264

258-
let impl_sig = infcx.replace_bound_vars_with_fresh_vars(
265+
let unnormalized_impl_sig = infcx.replace_bound_vars_with_fresh_vars(
259266
impl_m_span,
260267
infer::HigherRankedType,
261268
tcx.fn_sig(impl_m.def_id),
262269
);
270+
let unnormalized_impl_fty = tcx.mk_fn_ptr(ty::Binder::dummy(unnormalized_impl_sig));
263271

264272
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));
273+
let impl_fty = ocx.normalize(&norm_cause, param_env, unnormalized_impl_fty);
267274
debug!("compare_impl_method: impl_fty={:?}", impl_fty);
268275

269276
let trait_sig = tcx.bound_fn_sig(trait_m.def_id).subst(tcx, trait_to_placeholder_substs);
@@ -304,29 +311,108 @@ fn compare_predicate_entailment<'tcx>(
304311
return Err(emitted);
305312
}
306313

314+
if check_implied_wf == CheckImpliedWfMode::Check {
315+
// We need to check that the impl's args are well-formed given
316+
// the hybrid param-env (impl + trait method where-clauses).
317+
ocx.register_obligation(traits::Obligation::new(
318+
infcx.tcx,
319+
ObligationCause::dummy(),
320+
param_env,
321+
ty::Binder::dummy(ty::PredicateKind::WellFormed(unnormalized_impl_fty.into())),
322+
));
323+
}
324+
let emit_implied_wf_lint = || {
325+
infcx.tcx.struct_span_lint_hir(
326+
rustc_session::lint::builtin::IMPLIED_BOUNDS_ENTAILMENT,
327+
impl_m_hir_id,
328+
infcx.tcx.def_span(impl_m.def_id),
329+
"impl method assumes more implied bounds than the corresponding trait method",
330+
|lint| lint,
331+
);
332+
};
333+
307334
// Check that all obligations are satisfied by the implementation's
308335
// version.
309336
let errors = ocx.select_all_or_error();
310337
if !errors.is_empty() {
311-
let reported = infcx.err_ctxt().report_fulfillment_errors(&errors, None);
312-
return Err(reported);
338+
match check_implied_wf {
339+
CheckImpliedWfMode::Check => {
340+
return compare_predicate_entailment(
341+
tcx,
342+
impl_m,
343+
impl_m_span,
344+
trait_m,
345+
impl_trait_ref,
346+
CheckImpliedWfMode::Skip,
347+
)
348+
.map(|()| {
349+
// If the skip-mode was successful, emit a lint.
350+
emit_implied_wf_lint();
351+
});
352+
}
353+
CheckImpliedWfMode::Skip => {
354+
let reported = infcx.err_ctxt().report_fulfillment_errors(&errors, None);
355+
return Err(reported);
356+
}
357+
}
313358
}
314359

315360
// Finally, resolve all regions. This catches wily misuses of
316361
// lifetime parameters.
317-
let outlives_environment = OutlivesEnvironment::with_bounds(
362+
let outlives_env = OutlivesEnvironment::with_bounds(
318363
param_env,
319364
Some(infcx),
320-
infcx.implied_bounds_tys(param_env, impl_m_hir_id, wf_tys),
365+
infcx.implied_bounds_tys(param_env, impl_m_hir_id, wf_tys.clone()),
321366
);
322-
infcx.check_region_obligations_and_report_errors(
323-
impl_m.def_id.expect_local(),
324-
&outlives_environment,
367+
infcx.process_registered_region_obligations(
368+
outlives_env.region_bound_pairs(),
369+
outlives_env.param_env,
325370
);
371+
let errors = infcx.resolve_regions(&outlives_env);
372+
if !errors.is_empty() {
373+
// FIXME(compiler-errors): This can be simplified when IMPLIED_BOUNDS_ENTAILMENT
374+
// becomes a hard error (i.e. ideally we'd just call `resolve_regions_and_report_errors`
375+
match check_implied_wf {
376+
CheckImpliedWfMode::Check => {
377+
return compare_predicate_entailment(
378+
tcx,
379+
impl_m,
380+
impl_m_span,
381+
trait_m,
382+
impl_trait_ref,
383+
CheckImpliedWfMode::Skip,
384+
)
385+
.map(|()| {
386+
// If the skip-mode was successful, emit a lint.
387+
emit_implied_wf_lint();
388+
});
389+
}
390+
CheckImpliedWfMode::Skip => {
391+
if infcx.tainted_by_errors().is_none() {
392+
infcx.err_ctxt().report_region_errors(impl_m.def_id.expect_local(), &errors);
393+
}
394+
return Err(tcx
395+
.sess
396+
.delay_span_bug(rustc_span::DUMMY_SP, "error should have been emitted"));
397+
}
398+
}
399+
}
326400

327401
Ok(())
328402
}
329403

404+
#[derive(Debug, PartialEq, Eq)]
405+
enum CheckImpliedWfMode {
406+
/// Checks implied well-formedness of the impl method. If it fails, we will
407+
/// re-check with `Skip`, and emit a lint if it succeeds.
408+
Check,
409+
/// Skips checking implied well-formedness of the impl method, but will emit
410+
/// a lint if the `compare_predicate_entailment` succeeded. This means that
411+
/// the reason that we had failed earlier during `Check` was due to the impl
412+
/// having stronger requirements than the trait.
413+
Skip,
414+
}
415+
330416
fn compare_asyncness<'tcx>(
331417
tcx: TyCtxt<'tcx>,
332418
impl_m: &ty::AssocItem,

compiler/rustc_infer/src/infer/mod.rs

+5-1
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

+2-1
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

+42
Original file line numberDiff line numberDiff line change
@@ -3311,6 +3311,7 @@ declare_lint_pass! {
33113311
FFI_UNWIND_CALLS,
33123312
REPR_TRANSPARENT_EXTERNAL_PRIVATE_FIELDS,
33133313
NAMED_ARGUMENTS_USED_POSITIONALLY,
3314+
IMPLIED_BOUNDS_ENTAILMENT,
33143315
]
33153316
}
33163317

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

src/tools/clippy/tests/ui/borrow_interior_mutable_const/others.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ impl<T> StaticRef<T> {
4242
impl<T> std::ops::Deref for StaticRef<T> {
4343
type Target = T;
4444

45-
fn deref(&self) -> &'static T {
45+
fn deref(&self) -> &T {
4646
unsafe { &*self.ptr }
4747
}
4848
}

0 commit comments

Comments
 (0)