1+ use std:: fmt:: Write as _;
2+
13use clippy_utils:: diagnostics:: span_lint_and_sugg;
2- use clippy_utils:: source:: SpanRangeExt ;
4+ use clippy_utils:: source:: snippet_with_applicability ;
35use clippy_utils:: ty:: implements_trait;
46use clippy_utils:: { is_path_diagnostic_item, sugg} ;
57use 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 } ;
710use rustc_lint:: LateContext ;
8- use rustc_middle:: ty:: Ty ;
11+ use rustc_middle:: ty:: GenericParamDefKind ;
912use rustc_span:: sym;
1013
1114use 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}
0 commit comments