Skip to content

Commit 2434969

Browse files
committed
Improve diagnostics for the use of unstable library features
- only emits one error/lint (instead of one per missing feature) per usage of unstable and body-unstable items - only emits one future-name-collision lint (instead of one per missing feature) for unstable trait items - makes diagnostics for unstable, soft-unstable, const-unstable, and body-unstable library features translatable, using common subdiagnostic structs. - adds notes with features, reasons, and issue links to const-unstability errors - adds notes with issue links to soft_unstable lints - on nightly, adds `#![feature]` crate attr help to soft_unstable lints - on nightly, adds compiler-upgrade-suggestion notes to const-unstable and soft_unstable diagnostics
1 parent 8f284de commit 2434969

39 files changed

+505
-318
lines changed

compiler/rustc_const_eval/src/check_consts/check.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -716,7 +716,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
716716
let feature_gate_declared = gate_declared(feature);
717717
let implied_gate_declared = implied_by.is_some_and(gate_declared);
718718
if !feature_gate_declared && !implied_gate_declared {
719-
bad_gates.push(feature);
719+
bad_gates.push(gate.into());
720720
continue;
721721
}
722722

@@ -735,7 +735,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
735735
continue;
736736
}
737737

738-
bad_gates.push(feature);
738+
bad_gates.push(gate.into());
739739
}
740740
if !bad_gates.is_empty() {
741741
self.check_op(ops::FnCallUnstable(callee, bad_gates));

compiler/rustc_const_eval/src/check_consts/ops.rs

+26-18
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use rustc_hir as hir;
88
use rustc_hir::def_id::DefId;
99
use rustc_infer::infer::TyCtxtInferExt;
1010
use rustc_infer::traits::{ImplSource, Obligation, ObligationCause};
11+
use rustc_middle::middle::stability;
1112
use rustc_middle::mir::CallSource;
1213
use rustc_middle::span_bug;
1314
use rustc_middle::ty::print::{PrintTraitRefExt as _, with_no_trimmed_paths};
@@ -298,28 +299,35 @@ impl<'tcx> NonConstOp<'tcx> for FnCallNonConst<'tcx> {
298299
///
299300
/// Contains the names of the features that would allow the use of this function.
300301
#[derive(Debug)]
301-
pub(crate) struct FnCallUnstable(pub DefId, pub Vec<Symbol>);
302+
pub(crate) struct FnCallUnstable(pub DefId, pub Vec<stability::EvalDenial>);
302303

303304
impl<'tcx> NonConstOp<'tcx> for FnCallUnstable {
304305
fn build_error(&self, ccx: &ConstCx<'_, 'tcx>, span: Span) -> Diag<'tcx> {
305-
let FnCallUnstable(def_id, features) = self;
306-
307-
let mut err = ccx
308-
.dcx()
309-
.create_err(errors::UnstableConstFn { span, def_path: ccx.tcx.def_path_str(def_id) });
310-
311-
// FIXME: make this translatable
312-
#[allow(rustc::untranslatable_diagnostic)]
313-
if ccx.is_const_stable_const_fn() {
314-
err.help(fluent_generated::const_eval_const_stable);
315-
} else if ccx.tcx.sess.is_nightly_build() && !features.is_empty() {
316-
err.help(format!(
317-
"add `#![feature({})]` to the crate attributes to enable",
318-
features.iter().map(Symbol::as_str).intersperse(", ").collect::<String>(),
319-
));
320-
}
306+
let FnCallUnstable(def_id, denials) = self;
321307

322-
err
308+
let in_const_stable_context = ccx.is_const_stable_const_fn();
309+
// Only report unstable features when they're present.
310+
let (features, info) = if denials.is_empty() {
311+
(None, vec![])
312+
} else {
313+
let (features, info) = stability::unstable_notes(&denials);
314+
(Some(features), info)
315+
};
316+
// Only suggest adding `#![feature]` if it could help.
317+
let nightly_subdiags = if in_const_stable_context || denials.is_empty() {
318+
vec![]
319+
} else {
320+
stability::unstable_nightly_subdiags(&ccx.tcx.sess, &denials, None)
321+
};
322+
323+
ccx.dcx().create_err(errors::UnstableConstFn {
324+
span,
325+
def_path: ccx.tcx.def_path_str(def_id),
326+
in_const_stable_context,
327+
features,
328+
info,
329+
nightly_subdiags,
330+
})
323331
}
324332
}
325333

compiler/rustc_const_eval/src/errors.rs

+8
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,14 @@ pub(crate) struct UnstableConstFn {
115115
#[primary_span]
116116
pub span: Span,
117117
pub def_path: String,
118+
#[help(const_eval_const_stable)]
119+
pub in_const_stable_context: bool,
120+
#[subdiagnostic]
121+
pub features: Option<rustc_middle::error::UnstableLibraryFeatureNote>,
122+
#[subdiagnostic]
123+
pub info: Vec<rustc_middle::error::UnstableLibraryFeatureInfo>,
124+
#[subdiagnostic]
125+
pub nightly_subdiags: Vec<rustc_session::errors::NightlyFeatureDiagnostic>,
118126
}
119127

120128
#[derive(Diagnostic)]

compiler/rustc_hir_analysis/messages.ftl

-2
Original file line numberDiff line numberDiff line change
@@ -309,8 +309,6 @@ hir_analysis_missing_trait_item_suggestion = implement the missing item: `{$snip
309309
310310
hir_analysis_missing_trait_item_unstable = not all trait items implemented, missing: `{$missing_item_name}`
311311
.note = default implementation of `{$missing_item_name}` is unstable
312-
.some_note = use of unstable library feature `{$feature}`: {$reason}
313-
.none_note = use of unstable library feature `{$feature}`
314312
315313
hir_analysis_missing_type_params =
316314
the type {$parameterCount ->

compiler/rustc_hir_analysis/src/check/check.rs

+1-8
Original file line numberDiff line numberDiff line change
@@ -933,14 +933,7 @@ fn check_impl_items_against_trait<'tcx>(
933933
let full_impl_span = tcx.hir().span_with_body(tcx.local_def_id_to_hir_id(impl_id));
934934
match tcx.eval_default_body_stability(trait_item_id, full_impl_span) {
935935
EvalResult::Deny { denials, .. } => {
936-
for denial in denials {
937-
default_body_is_unstable(
938-
tcx,
939-
full_impl_span,
940-
trait_item_id,
941-
denial.unstability,
942-
);
943-
}
936+
default_body_is_unstable(tcx, full_impl_span, trait_item_id, &denials);
944937
}
945938

946939
// Unmarked default bodies are considered stable (at least for now).

compiler/rustc_hir_analysis/src/check/mod.rs

+11-29
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ use rustc_index::bit_set::BitSet;
8080
use rustc_infer::infer::outlives::env::OutlivesEnvironment;
8181
use rustc_infer::infer::{self, TyCtxtInferExt as _};
8282
use rustc_infer::traits::ObligationCause;
83+
use rustc_middle::middle::stability;
8384
use rustc_middle::query::Providers;
8485
use rustc_middle::ty::error::{ExpectedFound, TypeError};
8586
use rustc_middle::ty::{self, GenericArgs, GenericArgsRef, Ty, TyCtxt};
@@ -293,41 +294,22 @@ fn default_body_is_unstable(
293294
tcx: TyCtxt<'_>,
294295
impl_span: Span,
295296
item_did: DefId,
296-
unstability: rustc_attr::Unstability,
297+
denials: &[stability::EvalDenial],
297298
) {
298-
let rustc_attr::Unstability { feature, reason, issue, .. } = unstability;
299299
let missing_item_name = tcx.associated_item(item_did).name;
300-
let (mut some_note, mut none_note, mut reason_str) = (false, false, String::new());
301-
match reason.to_opt_reason() {
302-
Some(r) => {
303-
some_note = true;
304-
reason_str = r.to_string();
305-
}
306-
None => none_note = true,
307-
};
308-
309-
let mut err = tcx.dcx().create_err(errors::MissingTraitItemUnstable {
310-
span: impl_span,
311-
some_note,
312-
none_note,
313-
missing_item_name,
314-
feature,
315-
reason: reason_str,
316-
});
317-
318300
let inject_span = item_did
319301
.as_local()
320302
.and_then(|id| tcx.crate_level_attribute_injection_span(tcx.local_def_id_to_hir_id(id)));
321-
rustc_session::parse::add_feature_diagnostics_for_issue(
322-
&mut err,
323-
&tcx.sess,
324-
feature,
325-
rustc_feature::GateIssue::Library(issue),
326-
false,
327-
inject_span,
328-
);
303+
let (features, info) = stability::unstable_notes(denials);
304+
let nightly_subdiags = stability::unstable_nightly_subdiags(&tcx.sess, denials, inject_span);
329305

330-
err.emit();
306+
tcx.dcx().emit_err(errors::MissingTraitItemUnstable {
307+
span: impl_span,
308+
missing_item_name,
309+
features,
310+
info,
311+
nightly_subdiags,
312+
});
331313
}
332314

333315
/// Re-sugar `ty::GenericPredicates` in a way suitable to be used in structured suggestions.

compiler/rustc_hir_analysis/src/errors.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -977,13 +977,13 @@ pub(crate) struct MissingOneOfTraitItem {
977977
pub(crate) struct MissingTraitItemUnstable {
978978
#[primary_span]
979979
pub span: Span,
980-
#[note(hir_analysis_some_note)]
981-
pub some_note: bool,
982-
#[note(hir_analysis_none_note)]
983-
pub none_note: bool,
984980
pub missing_item_name: Symbol,
985-
pub feature: Symbol,
986-
pub reason: String,
981+
#[subdiagnostic]
982+
pub features: rustc_middle::error::UnstableLibraryFeatureNote,
983+
#[subdiagnostic]
984+
pub info: Vec<rustc_middle::error::UnstableLibraryFeatureInfo>,
985+
#[subdiagnostic]
986+
pub nightly_subdiags: Vec<rustc_session::errors::NightlyFeatureDiagnostic>,
987987
}
988988

989989
#[derive(Diagnostic)]

compiler/rustc_hir_typeck/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![feature(array_windows)]
55
#![feature(box_patterns)]
66
#![feature(if_let_guard)]
7+
#![feature(iter_intersperse)]
78
#![feature(let_chains)]
89
#![feature(never_type)]
910
#![feature(try_blocks)]

compiler/rustc_hir_typeck/src/method/probe.rs

+6-8
Original file line numberDiff line numberDiff line change
@@ -1329,10 +1329,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> {
13291329
if let stability::EvalResult::Deny { denials, .. } =
13301330
self.tcx.eval_stability(candidate.item.def_id, None, self.span, None)
13311331
{
1332-
uc.push((
1333-
candidate.clone(),
1334-
denials.iter().map(|d| d.unstability.feature).collect(),
1335-
));
1332+
uc.push((candidate.clone(), denials.iter().map(|d| d.feature).collect()));
13361333
return false;
13371334
}
13381335
true
@@ -1431,10 +1428,11 @@ impl<'tcx> Pick<'tcx> {
14311428
tcx.disabled_nightly_features(
14321429
lint,
14331430
Some(scope_expr_id),
1434-
self.unstable_candidates.iter().flat_map(|(candidate, features)| {
1435-
features.iter().map(|feature| {
1436-
(format!(" `{}`", tcx.def_path_str(candidate.item.def_id)), *feature)
1437-
})
1431+
self.unstable_candidates.iter().map(|(candidate, features)| {
1432+
(
1433+
format!(" `{}`", tcx.def_path_str(candidate.item.def_id)),
1434+
features.iter().map(Symbol::as_str).intersperse(", ").collect::<String>(),
1435+
)
14381436
}),
14391437
);
14401438
});

compiler/rustc_lint/src/context/diagnostics.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -382,8 +382,12 @@ pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, diag: &
382382
BuiltinLintDiag::MacroRuleNeverUsed(n, name) => {
383383
lints::MacroRuleNeverUsed { n: n + 1, name }.decorate_lint(diag);
384384
}
385-
BuiltinLintDiag::UnstableFeature(msg) => {
386-
lints::UnstableFeature { msg }.decorate_lint(diag);
385+
BuiltinLintDiag::SoftUnstableMacro { features } => {
386+
let denials = features
387+
.iter()
388+
.map(|&(feature, reason, issue)| stability::EvalDenial { feature, reason, issue })
389+
.collect::<Vec<_>>();
390+
stability::soft_unstable(sess, &denials, vec![]).decorate_lint(diag);
387391
}
388392
BuiltinLintDiag::AvoidUsingIntelSyntax => {
389393
lints::AvoidIntelSyntax.decorate_lint(diag);

compiler/rustc_lint/src/lints.rs

-10
Original file line numberDiff line numberDiff line change
@@ -2404,16 +2404,6 @@ pub(crate) struct MacroRuleNeverUsed {
24042404
pub name: Symbol,
24052405
}
24062406

2407-
pub(crate) struct UnstableFeature {
2408-
pub msg: DiagMessage,
2409-
}
2410-
2411-
impl<'a> LintDiagnostic<'a, ()> for UnstableFeature {
2412-
fn decorate_lint<'b>(self, diag: &'b mut Diag<'a, ()>) {
2413-
diag.primary_message(self.msg);
2414-
}
2415-
}
2416-
24172407
#[derive(LintDiagnostic)]
24182408
#[diag(lint_avoid_intel_syntax)]
24192409
pub(crate) struct AvoidIntelSyntax;

compiler/rustc_lint_defs/src/lib.rs

+7-2
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@
22
#![warn(unreachable_pub)]
33
// tidy-alphabetical-end
44

5+
use std::num::NonZero;
6+
57
use rustc_ast::node_id::NodeId;
68
use rustc_ast::{AttrId, Attribute};
79
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
810
use rustc_data_structures::stable_hasher::{
911
HashStable, StableCompare, StableHasher, ToStableHashKey,
1012
};
11-
use rustc_error_messages::{DiagMessage, MultiSpan};
13+
use rustc_error_messages::MultiSpan;
1214
use rustc_hir::def::Namespace;
1315
use rustc_hir::{HashStableContext, HirId, MissingLifetimeKind};
1416
use rustc_macros::{Decodable, Encodable, HashStable_Generic};
@@ -715,7 +717,10 @@ pub enum BuiltinLintDiag {
715717
MacroIsPrivate(Ident),
716718
UnusedMacroDefinition(Symbol),
717719
MacroRuleNeverUsed(usize, Symbol),
718-
UnstableFeature(DiagMessage),
720+
SoftUnstableMacro {
721+
/// The name, optional reason, and issue number for each disabled unstable feature used.
722+
features: Vec<(Symbol, Option<Symbol>, Option<NonZero<u32>>)>,
723+
},
719724
AvoidUsingIntelSyntax,
720725
AvoidUsingAttSyntax,
721726
IncompleteInclude,

compiler/rustc_middle/messages.ftl

+19
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,25 @@ middle_type_length_limit = reached the type-length limit while instantiating `{$
102102
middle_unknown_layout =
103103
the type `{$ty}` has an unknown layout
104104
105+
middle_unstable_library_feature = use of unstable library {$count ->
106+
[one] feature {$features}{$single_feature_has_reason ->
107+
[true] : {$reason_for_single_feature}
108+
*[false] {""}
109+
}
110+
*[other] features {$features}
111+
}
112+
113+
middle_unstable_library_feature_issue =
114+
see issue #{$issue} <https://github.com/rust-lang/rust/issues/{$issue}> for more information{$show_feature ->
115+
[true] {" "}about `{$feature}`
116+
*[false] {""}
117+
}
118+
119+
middle_unstable_library_feature_reason = reason for `{$feature}`: {$reason}
120+
121+
middle_unstable_library_feature_suggestion_for_allocator_api =
122+
consider wrapping the inner types in tuple
123+
105124
middle_values_too_big =
106125
values of the type `{$ty}` are too big for the target architecture
107126
middle_written_to_path = the full type name has been written to '{$path}'

0 commit comments

Comments
 (0)