Skip to content

Commit 222660b

Browse files
authored
Build complete usable type from a type-relative prefix (#14586)
Instead of looking for angle brackets in the source code, use the HIR and Ty interfaces to either copy the original type, or complete it with `_` placeholders if all type and const generic arguments are inferred. Fixes #14581 changelog: [`from_iter_instead_of_collect`]: show correct type in suggestion
2 parents 91658a7 + 042a54c commit 222660b

File tree

4 files changed

+188
-49
lines changed

4 files changed

+188
-49
lines changed
+47-48
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
1+
use std::fmt::Write as _;
2+
13
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::source::SpanRangeExt;
4+
use clippy_utils::source::snippet_with_applicability;
35
use clippy_utils::ty::implements_trait;
46
use clippy_utils::{is_path_diagnostic_item, sugg};
57
use rustc_errors::Applicability;
6-
use rustc_hir as hir;
8+
use rustc_hir::def::Res;
9+
use rustc_hir::{self as hir, Expr, ExprKind, GenericArg, QPath, TyKind};
710
use rustc_lint::LateContext;
8-
use rustc_middle::ty::Ty;
11+
use rustc_middle::ty::GenericParamDefKind;
912
use rustc_span::sym;
1013

1114
use super::FROM_ITER_INSTEAD_OF_COLLECT;
1215

13-
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Expr<'_>], func: &hir::Expr<'_>) {
16+
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>], func: &Expr<'_>) {
1417
if is_path_diagnostic_item(cx, func, sym::from_iter_fn)
15-
&& let ty = cx.typeck_results().expr_ty(expr)
1618
&& let arg_ty = cx.typeck_results().expr_ty(&args[0])
1719
&& let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator)
1820
&& implements_trait(cx, arg_ty, iter_id, &[])
1921
{
20-
// `expr` implements `FromIterator` trait
22+
let mut app = Applicability::MaybeIncorrect;
23+
let turbofish = match func.kind {
24+
ExprKind::Path(QPath::TypeRelative(hir_ty, _)) => build_full_type(cx, hir_ty, &mut app),
25+
ExprKind::Path(QPath::Resolved(Some(self_ty), _)) => build_full_type(cx, self_ty, &mut app),
26+
_ => return,
27+
};
2128
let iter_expr = sugg::Sugg::hir(cx, &args[0], "..").maybe_paren();
22-
let turbofish = extract_turbofish(cx, expr, ty);
2329
let sugg = format!("{iter_expr}.collect::<{turbofish}>()");
2430
span_lint_and_sugg(
2531
cx,
@@ -28,54 +34,47 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Exp
2834
"usage of `FromIterator::from_iter`",
2935
"use `.collect()` instead of `::from_iter()`",
3036
sugg,
31-
Applicability::MaybeIncorrect,
37+
app,
3238
);
3339
}
3440
}
3541

36-
fn extract_turbofish(cx: &LateContext<'_>, expr: &hir::Expr<'_>, ty: Ty<'_>) -> String {
37-
fn strip_angle_brackets(s: &str) -> Option<&str> {
38-
s.strip_prefix('<')?.strip_suffix('>')
39-
}
40-
41-
let call_site = expr.span.source_callsite();
42-
if let Some(snippet) = call_site.get_source_text(cx)
43-
&& let snippet_split = snippet.split("::").collect::<Vec<_>>()
44-
&& let Some((_, elements)) = snippet_split.split_last()
42+
/// Build a type which can be used in a turbofish syntax from `hir_ty`, either by copying the
43+
/// existing generic arguments with the exception of elided lifetimes, or by inserting placeholders
44+
/// for types and consts without default values.
45+
fn build_full_type(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, app: &mut Applicability) -> String {
46+
if let TyKind::Path(ty_qpath) = hir_ty.kind
47+
&& let QPath::Resolved(None, ty_path) = &ty_qpath
48+
&& let Res::Def(_, ty_did) = ty_path.res
4549
{
46-
if let [type_specifier, _] = snippet_split.as_slice()
47-
&& let Some(type_specifier) = strip_angle_brackets(type_specifier)
48-
&& let Some((type_specifier, ..)) = type_specifier.split_once(" as ")
49-
{
50-
type_specifier.to_string()
50+
let mut ty_str = itertools::join(ty_path.segments.iter().map(|s| s.ident), "::");
51+
let mut first = true;
52+
let mut append = |arg: &str| {
53+
write!(&mut ty_str, "{}{arg}", [", ", "<"][usize::from(first)]).unwrap();
54+
first = false;
55+
};
56+
if let Some(args) = ty_path.segments.last().and_then(|segment| segment.args) {
57+
args.args
58+
.iter()
59+
.filter(|arg| !matches!(arg, GenericArg::Lifetime(lt) if lt.is_elided()))
60+
.for_each(|arg| append(&snippet_with_applicability(cx, arg.span().source_callsite(), "_", app)));
5161
} else {
52-
// is there a type specifier? (i.e.: like `<u32>` in `collections::BTreeSet::<u32>::`)
53-
if let Some(type_specifier) = snippet_split.iter().find(|e| strip_angle_brackets(e).is_some()) {
54-
// remove the type specifier from the path elements
55-
let without_ts = elements
56-
.iter()
57-
.filter_map(|e| {
58-
if e == type_specifier {
59-
None
60-
} else {
61-
Some((*e).to_string())
62-
}
63-
})
64-
.collect::<Vec<_>>();
65-
// join and add the type specifier at the end (i.e.: `collections::BTreeSet<u32>`)
66-
format!("{}{type_specifier}", without_ts.join("::"))
67-
} else {
68-
// type is not explicitly specified so wildcards are needed
69-
// i.e.: 2 wildcards in `std::collections::BTreeMap<&i32, &char>`
70-
let ty_str = ty.to_string();
71-
let start = ty_str.find('<').unwrap_or(0);
72-
let end = ty_str.find('>').unwrap_or(ty_str.len());
73-
let nb_wildcard = ty_str[start..end].split(',').count();
74-
let wildcards = format!("_{}", ", _".repeat(nb_wildcard - 1));
75-
format!("{}<{wildcards}>", elements.join("::"))
76-
}
62+
cx.tcx
63+
.generics_of(ty_did)
64+
.own_params
65+
.iter()
66+
.filter(|param| {
67+
matches!(
68+
param.kind,
69+
GenericParamDefKind::Type { has_default: false, .. }
70+
| GenericParamDefKind::Const { has_default: false, .. }
71+
)
72+
})
73+
.for_each(|_| append("_"));
7774
}
75+
ty_str.push_str([">", ""][usize::from(first)]);
76+
ty_str
7877
} else {
79-
ty.to_string()
78+
snippet_with_applicability(cx, hir_ty.span.source_callsite(), "_", app).into()
8079
}
8180
}

Diff for: tests/ui/from_iter_instead_of_collect.fixed

+43
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,46 @@ fn main() {
7373
for _i in [1, 2, 3].iter().collect::<Vec<&i32>>() {}
7474
//~^ from_iter_instead_of_collect
7575
}
76+
77+
fn issue14581() {
78+
let nums = [0, 1, 2];
79+
let _ = &nums.iter().map(|&num| char::from_u32(num).unwrap()).collect::<String>();
80+
//~^ from_iter_instead_of_collect
81+
}
82+
83+
fn test_implicit_generic_args(iter: impl Iterator<Item = &'static i32> + Copy) {
84+
struct S<'l, T = i32, const A: usize = 3, const B: usize = 3> {
85+
a: [&'l T; A],
86+
b: [&'l T; B],
87+
}
88+
89+
impl<'l, T, const A: usize, const B: usize> FromIterator<&'l T> for S<'l, T, A, B> {
90+
fn from_iter<I: IntoIterator<Item = &'l T>>(_: I) -> Self {
91+
todo!()
92+
}
93+
}
94+
95+
let _ = iter.collect::<S<'static, i32, 7>>();
96+
//~^ from_iter_instead_of_collect
97+
98+
let _ = iter.collect::<S<'static, i32>>();
99+
//~^ from_iter_instead_of_collect
100+
101+
let _ = iter.collect::<S<'static, _, 7>>();
102+
//~^ from_iter_instead_of_collect
103+
104+
let _ = iter.collect::<S<'static, _, 7, 8>>();
105+
//~^ from_iter_instead_of_collect
106+
107+
let _ = iter.collect::<S<_, 7, 8>>();
108+
//~^ from_iter_instead_of_collect
109+
110+
let _ = iter.collect::<S<i32>>();
111+
//~^ from_iter_instead_of_collect
112+
113+
let _ = iter.collect::<S<i32>>();
114+
//~^ from_iter_instead_of_collect
115+
116+
let _ = iter.collect::<S>();
117+
//~^ from_iter_instead_of_collect
118+
}

Diff for: tests/ui/from_iter_instead_of_collect.rs

+43
Original file line numberDiff line numberDiff line change
@@ -73,3 +73,46 @@ fn main() {
7373
for _i in Vec::<&i32>::from_iter([1, 2, 3].iter()) {}
7474
//~^ from_iter_instead_of_collect
7575
}
76+
77+
fn issue14581() {
78+
let nums = [0, 1, 2];
79+
let _ = &String::from_iter(nums.iter().map(|&num| char::from_u32(num).unwrap()));
80+
//~^ from_iter_instead_of_collect
81+
}
82+
83+
fn test_implicit_generic_args(iter: impl Iterator<Item = &'static i32> + Copy) {
84+
struct S<'l, T = i32, const A: usize = 3, const B: usize = 3> {
85+
a: [&'l T; A],
86+
b: [&'l T; B],
87+
}
88+
89+
impl<'l, T, const A: usize, const B: usize> FromIterator<&'l T> for S<'l, T, A, B> {
90+
fn from_iter<I: IntoIterator<Item = &'l T>>(_: I) -> Self {
91+
todo!()
92+
}
93+
}
94+
95+
let _ = <S<'static, i32, 7>>::from_iter(iter);
96+
//~^ from_iter_instead_of_collect
97+
98+
let _ = <S<'static, i32>>::from_iter(iter);
99+
//~^ from_iter_instead_of_collect
100+
101+
let _ = <S<'static, _, 7>>::from_iter(iter);
102+
//~^ from_iter_instead_of_collect
103+
104+
let _ = <S<'static, _, 7, 8>>::from_iter(iter);
105+
//~^ from_iter_instead_of_collect
106+
107+
let _ = <S<'_, _, 7, 8>>::from_iter(iter);
108+
//~^ from_iter_instead_of_collect
109+
110+
let _ = <S<i32>>::from_iter(iter);
111+
//~^ from_iter_instead_of_collect
112+
113+
let _ = <S<'_, i32>>::from_iter(iter);
114+
//~^ from_iter_instead_of_collect
115+
116+
let _ = <S>::from_iter(iter);
117+
//~^ from_iter_instead_of_collect
118+
}

Diff for: tests/ui/from_iter_instead_of_collect.stderr

+55-1
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,59 @@ error: usage of `FromIterator::from_iter`
9191
LL | for _i in Vec::<&i32>::from_iter([1, 2, 3].iter()) {}
9292
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `[1, 2, 3].iter().collect::<Vec<&i32>>()`
9393

94-
error: aborting due to 15 previous errors
94+
error: usage of `FromIterator::from_iter`
95+
--> tests/ui/from_iter_instead_of_collect.rs:79:14
96+
|
97+
LL | let _ = &String::from_iter(nums.iter().map(|&num| char::from_u32(num).unwrap()));
98+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `nums.iter().map(|&num| char::from_u32(num).unwrap()).collect::<String>()`
99+
100+
error: usage of `FromIterator::from_iter`
101+
--> tests/ui/from_iter_instead_of_collect.rs:95:13
102+
|
103+
LL | let _ = <S<'static, i32, 7>>::from_iter(iter);
104+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter.collect::<S<'static, i32, 7>>()`
105+
106+
error: usage of `FromIterator::from_iter`
107+
--> tests/ui/from_iter_instead_of_collect.rs:98:13
108+
|
109+
LL | let _ = <S<'static, i32>>::from_iter(iter);
110+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter.collect::<S<'static, i32>>()`
111+
112+
error: usage of `FromIterator::from_iter`
113+
--> tests/ui/from_iter_instead_of_collect.rs:101:13
114+
|
115+
LL | let _ = <S<'static, _, 7>>::from_iter(iter);
116+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter.collect::<S<'static, _, 7>>()`
117+
118+
error: usage of `FromIterator::from_iter`
119+
--> tests/ui/from_iter_instead_of_collect.rs:104:13
120+
|
121+
LL | let _ = <S<'static, _, 7, 8>>::from_iter(iter);
122+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter.collect::<S<'static, _, 7, 8>>()`
123+
124+
error: usage of `FromIterator::from_iter`
125+
--> tests/ui/from_iter_instead_of_collect.rs:107:13
126+
|
127+
LL | let _ = <S<'_, _, 7, 8>>::from_iter(iter);
128+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter.collect::<S<_, 7, 8>>()`
129+
130+
error: usage of `FromIterator::from_iter`
131+
--> tests/ui/from_iter_instead_of_collect.rs:110:13
132+
|
133+
LL | let _ = <S<i32>>::from_iter(iter);
134+
| ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter.collect::<S<i32>>()`
135+
136+
error: usage of `FromIterator::from_iter`
137+
--> tests/ui/from_iter_instead_of_collect.rs:113:13
138+
|
139+
LL | let _ = <S<'_, i32>>::from_iter(iter);
140+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter.collect::<S<i32>>()`
141+
142+
error: usage of `FromIterator::from_iter`
143+
--> tests/ui/from_iter_instead_of_collect.rs:116:13
144+
|
145+
LL | let _ = <S>::from_iter(iter);
146+
| ^^^^^^^^^^^^^^^^^^^^ help: use `.collect()` instead of `::from_iter()`: `iter.collect::<S>()`
147+
148+
error: aborting due to 24 previous errors
95149

0 commit comments

Comments
 (0)