@@ -2,7 +2,7 @@ use std::cmp;
22
33use rustc_data_structures:: fx:: FxIndexMap ;
44use rustc_data_structures:: sorted_map:: SortedMap ;
5- use rustc_errors:: { Diag , MultiSpan } ;
5+ use rustc_errors:: { Diag , Diagnostic , MultiSpan } ;
66use rustc_hir:: { HirId , ItemLocalId } ;
77use rustc_lint_defs:: EditionFcw ;
88use rustc_macros:: { Decodable , Encodable , HashStable } ;
@@ -482,3 +482,205 @@ pub fn lint_level(
482482 }
483483 lint_level_impl ( sess, lint, level, span, Box :: new ( decorate) )
484484}
485+
486+ /// The innermost function for emitting lints implementing the [`trait@Diagnostic`] trait.
487+ ///
488+ /// If you are looking to implement a lint, look for higher level functions,
489+ /// for example:
490+ ///
491+ /// - [`TyCtxt::emit_node_span_lint`]
492+ /// - [`TyCtxt::node_span_lint`]
493+ /// - [`TyCtxt::emit_node_lint`]
494+ /// - [`TyCtxt::node_lint`]
495+ /// - `LintContext::opt_span_lint`
496+ ///
497+ /// This function will replace `lint_level` once all `LintDiagnostic` items have been migrated to
498+ /// `Diagnostic`.
499+ #[ track_caller]
500+ pub fn diag_lint_level < ' a , D : Diagnostic < ' a , ( ) > + ' a > (
501+ sess : & ' a Session ,
502+ lint : & ' static Lint ,
503+ level : LevelAndSource ,
504+ span : Option < MultiSpan > ,
505+ decorate : D ,
506+ ) {
507+ // Avoid codegen bloat from monomorphization by immediately doing dyn dispatch of `decorate` to
508+ // the "real" work.
509+ #[ track_caller]
510+ fn diag_lint_level_impl < ' a > (
511+ sess : & ' a Session ,
512+ lint : & ' static Lint ,
513+ level : LevelAndSource ,
514+ span : Option < MultiSpan > ,
515+ decorate : Box <
516+ dyn FnOnce ( rustc_errors:: DiagCtxtHandle < ' a > , rustc_errors:: Level ) -> Diag < ' a , ( ) > + ' a ,
517+ > ,
518+ ) {
519+ let LevelAndSource { level, lint_id, src } = level;
520+
521+ // Check for future incompatibility lints and issue a stronger warning.
522+ let future_incompatible = lint. future_incompatible ;
523+
524+ let has_future_breakage = future_incompatible. map_or (
525+ // Default allow lints trigger too often for testing.
526+ sess. opts . unstable_opts . future_incompat_test && lint. default_level != Level :: Allow ,
527+ |incompat| incompat. report_in_deps ,
528+ ) ;
529+
530+ // Convert lint level to error level.
531+ let err_level = match level {
532+ Level :: Allow => {
533+ if has_future_breakage {
534+ rustc_errors:: Level :: Allow
535+ } else {
536+ return ;
537+ }
538+ }
539+ Level :: Expect => {
540+ // This case is special as we actually allow the lint itself in this context, but
541+ // we can't return early like in the case for `Level::Allow` because we still
542+ // need the lint diagnostic to be emitted to `rustc_error::DiagCtxtInner`.
543+ //
544+ // We can also not mark the lint expectation as fulfilled here right away, as it
545+ // can still be cancelled in the decorate function. All of this means that we simply
546+ // create a `Diag` and continue as we would for warnings.
547+ rustc_errors:: Level :: Expect
548+ }
549+ Level :: ForceWarn => rustc_errors:: Level :: ForceWarning ,
550+ Level :: Warn => rustc_errors:: Level :: Warning ,
551+ Level :: Deny | Level :: Forbid => rustc_errors:: Level :: Error ,
552+ } ;
553+ // Finally, run `decorate`. `decorate` can call `trimmed_path_str` (directly or indirectly),
554+ // so we need to make sure when we do call `decorate` that the diagnostic is eventually
555+ // emitted or we'll get a `must_produce_diag` ICE.
556+ //
557+ // When is a diagnostic *eventually* emitted? Well, that is determined by 2 factors:
558+ // 1. If the corresponding `rustc_errors::Level` is beyond warning, i.e. `ForceWarning(_)`
559+ // or `Error`, then the diagnostic will be emitted regardless of CLI options.
560+ // 2. If the corresponding `rustc_errors::Level` is warning, then that can be affected by
561+ // `-A warnings` or `--cap-lints=xxx` on the command line. In which case, the diagnostic
562+ // will be emitted if `can_emit_warnings` is true.
563+ let skip = err_level == rustc_errors:: Level :: Warning && !sess. dcx ( ) . can_emit_warnings ( ) ;
564+
565+ let disable_suggestions = if let Some ( ref span) = span
566+ // If this code originates in a foreign macro, aka something that this crate
567+ // did not itself author, then it's likely that there's nothing this crate
568+ // can do about it. We probably want to skip the lint entirely.
569+ && span. primary_spans ( ) . iter ( ) . any ( |s| s. in_external_macro ( sess. source_map ( ) ) )
570+ {
571+ true
572+ } else {
573+ false
574+ } ;
575+
576+ let mut err: Diag < ' _ , ( ) > = if !skip {
577+ decorate ( sess. dcx ( ) , err_level)
578+ } else {
579+ Diag :: new ( sess. dcx ( ) , err_level, "" )
580+ } ;
581+ if let Some ( span) = span
582+ && err. span . primary_span ( ) . is_none ( )
583+ {
584+ // We can't use `err.span()` because it overwrites the labels, so we need to do it manually.
585+ for primary in span. primary_spans ( ) {
586+ err. span . push_primary_span ( * primary) ;
587+ }
588+ for ( label_span, label) in span. span_labels_raw ( ) {
589+ err. span . push_span_diag ( * label_span, label. clone ( ) ) ;
590+ }
591+ }
592+ if let Some ( lint_id) = lint_id {
593+ err. lint_id ( lint_id) ;
594+ }
595+
596+ if disable_suggestions {
597+ // Any suggestions made here are likely to be incorrect, so anything we
598+ // emit shouldn't be automatically fixed by rustfix.
599+ err. disable_suggestions ( ) ;
600+
601+ // If this is a future incompatible that is not an edition fixing lint
602+ // it'll become a hard error, so we have to emit *something*. Also,
603+ // if this lint occurs in the expansion of a macro from an external crate,
604+ // allow individual lints to opt-out from being reported.
605+ let incompatible = future_incompatible. is_some_and ( |f| f. reason . edition ( ) . is_none ( ) ) ;
606+
607+ if !incompatible && !lint. report_in_external_macro {
608+ err. cancel ( ) ;
609+
610+ // Don't continue further, since we don't want to have
611+ // `diag_span_note_once` called for a diagnostic that isn't emitted.
612+ return ;
613+ }
614+ }
615+
616+ err. is_lint ( lint. name_lower ( ) , has_future_breakage) ;
617+ // Lint diagnostics that are covered by the expect level will not be emitted outside
618+ // the compiler. It is therefore not necessary to add any information for the user.
619+ // This will therefore directly call the decorate function which will in turn emit
620+ // the diagnostic.
621+ if let Level :: Expect = level {
622+ err. emit ( ) ;
623+ return ;
624+ }
625+
626+ if let Some ( future_incompatible) = future_incompatible {
627+ let explanation = match future_incompatible. reason {
628+ FutureIncompatibilityReason :: FutureReleaseError ( _) => {
629+ "this was previously accepted by the compiler but is being phased out; \
630+ it will become a hard error in a future release!"
631+ . to_owned ( )
632+ }
633+ FutureIncompatibilityReason :: FutureReleaseSemanticsChange ( _) => {
634+ "this will change its meaning in a future release!" . to_owned ( )
635+ }
636+ FutureIncompatibilityReason :: EditionError ( EditionFcw { edition, .. } ) => {
637+ let current_edition = sess. edition ( ) ;
638+ format ! (
639+ "this is accepted in the current edition (Rust {current_edition}) but is a hard error in Rust {edition}!"
640+ )
641+ }
642+ FutureIncompatibilityReason :: EditionSemanticsChange ( EditionFcw {
643+ edition, ..
644+ } ) => {
645+ format ! ( "this changes meaning in Rust {edition}" )
646+ }
647+ FutureIncompatibilityReason :: EditionAndFutureReleaseError ( EditionFcw {
648+ edition,
649+ ..
650+ } ) => {
651+ format ! (
652+ "this was previously accepted by the compiler but is being phased out; \
653+ it will become a hard error in Rust {edition} and in a future release in all editions!"
654+ )
655+ }
656+ FutureIncompatibilityReason :: EditionAndFutureReleaseSemanticsChange (
657+ EditionFcw { edition, .. } ,
658+ ) => {
659+ format ! (
660+ "this changes meaning in Rust {edition} and in a future release in all editions!"
661+ )
662+ }
663+ FutureIncompatibilityReason :: Custom ( reason, _) => reason. to_owned ( ) ,
664+ FutureIncompatibilityReason :: Unreachable => unreachable ! ( ) ,
665+ } ;
666+
667+ if future_incompatible. explain_reason {
668+ err. warn ( explanation) ;
669+ }
670+
671+ let citation =
672+ format ! ( "for more information, see {}" , future_incompatible. reason. reference( ) ) ;
673+ err. note ( citation) ;
674+ }
675+
676+ explain_lint_level_source ( sess, lint, level, src, & mut err) ;
677+ err. emit ( ) ;
678+ }
679+ diag_lint_level_impl (
680+ sess,
681+ lint,
682+ level,
683+ span,
684+ Box :: new ( move |dcx, level| decorate. into_diag ( dcx, level) ) ,
685+ ) ;
686+ }
0 commit comments