about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2023-11-29 12:34:50 +0100
committerGitHub <noreply@github.com>2023-11-29 12:34:50 +0100
commit911a5ee7ff04bcb6d65c95f14670c81cfbcbdfac (patch)
tree60452c9513614cbf85c954ded9b6cdae5f8e0fc4
parentafe2d7392f7d013e19e540afd203af23c60e6919 (diff)
parent6b066a9305f03425c6627c16419d0d7197a4c526 (diff)
downloadrust-911a5ee7ff04bcb6d65c95f14670c81cfbcbdfac.tar.gz
rust-911a5ee7ff04bcb6d65c95f14670c81cfbcbdfac.zip
Rollup merge of #118333 - eduardosm:print-missing-target-features, r=est31
Print list of missing target features when calling a function with target features outside an unsafe block

Fixes https://github.com/rust-lang/rust/issues/108680

Supersedes https://github.com/rust-lang/rust/pull/109710. I used the same wording for the messages, but the implementation is different.

r? `@est31`
-rw-r--r--compiler/rustc_middle/src/mir/query.rs13
-rw-r--r--compiler/rustc_mir_build/messages.ftl36
-rw-r--r--compiler/rustc_mir_build/src/check_unsafety.rs80
-rw-r--r--compiler/rustc_mir_build/src/errors.rs25
-rw-r--r--compiler/rustc_mir_transform/messages.ftl13
-rw-r--r--compiler/rustc_mir_transform/src/check_unsafety.rs29
-rw-r--r--compiler/rustc_mir_transform/src/errors.rs125
-rw-r--r--tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.mir.stderr78
-rw-r--r--tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.rs20
-rw-r--r--tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.thir.stderr78
10 files changed, 374 insertions, 123 deletions
diff --git a/compiler/rustc_middle/src/mir/query.rs b/compiler/rustc_middle/src/mir/query.rs
index ab8fef5129b..98642adc190 100644
--- a/compiler/rustc_middle/src/mir/query.rs
+++ b/compiler/rustc_middle/src/mir/query.rs
@@ -27,7 +27,7 @@ pub enum UnsafetyViolationKind {
     UnsafeFn,
 }
 
-#[derive(Copy, Clone, PartialEq, TyEncodable, TyDecodable, HashStable, Debug)]
+#[derive(Clone, PartialEq, TyEncodable, TyDecodable, HashStable, Debug)]
 pub enum UnsafetyViolationDetails {
     CallToUnsafeFunction,
     UseOfInlineAssembly,
@@ -39,10 +39,17 @@ pub enum UnsafetyViolationDetails {
     AccessToUnionField,
     MutationOfLayoutConstrainedField,
     BorrowOfLayoutConstrainedField,
-    CallToFunctionWith,
+    CallToFunctionWith {
+        /// Target features enabled in callee's `#[target_feature]` but missing in
+        /// caller's `#[target_feature]`.
+        missing: Vec<Symbol>,
+        /// Target features in `missing` that are enabled at compile time
+        /// (e.g., with `-C target-feature`).
+        build_enabled: Vec<Symbol>,
+    },
 }
 
-#[derive(Copy, Clone, PartialEq, TyEncodable, TyDecodable, HashStable, Debug)]
+#[derive(Clone, PartialEq, TyEncodable, TyDecodable, HashStable, Debug)]
 pub struct UnsafetyViolation {
     pub source_info: SourceInfo,
     pub lint_root: hir::HirId,
diff --git a/compiler/rustc_mir_build/messages.ftl b/compiler/rustc_mir_build/messages.ftl
index 7dd0e7d4b92..c8d6c2114e9 100644
--- a/compiler/rustc_mir_build/messages.ftl
+++ b/compiler/rustc_mir_build/messages.ftl
@@ -30,12 +30,32 @@ mir_build_borrow_of_moved_value = borrow of moved value
 
 mir_build_call_to_fn_with_requires_unsafe =
     call to function `{$function}` with `#[target_feature]` is unsafe and requires unsafe block
-    .note = can only be called if the required target features are available
+    .help = in order for the call to be safe, the context requires the following additional target {$missing_target_features_count ->
+        [1] feature
+        *[count] features
+        }: {$missing_target_features}
+    .note = the {$build_target_features} target {$build_target_features_count ->
+        [1] feature
+        *[count] features
+        } being enabled in the build configuration does not remove the requirement to list {$build_target_features_count ->
+        [1] it
+        *[count] them
+        } in `#[target_feature]`
     .label = call to function with `#[target_feature]`
 
 mir_build_call_to_fn_with_requires_unsafe_unsafe_op_in_unsafe_fn_allowed =
     call to function `{$function}` with `#[target_feature]` is unsafe and requires unsafe function or block
-    .note = can only be called if the required target features are available
+    .help = in order for the call to be safe, the context requires the following additional target {$missing_target_features_count ->
+        [1] feature
+        *[count] features
+        }: {$missing_target_features}
+    .note = the {$build_target_features} target {$build_target_features_count ->
+        [1] feature
+        *[count] features
+        } being enabled in the build configuration does not remove the requirement to list {$build_target_features_count ->
+        [1] it
+        *[count] them
+        } in `#[target_feature]`
     .label = call to function with `#[target_feature]`
 
 mir_build_call_to_unsafe_fn_requires_unsafe =
@@ -330,7 +350,17 @@ mir_build_unsafe_op_in_unsafe_fn_borrow_of_layout_constrained_field_requires_uns
 
 mir_build_unsafe_op_in_unsafe_fn_call_to_fn_with_requires_unsafe =
     call to function `{$function}` with `#[target_feature]` is unsafe and requires unsafe block (error E0133)
-    .note = can only be called if the required target features are available
+    .help = in order for the call to be safe, the context requires the following additional target {$missing_target_features_count ->
+        [1] feature
+        *[count] features
+        }: {$missing_target_features}
+    .note = the {$build_target_features} target {$build_target_features_count ->
+        [1] feature
+        *[count] features
+        } being enabled in the build configuration does not remove the requirement to list {$build_target_features_count ->
+        [1] it
+        *[count] them
+        } in `#[target_feature]`
     .label = call to function with `#[target_feature]`
 
 mir_build_unsafe_op_in_unsafe_fn_call_to_unsafe_fn_requires_unsafe =
diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs
index ca532e3d292..c17fdf71b55 100644
--- a/compiler/rustc_mir_build/src/check_unsafety.rs
+++ b/compiler/rustc_mir_build/src/check_unsafety.rs
@@ -1,7 +1,10 @@
+use std::borrow::Cow;
+
 use crate::build::ExprCategory;
 use crate::errors::*;
 use rustc_middle::thir::visit::{self, Visitor};
 
+use rustc_errors::DiagnosticArgValue;
 use rustc_hir as hir;
 use rustc_middle::mir::BorrowKind;
 use rustc_middle::thir::*;
@@ -393,15 +396,29 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> {
                     // the call requires `unsafe`. Don't check this on wasm
                     // targets, though. For more information on wasm see the
                     // is_like_wasm check in hir_analysis/src/collect.rs
+                    let callee_features = &self.tcx.codegen_fn_attrs(func_did).target_features;
                     if !self.tcx.sess.target.options.is_like_wasm
-                        && !self
-                            .tcx
-                            .codegen_fn_attrs(func_did)
-                            .target_features
+                        && !callee_features
                             .iter()
                             .all(|feature| self.body_target_features.contains(feature))
                     {
-                        self.requires_unsafe(expr.span, CallToFunctionWith(func_did));
+                        let missing: Vec<_> = callee_features
+                            .iter()
+                            .copied()
+                            .filter(|feature| !self.body_target_features.contains(feature))
+                            .collect();
+                        let build_enabled = self
+                            .tcx
+                            .sess
+                            .target_features
+                            .iter()
+                            .copied()
+                            .filter(|feature| missing.contains(feature))
+                            .collect();
+                        self.requires_unsafe(
+                            expr.span,
+                            CallToFunctionWith { function: func_did, missing, build_enabled },
+                        );
                     }
                 }
             }
@@ -527,7 +544,7 @@ struct UnusedUnsafeWarning {
     enclosing_unsafe: Option<UnusedUnsafeEnclosing>,
 }
 
-#[derive(Clone, Copy, PartialEq)]
+#[derive(Clone, PartialEq)]
 enum UnsafeOpKind {
     CallToUnsafeFunction(Option<DefId>),
     UseOfInlineAssembly,
@@ -538,7 +555,15 @@ enum UnsafeOpKind {
     AccessToUnionField,
     MutationOfLayoutConstrainedField,
     BorrowOfLayoutConstrainedField,
-    CallToFunctionWith(DefId),
+    CallToFunctionWith {
+        function: DefId,
+        /// Target features enabled in callee's `#[target_feature]` but missing in
+        /// caller's `#[target_feature]`.
+        missing: Vec<Symbol>,
+        /// Target features in `missing` that are enabled at compile time
+        /// (e.g., with `-C target-feature`).
+        build_enabled: Vec<Symbol>,
+    },
 }
 
 use UnsafeOpKind::*;
@@ -659,13 +684,22 @@ impl UnsafeOpKind {
                     unsafe_not_inherited_note,
                 },
             ),
-            CallToFunctionWith(did) => tcx.emit_spanned_lint(
+            CallToFunctionWith { function, missing, build_enabled } => tcx.emit_spanned_lint(
                 UNSAFE_OP_IN_UNSAFE_FN,
                 hir_id,
                 span,
                 UnsafeOpInUnsafeFnCallToFunctionWithRequiresUnsafe {
                     span,
-                    function: &with_no_trimmed_paths!(tcx.def_path_str(*did)),
+                    function: &with_no_trimmed_paths!(tcx.def_path_str(*function)),
+                    missing_target_features: DiagnosticArgValue::StrListSepByAnd(
+                        missing.iter().map(|feature| Cow::from(feature.as_str())).collect(),
+                    ),
+                    missing_target_features_count: missing.len(),
+                    note: if build_enabled.is_empty() { None } else { Some(()) },
+                    build_target_features: DiagnosticArgValue::StrListSepByAnd(
+                        build_enabled.iter().map(|feature| Cow::from(feature.as_str())).collect(),
+                    ),
+                    build_target_features_count: build_enabled.len(),
                     unsafe_not_inherited_note,
                 },
             ),
@@ -822,18 +856,38 @@ impl UnsafeOpKind {
                     unsafe_not_inherited_note,
                 });
             }
-            CallToFunctionWith(did) if unsafe_op_in_unsafe_fn_allowed => {
+            CallToFunctionWith { function, missing, build_enabled }
+                if unsafe_op_in_unsafe_fn_allowed =>
+            {
                 tcx.sess.emit_err(CallToFunctionWithRequiresUnsafeUnsafeOpInUnsafeFnAllowed {
                     span,
+                    missing_target_features: DiagnosticArgValue::StrListSepByAnd(
+                        missing.iter().map(|feature| Cow::from(feature.as_str())).collect(),
+                    ),
+                    missing_target_features_count: missing.len(),
+                    note: if build_enabled.is_empty() { None } else { Some(()) },
+                    build_target_features: DiagnosticArgValue::StrListSepByAnd(
+                        build_enabled.iter().map(|feature| Cow::from(feature.as_str())).collect(),
+                    ),
+                    build_target_features_count: build_enabled.len(),
                     unsafe_not_inherited_note,
-                    function: &tcx.def_path_str(*did),
+                    function: &tcx.def_path_str(*function),
                 });
             }
-            CallToFunctionWith(did) => {
+            CallToFunctionWith { function, missing, build_enabled } => {
                 tcx.sess.emit_err(CallToFunctionWithRequiresUnsafe {
                     span,
+                    missing_target_features: DiagnosticArgValue::StrListSepByAnd(
+                        missing.iter().map(|feature| Cow::from(feature.as_str())).collect(),
+                    ),
+                    missing_target_features_count: missing.len(),
+                    note: if build_enabled.is_empty() { None } else { Some(()) },
+                    build_target_features: DiagnosticArgValue::StrListSepByAnd(
+                        build_enabled.iter().map(|feature| Cow::from(feature.as_str())).collect(),
+                    ),
+                    build_target_features_count: build_enabled.len(),
                     unsafe_not_inherited_note,
-                    function: &tcx.def_path_str(*did),
+                    function: &tcx.def_path_str(*function),
                 });
             }
         }
diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs
index 418f9bb9de9..8d2a559e73c 100644
--- a/compiler/rustc_mir_build/src/errors.rs
+++ b/compiler/rustc_mir_build/src/errors.rs
@@ -2,6 +2,7 @@ use crate::{
     fluent_generated as fluent,
     thir::pattern::{deconstruct_pat::WitnessPat, MatchCheckCtxt},
 };
+use rustc_errors::DiagnosticArgValue;
 use rustc_errors::{
     error_code, AddToDiagnostic, Applicability, Diagnostic, DiagnosticBuilder, ErrorGuaranteed,
     Handler, IntoDiagnostic, MultiSpan, SubdiagnosticMessage,
@@ -124,11 +125,17 @@ pub struct UnsafeOpInUnsafeFnBorrowOfLayoutConstrainedFieldRequiresUnsafe {
 
 #[derive(LintDiagnostic)]
 #[diag(mir_build_unsafe_op_in_unsafe_fn_call_to_fn_with_requires_unsafe)]
-#[note]
+#[help]
 pub struct UnsafeOpInUnsafeFnCallToFunctionWithRequiresUnsafe<'a> {
     #[label]
     pub span: Span,
     pub function: &'a str,
+    pub missing_target_features: DiagnosticArgValue<'a>,
+    pub missing_target_features_count: usize,
+    #[note]
+    pub note: Option<()>,
+    pub build_target_features: DiagnosticArgValue<'a>,
+    pub build_target_features_count: usize,
     #[subdiagnostic]
     pub unsafe_not_inherited_note: Option<UnsafeNotInheritedLintNote>,
 }
@@ -369,24 +376,36 @@ pub struct BorrowOfLayoutConstrainedFieldRequiresUnsafeUnsafeOpInUnsafeFnAllowed
 
 #[derive(Diagnostic)]
 #[diag(mir_build_call_to_fn_with_requires_unsafe, code = "E0133")]
-#[note]
+#[help]
 pub struct CallToFunctionWithRequiresUnsafe<'a> {
     #[primary_span]
     #[label]
     pub span: Span,
     pub function: &'a str,
+    pub missing_target_features: DiagnosticArgValue<'a>,
+    pub missing_target_features_count: usize,
+    #[note]
+    pub note: Option<()>,
+    pub build_target_features: DiagnosticArgValue<'a>,
+    pub build_target_features_count: usize,
     #[subdiagnostic]
     pub unsafe_not_inherited_note: Option<UnsafeNotInheritedNote>,
 }
 
 #[derive(Diagnostic)]
 #[diag(mir_build_call_to_fn_with_requires_unsafe_unsafe_op_in_unsafe_fn_allowed, code = "E0133")]
-#[note]
+#[help]
 pub struct CallToFunctionWithRequiresUnsafeUnsafeOpInUnsafeFnAllowed<'a> {
     #[primary_span]
     #[label]
     pub span: Span,
     pub function: &'a str,
+    pub missing_target_features: DiagnosticArgValue<'a>,
+    pub missing_target_features_count: usize,
+    #[note]
+    pub note: Option<()>,
+    pub build_target_features: DiagnosticArgValue<'a>,
+    pub build_target_features_count: usize,
     #[subdiagnostic]
     pub unsafe_not_inherited_note: Option<UnsafeNotInheritedNote>,
 }
diff --git a/compiler/rustc_mir_transform/messages.ftl b/compiler/rustc_mir_transform/messages.ftl
index 5a99afc45b0..b8dbdf18db3 100644
--- a/compiler/rustc_mir_transform/messages.ftl
+++ b/compiler/rustc_mir_transform/messages.ftl
@@ -42,8 +42,19 @@ mir_transform_requires_unsafe = {$details} is unsafe and requires unsafe {$op_in
     }
     .not_inherited = items do not inherit unsafety from separate enclosing items
 
+mir_transform_target_feature_call_help = in order for the call to be safe, the context requires the following additional target {$missing_target_features_count ->
+    [1] feature
+    *[count] features
+    }: {$missing_target_features}
+
 mir_transform_target_feature_call_label = call to function with `#[target_feature]`
-mir_transform_target_feature_call_note = can only be called if the required target features are available
+mir_transform_target_feature_call_note = the {$build_target_features} target {$build_target_features_count ->
+    [1] feature
+    *[count] features
+    } being enabled in the build configuration does not remove the requirement to list {$build_target_features_count ->
+    [1] it
+    *[count] them
+    } in `#[target_feature]`
 
 mir_transform_unaligned_packed_ref = reference to packed field is unaligned
     .note = packed structs are only aligned by one byte, and many modern architectures penalize unaligned field accesses
diff --git a/compiler/rustc_mir_transform/src/check_unsafety.rs b/compiler/rustc_mir_transform/src/check_unsafety.rs
index c63b24b194f..84e7362c3d0 100644
--- a/compiler/rustc_mir_transform/src/check_unsafety.rs
+++ b/compiler/rustc_mir_transform/src/check_unsafety.rs
@@ -287,19 +287,20 @@ impl<'tcx> UnsafetyChecker<'_, 'tcx> {
             .safety;
         match safety {
             // `unsafe` blocks are required in safe code
-            Safety::Safe => violations.into_iter().for_each(|&violation| {
+            Safety::Safe => violations.into_iter().for_each(|violation| {
                 match violation.kind {
                     UnsafetyViolationKind::General => {}
                     UnsafetyViolationKind::UnsafeFn => {
                         bug!("`UnsafetyViolationKind::UnsafeFn` in an `Safe` context")
                     }
                 }
-                if !self.violations.contains(&violation) {
-                    self.violations.push(violation)
+                if !self.violations.contains(violation) {
+                    self.violations.push(violation.clone())
                 }
             }),
             // With the RFC 2585, no longer allow `unsafe` operations in `unsafe fn`s
-            Safety::FnUnsafe => violations.into_iter().for_each(|&(mut violation)| {
+            Safety::FnUnsafe => violations.into_iter().for_each(|violation| {
+                let mut violation = violation.clone();
                 violation.kind = UnsafetyViolationKind::UnsafeFn;
                 if !self.violations.contains(&violation) {
                     self.violations.push(violation)
@@ -367,9 +368,22 @@ impl<'tcx> UnsafetyChecker<'_, 'tcx> {
 
         // Is `callee_features` a subset of `calling_features`?
         if !callee_features.iter().all(|feature| self_features.contains(feature)) {
+            let missing: Vec<_> = callee_features
+                .iter()
+                .copied()
+                .filter(|feature| !self_features.contains(feature))
+                .collect();
+            let build_enabled = self
+                .tcx
+                .sess
+                .target_features
+                .iter()
+                .copied()
+                .filter(|feature| missing.contains(feature))
+                .collect();
             self.require_unsafe(
                 UnsafetyViolationKind::General,
-                UnsafetyViolationDetails::CallToFunctionWith,
+                UnsafetyViolationDetails::CallToFunctionWith { missing, build_enabled },
             )
         }
     }
@@ -528,8 +542,9 @@ pub fn check_unsafety(tcx: TyCtxt<'_>, def_id: LocalDefId) {
     // Only suggest wrapping the entire function body in an unsafe block once
     let mut suggest_unsafe_block = true;
 
-    for &UnsafetyViolation { source_info, lint_root, kind, details } in violations.iter() {
-        let details = errors::RequiresUnsafeDetail { violation: details, span: source_info.span };
+    for &UnsafetyViolation { source_info, lint_root, kind, ref details } in violations.iter() {
+        let details =
+            errors::RequiresUnsafeDetail { violation: details.clone(), span: source_info.span };
 
         match kind {
             UnsafetyViolationKind::General => {
diff --git a/compiler/rustc_mir_transform/src/errors.rs b/compiler/rustc_mir_transform/src/errors.rs
index 5879a803946..3a5270f105a 100644
--- a/compiler/rustc_mir_transform/src/errors.rs
+++ b/compiler/rustc_mir_transform/src/errors.rs
@@ -1,6 +1,8 @@
+use std::borrow::Cow;
+
 use rustc_errors::{
-    Applicability, DecorateLint, DiagnosticBuilder, DiagnosticMessage, EmissionGuarantee, Handler,
-    IntoDiagnostic,
+    Applicability, DecorateLint, DiagnosticArgValue, DiagnosticBuilder, DiagnosticMessage,
+    EmissionGuarantee, Handler, IntoDiagnostic,
 };
 use rustc_macros::{Diagnostic, LintDiagnostic, Subdiagnostic};
 use rustc_middle::mir::{AssertKind, UnsafetyViolationDetails};
@@ -9,6 +11,8 @@ use rustc_session::lint::{self, Lint};
 use rustc_span::def_id::DefId;
 use rustc_span::Span;
 
+use crate::fluent_generated as fluent;
+
 #[derive(LintDiagnostic)]
 pub(crate) enum ConstMutate {
     #[diag(mir_transform_const_modify)]
@@ -61,72 +65,105 @@ pub(crate) struct RequiresUnsafe {
 impl<'sess, G: EmissionGuarantee> IntoDiagnostic<'sess, G> for RequiresUnsafe {
     #[track_caller]
     fn into_diagnostic(self, handler: &'sess Handler) -> DiagnosticBuilder<'sess, G> {
-        let mut diag =
-            handler.struct_diagnostic(crate::fluent_generated::mir_transform_requires_unsafe);
+        let mut diag = handler.struct_diagnostic(fluent::mir_transform_requires_unsafe);
         diag.code(rustc_errors::DiagnosticId::Error("E0133".to_string()));
         diag.set_span(self.span);
         diag.span_label(self.span, self.details.label());
-        diag.note(self.details.note());
         let desc = handler.eagerly_translate_to_string(self.details.label(), [].into_iter());
         diag.set_arg("details", desc);
         diag.set_arg("op_in_unsafe_fn_allowed", self.op_in_unsafe_fn_allowed);
+        self.details.add_subdiagnostics(&mut diag);
         if let Some(sp) = self.enclosing {
-            diag.span_label(sp, crate::fluent_generated::mir_transform_not_inherited);
+            diag.span_label(sp, fluent::mir_transform_not_inherited);
         }
         diag
     }
 }
 
-#[derive(Copy, Clone)]
+#[derive(Clone)]
 pub(crate) struct RequiresUnsafeDetail {
     pub span: Span,
     pub violation: UnsafetyViolationDetails,
 }
 
 impl RequiresUnsafeDetail {
-    fn note(self) -> DiagnosticMessage {
+    fn add_subdiagnostics<G: EmissionGuarantee>(&self, diag: &mut DiagnosticBuilder<'_, G>) {
         use UnsafetyViolationDetails::*;
         match self.violation {
-            CallToUnsafeFunction => crate::fluent_generated::mir_transform_call_to_unsafe_note,
-            UseOfInlineAssembly => crate::fluent_generated::mir_transform_use_of_asm_note,
+            CallToUnsafeFunction => {
+                diag.note(fluent::mir_transform_call_to_unsafe_note);
+            }
+            UseOfInlineAssembly => {
+                diag.note(fluent::mir_transform_use_of_asm_note);
+            }
             InitializingTypeWith => {
-                crate::fluent_generated::mir_transform_initializing_valid_range_note
+                diag.note(fluent::mir_transform_initializing_valid_range_note);
+            }
+            CastOfPointerToInt => {
+                diag.note(fluent::mir_transform_const_ptr2int_note);
+            }
+            UseOfMutableStatic => {
+                diag.note(fluent::mir_transform_use_of_static_mut_note);
+            }
+            UseOfExternStatic => {
+                diag.note(fluent::mir_transform_use_of_extern_static_note);
+            }
+            DerefOfRawPointer => {
+                diag.note(fluent::mir_transform_deref_ptr_note);
+            }
+            AccessToUnionField => {
+                diag.note(fluent::mir_transform_union_access_note);
             }
-            CastOfPointerToInt => crate::fluent_generated::mir_transform_const_ptr2int_note,
-            UseOfMutableStatic => crate::fluent_generated::mir_transform_use_of_static_mut_note,
-            UseOfExternStatic => crate::fluent_generated::mir_transform_use_of_extern_static_note,
-            DerefOfRawPointer => crate::fluent_generated::mir_transform_deref_ptr_note,
-            AccessToUnionField => crate::fluent_generated::mir_transform_union_access_note,
             MutationOfLayoutConstrainedField => {
-                crate::fluent_generated::mir_transform_mutation_layout_constrained_note
+                diag.note(fluent::mir_transform_mutation_layout_constrained_note);
             }
             BorrowOfLayoutConstrainedField => {
-                crate::fluent_generated::mir_transform_mutation_layout_constrained_borrow_note
+                diag.note(fluent::mir_transform_mutation_layout_constrained_borrow_note);
+            }
+            CallToFunctionWith { ref missing, ref build_enabled } => {
+                diag.help(fluent::mir_transform_target_feature_call_help);
+                diag.set_arg(
+                    "missing_target_features",
+                    DiagnosticArgValue::StrListSepByAnd(
+                        missing.iter().map(|feature| Cow::from(feature.as_str())).collect(),
+                    ),
+                );
+                diag.set_arg("missing_target_features_count", missing.len());
+                if !build_enabled.is_empty() {
+                    diag.note(fluent::mir_transform_target_feature_call_note);
+                    diag.set_arg(
+                        "build_target_features",
+                        DiagnosticArgValue::StrListSepByAnd(
+                            build_enabled
+                                .iter()
+                                .map(|feature| Cow::from(feature.as_str()))
+                                .collect(),
+                        ),
+                    );
+                    diag.set_arg("build_target_features_count", build_enabled.len());
+                }
             }
-            CallToFunctionWith => crate::fluent_generated::mir_transform_target_feature_call_note,
         }
     }
 
-    fn label(self) -> DiagnosticMessage {
+    fn label(&self) -> DiagnosticMessage {
         use UnsafetyViolationDetails::*;
         match self.violation {
-            CallToUnsafeFunction => crate::fluent_generated::mir_transform_call_to_unsafe_label,
-            UseOfInlineAssembly => crate::fluent_generated::mir_transform_use_of_asm_label,
-            InitializingTypeWith => {
-                crate::fluent_generated::mir_transform_initializing_valid_range_label
-            }
-            CastOfPointerToInt => crate::fluent_generated::mir_transform_const_ptr2int_label,
-            UseOfMutableStatic => crate::fluent_generated::mir_transform_use_of_static_mut_label,
-            UseOfExternStatic => crate::fluent_generated::mir_transform_use_of_extern_static_label,
-            DerefOfRawPointer => crate::fluent_generated::mir_transform_deref_ptr_label,
-            AccessToUnionField => crate::fluent_generated::mir_transform_union_access_label,
+            CallToUnsafeFunction => fluent::mir_transform_call_to_unsafe_label,
+            UseOfInlineAssembly => fluent::mir_transform_use_of_asm_label,
+            InitializingTypeWith => fluent::mir_transform_initializing_valid_range_label,
+            CastOfPointerToInt => fluent::mir_transform_const_ptr2int_label,
+            UseOfMutableStatic => fluent::mir_transform_use_of_static_mut_label,
+            UseOfExternStatic => fluent::mir_transform_use_of_extern_static_label,
+            DerefOfRawPointer => fluent::mir_transform_deref_ptr_label,
+            AccessToUnionField => fluent::mir_transform_union_access_label,
             MutationOfLayoutConstrainedField => {
-                crate::fluent_generated::mir_transform_mutation_layout_constrained_label
+                fluent::mir_transform_mutation_layout_constrained_label
             }
             BorrowOfLayoutConstrainedField => {
-                crate::fluent_generated::mir_transform_mutation_layout_constrained_borrow_label
+                fluent::mir_transform_mutation_layout_constrained_borrow_label
             }
-            CallToFunctionWith => crate::fluent_generated::mir_transform_target_feature_call_label,
+            CallToFunctionWith { .. } => fluent::mir_transform_target_feature_call_label,
         }
     }
 }
@@ -151,12 +188,12 @@ impl<'a> DecorateLint<'a, ()> for UnsafeOpInUnsafeFn {
         let desc = handler.eagerly_translate_to_string(self.details.label(), [].into_iter());
         diag.set_arg("details", desc);
         diag.span_label(self.details.span, self.details.label());
-        diag.note(self.details.note());
+        self.details.add_subdiagnostics(diag);
 
         if let Some((start, end, fn_sig)) = self.suggest_unsafe_block {
-            diag.span_note(fn_sig, crate::fluent_generated::mir_transform_note);
+            diag.span_note(fn_sig, fluent::mir_transform_note);
             diag.tool_only_multipart_suggestion(
-                crate::fluent_generated::mir_transform_suggestion,
+                fluent::mir_transform_suggestion,
                 vec![(start, " unsafe {".into()), (end, "}".into())],
                 Applicability::MaybeIncorrect,
             );
@@ -166,7 +203,7 @@ impl<'a> DecorateLint<'a, ()> for UnsafeOpInUnsafeFn {
     }
 
     fn msg(&self) -> DiagnosticMessage {
-        crate::fluent_generated::mir_transform_unsafe_op_in_unsafe_fn
+        fluent::mir_transform_unsafe_op_in_unsafe_fn
     }
 }
 
@@ -193,12 +230,8 @@ impl<'a, P: std::fmt::Debug> DecorateLint<'a, ()> for AssertLint<P> {
 
     fn msg(&self) -> DiagnosticMessage {
         match self {
-            AssertLint::ArithmeticOverflow(..) => {
-                crate::fluent_generated::mir_transform_arithmetic_overflow
-            }
-            AssertLint::UnconditionalPanic(..) => {
-                crate::fluent_generated::mir_transform_operation_will_panic
-            }
+            AssertLint::ArithmeticOverflow(..) => fluent::mir_transform_arithmetic_overflow,
+            AssertLint::UnconditionalPanic(..) => fluent::mir_transform_operation_will_panic,
         }
     }
 }
@@ -255,11 +288,11 @@ impl<'a> DecorateLint<'a, ()> for MustNotSupend<'_, '_> {
         self,
         diag: &'b mut rustc_errors::DiagnosticBuilder<'a, ()>,
     ) -> &'b mut rustc_errors::DiagnosticBuilder<'a, ()> {
-        diag.span_label(self.yield_sp, crate::fluent_generated::_subdiag::label);
+        diag.span_label(self.yield_sp, fluent::_subdiag::label);
         if let Some(reason) = self.reason {
             diag.subdiagnostic(reason);
         }
-        diag.span_help(self.src_sp, crate::fluent_generated::_subdiag::help);
+        diag.span_help(self.src_sp, fluent::_subdiag::help);
         diag.set_arg("pre", self.pre);
         diag.set_arg("def_path", self.tcx.def_path_str(self.def_id));
         diag.set_arg("post", self.post);
@@ -267,7 +300,7 @@ impl<'a> DecorateLint<'a, ()> for MustNotSupend<'_, '_> {
     }
 
     fn msg(&self) -> rustc_errors::DiagnosticMessage {
-        crate::fluent_generated::mir_transform_must_not_suspend
+        fluent::mir_transform_must_not_suspend
     }
 }
 
diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.mir.stderr b/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.mir.stderr
index 0ef7b8b09f1..cabc475fa61 100644
--- a/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.mir.stderr
+++ b/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.mir.stderr
@@ -1,83 +1,115 @@
 error[E0133]: call to function with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:23:5
+  --> $DIR/safe-calls.rs:28:5
    |
 LL |     sse2();
    |     ^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: sse2
+   = note: the sse2 target feature being enabled in the build configuration does not remove the requirement to list it in `#[target_feature]`
 
 error[E0133]: call to function with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:26:5
+  --> $DIR/safe-calls.rs:31:5
    |
 LL |     avx_bmi2();
    |     ^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target features: avx and bmi2
 
 error[E0133]: call to function with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:29:5
+  --> $DIR/safe-calls.rs:34:5
    |
 LL |     Quux.avx_bmi2();
    |     ^^^^^^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target features: avx and bmi2
 
 error[E0133]: call to function with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:36:5
+  --> $DIR/safe-calls.rs:41:5
    |
 LL |     avx_bmi2();
    |     ^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target features: avx and bmi2
 
 error[E0133]: call to function with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:39:5
+  --> $DIR/safe-calls.rs:44:5
    |
 LL |     Quux.avx_bmi2();
    |     ^^^^^^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target features: avx and bmi2
 
 error[E0133]: call to function with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:46:5
+  --> $DIR/safe-calls.rs:51:5
    |
 LL |     sse2();
    |     ^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: sse2
+   = note: the sse2 target feature being enabled in the build configuration does not remove the requirement to list it in `#[target_feature]`
 
 error[E0133]: call to function with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:49:5
+  --> $DIR/safe-calls.rs:54:5
    |
 LL |     avx_bmi2();
    |     ^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: bmi2
 
 error[E0133]: call to function with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:52:5
+  --> $DIR/safe-calls.rs:57:5
    |
 LL |     Quux.avx_bmi2();
    |     ^^^^^^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: bmi2
 
 error[E0133]: call to function with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:60:5
+  --> $DIR/safe-calls.rs:65:5
    |
 LL |     sse2();
    |     ^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: sse2
+   = note: the sse2 target feature being enabled in the build configuration does not remove the requirement to list it in `#[target_feature]`
 
 error[E0133]: call to function with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:65:18
+  --> $DIR/safe-calls.rs:70:15
    |
-LL | const name: () = sse2();
-   |                  ^^^^^^ call to function with `#[target_feature]`
+LL | const _: () = sse2();
+   |               ^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: sse2
+   = note: the sse2 target feature being enabled in the build configuration does not remove the requirement to list it in `#[target_feature]`
 
-error: aborting due to 10 previous errors
+error[E0133]: call to function with `#[target_feature]` is unsafe and requires unsafe function or block
+  --> $DIR/safe-calls.rs:74:15
+   |
+LL | const _: () = sse2_and_fxsr();
+   |               ^^^^^^^^^^^^^^^ call to function with `#[target_feature]`
+   |
+   = help: in order for the call to be safe, the context requires the following additional target features: sse2 and fxsr
+   = note: the fxsr and sse2 target features being enabled in the build configuration does not remove the requirement to list them in `#[target_feature]`
+
+error: call to function with `#[target_feature]` is unsafe and requires unsafe block (error E0133)
+  --> $DIR/safe-calls.rs:82:5
+   |
+LL |     sse2();
+   |     ^^^^^^ call to function with `#[target_feature]`
+   |
+   = help: in order for the call to be safe, the context requires the following additional target feature: sse2
+   = note: the sse2 target feature being enabled in the build configuration does not remove the requirement to list it in `#[target_feature]`
+note: an unsafe function restricts its caller, but its body is safe by default
+  --> $DIR/safe-calls.rs:81:1
+   |
+LL | unsafe fn needs_unsafe_block() {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+note: the lint level is defined here
+  --> $DIR/safe-calls.rs:78:8
+   |
+LL | #[deny(unsafe_op_in_unsafe_fn)]
+   |        ^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 12 previous errors
 
 For more information about this error, try `rustc --explain E0133`.
diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.rs b/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.rs
index cebc6f94784..f17dab269bc 100644
--- a/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.rs
+++ b/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.rs
@@ -7,6 +7,11 @@
 #[target_feature(enable = "sse2")]
 const fn sse2() {}
 
+#[target_feature(enable = "sse2")]
+#[target_feature(enable = "fxsr")]
+const fn sse2_and_fxsr() {}
+
+
 #[target_feature(enable = "avx")]
 #[target_feature(enable = "bmi2")]
 fn avx_bmi2() {}
@@ -62,8 +67,21 @@ fn qux() {
     //[thir]~^^ ERROR call to function `sse2` with `#[target_feature]` is unsafe
 }
 
-const name: () = sse2();
+const _: () = sse2();
 //[mir]~^ ERROR call to function with `#[target_feature]` is unsafe
 //[thir]~^^ ERROR call to function `sse2` with `#[target_feature]` is unsafe
 
+const _: () = sse2_and_fxsr();
+//[mir]~^ ERROR call to function with `#[target_feature]` is unsafe
+//[thir]~^^ ERROR call to function `sse2_and_fxsr` with `#[target_feature]` is unsafe
+
+#[deny(unsafe_op_in_unsafe_fn)]
+#[target_feature(enable = "avx")]
+#[target_feature(enable = "bmi2")]
+unsafe fn needs_unsafe_block() {
+    sse2();
+    //[mir]~^ ERROR call to function with `#[target_feature]` is unsafe
+    //[thir]~^^ ERROR call to function `sse2` with `#[target_feature]` is unsafe
+}
+
 fn main() {}
diff --git a/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.thir.stderr b/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.thir.stderr
index c75ac6e8b9a..13b58fde862 100644
--- a/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.thir.stderr
+++ b/tests/ui/rfcs/rfc-2396-target_feature-11/safe-calls.thir.stderr
@@ -1,83 +1,115 @@
 error[E0133]: call to function `sse2` with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:23:5
+  --> $DIR/safe-calls.rs:28:5
    |
 LL |     sse2();
    |     ^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: sse2
+   = note: the sse2 target feature being enabled in the build configuration does not remove the requirement to list it in `#[target_feature]`
 
 error[E0133]: call to function `avx_bmi2` with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:26:5
+  --> $DIR/safe-calls.rs:31:5
    |
 LL |     avx_bmi2();
    |     ^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target features: avx and bmi2
 
 error[E0133]: call to function `Quux::avx_bmi2` with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:29:5
+  --> $DIR/safe-calls.rs:34:5
    |
 LL |     Quux.avx_bmi2();
    |     ^^^^^^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target features: avx and bmi2
 
 error[E0133]: call to function `avx_bmi2` with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:36:5
+  --> $DIR/safe-calls.rs:41:5
    |
 LL |     avx_bmi2();
    |     ^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target features: avx and bmi2
 
 error[E0133]: call to function `Quux::avx_bmi2` with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:39:5
+  --> $DIR/safe-calls.rs:44:5
    |
 LL |     Quux.avx_bmi2();
    |     ^^^^^^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target features: avx and bmi2
 
 error[E0133]: call to function `sse2` with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:46:5
+  --> $DIR/safe-calls.rs:51:5
    |
 LL |     sse2();
    |     ^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: sse2
+   = note: the sse2 target feature being enabled in the build configuration does not remove the requirement to list it in `#[target_feature]`
 
 error[E0133]: call to function `avx_bmi2` with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:49:5
+  --> $DIR/safe-calls.rs:54:5
    |
 LL |     avx_bmi2();
    |     ^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: bmi2
 
 error[E0133]: call to function `Quux::avx_bmi2` with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:52:5
+  --> $DIR/safe-calls.rs:57:5
    |
 LL |     Quux.avx_bmi2();
    |     ^^^^^^^^^^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: bmi2
 
 error[E0133]: call to function `sse2` with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:60:5
+  --> $DIR/safe-calls.rs:65:5
    |
 LL |     sse2();
    |     ^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: sse2
+   = note: the sse2 target feature being enabled in the build configuration does not remove the requirement to list it in `#[target_feature]`
 
 error[E0133]: call to function `sse2` with `#[target_feature]` is unsafe and requires unsafe function or block
-  --> $DIR/safe-calls.rs:65:18
+  --> $DIR/safe-calls.rs:70:15
    |
-LL | const name: () = sse2();
-   |                  ^^^^^^ call to function with `#[target_feature]`
+LL | const _: () = sse2();
+   |               ^^^^^^ call to function with `#[target_feature]`
    |
-   = note: can only be called if the required target features are available
+   = help: in order for the call to be safe, the context requires the following additional target feature: sse2
+   = note: the sse2 target feature being enabled in the build configuration does not remove the requirement to list it in `#[target_feature]`
 
-error: aborting due to 10 previous errors
+error[E0133]: call to function `sse2_and_fxsr` with `#[target_feature]` is unsafe and requires unsafe function or block
+  --> $DIR/safe-calls.rs:74:15
+   |
+LL | const _: () = sse2_and_fxsr();
+   |               ^^^^^^^^^^^^^^^ call to function with `#[target_feature]`
+   |
+   = help: in order for the call to be safe, the context requires the following additional target features: sse2 and fxsr
+   = note: the fxsr and sse2 target features being enabled in the build configuration does not remove the requirement to list them in `#[target_feature]`
+
+error: call to function `sse2` with `#[target_feature]` is unsafe and requires unsafe block (error E0133)
+  --> $DIR/safe-calls.rs:82:5
+   |
+LL |     sse2();
+   |     ^^^^^^ call to function with `#[target_feature]`
+   |
+   = help: in order for the call to be safe, the context requires the following additional target feature: sse2
+   = note: the sse2 target feature being enabled in the build configuration does not remove the requirement to list it in `#[target_feature]`
+note: an unsafe function restricts its caller, but its body is safe by default
+  --> $DIR/safe-calls.rs:81:1
+   |
+LL | unsafe fn needs_unsafe_block() {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+note: the lint level is defined here
+  --> $DIR/safe-calls.rs:78:8
+   |
+LL | #[deny(unsafe_op_in_unsafe_fn)]
+   |        ^^^^^^^^^^^^^^^^^^^^^^
+
+error: aborting due to 12 previous errors
 
 For more information about this error, try `rustc --explain E0133`.