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