Skip to content
2 changes: 1 addition & 1 deletion compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
attribute_parser: AttributeParser::new(
tcx.sess,
tcx.features(),
tcx.registered_tools(()),
tcx.registered_attr_tools(()),
ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
),
delayed_lints: Vec::new(),
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -634,7 +634,7 @@ fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate)
let mut errored = false;

if let Some(Attribute::Parsed(AttributeKind::Feature(feature_idents, first_span))) =
AttributeParser::parse_limited(sess, &krate.attrs, &[sym::feature])
AttributeParser::parse_limited_sym(sess, &krate.attrs, &[sym::feature])
{
// `feature(...)` used on non-nightly. This is definitely an error.
let mut err = diagnostics::FeatureOnNonNightly {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ impl AttributeParser for NakedParser {

let span = self.span?;

let Some(tools) = cx.tools else {
let Some(tools) = cx.attr_tools else {
unreachable!("tools required while parsing attributes");
};

Expand Down
109 changes: 74 additions & 35 deletions compiler/rustc_attr_parsing/src/attributes/crate_level.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
use rustc_data_structures::fx::FxIndexSet;
use rustc_feature::AttributeStability;
use rustc_hir::attrs::{CrateType, WindowsSubsystemKind};
use rustc_session::lint::builtin::UNKNOWN_CRATE_TYPES;
use rustc_span::Symbol;
use rustc_span::edit_distance::find_best_match_for_name_with_substrings;

use super::prelude::*;
use crate::diagnostics::{UnknownCrateTypes, UnknownCrateTypesSuggestion};
use crate::diagnostics::{ToolReserved, UnknownCrateTypes, UnknownCrateTypesSuggestion};

pub(crate) struct CrateNameParser;

Expand Down Expand Up @@ -317,49 +318,87 @@ impl CombineAttributeParser for FeatureParser {
}
}

pub(crate) struct RegisterToolParser;
#[derive(Default)]
pub(crate) struct RegisterToolParser {
attr_tools: FxIndexSet<Ident>,
lint_tools: FxIndexSet<Ident>,
}

impl CombineAttributeParser for RegisterToolParser {
const PATH: &[Symbol] = &[sym::register_tool];
type Item = Ident;
const CONVERT: ConvertFn<Self::Item> = |tools, _span| AttributeKind::RegisterTool(tools);
const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Crate)]);
const TEMPLATE: AttributeTemplate = template!(List: &["tool1, tool2, ..."]);
const STABILITY: AttributeStability = unstable!(register_tool);
fn parse_register_tool(
tools: &mut [&mut FxIndexSet<Ident>],
cx: &mut AcceptContext<'_, '_>,
args: &ArgParser,
) {
let Some(list) = cx.expect_list(args, cx.attr_span) else {
return;
};

if list.is_empty() {
let attr_span = cx.attr_span;
cx.adcx().warn_empty_attribute(attr_span);
}

fn extend(
cx: &mut AcceptContext<'_, '_>,
args: &ArgParser,
) -> impl IntoIterator<Item = Self::Item> {
let Some(list) = cx.expect_list(args, cx.attr_span) else {
return Vec::new();
for elem in list.mixed() {
let Some(elem) = elem.meta_item() else {
cx.adcx().expected_identifier(elem.span());
continue;
};
let Some(()) = cx.expect_no_args(elem.args()) else {
continue;
};

if list.is_empty() {
let attr_span = cx.attr_span;
cx.adcx().warn_empty_attribute(attr_span);
let path = elem.path();
let Some(ident) = path.word() else {
cx.adcx().expected_identifier(path.span());
continue;
};

if ident.name == sym::rustc {
cx.should_emit
.emit_err(cx.dcx().create_err(ToolReserved { span: ident.span, tool: ident }));
continue;
}

let mut res = Vec::new();
for tools in tools.iter_mut() {
tools.insert(ident);
}
}
}

for elem in list.mixed() {
let Some(elem) = elem.meta_item() else {
cx.adcx().expected_identifier(elem.span());
continue;
};
let Some(()) = cx.expect_no_args(elem.args()) else {
continue;
};
impl AttributeParser for RegisterToolParser {
const ATTRIBUTES: AcceptMapping<Self> = &[
(
&[sym::register_tool],
template!(List: &["tool1, tool2, ..."]),
unstable!(register_tool),
|this, cx, args| {
parse_register_tool(&mut [&mut this.attr_tools, &mut this.lint_tools], cx, args)
},
),
(
&[sym::register_attribute_tool],
template!(List: &["tool1, tool2, ..."]),
unstable!(register_tool),
|this, cx, args| parse_register_tool(&mut [&mut this.attr_tools], cx, args),
),
(
&[sym::register_lint_tool],
template!(List: &["tool1, tool2, ..."]),
unstable!(register_tool),
|this, cx, args| parse_register_tool(&mut [&mut this.lint_tools], cx, args),
),
];

let path = elem.path();
let Some(ident) = path.word() else {
cx.adcx().expected_identifier(path.span());
continue;
};
const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Crate)]);

res.push(ident);
fn finalize(self, _cx: &FinalizeContext<'_, '_>) -> Option<AttributeKind> {
if self.attr_tools.is_empty() && self.lint_tools.is_empty() {
None
} else {
Some(AttributeKind::RegisterTool {
attr_tools: self.attr_tools.into_iter().collect(),
lint_tools: self.lint_tools.into_iter().collect(),
})
}

res
}
}
2 changes: 1 addition & 1 deletion compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ attribute_parsers!(
OnUnknownParser,
OnUnmatchedArgsParser,
OpaqueParser,
RegisterToolParser,
RustcAlignParser,
RustcAlignStaticParser,
RustcCguTestAttributeParser,
Expand All @@ -188,7 +189,6 @@ attribute_parsers!(
Combine<FeatureParser>,
Combine<ForceTargetFeatureParser>,
Combine<LinkParser>,
Combine<RegisterToolParser>,
Combine<ReprParser>,
Combine<RustcAllowConstFnUnstableParser>,
Combine<RustcCleanParser>,
Expand Down
10 changes: 9 additions & 1 deletion compiler/rustc_attr_parsing/src/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use rustc_errors::{Applicability, DiagArgValue, E0232, E0264, MultiSpan};
use rustc_hir::AttrPath;
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_span::{Span, Symbol};
use rustc_span::{Ident, Span, Symbol};

#[derive(Diagnostic)]
#[diag("`{$name}` attribute cannot be used at crate level")]
Expand Down Expand Up @@ -828,3 +828,11 @@ pub(crate) struct UnknownExternLangItem {
pub span: Span,
pub lang_item: Symbol,
}

#[derive(Diagnostic)]
#[diag("tool `{$tool}` is reserved and cannot be registered")]
pub(crate) struct ToolReserved {
#[primary_span]
pub(crate) span: Span,
pub(crate) tool: Ident,
}
82 changes: 57 additions & 25 deletions compiler/rustc_attr_parsing/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,15 @@ pub struct EmitAttribute(
/// Context created once, for example as part of the ast lowering
/// context, through which all attributes can be lowered.
pub struct AttributeParser<'sess> {
pub(crate) tools: Option<&'sess RegisteredTools>,
pub(crate) attr_tools: Option<&'sess RegisteredTools>,
pub(crate) features: Option<&'sess Features>,
pub(crate) sess: &'sess Session,
pub(crate) should_emit: ShouldEmit,

/// *Only* parse attributes with this symbol.
/// *Only* parse attributes that passes this filter.
///
/// Used in cases where we want the lowering infrastructure for parse just a single attribute.
parse_only: Option<&'static [Symbol]>,
/// Used in cases where we want the lowering infrastructure for parse just limited attributes.
parse_filter: Option<&'sess dyn Fn(&ast::Attribute) -> bool>,
}

impl<'sess> AttributeParser<'sess> {
Expand All @@ -58,8 +58,8 @@ impl<'sess> AttributeParser<'sess> {
/// `rustc_ast_lowering`. Some attributes require access to features to parse, which would
/// crash if you tried to do so through [`parse_limited`](Self::parse_limited).
///
/// To make sure use is limited, supply a `Symbol` you'd like to parse. Only attributes with
/// that symbol are picked out of the list of instructions and parsed. Those are returned.
/// To make sure use is limited, supply a filter. Only attributes that passes the filter are
/// picked out of the list of instructions and parsed. Those are returned.
///
/// No diagnostics will be emitted when parsing limited. Lints are not emitted at all, while
/// errors will be emitted as a delayed bugs. in other words, we *expect* attributes parsed
Expand All @@ -69,42 +69,48 @@ impl<'sess> AttributeParser<'sess> {
pub fn parse_limited(
sess: &'sess Session,
attrs: &[ast::Attribute],
sym: &'static [Symbol],
parse_filter: &dyn Fn(&ast::Attribute) -> bool,
) -> Option<Attribute> {
Self::parse_limited_should_emit(
sess,
attrs,
sym,
parse_filter,
// Because we're not emitting warnings/errors, the target should not matter
DUMMY_SP,
CRATE_NODE_ID,
Target::Crate,
None,
ShouldEmit::Nothing,
)
}

/// This does the same as `parse_limited`, except that it takes a fixed symbol instead of a
/// filter.
pub fn parse_limited_sym(
sess: &'sess Session,
attrs: &[ast::Attribute],
sym: &'static [Symbol],
) -> Option<Attribute> {
Self::parse_limited(sess, attrs, &|attr| attr.path_matches(sym))
}

/// This does the same as `parse_limited`, except it has a `should_emit` parameter which allows it to emit errors.
/// Usually you want `parse_limited`, which emits no errors.
///
/// Due to this function not taking in `RegisteredTools`, *do not* use this for parsing any lint attributes
pub fn parse_limited_should_emit(
sess: &'sess Session,
attrs: &[ast::Attribute],
sym: &'static [Symbol],
parse_filter: &dyn Fn(&ast::Attribute) -> bool,
target_span: Span,
target_node_id: NodeId,
target: Target,
features: Option<&'sess Features>,
should_emit: ShouldEmit,
) -> Option<Attribute> {
let mut parsed = Self::parse_limited_all(
sess,
attrs,
Some(sym),
target,
Some(parse_filter),
Target::Crate,
target_span,
target_node_id,
CRATE_NODE_ID,
features,
should_emit,
None,
Expand All @@ -113,25 +119,45 @@ impl<'sess> AttributeParser<'sess> {
parsed.pop()
}

/// This does the same as `parse_limited_should_emit`, except that it takes a fixed symbol
/// instead of a filter.
pub fn parse_limited_sym_should_emit(
sess: &'sess Session,
attrs: &[ast::Attribute],
sym: &'static [Symbol],
target_span: Span,
features: Option<&'sess Features>,
should_emit: ShouldEmit,
) -> Option<Attribute> {
Self::parse_limited_should_emit(
sess,
attrs,
&|attr| attr.path_matches(sym),
target_span,
features,
should_emit,
)
}

/// This method allows you to parse a list of attributes *before* `rustc_ast_lowering`.
/// This can be used for attributes that would be removed before `rustc_ast_lowering`, such as attributes on macro calls.
///
/// Try to use this as little as possible. Attributes *should* be lowered during
/// `rustc_ast_lowering`. Some attributes require access to features to parse, which would
/// crash if you tried to do so through [`parse_limited_all`](Self::parse_limited_all).
/// Therefore, if `parse_only` is None, then features *must* be provided.
/// Therefore, if `parse_filter` is None, then features *must* be provided.
pub fn parse_limited_all(
sess: &'sess Session,
attrs: &[ast::Attribute],
parse_only: Option<&'static [Symbol]>,
parse_filter: Option<&dyn Fn(&ast::Attribute) -> bool>,
target: Target,
target_span: Span,
target_node_id: NodeId,
features: Option<&'sess Features>,
should_emit: ShouldEmit,
tools: Option<&'sess RegisteredTools>,
attr_tools: Option<&'sess RegisteredTools>,
) -> Vec<Attribute> {
let mut p = Self { features, tools, parse_only, sess, should_emit };
let mut p = AttributeParser { features, attr_tools, parse_filter, sess, should_emit };
p.parse_attribute_list(
attrs,
target_span,
Expand Down Expand Up @@ -210,7 +236,7 @@ impl<'sess> AttributeParser<'sess> {
parse_fn: fn(cx: &mut AcceptContext<'_, '_>, item: &I) -> T,
template: &AttributeTemplate,
) -> T {
let mut parser = Self { features, tools: None, parse_only: None, sess, should_emit };
let mut parser = Self { features, attr_tools: None, parse_filter: None, sess, should_emit };
let mut emit_lint = |lint_id: LintId, span: MultiSpan, kind: EmitAttribute| {
sess.psess.dyn_buffer_lint_sess(lint_id.lint, span, target_node_id, kind.0)
};
Expand Down Expand Up @@ -250,10 +276,16 @@ impl<'sess> AttributeParser<'sess> {
pub fn new(
sess: &'sess Session,
features: &'sess Features,
tools: &'sess RegisteredTools,
attr_tools: &'sess RegisteredTools,
should_emit: ShouldEmit,
) -> Self {
Self { features: Some(features), tools: Some(tools), parse_only: None, sess, should_emit }
Self {
features: Some(features),
attr_tools: Some(attr_tools),
parse_filter: None,
sess,
should_emit,
}
}

pub(crate) fn sess(&self) -> &'sess Session {
Expand Down Expand Up @@ -297,8 +329,8 @@ impl<'sess> AttributeParser<'sess> {

for attr in attrs {
// If we're only looking for a single attribute, skip all the ones we don't care about.
if let Some(expected) = self.parse_only {
if !attr.path_matches(expected) {
if let Some(filter) = self.parse_filter {
if !filter(attr) {
continue;
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_builtin_macros/src/deriving/generic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ impl<'a> TraitDef<'a> {
match item {
Annotatable::Item(item) => {
let is_packed = matches!(
AttributeParser::parse_limited(cx.sess, &item.attrs, &[sym::repr]),
AttributeParser::parse_limited_sym(cx.sess, &item.attrs, &[sym::repr]),
Some(Attribute::Parsed(AttributeKind::Repr { reprs, .. })) if reprs.iter().any(|(x, _)| matches!(x, ReprPacked(..)))
);

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_builtin_macros/src/proc_macro_harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ impl<'a> CollectProcMacros<'a> {
attr: &'a ast::Attribute,
) {
let Some(rustc_hir::Attribute::Parsed(AttributeKind::ProcMacroDerive { .. })) =
AttributeParser::parse_limited(
AttributeParser::parse_limited_sym(
self.session,
slice::from_ref(attr),
&[sym::proc_macro_derive],
Expand Down
Loading
Loading