Suggest turning APITs into generics in opaque overcaptures

This commit is contained in:
Michael Goulet 2024-11-09 19:16:43 +00:00
parent b73478b6ee
commit ad20906065
7 changed files with 202 additions and 41 deletions

View File

@ -346,7 +346,6 @@ lint_impl_trait_overcaptures = `{$self_ty}` will capture more lifetimes than pos
*[other] these lifetimes are *[other] these lifetimes are
} in scope but not mentioned in the type's bounds } in scope but not mentioned in the type's bounds
.note2 = all lifetimes in scope will be captured by `impl Trait`s in edition 2024 .note2 = all lifetimes in scope will be captured by `impl Trait`s in edition 2024
.suggestion = use the precise capturing `use<...>` syntax to make the captures explicit
lint_impl_trait_redundant_captures = all possible in-scope parameters are already captured, so `use<...>` syntax is redundant lint_impl_trait_redundant_captures = all possible in-scope parameters are already captured, so `use<...>` syntax is redundant
.suggestion = remove the `use<...>` syntax .suggestion = remove the `use<...>` syntax

View File

@ -3,7 +3,7 @@
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
use rustc_data_structures::unord::UnordSet; use rustc_data_structures::unord::UnordSet;
use rustc_errors::{Applicability, LintDiagnostic}; use rustc_errors::{LintDiagnostic, Subdiagnostic};
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def::DefKind; use rustc_hir::def::DefKind;
use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::def_id::{DefId, LocalDefId};
@ -22,6 +22,7 @@
use rustc_session::{declare_lint, declare_lint_pass}; use rustc_session::{declare_lint, declare_lint_pass};
use rustc_span::edition::Edition; use rustc_span::edition::Edition;
use rustc_span::{Span, Symbol}; use rustc_span::{Span, Symbol};
use rustc_trait_selection::errors::{impl_trait_overcapture_suggestion, AddPreciseCapturingForOvercapture};
use rustc_trait_selection::traits::ObligationCtxt; use rustc_trait_selection::traits::ObligationCtxt;
use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt; use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt;
@ -334,32 +335,12 @@ fn visit_ty(&mut self, t: Ty<'tcx>) {
// If we have uncaptured args, and if the opaque doesn't already have // If we have uncaptured args, and if the opaque doesn't already have
// `use<>` syntax on it, and we're < edition 2024, then warn the user. // `use<>` syntax on it, and we're < edition 2024, then warn the user.
if !uncaptured_args.is_empty() { if !uncaptured_args.is_empty() {
let suggestion = if let Ok(snippet) = let suggestion = impl_trait_overcapture_suggestion(
self.tcx.sess.source_map().span_to_snippet(opaque_span) self.tcx,
&& snippet.starts_with("impl ") opaque_def_id,
{ self.parent_def_id,
let (lifetimes, others): (Vec<_>, Vec<_>) = captured,
captured.into_iter().partition(|def_id| { );
self.tcx.def_kind(*def_id) == DefKind::LifetimeParam
});
// Take all lifetime params first, then all others (ty/ct).
let generics: Vec<_> = lifetimes
.into_iter()
.chain(others)
.map(|def_id| self.tcx.item_name(def_id).to_string())
.collect();
// Make sure that we're not trying to name any APITs
if generics.iter().all(|name| !name.starts_with("impl ")) {
Some((
format!(" + use<{}>", generics.join(", ")),
opaque_span.shrink_to_hi(),
))
} else {
None
}
} else {
None
};
let uncaptured_spans: Vec<_> = uncaptured_args let uncaptured_spans: Vec<_> = uncaptured_args
.into_iter() .into_iter()
@ -451,7 +432,7 @@ struct ImplTraitOvercapturesLint<'tcx> {
uncaptured_spans: Vec<Span>, uncaptured_spans: Vec<Span>,
self_ty: Ty<'tcx>, self_ty: Ty<'tcx>,
num_captured: usize, num_captured: usize,
suggestion: Option<(String, Span)>, suggestion: Option<AddPreciseCapturingForOvercapture>,
} }
impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> { impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> {
@ -461,13 +442,8 @@ fn decorate_lint<'b>(self, diag: &'b mut rustc_errors::Diag<'a, ()>) {
.arg("num_captured", self.num_captured) .arg("num_captured", self.num_captured)
.span_note(self.uncaptured_spans, fluent::lint_note) .span_note(self.uncaptured_spans, fluent::lint_note)
.note(fluent::lint_note2); .note(fluent::lint_note2);
if let Some((suggestion, span)) = self.suggestion { if let Some(suggestion) = self.suggestion {
diag.span_suggestion( suggestion.add_to_diag(diag);
span,
fluent::lint_suggestion,
suggestion,
Applicability::MachineApplicable,
);
} }
} }
} }

View File

@ -280,6 +280,8 @@ trait_selection_outlives_content = lifetime of reference outlives lifetime of bo
trait_selection_precise_capturing_existing = add `{$new_lifetime}` to the `use<...>` bound to explicitly capture it trait_selection_precise_capturing_existing = add `{$new_lifetime}` to the `use<...>` bound to explicitly capture it
trait_selection_precise_capturing_new = add a `use<...>` bound to explicitly capture `{$new_lifetime}` trait_selection_precise_capturing_new = add a `use<...>` bound to explicitly capture `{$new_lifetime}`
trait_selection_precise_capturing_overcaptures = use the precise capturing `use<...>` syntax to make the captures explicit
trait_selection_precise_capturing_new_but_apit = add a `use<...>` bound to explicitly capture `{$new_lifetime}` after turning all argument-position `impl Trait` into type parameters, noting that this possibly affects the API of this crate trait_selection_precise_capturing_new_but_apit = add a `use<...>` bound to explicitly capture `{$new_lifetime}` after turning all argument-position `impl Trait` into type parameters, noting that this possibly affects the API of this crate
trait_selection_prlf_defined_with_sub = the lifetime `{$sub_symbol}` defined here... trait_selection_prlf_defined_with_sub = the lifetime `{$sub_symbol}` defined here...
@ -455,7 +457,9 @@ trait_selection_unable_to_construct_constant_value = unable to construct a const
trait_selection_unknown_format_parameter_for_on_unimplemented_attr = there is no parameter `{$argument_name}` on trait `{$trait_name}` trait_selection_unknown_format_parameter_for_on_unimplemented_attr = there is no parameter `{$argument_name}` on trait `{$trait_name}`
.help = expect either a generic argument name or {"`{Self}`"} as format argument .help = expect either a generic argument name or {"`{Self}`"} as format argument
trait_selection_warn_removing_apit_params = you could use a `use<...>` bound to explicitly capture `{$new_lifetime}`, but argument-position `impl Trait`s are not nameable trait_selection_warn_removing_apit_params_for_undercapture = you could use a `use<...>` bound to explicitly capture `{$new_lifetime}`, but argument-position `impl Trait`s are not nameable
trait_selection_warn_removing_apit_params_for_overcapture = you could use a `use<...>` bound to explicitly specify captures, but argument-position `impl Trait`s are not nameable
trait_selection_where_copy_predicates = copy the `where` clause predicates from the trait trait_selection_where_copy_predicates = copy the `where` clause predicates from the trait

View File

@ -1,13 +1,14 @@
use std::path::PathBuf; use std::path::PathBuf;
use rustc_data_structures::fx::FxHashSet; use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_errors::codes::*; use rustc_errors::codes::*;
use rustc_errors::{ use rustc_errors::{
Applicability, Diag, DiagCtxtHandle, DiagMessage, DiagStyledString, Diagnostic, Applicability, Diag, DiagCtxtHandle, DiagMessage, DiagStyledString, Diagnostic,
EmissionGuarantee, IntoDiagArg, Level, MultiSpan, SubdiagMessageOp, Subdiagnostic, EmissionGuarantee, IntoDiagArg, Level, MultiSpan, SubdiagMessageOp, Subdiagnostic,
}; };
use rustc_hir::def::DefKind;
use rustc_hir as hir; use rustc_hir as hir;
use rustc_hir::def_id::LocalDefId; use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::intravisit::{Visitor, walk_ty}; use rustc_hir::intravisit::{Visitor, walk_ty};
use rustc_hir::{FnRetTy, GenericParamKind}; use rustc_hir::{FnRetTy, GenericParamKind};
use rustc_macros::{Diagnostic, Subdiagnostic}; use rustc_macros::{Diagnostic, Subdiagnostic};
@ -1792,6 +1793,123 @@ fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
self.suggs, self.suggs,
Applicability::MaybeIncorrect, Applicability::MaybeIncorrect,
); );
diag.span_note(self.apit_spans, fluent::trait_selection_warn_removing_apit_params); diag.span_note(self.apit_spans, fluent::trait_selection_warn_removing_apit_params_for_undercapture);
} }
} }
pub fn impl_trait_overcapture_suggestion<'tcx>(
tcx: TyCtxt<'tcx>,
opaque_def_id: LocalDefId,
fn_def_id: LocalDefId,
captured_args: FxIndexSet<DefId>,
) -> Option<AddPreciseCapturingForOvercapture> {
let generics = tcx.generics_of(fn_def_id);
let mut captured_lifetimes = FxIndexSet::default();
let mut captured_non_lifetimes = FxIndexSet::default();
let mut synthetics = vec![];
for arg in captured_args {
if tcx.def_kind(arg) == DefKind::LifetimeParam {
captured_lifetimes.insert(tcx.item_name(arg));
} else {
let idx = generics.param_def_id_to_index(tcx, arg).expect("expected arg in scope");
let param = generics.param_at(idx as usize, tcx);
if param.kind.is_synthetic() {
synthetics.push((tcx.def_span(arg), param.name));
} else {
captured_non_lifetimes.insert(tcx.item_name(arg));
}
}
}
let mut next_fresh_param = || {
["T", "U", "V", "W", "X", "Y", "A", "B", "C"]
.into_iter()
.map(Symbol::intern)
.chain((0..).map(|i| Symbol::intern(&format!("T{i}"))))
.find(|s| captured_non_lifetimes.insert(*s))
.unwrap()
};
let mut suggs = vec![];
let mut apit_spans = vec![];
if !synthetics.is_empty() {
let mut new_params = String::new();
for (i, (span, name)) in synthetics.into_iter().enumerate() {
apit_spans.push(span);
let fresh_param = next_fresh_param();
// Suggest renaming.
suggs.push((span, fresh_param.to_string()));
// Super jank. Turn `impl Trait` into `T: Trait`.
//
// This currently involves stripping the `impl` from the name of
// the parameter, since APITs are always named after how they are
// rendered in the AST. This sucks! But to recreate the bound list
// from the APIT itself would be miserable, so we're stuck with
// this for now!
if i > 0 {
new_params += ", ";
}
let name_as_bounds = name.as_str().trim_start_matches("impl").trim_start();
new_params += fresh_param.as_str();
new_params += ": ";
new_params += name_as_bounds;
}
let Some(generics) = tcx.hir().get_generics(fn_def_id) else {
// This shouldn't happen, but don't ICE.
return None;
};
// Add generics or concatenate to the end of the list.
suggs.push(if let Some(params_span) = generics.span_for_param_suggestion() {
(params_span, format!(", {new_params}"))
} else {
(generics.span, format!("<{new_params}>"))
});
}
let concatenated_bounds = captured_lifetimes
.into_iter()
.chain(captured_non_lifetimes)
.map(|sym| sym.to_string())
.collect::<Vec<_>>()
.join(", ");
suggs.push((
tcx.def_span(opaque_def_id).shrink_to_hi(),
format!(" + use<{concatenated_bounds}>"),
));
Some(AddPreciseCapturingForOvercapture {
suggs,
apit_spans,
})
}
pub struct AddPreciseCapturingForOvercapture {
pub suggs: Vec<(Span, String)>,
pub apit_spans: Vec<Span>,
}
impl Subdiagnostic for AddPreciseCapturingForOvercapture {
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
self,
diag: &mut Diag<'_, G>,
_f: &F,
) {
diag.multipart_suggestion_verbose(
fluent::trait_selection_precise_capturing_overcaptures,
self.suggs,
Applicability::MaybeIncorrect,
);
if !self.apit_spans.is_empty() {
diag.span_note(self.apit_spans, fluent::trait_selection_warn_removing_apit_params_for_overcapture);
}
}
}

View File

@ -29,4 +29,12 @@ fn hrtb() -> impl for<'a> Higher<'a, Output = impl Sized + use<>> {}
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024 //~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
//~| WARN this changes meaning in Rust 2024 //~| WARN this changes meaning in Rust 2024
fn apit<T: Sized>(_: &T) -> impl Sized + use<T> {}
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
//~| WARN this changes meaning in Rust 2024
fn apit2<U, T: Sized>(_: &T, _: U) -> impl Sized + use<U, T> {}
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
//~| WARN this changes meaning in Rust 2024
fn main() {} fn main() {}

View File

@ -29,4 +29,12 @@ fn hrtb() -> impl for<'a> Higher<'a, Output = impl Sized> {}
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024 //~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
//~| WARN this changes meaning in Rust 2024 //~| WARN this changes meaning in Rust 2024
fn apit(_: &impl Sized) -> impl Sized {}
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
//~| WARN this changes meaning in Rust 2024
fn apit2<U>(_: &impl Sized, _: U) -> impl Sized {}
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
//~| WARN this changes meaning in Rust 2024
fn main() {} fn main() {}

View File

@ -79,5 +79,53 @@ help: use the precise capturing `use<...>` syntax to make the captures explicit
LL | fn hrtb() -> impl for<'a> Higher<'a, Output = impl Sized + use<>> {} LL | fn hrtb() -> impl for<'a> Higher<'a, Output = impl Sized + use<>> {}
| +++++++ | +++++++
error: aborting due to 4 previous errors error: `impl Sized` will capture more lifetimes than possibly intended in edition 2024
--> $DIR/overcaptures-2024.rs:32:28
|
LL | fn apit(_: &impl Sized) -> impl Sized {}
| ^^^^^^^^^^
|
= warning: this changes meaning in Rust 2024
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/rpit-lifetime-capture.html>
note: specifically, this lifetime is in scope but not mentioned in the type's bounds
--> $DIR/overcaptures-2024.rs:32:12
|
LL | fn apit(_: &impl Sized) -> impl Sized {}
| ^
= note: all lifetimes in scope will be captured by `impl Trait`s in edition 2024
note: you could use a `use<...>` bound to explicitly specify captures, but argument-position `impl Trait`s are not nameable
--> $DIR/overcaptures-2024.rs:32:13
|
LL | fn apit(_: &impl Sized) -> impl Sized {}
| ^^^^^^^^^^
help: use the precise capturing `use<...>` syntax to make the captures explicit
|
LL | fn apit<T: Sized>(_: &T) -> impl Sized + use<T> {}
| ++++++++++ ~ ++++++++
error: `impl Sized` will capture more lifetimes than possibly intended in edition 2024
--> $DIR/overcaptures-2024.rs:36:38
|
LL | fn apit2<U>(_: &impl Sized, _: U) -> impl Sized {}
| ^^^^^^^^^^
|
= warning: this changes meaning in Rust 2024
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/rpit-lifetime-capture.html>
note: specifically, this lifetime is in scope but not mentioned in the type's bounds
--> $DIR/overcaptures-2024.rs:36:16
|
LL | fn apit2<U>(_: &impl Sized, _: U) -> impl Sized {}
| ^
= note: all lifetimes in scope will be captured by `impl Trait`s in edition 2024
note: you could use a `use<...>` bound to explicitly specify captures, but argument-position `impl Trait`s are not nameable
--> $DIR/overcaptures-2024.rs:36:17
|
LL | fn apit2<U>(_: &impl Sized, _: U) -> impl Sized {}
| ^^^^^^^^^^
help: use the precise capturing `use<...>` syntax to make the captures explicit
|
LL | fn apit2<U, T: Sized>(_: &T, _: U) -> impl Sized + use<U, T> {}
| ++++++++++ ~ +++++++++++
error: aborting due to 6 previous errors