1
+ use std:: fmt:: Write as _;
2
+
1
3
use clippy_utils:: diagnostics:: span_lint_and_sugg;
2
- use clippy_utils:: source:: SpanRangeExt ;
4
+ use clippy_utils:: source:: snippet_with_applicability ;
3
5
use clippy_utils:: ty:: implements_trait;
4
6
use clippy_utils:: { is_path_diagnostic_item, sugg} ;
5
7
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 } ;
7
10
use rustc_lint:: LateContext ;
8
- use rustc_middle:: ty:: Ty ;
11
+ use rustc_middle:: ty:: GenericParamDefKind ;
9
12
use rustc_span:: sym;
10
13
11
14
use super :: FROM_ITER_INSTEAD_OF_COLLECT ;
12
15
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 < ' _ > ) {
14
17
if is_path_diagnostic_item ( cx, func, sym:: from_iter_fn)
15
- && let ty = cx. typeck_results ( ) . expr_ty ( expr)
16
18
&& let arg_ty = cx. typeck_results ( ) . expr_ty ( & args[ 0 ] )
17
19
&& let Some ( iter_id) = cx. tcx . get_diagnostic_item ( sym:: Iterator )
18
20
&& implements_trait ( cx, arg_ty, iter_id, & [ ] )
19
21
{
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
+ } ;
21
28
let iter_expr = sugg:: Sugg :: hir ( cx, & args[ 0 ] , ".." ) . maybe_paren ( ) ;
22
- let turbofish = extract_turbofish ( cx, expr, ty) ;
23
29
let sugg = format ! ( "{iter_expr}.collect::<{turbofish}>()" ) ;
24
30
span_lint_and_sugg (
25
31
cx,
@@ -28,54 +34,47 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, args: &[hir::Exp
28
34
"usage of `FromIterator::from_iter`" ,
29
35
"use `.collect()` instead of `::from_iter()`" ,
30
36
sugg,
31
- Applicability :: MaybeIncorrect ,
37
+ app ,
32
38
) ;
33
39
}
34
40
}
35
41
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
45
49
{
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) ) ) ;
51
61
} 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 ( "_" ) ) ;
77
74
}
75
+ ty_str. push_str ( [ ">" , "" ] [ usize:: from ( first) ] ) ;
76
+ ty_str
78
77
} else {
79
- ty . to_string ( )
78
+ snippet_with_applicability ( cx , hir_ty . span . source_callsite ( ) , "_" , app ) . into ( )
80
79
}
81
80
}
0 commit comments