Skip to content

Commit ad20906

Browse files
Suggest turning APITs into generics in opaque overcaptures
1 parent b73478b commit ad20906

File tree

7 files changed

+202
-41
lines changed

7 files changed

+202
-41
lines changed

Diff for: compiler/rustc_lint/messages.ftl

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

Diff for: compiler/rustc_lint/src/impl_trait_overcaptures.rs

+11-35
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::cell::LazyCell;
33

44
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
55
use rustc_data_structures::unord::UnordSet;
6-
use rustc_errors::{Applicability, LintDiagnostic};
6+
use rustc_errors::{LintDiagnostic, Subdiagnostic};
77
use rustc_hir as hir;
88
use rustc_hir::def::DefKind;
99
use rustc_hir::def_id::{DefId, LocalDefId};
@@ -22,6 +22,7 @@ use rustc_session::lint::FutureIncompatibilityReason;
2222
use rustc_session::{declare_lint, declare_lint_pass};
2323
use rustc_span::edition::Edition;
2424
use rustc_span::{Span, Symbol};
25+
use rustc_trait_selection::errors::{impl_trait_overcapture_suggestion, AddPreciseCapturingForOvercapture};
2526
use rustc_trait_selection::traits::ObligationCtxt;
2627
use rustc_trait_selection::traits::outlives_bounds::InferCtxtExt;
2728

@@ -334,32 +335,12 @@ where
334335
// If we have uncaptured args, and if the opaque doesn't already have
335336
// `use<>` syntax on it, and we're < edition 2024, then warn the user.
336337
if !uncaptured_args.is_empty() {
337-
let suggestion = if let Ok(snippet) =
338-
self.tcx.sess.source_map().span_to_snippet(opaque_span)
339-
&& snippet.starts_with("impl ")
340-
{
341-
let (lifetimes, others): (Vec<_>, Vec<_>) =
342-
captured.into_iter().partition(|def_id| {
343-
self.tcx.def_kind(*def_id) == DefKind::LifetimeParam
344-
});
345-
// Take all lifetime params first, then all others (ty/ct).
346-
let generics: Vec<_> = lifetimes
347-
.into_iter()
348-
.chain(others)
349-
.map(|def_id| self.tcx.item_name(def_id).to_string())
350-
.collect();
351-
// Make sure that we're not trying to name any APITs
352-
if generics.iter().all(|name| !name.starts_with("impl ")) {
353-
Some((
354-
format!(" + use<{}>", generics.join(", ")),
355-
opaque_span.shrink_to_hi(),
356-
))
357-
} else {
358-
None
359-
}
360-
} else {
361-
None
362-
};
338+
let suggestion = impl_trait_overcapture_suggestion(
339+
self.tcx,
340+
opaque_def_id,
341+
self.parent_def_id,
342+
captured,
343+
);
363344

364345
let uncaptured_spans: Vec<_> = uncaptured_args
365346
.into_iter()
@@ -451,7 +432,7 @@ struct ImplTraitOvercapturesLint<'tcx> {
451432
uncaptured_spans: Vec<Span>,
452433
self_ty: Ty<'tcx>,
453434
num_captured: usize,
454-
suggestion: Option<(String, Span)>,
435+
suggestion: Option<AddPreciseCapturingForOvercapture>,
455436
}
456437

457438
impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> {
@@ -461,13 +442,8 @@ impl<'a> LintDiagnostic<'a, ()> for ImplTraitOvercapturesLint<'_> {
461442
.arg("num_captured", self.num_captured)
462443
.span_note(self.uncaptured_spans, fluent::lint_note)
463444
.note(fluent::lint_note2);
464-
if let Some((suggestion, span)) = self.suggestion {
465-
diag.span_suggestion(
466-
span,
467-
fluent::lint_suggestion,
468-
suggestion,
469-
Applicability::MachineApplicable,
470-
);
445+
if let Some(suggestion) = self.suggestion {
446+
suggestion.add_to_diag(diag);
471447
}
472448
}
473449
}

Diff for: compiler/rustc_trait_selection/messages.ftl

+5-1
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,8 @@ trait_selection_outlives_content = lifetime of reference outlives lifetime of bo
280280
trait_selection_precise_capturing_existing = add `{$new_lifetime}` to the `use<...>` bound to explicitly capture it
281281
trait_selection_precise_capturing_new = add a `use<...>` bound to explicitly capture `{$new_lifetime}`
282282
283+
trait_selection_precise_capturing_overcaptures = use the precise capturing `use<...>` syntax to make the captures explicit
284+
283285
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
284286
285287
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
455457
trait_selection_unknown_format_parameter_for_on_unimplemented_attr = there is no parameter `{$argument_name}` on trait `{$trait_name}`
456458
.help = expect either a generic argument name or {"`{Self}`"} as format argument
457459
458-
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
460+
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
461+
462+
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
459463
460464
trait_selection_where_copy_predicates = copy the `where` clause predicates from the trait
461465

Diff for: compiler/rustc_trait_selection/src/errors.rs

+121-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use std::path::PathBuf;
22

3-
use rustc_data_structures::fx::FxHashSet;
3+
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
44
use rustc_errors::codes::*;
55
use rustc_errors::{
66
Applicability, Diag, DiagCtxtHandle, DiagMessage, DiagStyledString, Diagnostic,
77
EmissionGuarantee, IntoDiagArg, Level, MultiSpan, SubdiagMessageOp, Subdiagnostic,
88
};
9+
use rustc_hir::def::DefKind;
910
use rustc_hir as hir;
10-
use rustc_hir::def_id::LocalDefId;
11+
use rustc_hir::def_id::{DefId, LocalDefId};
1112
use rustc_hir::intravisit::{Visitor, walk_ty};
1213
use rustc_hir::{FnRetTy, GenericParamKind};
1314
use rustc_macros::{Diagnostic, Subdiagnostic};
@@ -1792,6 +1793,123 @@ impl Subdiagnostic for AddPreciseCapturingAndParams {
17921793
self.suggs,
17931794
Applicability::MaybeIncorrect,
17941795
);
1795-
diag.span_note(self.apit_spans, fluent::trait_selection_warn_removing_apit_params);
1796+
diag.span_note(self.apit_spans, fluent::trait_selection_warn_removing_apit_params_for_undercapture);
17961797
}
17971798
}
1799+
1800+
pub fn impl_trait_overcapture_suggestion<'tcx>(
1801+
tcx: TyCtxt<'tcx>,
1802+
opaque_def_id: LocalDefId,
1803+
fn_def_id: LocalDefId,
1804+
captured_args: FxIndexSet<DefId>,
1805+
) -> Option<AddPreciseCapturingForOvercapture> {
1806+
let generics = tcx.generics_of(fn_def_id);
1807+
1808+
let mut captured_lifetimes = FxIndexSet::default();
1809+
let mut captured_non_lifetimes = FxIndexSet::default();
1810+
let mut synthetics = vec![];
1811+
1812+
for arg in captured_args {
1813+
if tcx.def_kind(arg) == DefKind::LifetimeParam {
1814+
captured_lifetimes.insert(tcx.item_name(arg));
1815+
} else {
1816+
let idx = generics.param_def_id_to_index(tcx, arg).expect("expected arg in scope");
1817+
let param = generics.param_at(idx as usize, tcx);
1818+
if param.kind.is_synthetic() {
1819+
synthetics.push((tcx.def_span(arg), param.name));
1820+
} else {
1821+
captured_non_lifetimes.insert(tcx.item_name(arg));
1822+
}
1823+
}
1824+
}
1825+
1826+
let mut next_fresh_param = || {
1827+
["T", "U", "V", "W", "X", "Y", "A", "B", "C"]
1828+
.into_iter()
1829+
.map(Symbol::intern)
1830+
.chain((0..).map(|i| Symbol::intern(&format!("T{i}"))))
1831+
.find(|s| captured_non_lifetimes.insert(*s))
1832+
.unwrap()
1833+
};
1834+
1835+
let mut suggs = vec![];
1836+
let mut apit_spans = vec![];
1837+
1838+
if !synthetics.is_empty() {
1839+
let mut new_params = String::new();
1840+
for (i, (span, name)) in synthetics.into_iter().enumerate() {
1841+
apit_spans.push(span);
1842+
1843+
let fresh_param = next_fresh_param();
1844+
1845+
// Suggest renaming.
1846+
suggs.push((span, fresh_param.to_string()));
1847+
1848+
// Super jank. Turn `impl Trait` into `T: Trait`.
1849+
//
1850+
// This currently involves stripping the `impl` from the name of
1851+
// the parameter, since APITs are always named after how they are
1852+
// rendered in the AST. This sucks! But to recreate the bound list
1853+
// from the APIT itself would be miserable, so we're stuck with
1854+
// this for now!
1855+
if i > 0 {
1856+
new_params += ", ";
1857+
}
1858+
let name_as_bounds = name.as_str().trim_start_matches("impl").trim_start();
1859+
new_params += fresh_param.as_str();
1860+
new_params += ": ";
1861+
new_params += name_as_bounds;
1862+
}
1863+
1864+
let Some(generics) = tcx.hir().get_generics(fn_def_id) else {
1865+
// This shouldn't happen, but don't ICE.
1866+
return None;
1867+
};
1868+
1869+
// Add generics or concatenate to the end of the list.
1870+
suggs.push(if let Some(params_span) = generics.span_for_param_suggestion() {
1871+
(params_span, format!(", {new_params}"))
1872+
} else {
1873+
(generics.span, format!("<{new_params}>"))
1874+
});
1875+
}
1876+
1877+
let concatenated_bounds = captured_lifetimes
1878+
.into_iter()
1879+
.chain(captured_non_lifetimes)
1880+
.map(|sym| sym.to_string())
1881+
.collect::<Vec<_>>()
1882+
.join(", ");
1883+
1884+
suggs.push((
1885+
tcx.def_span(opaque_def_id).shrink_to_hi(),
1886+
format!(" + use<{concatenated_bounds}>"),
1887+
));
1888+
1889+
Some(AddPreciseCapturingForOvercapture {
1890+
suggs,
1891+
apit_spans,
1892+
})
1893+
}
1894+
1895+
pub struct AddPreciseCapturingForOvercapture {
1896+
pub suggs: Vec<(Span, String)>,
1897+
pub apit_spans: Vec<Span>,
1898+
}
1899+
1900+
impl Subdiagnostic for AddPreciseCapturingForOvercapture {
1901+
fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>(
1902+
self,
1903+
diag: &mut Diag<'_, G>,
1904+
_f: &F,
1905+
) {
1906+
diag.multipart_suggestion_verbose(
1907+
fluent::trait_selection_precise_capturing_overcaptures,
1908+
self.suggs,
1909+
Applicability::MaybeIncorrect,
1910+
);
1911+
if !self.apit_spans.is_empty() {
1912+
diag.span_note(self.apit_spans, fluent::trait_selection_warn_removing_apit_params_for_overcapture);
1913+
}
1914+
}
1915+
}

Diff for: tests/ui/impl-trait/precise-capturing/overcaptures-2024.fixed

+8
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,12 @@ fn hrtb() -> impl for<'a> Higher<'a, Output = impl Sized + use<>> {}
2929
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
3030
//~| WARN this changes meaning in Rust 2024
3131

32+
fn apit<T: Sized>(_: &T) -> impl Sized + use<T> {}
33+
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
34+
//~| WARN this changes meaning in Rust 2024
35+
36+
fn apit2<U, T: Sized>(_: &T, _: U) -> impl Sized + use<U, T> {}
37+
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
38+
//~| WARN this changes meaning in Rust 2024
39+
3240
fn main() {}

Diff for: tests/ui/impl-trait/precise-capturing/overcaptures-2024.rs

+8
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,12 @@ fn hrtb() -> impl for<'a> Higher<'a, Output = impl Sized> {}
2929
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
3030
//~| WARN this changes meaning in Rust 2024
3131

32+
fn apit(_: &impl Sized) -> impl Sized {}
33+
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
34+
//~| WARN this changes meaning in Rust 2024
35+
36+
fn apit2<U>(_: &impl Sized, _: U) -> impl Sized {}
37+
//~^ ERROR `impl Sized` will capture more lifetimes than possibly intended in edition 2024
38+
//~| WARN this changes meaning in Rust 2024
39+
3240
fn main() {}

Diff for: tests/ui/impl-trait/precise-capturing/overcaptures-2024.stderr

+49-1
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,53 @@ help: use the precise capturing `use<...>` syntax to make the captures explicit
7979
LL | fn hrtb() -> impl for<'a> Higher<'a, Output = impl Sized + use<>> {}
8080
| +++++++
8181

82-
error: aborting due to 4 previous errors
82+
error: `impl Sized` will capture more lifetimes than possibly intended in edition 2024
83+
--> $DIR/overcaptures-2024.rs:32:28
84+
|
85+
LL | fn apit(_: &impl Sized) -> impl Sized {}
86+
| ^^^^^^^^^^
87+
|
88+
= warning: this changes meaning in Rust 2024
89+
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/rpit-lifetime-capture.html>
90+
note: specifically, this lifetime is in scope but not mentioned in the type's bounds
91+
--> $DIR/overcaptures-2024.rs:32:12
92+
|
93+
LL | fn apit(_: &impl Sized) -> impl Sized {}
94+
| ^
95+
= note: all lifetimes in scope will be captured by `impl Trait`s in edition 2024
96+
note: you could use a `use<...>` bound to explicitly specify captures, but argument-position `impl Trait`s are not nameable
97+
--> $DIR/overcaptures-2024.rs:32:13
98+
|
99+
LL | fn apit(_: &impl Sized) -> impl Sized {}
100+
| ^^^^^^^^^^
101+
help: use the precise capturing `use<...>` syntax to make the captures explicit
102+
|
103+
LL | fn apit<T: Sized>(_: &T) -> impl Sized + use<T> {}
104+
| ++++++++++ ~ ++++++++
105+
106+
error: `impl Sized` will capture more lifetimes than possibly intended in edition 2024
107+
--> $DIR/overcaptures-2024.rs:36:38
108+
|
109+
LL | fn apit2<U>(_: &impl Sized, _: U) -> impl Sized {}
110+
| ^^^^^^^^^^
111+
|
112+
= warning: this changes meaning in Rust 2024
113+
= note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/rpit-lifetime-capture.html>
114+
note: specifically, this lifetime is in scope but not mentioned in the type's bounds
115+
--> $DIR/overcaptures-2024.rs:36:16
116+
|
117+
LL | fn apit2<U>(_: &impl Sized, _: U) -> impl Sized {}
118+
| ^
119+
= note: all lifetimes in scope will be captured by `impl Trait`s in edition 2024
120+
note: you could use a `use<...>` bound to explicitly specify captures, but argument-position `impl Trait`s are not nameable
121+
--> $DIR/overcaptures-2024.rs:36:17
122+
|
123+
LL | fn apit2<U>(_: &impl Sized, _: U) -> impl Sized {}
124+
| ^^^^^^^^^^
125+
help: use the precise capturing `use<...>` syntax to make the captures explicit
126+
|
127+
LL | fn apit2<U, T: Sized>(_: &T, _: U) -> impl Sized + use<U, T> {}
128+
| ++++++++++ ~ +++++++++++
129+
130+
error: aborting due to 6 previous errors
83131

0 commit comments

Comments
 (0)