1
1
use super :: implicit_clone:: is_clone_like;
2
2
use super :: unnecessary_iter_cloned:: { self , is_into_iter} ;
3
+ use crate :: rustc_middle:: ty:: Subst ;
3
4
use clippy_utils:: diagnostics:: span_lint_and_sugg;
4
5
use clippy_utils:: source:: snippet_opt;
5
- use clippy_utils:: ty:: {
6
- get_associated_type, get_iterator_item_ty, implements_trait, is_copy, is_type_diagnostic_item, peel_mid_ty_refs,
7
- } ;
8
- use clippy_utils:: { fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item} ;
6
+ use clippy_utils:: ty:: { get_associated_type, get_iterator_item_ty, implements_trait, is_copy, peel_mid_ty_refs} ;
7
+ use clippy_utils:: visitors:: find_all_ret_expressions;
8
+ use clippy_utils:: { fn_def_id, get_parent_expr, is_diag_item_method, is_diag_trait_item, return_ty} ;
9
9
use clippy_utils:: { meets_msrv, msrvs} ;
10
10
use rustc_errors:: Applicability ;
11
- use rustc_hir:: { def_id:: DefId , BorrowKind , Expr , ExprKind } ;
11
+ use rustc_hir:: { def_id:: DefId , BorrowKind , Expr , ExprKind , ItemKind , Node } ;
12
+ use rustc_infer:: infer:: TyCtxtInferExt ;
12
13
use rustc_lint:: LateContext ;
13
14
use rustc_middle:: mir:: Mutability ;
14
15
use rustc_middle:: ty:: adjustment:: { Adjust , Adjustment , OverloadedDeref } ;
15
16
use rustc_middle:: ty:: subst:: { GenericArg , GenericArgKind , SubstsRef } ;
16
- use rustc_middle:: ty:: { self , PredicateKind , ProjectionPredicate , TraitPredicate , Ty } ;
17
+ use rustc_middle:: ty:: EarlyBinder ;
18
+ use rustc_middle:: ty:: { self , ParamTy , PredicateKind , ProjectionPredicate , TraitPredicate , Ty } ;
17
19
use rustc_semver:: RustcVersion ;
18
20
use rustc_span:: { sym, Symbol } ;
21
+ use rustc_trait_selection:: traits:: { query:: evaluate_obligation:: InferCtxtExt as _, Obligation , ObligationCause } ;
22
+ use rustc_typeck:: check:: { FnCtxt , Inherited } ;
19
23
use std:: cmp:: max;
20
24
21
25
use super :: UNNECESSARY_TO_OWNED ;
@@ -33,7 +37,7 @@ pub fn check<'tcx>(
33
37
then {
34
38
if is_cloned_or_copied( cx, method_name, method_def_id) {
35
39
unnecessary_iter_cloned:: check( cx, expr, method_name, receiver) ;
36
- } else if is_to_owned_like( cx, method_name, method_def_id) {
40
+ } else if is_to_owned_like( cx, expr , method_name, method_def_id) {
37
41
// At this point, we know the call is of a `to_owned`-like function. The functions
38
42
// `check_addr_of_expr` and `check_call_arg` determine whether the call is unnecessary
39
43
// based on its context, that is, whether it is a referent in an `AddrOf` expression, an
@@ -245,65 +249,26 @@ fn check_other_call_arg<'tcx>(
245
249
) -> bool {
246
250
if_chain ! {
247
251
if let Some ( ( maybe_call, maybe_arg) ) = skip_addr_of_ancestors( cx, expr) ;
248
- if let Some ( ( callee_def_id, call_substs , call_args) ) = get_callee_substs_and_args( cx, maybe_call) ;
252
+ if let Some ( ( callee_def_id, _ , call_args) ) = get_callee_substs_and_args( cx, maybe_call) ;
249
253
let fn_sig = cx. tcx. fn_sig( callee_def_id) . skip_binder( ) ;
250
254
if let Some ( i) = call_args. iter( ) . position( |arg| arg. hir_id == maybe_arg. hir_id) ;
251
255
if let Some ( input) = fn_sig. inputs( ) . get( i) ;
252
256
let ( input, n_refs) = peel_mid_ty_refs( * input) ;
253
- if let ( trait_predicates, projection_predicates ) = get_input_traits_and_projections( cx, callee_def_id, input) ;
257
+ if let ( trait_predicates, _ ) = get_input_traits_and_projections( cx, callee_def_id, input) ;
254
258
if let Some ( sized_def_id) = cx. tcx. lang_items( ) . sized_trait( ) ;
255
259
if let [ trait_predicate] = trait_predicates
256
260
. iter( )
257
261
. filter( |trait_predicate| trait_predicate. def_id( ) != sized_def_id)
258
262
. collect:: <Vec <_>>( ) [ ..] ;
259
263
if let Some ( deref_trait_id) = cx. tcx. get_diagnostic_item( sym:: Deref ) ;
260
264
if let Some ( as_ref_trait_id) = cx. tcx. get_diagnostic_item( sym:: AsRef ) ;
265
+ if trait_predicate. def_id( ) == deref_trait_id || trait_predicate. def_id( ) == as_ref_trait_id;
261
266
let receiver_ty = cx. typeck_results( ) . expr_ty( receiver) ;
262
- // If the callee has type parameters, they could appear in `projection_predicate.ty` or the
263
- // types of `trait_predicate.trait_ref.substs`.
264
- if if trait_predicate. def_id( ) == deref_trait_id {
265
- if let [ projection_predicate] = projection_predicates[ ..] {
266
- let normalized_ty =
267
- cx. tcx
268
- . subst_and_normalize_erasing_regions( call_substs, cx. param_env, projection_predicate. term) ;
269
- implements_trait( cx, receiver_ty, deref_trait_id, & [ ] )
270
- && get_associated_type( cx, receiver_ty, deref_trait_id, "Target" )
271
- . map_or( false , |ty| ty:: Term :: Ty ( ty) == normalized_ty)
272
- } else {
273
- false
274
- }
275
- } else if trait_predicate. def_id( ) == as_ref_trait_id {
276
- let composed_substs = compose_substs(
277
- cx,
278
- & trait_predicate. trait_ref. substs. iter( ) . skip( 1 ) . collect:: <Vec <_>>( ) [ ..] ,
279
- call_substs,
280
- ) ;
281
- // if `expr` is a `String` and generic target is [u8], skip
282
- // (https://github.com/rust-lang/rust-clippy/issues/9317).
283
- if let [ subst] = composed_substs[ ..]
284
- && let GenericArgKind :: Type ( arg_ty) = subst. unpack( )
285
- && arg_ty. is_slice( )
286
- && let inner_ty = arg_ty. builtin_index( ) . unwrap( )
287
- && let ty:: Uint ( ty:: UintTy :: U8 ) = inner_ty. kind( )
288
- && let self_ty = cx. typeck_results( ) . expr_ty( expr) . peel_refs( )
289
- && is_type_diagnostic_item( cx, self_ty, sym:: String ) {
290
- false
291
- } else {
292
- implements_trait( cx, receiver_ty, as_ref_trait_id, & composed_substs)
293
- }
294
- } else {
295
- false
296
- } ;
267
+ if can_change_type( cx, maybe_arg, receiver_ty) ;
297
268
// We can't add an `&` when the trait is `Deref` because `Target = &T` won't match
298
269
// `Target = T`.
299
270
if n_refs > 0 || is_copy( cx, receiver_ty) || trait_predicate. def_id( ) != deref_trait_id;
300
271
let n_refs = max( n_refs, if is_copy( cx, receiver_ty) { 0 } else { 1 } ) ;
301
- // If the trait is `AsRef` and the input type variable `T` occurs in the output type, then
302
- // `T` must not be instantiated with a reference
303
- // (https://github.com/rust-lang/rust-clippy/issues/8507).
304
- if ( n_refs == 0 && !receiver_ty. is_ref( ) )
305
- || trait_predicate. def_id( ) != as_ref_trait_id
306
- || !fn_sig. output( ) . contains( input) ;
307
272
if let Some ( receiver_snippet) = snippet_opt( cx, receiver. span) ;
308
273
then {
309
274
span_lint_and_sugg(
@@ -389,22 +354,102 @@ fn get_input_traits_and_projections<'tcx>(
389
354
( trait_predicates, projection_predicates)
390
355
}
391
356
392
- /// Composes two substitutions by applying the latter to the types of the former.
393
- fn compose_substs < ' tcx > (
394
- cx : & LateContext < ' tcx > ,
395
- left : & [ GenericArg < ' tcx > ] ,
396
- right : SubstsRef < ' tcx > ,
397
- ) -> Vec < GenericArg < ' tcx > > {
398
- left. iter ( )
399
- . map ( |arg| {
400
- if let GenericArgKind :: Type ( arg_ty) = arg. unpack ( ) {
401
- let normalized_ty = cx. tcx . subst_and_normalize_erasing_regions ( right, cx. param_env , arg_ty) ;
402
- GenericArg :: from ( normalized_ty)
403
- } else {
404
- * arg
357
+ fn can_change_type < ' a > ( cx : & LateContext < ' a > , mut expr : & ' a Expr < ' a > , mut ty : Ty < ' a > ) -> bool {
358
+ for ( _, node) in cx. tcx . hir ( ) . parent_iter ( expr. hir_id ) {
359
+ match node {
360
+ Node :: Stmt ( _) => return true ,
361
+ Node :: Block ( ..) => continue ,
362
+ Node :: Item ( item) => {
363
+ if let ItemKind :: Fn ( _, _, body_id) = & item. kind
364
+ && let output_ty = return_ty ( cx, item. hir_id ( ) )
365
+ && let local_def_id = cx. tcx . hir ( ) . local_def_id ( item. hir_id ( ) )
366
+ && Inherited :: build ( cx. tcx , local_def_id) . enter ( |inherited| {
367
+ let fn_ctxt = FnCtxt :: new ( & inherited, cx. param_env , item. hir_id ( ) ) ;
368
+ fn_ctxt. can_coerce ( ty, output_ty)
369
+ } ) {
370
+ if has_lifetime ( output_ty) && has_lifetime ( ty) {
371
+ return false ;
372
+ }
373
+ let body = cx. tcx . hir ( ) . body ( * body_id) ;
374
+ let body_expr = & body. value ;
375
+ let mut count = 0 ;
376
+ return find_all_ret_expressions ( cx, body_expr, |_| { count += 1 ; count <= 1 } ) ;
377
+ }
405
378
}
406
- } )
407
- . collect ( )
379
+ Node :: Expr ( parent_expr) => {
380
+ if let Some ( ( callee_def_id, call_substs, call_args) ) = get_callee_substs_and_args ( cx, parent_expr) {
381
+ let fn_sig = cx. tcx . fn_sig ( callee_def_id) . skip_binder ( ) ;
382
+ if let Some ( arg_index) = call_args. iter ( ) . position ( |arg| arg. hir_id == expr. hir_id )
383
+ && let Some ( param_ty) = fn_sig. inputs ( ) . get ( arg_index)
384
+ && let ty:: Param ( ParamTy { index : param_index , ..} ) = param_ty. kind ( )
385
+ {
386
+ if fn_sig
387
+ . inputs ( )
388
+ . iter ( )
389
+ . enumerate ( )
390
+ . filter ( |( i, _) | * i != arg_index)
391
+ . any ( |( _, ty) | ty. contains ( * param_ty) )
392
+ {
393
+ return false ;
394
+ }
395
+
396
+ let mut trait_predicates = cx. tcx . param_env ( callee_def_id)
397
+ . caller_bounds ( ) . iter ( ) . filter ( |predicate| {
398
+ if let PredicateKind :: Trait ( trait_predicate) = predicate. kind ( ) . skip_binder ( )
399
+ && trait_predicate. trait_ref . self_ty ( ) == * param_ty {
400
+ true
401
+ } else {
402
+ false
403
+ }
404
+ } ) ;
405
+
406
+ let new_subst = cx. tcx . mk_substs (
407
+ call_substs. iter ( )
408
+ . enumerate ( )
409
+ . map ( |( i, t) |
410
+ if i == ( * param_index as usize ) {
411
+ GenericArg :: from ( ty)
412
+ } else {
413
+ t
414
+ } ) ) ;
415
+
416
+ if trait_predicates. any ( |predicate| {
417
+ let predicate = EarlyBinder ( predicate) . subst ( cx. tcx , new_subst) ;
418
+ let obligation = Obligation :: new ( ObligationCause :: dummy ( ) , cx. param_env , predicate) ;
419
+ !cx. tcx
420
+ . infer_ctxt ( )
421
+ . enter ( |infcx| infcx. predicate_must_hold_modulo_regions ( & obligation) )
422
+ } ) {
423
+ return false ;
424
+ }
425
+
426
+ let output_ty = fn_sig. output ( ) ;
427
+ if output_ty. contains ( * param_ty) {
428
+ if let Ok ( new_ty) = cx. tcx . try_subst_and_normalize_erasing_regions (
429
+ new_subst, cx. param_env , output_ty) {
430
+ expr = parent_expr;
431
+ ty = new_ty;
432
+ continue ;
433
+ }
434
+ return false ;
435
+ }
436
+
437
+ return true ;
438
+ }
439
+ } else if let ExprKind :: Block ( ..) = parent_expr. kind {
440
+ continue ;
441
+ }
442
+ return false ;
443
+ } ,
444
+ _ => return false ,
445
+ }
446
+ }
447
+
448
+ false
449
+ }
450
+
451
+ fn has_lifetime ( ty : Ty < ' _ > ) -> bool {
452
+ ty. walk ( ) . any ( |t| matches ! ( t. unpack( ) , GenericArgKind :: Lifetime ( _) ) )
408
453
}
409
454
410
455
/// Returns true if the named method is `Iterator::cloned` or `Iterator::copied`.
@@ -415,18 +460,38 @@ fn is_cloned_or_copied(cx: &LateContext<'_>, method_name: Symbol, method_def_id:
415
460
416
461
/// Returns true if the named method can be used to convert the receiver to its "owned"
417
462
/// representation.
418
- fn is_to_owned_like ( cx : & LateContext < ' _ > , method_name : Symbol , method_def_id : DefId ) -> bool {
463
+ fn is_to_owned_like < ' a > ( cx : & LateContext < ' a > , call_expr : & Expr < ' a > , method_name : Symbol , method_def_id : DefId ) -> bool {
419
464
is_clone_like ( cx, method_name. as_str ( ) , method_def_id)
420
465
|| is_cow_into_owned ( cx, method_name, method_def_id)
421
- || is_to_string ( cx, method_name, method_def_id)
466
+ || is_to_string_on_string_like ( cx, call_expr , method_name, method_def_id)
422
467
}
423
468
424
469
/// Returns true if the named method is `Cow::into_owned`.
425
470
fn is_cow_into_owned ( cx : & LateContext < ' _ > , method_name : Symbol , method_def_id : DefId ) -> bool {
426
471
method_name. as_str ( ) == "into_owned" && is_diag_item_method ( cx, method_def_id, sym:: Cow )
427
472
}
428
473
429
- /// Returns true if the named method is `ToString::to_string`.
430
- fn is_to_string ( cx : & LateContext < ' _ > , method_name : Symbol , method_def_id : DefId ) -> bool {
431
- method_name == sym:: to_string && is_diag_trait_item ( cx, method_def_id, sym:: ToString )
474
+ /// Returns true if the named method is `ToString::to_string` and it's called on a type that
475
+ /// is string-like i.e. implements `AsRef<str>` or `Deref<str>`.
476
+ fn is_to_string_on_string_like < ' a > (
477
+ cx : & LateContext < ' _ > ,
478
+ call_expr : & ' a Expr < ' a > ,
479
+ method_name : Symbol ,
480
+ method_def_id : DefId ,
481
+ ) -> bool {
482
+ if method_name != sym:: to_string || !is_diag_trait_item ( cx, method_def_id, sym:: ToString ) {
483
+ return false ;
484
+ }
485
+
486
+ if let Some ( substs) = cx. typeck_results ( ) . node_substs_opt ( call_expr. hir_id )
487
+ && let [ generic_arg] = substs. as_slice ( )
488
+ && let GenericArgKind :: Type ( ty) = generic_arg. unpack ( )
489
+ && let Some ( deref_trait_id) = cx. tcx . get_diagnostic_item ( sym:: Deref )
490
+ && let Some ( as_ref_trait_id) = cx. tcx . get_diagnostic_item ( sym:: AsRef )
491
+ && ( implements_trait ( cx, ty, deref_trait_id, & [ cx. tcx . types . str_ . into ( ) ] ) ||
492
+ implements_trait ( cx, ty, as_ref_trait_id, & [ cx. tcx . types . str_ . into ( ) ] ) ) {
493
+ true
494
+ } else {
495
+ false
496
+ }
432
497
}
0 commit comments