Skip to content
Merged
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
68 changes: 67 additions & 1 deletion compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
use std::ops::Range;

use rustc_ast::PathSegment;
use rustc_errors::{Diagnostic, MultiSpan};
use rustc_hir::attrs::diagnostic::{
Directive, Filter, FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name,
NameValue, Piece, Predicate,
};
use rustc_lint_defs::LintId;
use rustc_parse_format::{
Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position,
};
use rustc_session::lint::builtin::{
MALFORMED_DIAGNOSTIC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FILTERS,
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS,
MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, UNKNOWN_DIAGNOSTIC_ATTRIBUTES,
};
use rustc_span::edit_distance::find_best_match_for_name;
use rustc_span::{Ident, InnerSpan, Span, Symbol, kw, sym};
use thin_vec::{ThinVec, thin_vec};

Expand All @@ -20,6 +24,7 @@ use crate::diagnostics::{
MissingOptionsForDiagnosticAttribute, NonMetaItemDiagnosticAttribute, WrappedParserError,
};
use crate::parser::{ArgParser, MetaItemListParser, MetaItemOrLitParser, MetaItemParser};
use crate::{EmitAttribute, diagnostics};

pub(crate) mod do_not_recommend;
pub(crate) mod on_const;
Expand All @@ -30,6 +35,67 @@ pub(crate) mod on_unknown;
pub(crate) mod on_unmatched_args;
pub(crate) mod opaque;

impl<'sess> crate::AttributeParser<'sess> {
pub(crate) fn unknown_diagnostic_attr(
&self,
segment: &PathSegment,
mut emit_lint: impl FnMut(LintId, MultiSpan, EmitAttribute),
) {
const DIAGNOSTIC_ATTRIBUTES: [(
Symbol, /* name */
Option<Symbol>, /* feature gate */
); 8] = [
(sym::on_unimplemented, None),
(sym::do_not_recommend, None),
(sym::on_move, Some(sym::diagnostic_on_move)),
(sym::on_const, Some(sym::diagnostic_on_const)),
(sym::on_unknown, Some(sym::diagnostic_on_unknown)),
(sym::on_unmatched_args, Some(sym::diagnostic_on_unmatched_args)),
(sym::on_type_error, Some(sym::diagnostic_on_type_error)),
(sym::opaque, Some(sym::diagnostic_opaque)),
];
// No need to emit a lint if features aren't available.
let Some(features) = self.features else { return };
let span = segment.span();
let candidates = DIAGNOSTIC_ATTRIBUTES
.iter()
.filter_map(|(attr, feature)| {
feature.is_none_or(|f| features.enabled(f)).then_some(*attr)
})
.collect::<Vec<_>>();

let typo = find_best_match_for_name(&candidates, segment.ident.name, None)
.map(|typo_name| diagnostics::UnknownDiagnosticAttributeTypo { span, typo_name });
emit_lint(
LintId::of(UNKNOWN_DIAGNOSTIC_ATTRIBUTES),
span.into(),
EmitAttribute(Box::new(move |dcx, level, _| {
diagnostics::UnknownDiagnosticAttribute { typo }.into_diag(dcx, level)
})),
)
}
}

#[rustc_macro_transparency = "transparent"]
macro gate_diagnostic_attr($feature:ident) {{
if let Some(features) = cx.features_option()
&& !features.$feature()
{
args.ignore_args();
let nightly_build = cx.sess.is_nightly_build();
let span = cx.attr_span;
cx.emit_lint(
rustc_lint_defs::builtin::UNKNOWN_DIAGNOSTIC_ATTRIBUTES,
$crate::diagnostics::UnstableDiagnosticAttribute {
feature: sym::$feature,
nightly_build,
},
span,
);
return;
}
}}

#[derive(Copy, Clone)]
pub(crate) enum Mode {
/// `#[rustc_on_unimplemented]`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,9 @@ impl AttributeParser for OnConstParser {
const ATTRIBUTES: AcceptMapping<Self> = &[(
&[sym::diagnostic, sym::on_const],
template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]),
AttributeStability::Stable, // Unstable, stability checked manually in the parser
AttributeStability::Stable, // Unstable, stability checked manually below
|this, cx, args| {
if !cx.features().diagnostic_on_const() {
// `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs
args.ignore_args();
return;
}
gate_diagnostic_attr!(diagnostic_on_const);

let path_span = cx.attr_path.span;
this.path_span = Some(path_span);
Expand Down
34 changes: 12 additions & 22 deletions compiler/rustc_attr_parsing/src/attributes/diagnostic/on_move.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ use rustc_span::sym;

use crate::attributes::diagnostic::*;
use crate::attributes::prelude::*;
use crate::context::AcceptContext;
use crate::parser::ArgParser;
use crate::target_checking::AllowedTargets;
use crate::template;

Expand All @@ -15,31 +13,23 @@ pub(crate) struct OnMoveParser {
directive: Option<(Span, Directive)>,
}

impl OnMoveParser {
fn parse<'sess>(&mut self, cx: &mut AcceptContext<'_, 'sess>, args: &ArgParser, mode: Mode) {
if !cx.features().diagnostic_on_move() {
// `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs
args.ignore_args();
return;
}

let span = cx.attr_span;
self.span = Some(span);

let Some(items) = parse_list(cx, args, mode) else { return };

if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) {
merge_directives(cx, &mut self.directive, (span, directive));
}
}
}
impl AttributeParser for OnMoveParser {
const ATTRIBUTES: AcceptMapping<Self> = &[(
&[sym::diagnostic, sym::on_move],
template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]),
AttributeStability::Stable, // Unstable, stability checked manually in the parser
AttributeStability::Stable, // Unstable, stability checked manually below
|this, cx, args| {
this.parse(cx, args, Mode::DiagnosticOnMove);
gate_diagnostic_attr!(diagnostic_on_move);

let span = cx.attr_span;
this.span = Some(span);
let mode = Mode::DiagnosticOnMove;

let Some(items) = parse_list(cx, args, mode) else { return };

if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) {
merge_directives(cx, &mut this.directive, (span, directive));
}
},
)];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ use rustc_span::sym;
use crate::attributes::AttributeStability;
use crate::attributes::diagnostic::*;
use crate::attributes::prelude::*;
use crate::context::AcceptContext;
use crate::parser::ArgParser;
use crate::target_checking::AllowedTargets;
use crate::template;

Expand All @@ -15,32 +13,22 @@ pub(crate) struct OnTypeErrorParser {
directive: Option<(Span, Directive)>,
}

impl OnTypeErrorParser {
fn parse<'sess>(&mut self, cx: &mut AcceptContext<'_, 'sess>, args: &ArgParser, mode: Mode) {
if !cx.features().diagnostic_on_type_error() {
// `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs
args.ignore_args();
return;
}

let span = cx.attr_span;
self.span = Some(span);

let Some(items) = parse_list(cx, args, mode) else { return };

if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) {
merge_directives(cx, &mut self.directive, (span, directive));
}
}
}

impl AttributeParser for OnTypeErrorParser {
const ATTRIBUTES: AcceptMapping<Self> = &[(
&[sym::diagnostic, sym::on_type_error],
template!(List: &[r#"note = "...""#]),
AttributeStability::Stable,
AttributeStability::Stable, // Unstable, stability checked manually below
|this, cx, args| {
this.parse(cx, args, Mode::DiagnosticOnTypeError);
gate_diagnostic_attr!(diagnostic_on_type_error);

let span = cx.attr_span;
this.span = Some(span);
let mode = Mode::DiagnosticOnTypeError;
let Some(items) = parse_list(cx, args, mode) else { return };

if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) {
merge_directives(cx, &mut this.directive, (span, directive));
}
},
)];

Expand Down
34 changes: 12 additions & 22 deletions compiler/rustc_attr_parsing/src/attributes/diagnostic/on_unknown.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,23 @@ pub(crate) struct OnUnknownParser {
directive: Option<(Span, Directive)>,
}

impl OnUnknownParser {
fn parse<'sess>(&mut self, cx: &mut AcceptContext<'_, 'sess>, args: &ArgParser, mode: Mode) {
if let Some(features) = cx.features
&& !features.diagnostic_on_unknown()
{
// `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs
args.ignore_args();
return;
}
let span = cx.attr_span;
self.span = Some(span);

let Some(items) = parse_list(cx, args, mode) else { return };

if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) {
merge_directives(cx, &mut self.directive, (span, directive));
};
}
}

impl AttributeParser for OnUnknownParser {
const ATTRIBUTES: AcceptMapping<Self> = &[(
&[sym::diagnostic, sym::on_unknown],
template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]),
AttributeStability::Stable, // Unstable, stability checked manually in the parser
AttributeStability::Stable, // Unstable, stability checked manually below
|this, cx, args| {
this.parse(cx, args, Mode::DiagnosticOnUnknown);
gate_diagnostic_attr!(diagnostic_on_unknown);

let span = cx.attr_span;
this.span = Some(span);
let mode = Mode::DiagnosticOnUnknown;

let Some(items) = parse_list(cx, args, mode) else { return };

if let Some(directive) = parse_directive_items(cx, mode, items.mixed(), true) {
merge_directives(cx, &mut this.directive, (span, directive));
};
},
)];
// "Allowed" for all targets, but noop for all but use statements.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,9 @@ impl AttributeParser for OnUnmatchedArgsParser {
const ATTRIBUTES: AcceptMapping<Self> = &[(
&[sym::diagnostic, sym::on_unmatched_args],
template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]),
AttributeStability::Stable, // Unstable, stability checked manually in the parser
AttributeStability::Stable, // Unstable, stability checked manually below
|this, cx, args| {
if !cx.features().diagnostic_on_unmatched_args() {
// `UnknownDiagnosticAttribute` is emitted in rustc_resolve/macros.rs
args.ignore_args();
return;
}
gate_diagnostic_attr!(diagnostic_on_unmatched_args);

let span = cx.attr_span;
this.span = Some(span);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use rustc_hir::attrs::AttributeKind;
use rustc_session::lint::builtin::MALFORMED_DIAGNOSTIC_ATTRIBUTES;
use rustc_span::{Span, sym};

use crate::attributes::diagnostic::gate_diagnostic_attr;
use crate::attributes::{AcceptMapping, AttributeParser};
use crate::context::{AcceptContext, FinalizeContext};
use crate::diagnostics::OpaqueDoesNotExpectArgs;
Expand All @@ -22,11 +23,9 @@ impl AttributeParser for OpaqueParser {
(
&[sym::diagnostic, sym::opaque],
template!(Word),
AttributeStability::Stable, // Unstable, stability checked manually in the parser
AttributeStability::Stable, // Unstable, stability checked manually below
|this, cx, args| {
if !cx.features().diagnostic_opaque() {
return;
}
gate_diagnostic_attr!(diagnostic_opaque);
this.parse(cx, args);
},
),
Expand Down
29 changes: 29 additions & 0 deletions compiler/rustc_attr_parsing/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -848,3 +848,32 @@ pub(crate) struct ToolReserved {
pub(crate) span: Span,
pub(crate) tool: Ident,
}

#[derive(Diagnostic)]
#[diag("unknown diagnostic attribute")]
pub(crate) struct UnknownDiagnosticAttribute {
#[subdiagnostic]
pub typo: Option<UnknownDiagnosticAttributeTypo>,
}

#[derive(Subdiagnostic)]
#[suggestion(
"an attribute with a similar name exists",
style = "verbose",
code = "{typo_name}",
applicability = "machine-applicable"
)]
pub(crate) struct UnknownDiagnosticAttributeTypo {
#[primary_span]
pub span: Span,
pub typo_name: Symbol,
}

#[derive(Diagnostic)]
#[diag("unknown diagnostic attribute")]
pub(crate) struct UnstableDiagnosticAttribute {
#[note("this is an experimental diagnostic attribute")]
#[help("add `#![feature({$feature})]` to the crate attributes to enable")]
pub nightly_build: bool,
pub feature: Symbol,
}
3 changes: 3 additions & 0 deletions compiler/rustc_attr_parsing/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ impl<'sess> AttributeParser<'sess> {
self.sess
}

#[track_caller]
pub(crate) fn features(&self) -> &'sess Features {
self.features.expect("features not available at this point in the compiler")
}
Expand Down Expand Up @@ -451,6 +452,8 @@ impl<'sess> AttributeParser<'sess> {
if !cx.shared.has_lint_been_emitted.load(Ordering::Relaxed) {
cx.shared.cx.check_args_used(attr, &args)
}
} else if let [sym::diagnostic, _unknown, ..] = &*parts {
self.unknown_diagnostic_attr(&n.item.path.segments[1], &mut emit_lint);
} else {
let attr = AttrItem {
path: attr_path.clone(),
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_attr_parsing/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,11 @@
//! [`rustc_passes::check_attr`]: ../rustc_passes/check_attr/index.html

// tidy-alphabetical-start
#![expect(internal_features, reason = "rustc_attrs")]
#![feature(decl_macro)]
#![feature(deref_patterns)]
#![feature(iter_intersperse)]
#![feature(rustc_attrs)]
#![feature(try_blocks)]
#![recursion_limit = "256"]
// tidy-alphabetical-end
Expand Down
24 changes: 0 additions & 24 deletions compiler/rustc_resolve/src/diagnostics/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1503,30 +1503,6 @@ pub(crate) struct RedundantImportVisibility {
pub max_vis: String,
}

#[derive(Diagnostic)]
#[diag("unknown diagnostic attribute")]
pub(crate) struct UnknownDiagnosticAttribute {
#[subdiagnostic]
pub help: Option<UnknownDiagnosticAttributeHelp>,
}

#[derive(Subdiagnostic)]
pub(crate) enum UnknownDiagnosticAttributeHelp {
#[suggestion(
"an attribute with a similar name exists",
style = "verbose",
code = "{typo_name}",
applicability = "machine-applicable"
)]
Typo {
#[primary_span]
span: Span,
typo_name: Symbol,
},
#[help("add `#![feature({$feature})]` to the crate attributes to enable")]
UseFeature { feature: Symbol },
}

// FIXME: Make this properly translatable.
pub(crate) struct Ambiguity {
pub ident: Ident,
Expand Down
Loading
Loading