@@ -8,7 +8,7 @@ use rustc_hir::{
88 BinOp , BinOpKind , Expr , ExprKind , GenericArg , HirId , Impl , Item , ItemKind , Node , Pat , PatKind ,
99 Path , PathSegment , QPath , Ty , TyKind ,
1010} ;
11- use rustc_middle:: ty:: { self , Ty as MiddleTy } ;
11+ use rustc_middle:: ty:: { self , GenericArgsRef , Ty as MiddleTy } ;
1212use rustc_session:: { declare_lint_pass, declare_tool_lint} ;
1313use rustc_span:: hygiene:: { ExpnKind , MacroKind } ;
1414use rustc_span:: symbol:: { kw, sym, Symbol } ;
@@ -415,14 +415,17 @@ declare_lint_pass!(Diagnostics => [UNTRANSLATABLE_DIAGNOSTIC, DIAGNOSTIC_OUTSIDE
415415
416416impl LateLintPass < ' _ > for Diagnostics {
417417 fn check_expr ( & mut self , cx : & LateContext < ' _ > , expr : & Expr < ' _ > ) {
418+ let collect_args_tys_and_spans = |args : & [ Expr < ' _ > ] , reserve_one_extra : bool | {
419+ let mut result = Vec :: with_capacity ( args. len ( ) + usize:: from ( reserve_one_extra) ) ;
420+ result. extend ( args. iter ( ) . map ( |arg| ( cx. typeck_results ( ) . expr_ty ( arg) , arg. span ) ) ) ;
421+ result
422+ } ;
418423 // Only check function calls and method calls.
419- let ( span, def_id, fn_gen_args, call_tys ) = match expr. kind {
424+ let ( span, def_id, fn_gen_args, arg_tys_and_spans ) = match expr. kind {
420425 ExprKind :: Call ( callee, args) => {
421426 match cx. typeck_results ( ) . node_type ( callee. hir_id ) . kind ( ) {
422427 & ty:: FnDef ( def_id, fn_gen_args) => {
423- let call_tys: Vec < _ > =
424- args. iter ( ) . map ( |arg| cx. typeck_results ( ) . expr_ty ( arg) ) . collect ( ) ;
425- ( callee. span , def_id, fn_gen_args, call_tys)
428+ ( callee. span , def_id, fn_gen_args, collect_args_tys_and_spans ( args, false ) )
426429 }
427430 _ => return , // occurs for fns passed as args
428431 }
@@ -432,66 +435,94 @@ impl LateLintPass<'_> for Diagnostics {
432435 else {
433436 return ;
434437 } ;
435- let mut call_tys: Vec < _ > =
436- args. iter ( ) . map ( |arg| cx. typeck_results ( ) . expr_ty ( arg) ) . collect ( ) ;
437- call_tys. insert ( 0 , cx. tcx . types . self_param ) ; // dummy inserted for `self`
438- ( span, def_id, fn_gen_args, call_tys)
438+ let mut args = collect_args_tys_and_spans ( args, true ) ;
439+ args. insert ( 0 , ( cx. tcx . types . self_param , _recv. span ) ) ; // dummy inserted for `self`
440+ ( span, def_id, fn_gen_args, args)
439441 }
440442 _ => return ,
441443 } ;
442444
443- // Is the callee marked with `#[rustc_lint_diagnostics]`?
444- let has_attr = ty:: Instance :: try_resolve ( cx. tcx , cx. param_env , def_id, fn_gen_args)
445- . ok ( )
446- . flatten ( )
447- . is_some_and ( |inst| cx. tcx . has_attr ( inst. def_id ( ) , sym:: rustc_lint_diagnostics) ) ;
448-
449- // Closure: is the type `{D,Subd}iagMessage`?
450- let is_diag_message = |ty : MiddleTy < ' _ > | {
451- if let Some ( adt_def) = ty. ty_adt_def ( )
452- && let Some ( name) = cx. tcx . get_diagnostic_name ( adt_def. did ( ) )
453- && matches ! ( name, sym:: DiagMessage | sym:: SubdiagMessage )
454- {
455- true
456- } else {
457- false
458- }
459- } ;
445+ Self :: diagnostic_outside_of_impl ( cx, span, expr. hir_id , def_id, fn_gen_args) ;
446+ Self :: untranslatable_diagnostic ( cx, def_id, & arg_tys_and_spans) ;
447+ }
448+ }
460449
461- // Does the callee have one or more `impl Into<{D,Subd}iagMessage>` parameters?
462- let mut impl_into_diagnostic_message_params = vec ! [ ] ;
450+ impl Diagnostics {
451+ // Is the type `{D,Subd}iagMessage`?
452+ fn is_diag_message < ' cx > ( cx : & LateContext < ' cx > , ty : MiddleTy < ' cx > ) -> bool {
453+ if let Some ( adt_def) = ty. ty_adt_def ( )
454+ && let Some ( name) = cx. tcx . get_diagnostic_name ( adt_def. did ( ) )
455+ && matches ! ( name, sym:: DiagMessage | sym:: SubdiagMessage )
456+ {
457+ true
458+ } else {
459+ false
460+ }
461+ }
462+
463+ fn untranslatable_diagnostic < ' cx > (
464+ cx : & LateContext < ' cx > ,
465+ def_id : DefId ,
466+ arg_tys_and_spans : & [ ( MiddleTy < ' cx > , Span ) ] ,
467+ ) {
463468 let fn_sig = cx. tcx . fn_sig ( def_id) . instantiate_identity ( ) . skip_binder ( ) ;
464469 let predicates = cx. tcx . predicates_of ( def_id) . instantiate_identity ( cx. tcx ) . predicates ;
465470 for ( i, & param_ty) in fn_sig. inputs ( ) . iter ( ) . enumerate ( ) {
466- if let ty:: Param ( p ) = param_ty. kind ( ) {
471+ if let ty:: Param ( sig_param ) = param_ty. kind ( ) {
467472 // It is a type parameter. Check if it is `impl Into<{D,Subd}iagMessage>`.
468473 for pred in predicates. iter ( ) {
469474 if let Some ( trait_pred) = pred. as_trait_clause ( )
470475 && let trait_ref = trait_pred. skip_binder ( ) . trait_ref
471476 && trait_ref. self_ty ( ) == param_ty // correct predicate for the param?
472477 && cx. tcx . is_diagnostic_item ( sym:: Into , trait_ref. def_id )
473478 && let ty1 = trait_ref. args . type_at ( 1 )
474- && is_diag_message ( ty1)
479+ && Self :: is_diag_message ( cx , ty1)
475480 {
476- impl_into_diagnostic_message_params. push ( ( i, p. name ) ) ;
481+ // Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg
482+ // with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an
483+ // `UNTRANSLATABLE_DIAGNOSTIC` lint.
484+ let ( arg_ty, arg_span) = arg_tys_and_spans[ i] ;
485+
486+ // Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`?
487+ let is_translatable = Self :: is_diag_message ( cx, arg_ty)
488+ || matches ! ( arg_ty. kind( ) , ty:: Param ( arg_param) if arg_param. name == sig_param. name) ;
489+ if !is_translatable {
490+ cx. emit_span_lint (
491+ UNTRANSLATABLE_DIAGNOSTIC ,
492+ arg_span,
493+ UntranslatableDiag ,
494+ ) ;
495+ }
477496 }
478497 }
479498 }
480499 }
500+ }
481501
482- // Is the callee interesting?
483- if !has_attr && impl_into_diagnostic_message_params. is_empty ( ) {
502+ fn diagnostic_outside_of_impl < ' cx > (
503+ cx : & LateContext < ' cx > ,
504+ span : Span ,
505+ current_id : HirId ,
506+ def_id : DefId ,
507+ fn_gen_args : GenericArgsRef < ' cx > ,
508+ ) {
509+ // Is the callee marked with `#[rustc_lint_diagnostics]`?
510+ let Some ( inst) =
511+ ty:: Instance :: try_resolve ( cx. tcx , cx. param_env , def_id, fn_gen_args) . ok ( ) . flatten ( )
512+ else {
484513 return ;
485- }
514+ } ;
515+ let has_attr = cx. tcx . has_attr ( inst. def_id ( ) , sym:: rustc_lint_diagnostics) ;
516+ if !has_attr {
517+ return ;
518+ } ;
486519
487- // Is the parent method marked with `#[rustc_lint_diagnostics]`?
488- let mut parent_has_attr = false ;
489- for ( hir_id, _parent) in cx. tcx . hir ( ) . parent_iter ( expr. hir_id ) {
520+ for ( hir_id, _parent) in cx. tcx . hir ( ) . parent_iter ( current_id) {
490521 if let Some ( owner_did) = hir_id. as_owner ( )
491522 && cx. tcx . has_attr ( owner_did, sym:: rustc_lint_diagnostics)
492523 {
493- parent_has_attr = true ;
494- break ;
524+ // The parent method is marked with `#[rustc_lint_diagnostics]`
525+ return ;
495526 }
496527 }
497528
@@ -500,37 +531,22 @@ impl LateLintPass<'_> for Diagnostics {
500531 // - inside a parent function that is itself marked with `#[rustc_lint_diagnostics]`.
501532 //
502533 // Otherwise, emit a `DIAGNOSTIC_OUTSIDE_OF_IMPL` lint.
503- if has_attr && !parent_has_attr {
504- let mut is_inside_appropriate_impl = false ;
505- for ( _hir_id, parent) in cx. tcx . hir ( ) . parent_iter ( expr. hir_id ) {
506- debug ! ( ?parent) ;
507- if let Node :: Item ( Item { kind : ItemKind :: Impl ( impl_) , .. } ) = parent
508- && let Impl { of_trait : Some ( of_trait) , .. } = impl_
509- && let Some ( def_id) = of_trait. trait_def_id ( )
510- && let Some ( name) = cx. tcx . get_diagnostic_name ( def_id)
511- && matches ! ( name, sym:: Diagnostic | sym:: Subdiagnostic | sym:: LintDiagnostic )
512- {
513- is_inside_appropriate_impl = true ;
514- break ;
515- }
516- }
517- debug ! ( ?is_inside_appropriate_impl) ;
518- if !is_inside_appropriate_impl {
519- cx. emit_span_lint ( DIAGNOSTIC_OUTSIDE_OF_IMPL , span, DiagOutOfImpl ) ;
534+ let mut is_inside_appropriate_impl = false ;
535+ for ( _hir_id, parent) in cx. tcx . hir ( ) . parent_iter ( current_id) {
536+ debug ! ( ?parent) ;
537+ if let Node :: Item ( Item { kind : ItemKind :: Impl ( impl_) , .. } ) = parent
538+ && let Impl { of_trait : Some ( of_trait) , .. } = impl_
539+ && let Some ( def_id) = of_trait. trait_def_id ( )
540+ && let Some ( name) = cx. tcx . get_diagnostic_name ( def_id)
541+ && matches ! ( name, sym:: Diagnostic | sym:: Subdiagnostic | sym:: LintDiagnostic )
542+ {
543+ is_inside_appropriate_impl = true ;
544+ break ;
520545 }
521546 }
522-
523- // Calls to methods with an `impl Into<{D,Subd}iagMessage>` parameter must be passed an arg
524- // with type `{D,Subd}iagMessage` or `impl Into<{D,Subd}iagMessage>`. Otherwise, emit an
525- // `UNTRANSLATABLE_DIAGNOSTIC` lint.
526- for ( param_i, param_i_p_name) in impl_into_diagnostic_message_params {
527- // Is the arg type `{Sub,D}iagMessage`or `impl Into<{Sub,D}iagMessage>`?
528- let arg_ty = call_tys[ param_i] ;
529- let is_translatable = is_diag_message ( arg_ty)
530- || matches ! ( arg_ty. kind( ) , ty:: Param ( p) if p. name == param_i_p_name) ;
531- if !is_translatable {
532- cx. emit_span_lint ( UNTRANSLATABLE_DIAGNOSTIC , span, UntranslatableDiag ) ;
533- }
547+ debug ! ( ?is_inside_appropriate_impl) ;
548+ if !is_inside_appropriate_impl {
549+ cx. emit_span_lint ( DIAGNOSTIC_OUTSIDE_OF_IMPL , span, DiagOutOfImpl ) ;
534550 }
535551 }
536552}
0 commit comments