Skip to content

Commit 4b9f5e4

Browse files
committed
port #[feature] to the new attribute parsing infrastructure
1 parent dfbaf7e commit 4b9f5e4

File tree

8 files changed

+157
-73
lines changed

8 files changed

+157
-73
lines changed

compiler/rustc_attr_parsing/messages.ftl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,3 +251,7 @@ attr_parsing_whole_archive_needs_static =
251251
attr_parsing_limit_invalid =
252252
`limit` must be a non-negative integer
253253
.label = {$error_str}
254+
255+
attr_parsing_feature_single_word =
256+
rust features are always a single identifier, not paths with multiple segments
257+
.help = did you maybe mean `{$first_segment}`?

compiler/rustc_attr_parsing/src/attributes/crate_level.rs

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use std::num::IntErrorKind;
33
use rustc_hir::limit::Limit;
44

55
use super::prelude::*;
6-
use crate::session_diagnostics::LimitInvalid;
6+
use crate::session_diagnostics::{FeatureExpectedSingleWord, LimitInvalid};
77

88
impl<S: Stage> AcceptContext<'_, '_, S> {
99
fn parse_limit_int(&self, nv: &NameValueParser) -> Option<Limit> {
@@ -183,3 +183,58 @@ impl<S: Stage> NoArgsAttributeParser<S> for RustcCoherenceIsCoreParser {
183183
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
184184
const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcCoherenceIsCore;
185185
}
186+
187+
pub(crate) struct FeatureParser;
188+
189+
impl<S: Stage> CombineAttributeParser<S> for FeatureParser {
190+
const PATH: &[Symbol] = &[sym::feature];
191+
type Item = Ident;
192+
const CONVERT: ConvertFn<Self::Item> = AttributeKind::Feature;
193+
194+
// FIXME: recursion limit is allowed on all targets and ignored,
195+
// even though it should only be valid on crates of course
196+
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::AllowList(ALL_TARGETS);
197+
const TEMPLATE: AttributeTemplate = template!(List: &["feature1, feature2, ..."]);
198+
199+
fn extend<'c>(
200+
cx: &'c mut AcceptContext<'_, '_, S>,
201+
args: &'c ArgParser<'_>,
202+
) -> impl IntoIterator<Item = Self::Item> + 'c {
203+
let ArgParser::List(list) = args else {
204+
cx.expected_list(cx.attr_span);
205+
return Vec::new();
206+
};
207+
208+
if list.is_empty() {
209+
cx.warn_empty_attribute(cx.attr_span);
210+
}
211+
212+
let mut res = Vec::new();
213+
214+
for elem in list.mixed() {
215+
let Some(elem) = elem.meta_item() else {
216+
cx.expected_identifier(elem.span());
217+
continue;
218+
};
219+
if let Err(arg_span) = elem.args().no_args() {
220+
cx.expected_no_args(arg_span);
221+
continue;
222+
}
223+
224+
let path = elem.path();
225+
let Some(ident) = path.word() else {
226+
let first_segment = elem.path().segments().next().expect("at least one segment");
227+
cx.emit_err(FeatureExpectedSingleWord {
228+
span: path.span(),
229+
first_segment_span: first_segment.span,
230+
first_segment: first_segment.name,
231+
});
232+
continue;
233+
};
234+
235+
res.push(ident);
236+
}
237+
238+
res
239+
}
240+
}

compiler/rustc_attr_parsing/src/context.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ use crate::attributes::codegen_attrs::{
2525
};
2626
use crate::attributes::confusables::ConfusablesParser;
2727
use crate::attributes::crate_level::{
28-
CrateNameParser, MoveSizeLimitParser, NoCoreParser, NoStdParser, PatternComplexityLimitParser,
29-
RecursionLimitParser, RustcCoherenceIsCoreParser, TypeLengthLimitParser,
28+
CrateNameParser, FeatureParser, MoveSizeLimitParser, NoCoreParser, NoStdParser,
29+
PatternComplexityLimitParser, RecursionLimitParser, RustcCoherenceIsCoreParser,
30+
TypeLengthLimitParser,
3031
};
3132
use crate::attributes::deprecation::DeprecationParser;
3233
use crate::attributes::dummy::DummyParser;
@@ -163,6 +164,7 @@ attribute_parsers!(
163164
// tidy-alphabetical-start
164165
Combine<AllowConstFnUnstableParser>,
165166
Combine<AllowInternalUnstableParser>,
167+
Combine<FeatureParser>,
166168
Combine<ForceTargetFeatureParser>,
167169
Combine<LinkParser>,
168170
Combine<ReprParser>,

compiler/rustc_attr_parsing/src/session_diagnostics.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -940,3 +940,14 @@ pub(crate) struct LimitInvalid<'a> {
940940
pub value_span: Span,
941941
pub error_str: &'a str,
942942
}
943+
944+
#[derive(Diagnostic)]
945+
#[diag(attr_parsing_feature_single_word)]
946+
pub(crate) struct FeatureExpectedSingleWord {
947+
#[primary_span]
948+
pub span: Span,
949+
950+
#[help]
951+
pub first_segment_span: Span,
952+
pub first_segment: Symbol,
953+
}

compiler/rustc_hir/src/attrs/data_structures.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,9 @@ pub enum AttributeKind {
511511
/// Represents `#[export_stable]`.
512512
ExportStable,
513513

514+
/// Represents `#[feature(...)]`
515+
Feature(ThinVec<Ident>, Span),
516+
514517
/// Represents `#[ffi_const]`.
515518
FfiConst(Span),
516519

compiler/rustc_hir/src/attrs/encode_cross_crate.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ impl AttributeKind {
4444
Dummy => No,
4545
ExportName { .. } => Yes,
4646
ExportStable => No,
47+
Feature(..) => No,
4748
FfiConst(..) => No,
4849
FfiPure(..) => No,
4950
Fundamental { .. } => Yes,

compiler/rustc_passes/src/check_attr.rs

Lines changed: 73 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
276276
| AttributeKind::NoCore { .. }
277277
| AttributeKind::NoStd { .. }
278278
| AttributeKind::RustcCoherenceIsCore(..)
279+
| AttributeKind::Feature(..)
279280
) => { /* do nothing */ }
280281
Attribute::Unparsed(attr_item) => {
281282
style = Some(attr_item.style);
@@ -1893,75 +1894,82 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
18931894
fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute, style: Option<AttrStyle>) {
18941895
// Warn on useless empty attributes.
18951896
// FIXME(jdonszelmann): this lint should be moved to attribute parsing, see `AcceptContext::warn_empty_attribute`
1896-
let note = if attr.has_any_name(&[
1897-
sym::allow,
1898-
sym::expect,
1899-
sym::warn,
1900-
sym::deny,
1901-
sym::forbid,
1902-
sym::feature,
1903-
]) && attr.meta_item_list().is_some_and(|list| list.is_empty())
1904-
{
1905-
errors::UnusedNote::EmptyList { name: attr.name().unwrap() }
1906-
} else if attr.has_any_name(&[sym::allow, sym::warn, sym::deny, sym::forbid, sym::expect])
1907-
&& let Some(meta) = attr.meta_item_list()
1908-
&& let [meta] = meta.as_slice()
1909-
&& let Some(item) = meta.meta_item()
1910-
&& let MetaItemKind::NameValue(_) = &item.kind
1911-
&& item.path == sym::reason
1912-
{
1913-
errors::UnusedNote::NoLints { name: attr.name().unwrap() }
1914-
} else if attr.has_any_name(&[sym::allow, sym::warn, sym::deny, sym::forbid, sym::expect])
1915-
&& let Some(meta) = attr.meta_item_list()
1916-
&& meta.iter().any(|meta| {
1917-
meta.meta_item().map_or(false, |item| item.path == sym::linker_messages)
1918-
})
1919-
{
1920-
if hir_id != CRATE_HIR_ID {
1921-
match style {
1922-
Some(ast::AttrStyle::Outer) => {
1923-
let attr_span = attr.span();
1924-
let bang_position = self
1925-
.tcx
1926-
.sess
1927-
.source_map()
1928-
.span_until_char(attr_span, '[')
1929-
.shrink_to_hi();
1930-
1931-
self.tcx.emit_node_span_lint(
1897+
let note =
1898+
if attr.has_any_name(&[sym::allow, sym::expect, sym::warn, sym::deny, sym::forbid])
1899+
&& attr.meta_item_list().is_some_and(|list| list.is_empty())
1900+
{
1901+
errors::UnusedNote::EmptyList { name: attr.name().unwrap() }
1902+
} else if attr.has_any_name(&[
1903+
sym::allow,
1904+
sym::warn,
1905+
sym::deny,
1906+
sym::forbid,
1907+
sym::expect,
1908+
]) && let Some(meta) = attr.meta_item_list()
1909+
&& let [meta] = meta.as_slice()
1910+
&& let Some(item) = meta.meta_item()
1911+
&& let MetaItemKind::NameValue(_) = &item.kind
1912+
&& item.path == sym::reason
1913+
{
1914+
errors::UnusedNote::NoLints { name: attr.name().unwrap() }
1915+
} else if attr.has_any_name(&[
1916+
sym::allow,
1917+
sym::warn,
1918+
sym::deny,
1919+
sym::forbid,
1920+
sym::expect,
1921+
]) && let Some(meta) = attr.meta_item_list()
1922+
&& meta.iter().any(|meta| {
1923+
meta.meta_item().map_or(false, |item| item.path == sym::linker_messages)
1924+
})
1925+
{
1926+
if hir_id != CRATE_HIR_ID {
1927+
match style {
1928+
Some(ast::AttrStyle::Outer) => {
1929+
let attr_span = attr.span();
1930+
let bang_position = self
1931+
.tcx
1932+
.sess
1933+
.source_map()
1934+
.span_until_char(attr_span, '[')
1935+
.shrink_to_hi();
1936+
1937+
self.tcx.emit_node_span_lint(
1938+
UNUSED_ATTRIBUTES,
1939+
hir_id,
1940+
attr_span,
1941+
errors::OuterCrateLevelAttr {
1942+
suggestion: errors::OuterCrateLevelAttrSuggestion {
1943+
bang_position,
1944+
},
1945+
},
1946+
)
1947+
}
1948+
Some(ast::AttrStyle::Inner) | None => self.tcx.emit_node_span_lint(
19321949
UNUSED_ATTRIBUTES,
19331950
hir_id,
1934-
attr_span,
1935-
errors::OuterCrateLevelAttr {
1936-
suggestion: errors::OuterCrateLevelAttrSuggestion { bang_position },
1937-
},
1938-
)
1939-
}
1940-
Some(ast::AttrStyle::Inner) | None => self.tcx.emit_node_span_lint(
1941-
UNUSED_ATTRIBUTES,
1942-
hir_id,
1943-
attr.span(),
1944-
errors::InnerCrateLevelAttr,
1945-
),
1946-
};
1947-
return;
1948-
} else {
1949-
let never_needs_link = self
1950-
.tcx
1951-
.crate_types()
1952-
.iter()
1953-
.all(|kind| matches!(kind, CrateType::Rlib | CrateType::Staticlib));
1954-
if never_needs_link {
1955-
errors::UnusedNote::LinkerMessagesBinaryCrateOnly
1956-
} else {
1951+
attr.span(),
1952+
errors::InnerCrateLevelAttr,
1953+
),
1954+
};
19571955
return;
1956+
} else {
1957+
let never_needs_link = self
1958+
.tcx
1959+
.crate_types()
1960+
.iter()
1961+
.all(|kind| matches!(kind, CrateType::Rlib | CrateType::Staticlib));
1962+
if never_needs_link {
1963+
errors::UnusedNote::LinkerMessagesBinaryCrateOnly
1964+
} else {
1965+
return;
1966+
}
19581967
}
1959-
}
1960-
} else if attr.has_name(sym::default_method_body_is_const) {
1961-
errors::UnusedNote::DefaultMethodBodyConst
1962-
} else {
1963-
return;
1964-
};
1968+
} else if attr.has_name(sym::default_method_body_is_const) {
1969+
errors::UnusedNote::DefaultMethodBodyConst
1970+
} else {
1971+
return;
1972+
};
19651973

19661974
self.tcx.emit_node_span_lint(
19671975
UNUSED_ATTRIBUTES,

src/tools/rust-analyzer/crates/hir-expand/src/attrs.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,22 @@ use std::{borrow::Cow, fmt, ops};
55
use base_db::Crate;
66
use cfg::{CfgExpr, CfgOptions};
77
use either::Either;
8-
use intern::{Interned, Symbol, sym};
8+
use intern::{sym, Interned, Symbol};
99

1010
use mbe::{DelimiterKind, Punct};
11-
use smallvec::{SmallVec, smallvec};
11+
use smallvec::{smallvec, SmallVec};
1212
use span::{Span, SyntaxContext};
1313
use syntax::unescape;
14-
use syntax::{AstNode, AstToken, SyntaxNode, ast, match_ast};
15-
use syntax_bridge::{DocCommentDesugarMode, desugar_doc_comment_text, syntax_node_to_token_tree};
14+
use syntax::{ast, match_ast, AstNode, AstToken, SyntaxNode};
15+
use syntax_bridge::{desugar_doc_comment_text, syntax_node_to_token_tree, DocCommentDesugarMode};
1616
use triomphe::ThinArc;
1717

1818
use crate::{
1919
db::ExpandDatabase,
2020
mod_path::ModPath,
2121
name::Name,
2222
span_map::SpanMapRef,
23-
tt::{self, TopSubtree, token_to_literal},
23+
tt::{self, token_to_literal, TopSubtree},
2424
};
2525

2626
/// Syntactical attributes, without filtering of `cfg_attr`s.

0 commit comments

Comments
 (0)