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
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 @@ -654,7 +654,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 @@ -283,7 +283,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
124 changes: 89 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,102 @@ 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);
const PREDEFINED_TOOLS: &[Symbol] =
&[sym::clippy, sym::rustfmt, sym::diagnostic, sym::miri, sym::rust_analyzer];

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 PREDEFINED_TOOLS.iter().any(|&tool| tool == ident.name) {
cx.should_emit.emit_err(cx.dcx().create_err(ToolReserved {
span: ident.span,
tool: ident,
reason: "predefined",
}));
continue;
}

let mut res = Vec::new();
if ident.name == sym::rustc {
cx.should_emit.emit_err(cx.dcx().create_err(ToolReserved {
span: ident.span,
tool: ident,
reason: "reserved",
}));
continue;
}

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;
};
for tools in tools.iter_mut() {
tools.insert(ident);
}
}
}

let path = elem.path();
let Some(ident) = path.word() else {
cx.adcx().expected_identifier(path.span());
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),
),
];

res.push(ident);
}
const ALLOWED_TARGETS: AllowedTargets<'_> = AllowedTargets::AllowList(&[Allow(Target::Crate)]);

res
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,
lint_tools: self.lint_tools,
})
}
}
}
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 @@ -151,6 +151,7 @@ attribute_parsers!(
OnUnimplementedParser,
OnUnknownParser,
OnUnmatchedArgsParser,
RegisterToolParser,
RustcAlignParser,
RustcAlignStaticParser,
RustcCguTestAttributeParser,
Expand All @@ -165,7 +166,6 @@ attribute_parsers!(
Combine<FeatureParser>,
Combine<ForceTargetFeatureParser>,
Combine<LinkParser>,
Combine<RegisterToolParser>,
Combine<ReprParser>,
Combine<RustcAllowConstFnUnstableParser>,
Combine<RustcCleanParser>,
Expand Down
11 changes: 10 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 @@ -824,3 +824,12 @@ pub(crate) struct UnknownExternLangItem {
pub span: Span,
pub lang_item: Symbol,
}

#[derive(Diagnostic)]
#[diag("tool `{$tool}` is {$reason} and cannot be registered")]
pub(crate) struct ToolReserved {
#[primary_span]
pub(crate) span: Span,
pub(crate) tool: Ident,
pub(crate) reason: &'static str,
}
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 @@ -37,15 +37,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 @@ -57,8 +57,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 @@ -68,42 +68,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 @@ -112,25 +118,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 @@ -212,7 +238,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 @@ -252,10 +278,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 @@ -299,8 +331,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
Loading
Loading