Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1585,6 +1585,7 @@ pub fn is_stable_diagnostic_attribute(sym: Symbol, features: &Features) -> bool
match sym {
sym::on_unimplemented | sym::do_not_recommend => true,
sym::on_const => features.diagnostic_on_const(),
sym::on_unknown_item => features.diagnostic_on_unknown_item(),
_ => false,
}
}
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,8 @@ declare_features! (
(unstable, derive_from, "1.91.0", Some(144889)),
/// Allows giving non-const impls custom diagnostic messages if attempted to be used as const
(unstable, diagnostic_on_const, "1.93.0", Some(143874)),
/// Allows giving unresolved imports a custom diagnostic message
(unstable, diagnostic_on_unknown_item, "CURRENT_RUSTC_VERSION", Some(152900)),
/// Allows `#[doc(cfg(...))]`.
(unstable, doc_cfg, "1.21.0", Some(43781)),
/// Allows `#[doc(masked)]`.
Expand Down
111 changes: 109 additions & 2 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,8 @@ use rustc_middle::{bug, span_bug};
use rustc_session::config::CrateType;
use rustc_session::lint;
use rustc_session::lint::builtin::{
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MISPLACED_DIAGNOSTIC_ATTRIBUTES,
UNUSED_ATTRIBUTES,
CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_ATTRIBUTES,
MISPLACED_DIAGNOSTIC_ATTRIBUTES, UNUSED_ATTRIBUTES,
};
use rustc_session::parse::feature_err;
use rustc_span::edition::Edition;
Expand Down Expand Up @@ -74,6 +74,38 @@ struct DiagnosticOnConstOnlyForNonConstTraitImpls {
item_span: Span,
}

#[derive(LintDiagnostic)]
#[diag("`#[diagnostic::on_unknown_item]` can only be applied to `use` statements")]
struct DiagnosticOnUnknownItemOnlyForImports;

#[derive(LintDiagnostic)]
#[diag("malformed `#[diagnostic::on_unknown_item]` attribute")]
#[help("at least one of the following options is required: `message`, `label` or `note`")]
pub(crate) struct MalformedOnUnknownItemAttr {
#[label("the `#[diagnostic::on_unknown_item]` attribute expects at least one option")]
pub span: Span,
}

#[derive(LintDiagnostic)]
#[diag("`{$option_name}` is ignored due to previous definition of `{$option_name}`")]
#[help("consider removing the second `{$option_name}` as it is ignored anyway")]
pub(crate) struct IgnoredDiagnosticOption {
pub option_name: &'static str,
#[label("`{$option_name}` is already declared here")]
pub span: Span,
#[label("`{$option_name}` is first declared here")]
pub prev_span: Span,
}

#[derive(LintDiagnostic)]
#[diag("unknown option `{$option_name}` for the `#[diagnostic::on_unknown_item]` attribute")]
#[help("only `message`, `note` and `label` are allowed as options")]
pub(crate) struct UnknownOptionForOnUnknownItemAttr {
pub option_name: String,
#[label("`{$option_name}` is an invalid option")]
pub span: Span,
}

fn target_from_impl_item<'tcx>(tcx: TyCtxt<'tcx>, impl_item: &hir::ImplItem<'_>) -> Target {
match impl_item.kind {
hir::ImplItemKind::Const(..) => Target::AssocConst,
Expand Down Expand Up @@ -388,6 +420,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
[sym::diagnostic, sym::on_const, ..] => {
self.check_diagnostic_on_const(attr.span(), hir_id, target, item)
}
[sym::diagnostic, sym::on_unknown_item, ..] => {
self.check_diagnostic_on_unknown_item(attr.span(), hir_id, target, attr)
}
[sym::autodiff_forward, ..] | [sym::autodiff_reverse, ..] => {
self.check_autodiff(hir_id, attr, span, target)
}
Expand Down Expand Up @@ -619,6 +654,78 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
}
}

/// Checks if `#[diagnostic::on_unknown_item]` is applied to an import definition
fn check_diagnostic_on_unknown_item(
&self,
attr_span: Span,
hir_id: HirId,
target: Target,
attr: &Attribute,
) {
if !matches!(target, Target::Use) {
self.tcx.emit_node_span_lint(
MISPLACED_DIAGNOSTIC_ATTRIBUTES,
hir_id,
attr_span,
DiagnosticOnUnknownItemOnlyForImports,
);
}
if let Some(meta) = attr.meta_item_list() {
let mut message = None;
let mut label = None;
for item in meta {
if item.has_name(sym::message) {
if let Some(message_span) = message {
self.tcx.emit_node_span_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
hir_id,
item.span(),
IgnoredDiagnosticOption {
option_name: "message",
span: item.span(),
prev_span: message_span,
},
);
}
message = Some(item.span());
} else if item.has_name(sym::label) {
if let Some(label_span) = label {
self.tcx.emit_node_span_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
hir_id,
item.span(),
IgnoredDiagnosticOption {
option_name: "label",
span: item.span(),
prev_span: label_span,
},
);
}
label = Some(item.span());
} else if item.has_name(sym::note) {
// accept any number of notes
} else {
self.tcx.emit_node_span_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
hir_id,
item.span(),
UnknownOptionForOnUnknownItemAttr {
option_name: item.name().map(|s| s.to_string()).unwrap_or_default(),
span: item.span(),
},
);
}
}
} else {
self.tcx.emit_node_span_lint(
MALFORMED_DIAGNOSTIC_ATTRIBUTES,
hir_id,
attr_span,
MalformedOnUnknownItemAttr { span: attr_span },
);
}
}

/// Checks if `#[diagnostic::on_const]` is applied to a trait impl
fn check_diagnostic_on_const(
&self,
Expand Down
6 changes: 5 additions & 1 deletion compiler/rustc_resolve/src/build_reduced_graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use tracing::debug;

use crate::Namespace::{MacroNS, TypeNS, ValueNS};
use crate::def_collector::collect_definitions;
use crate::imports::{ImportData, ImportKind};
use crate::imports::{ImportData, ImportKind, OnUnknownItemData};
use crate::macros::{MacroRulesDecl, MacroRulesScope, MacroRulesScopeRef};
use crate::ref_mut::CmCell;
use crate::{
Expand Down Expand Up @@ -538,6 +538,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
root_id,
vis,
vis_span: item.vis.span,
on_unknown_item_attr: OnUnknownItemData::from_attrs(&item.attrs),
});

self.r.indeterminate_imports.push(import);
Expand Down Expand Up @@ -1035,6 +1036,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
module_path: Vec::new(),
vis,
vis_span: item.vis.span,
on_unknown_item_attr: OnUnknownItemData::from_attrs(&item.attrs),
});
if used {
self.r.import_use_map.insert(import, Used::Other);
Expand Down Expand Up @@ -1167,6 +1169,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
module_path: Vec::new(),
vis: Visibility::Restricted(CRATE_DEF_ID),
vis_span: item.vis.span,
on_unknown_item_attr: OnUnknownItemData::from_attrs(&item.attrs),
})
};

Expand Down Expand Up @@ -1338,6 +1341,7 @@ impl<'a, 'ra, 'tcx> BuildReducedGraphVisitor<'a, 'ra, 'tcx> {
module_path: Vec::new(),
vis,
vis_span: item.vis.span,
on_unknown_item_attr: OnUnknownItemData::from_attrs(&item.attrs),
});
self.r.import_use_map.insert(import, Used::Other);
let import_decl = self.r.new_import_decl(decl, import);
Expand Down
103 changes: 95 additions & 8 deletions compiler/rustc_resolve/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use std::mem;

use rustc_ast::NodeId;
use rustc_ast::{NodeId, ast};
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
use rustc_data_structures::intern::Interned;
use rustc_errors::codes::*;
Expand Down Expand Up @@ -140,6 +140,57 @@ impl<'ra> std::fmt::Debug for ImportKind<'ra> {
}
}

#[derive(Debug, Clone, Default)]
pub(crate) struct OnUnknownItemData {
pub(crate) message: Option<String>,
pub(crate) label: Option<String>,
pub(crate) notes: Option<Vec<String>>,
}

impl OnUnknownItemData {
pub(crate) fn from_attrs(attrs: &[ast::Attribute]) -> Option<OnUnknownItemData> {
// the attribute syntax is checked in the check_attr
// ast pass, so we just consume any valid
// options here and ignore everything else
let mut out = OnUnknownItemData::default();
for attr in
attrs.iter().filter(|a| a.path_matches(&[sym::diagnostic, sym::on_unknown_item]))
{
if let Some(meta) = attr.meta_item_list() {
for item in meta {
if item.has_name(sym::message) {
if out.message.is_none()
&& let Some(message) = item.value_str()
{
out.message = Some(message.as_str().to_owned());
}
} else if item.has_name(sym::label) {
if out.label.is_none()
&& let Some(label) = item.value_str()
{
out.label = Some(label.as_str().to_owned());
}
} else if item.has_name(sym::note) {
if let Some(note) = item.value_str() {
out.notes = Some(out.notes.unwrap_or_default());
out.notes
.as_mut()
.expect("We initialized it above")
.push(note.as_str().to_owned());
}
}
}
}
}

if out.message.is_none() && out.label.is_none() && out.notes.is_none() {
None
} else {
Some(out)
}
}
}

/// One import.
#[derive(Debug, Clone)]
pub(crate) struct ImportData<'ra> {
Expand Down Expand Up @@ -186,6 +237,11 @@ pub(crate) struct ImportData<'ra> {

/// Span of the visibility.
pub vis_span: Span,

/// A `#[diagnostic::on_unknown_item]` attribute applied
/// to the given import. This allows crates to specify
/// custom error messages for a specific import
pub on_unknown_item_attr: Option<OnUnknownItemData>,
}

/// All imports are unique and allocated on a same arena,
Expand Down Expand Up @@ -284,6 +340,7 @@ struct UnresolvedImportError {
segment: Option<Symbol>,
/// comes from `PathRes::Failed { module }`
module: Option<DefId>,
on_unknown_item_attr: Option<OnUnknownItemData>,
}

// Reexports of the form `pub use foo as bar;` where `foo` is `extern crate foo;`
Expand Down Expand Up @@ -693,6 +750,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
candidates: None,
segment: None,
module: None,
on_unknown_item_attr: import.on_unknown_item_attr.clone(),
};
errors.push((*import, err))
}
Expand Down Expand Up @@ -815,19 +873,45 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
format!("`{path}`")
})
.collect::<Vec<_>>();
let msg = format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),);

let mut diag = struct_span_code_err!(self.dcx(), span, E0432, "{msg}");
let default_message =
format!("unresolved import{} {}", pluralize!(paths.len()), paths.join(", "),);
let mut diag = if self.tcx.features().diagnostic_on_unknown_item()
&& let Some(message) =
errors[0].1.on_unknown_item_attr.as_mut().and_then(|a| a.message.take())
{
let mut diag = struct_span_code_err!(self.dcx(), span, E0432, "{message}");
diag.note(default_message);
diag
} else {
struct_span_code_err!(self.dcx(), span, E0432, "{default_message}")
};

if let Some((_, UnresolvedImportError { note: Some(note), .. })) = errors.iter().last() {
if self.tcx.features().diagnostic_on_unknown_item()
&& let Some(notes) =
errors[0].1.on_unknown_item_attr.as_mut().and_then(|a| a.notes.take())
{
for note in notes {
diag.note(note);
}
} else if let Some((_, UnresolvedImportError { note: Some(note), .. })) =
errors.iter().last()
{
diag.note(note.clone());
}

/// Upper limit on the number of `span_label` messages.
const MAX_LABEL_COUNT: usize = 10;

for (import, err) in errors.into_iter().take(MAX_LABEL_COUNT) {
if let Some(label) = err.label {
for (import, mut err) in errors.into_iter().take(MAX_LABEL_COUNT) {
if self.tcx.features().diagnostic_on_unknown_item()
&& let Some(label) = err
.on_unknown_item_attr
.as_mut()
.and_then(|a| a.label.take())
.or(err.label.clone())
{
diag.span_label(err.span, label);
} else if let Some(label) = err.label {
diag.span_label(err.span, label);
}

Expand Down Expand Up @@ -1088,6 +1172,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
candidates: None,
segment: Some(segment_name),
module,
on_unknown_item_attr: import.on_unknown_item_attr.clone(),
},
None => UnresolvedImportError {
span,
Expand All @@ -1097,6 +1182,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
candidates: None,
segment: Some(segment_name),
module,
on_unknown_item_attr: import.on_unknown_item_attr.clone(),
},
};
return Some(err);
Expand Down Expand Up @@ -1139,6 +1225,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
candidates: None,
segment: None,
module: None,
on_unknown_item_attr: None,
});
}
if let Some(max_vis) = max_vis.get()
Expand Down Expand Up @@ -1339,7 +1426,6 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {

let parent_suggestion =
self.lookup_import_candidates(ident, TypeNS, &import.parent_scope, |_| true);

Some(UnresolvedImportError {
span: import.span,
label: Some(label),
Expand All @@ -1358,6 +1444,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
}
}),
segment: Some(ident.name),
on_unknown_item_attr: import.on_unknown_item_attr.clone(),
})
} else {
// `resolve_ident_in_module` reported a privacy error.
Expand Down
Loading
Loading