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 Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3417,6 +3417,7 @@ dependencies = [
"rustc_errors",
"rustc_feature",
"rustc_fluent_macro",
"rustc_hir",
"rustc_macros",
"rustc_session",
"rustc_span",
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast_passes/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ rustc_data_structures = { path = "../rustc_data_structures" }
rustc_errors = { path = "../rustc_errors" }
rustc_feature = { path = "../rustc_feature" }
rustc_fluent_macro = { path = "../rustc_fluent_macro" }
rustc_hir = { path = "../rustc_hir" }
rustc_macros = { path = "../rustc_macros" }
rustc_session = { path = "../rustc_session" }
rustc_span = { path = "../rustc_span" }
Expand Down
23 changes: 18 additions & 5 deletions compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
use rustc_ast as ast;
use rustc_ast::visit::{self, AssocCtxt, FnCtxt, FnKind, Visitor};
use rustc_ast::{NodeId, PatKind, attr, token};
use rustc_attr_parsing::AttributeParser;
use rustc_feature::{AttributeGate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute, Features};
use rustc_hir::Attribute;
use rustc_hir::attrs::AttributeKind;
use rustc_session::Session;
use rustc_session::parse::{feature_err, feature_warn};
use rustc_span::source_map::Spanned;
use rustc_span::{Span, Symbol, sym};
use rustc_span::{DUMMY_SP, Span, Symbol, sym};
use thin_vec::ThinVec;

use crate::errors;
Expand Down Expand Up @@ -587,17 +590,27 @@ fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate)
return;
}
let mut errored = false;
for attr in krate.attrs.iter().filter(|attr| attr.has_name(sym::feature)) {

if let Some(Attribute::Parsed(AttributeKind::Feature(feature_idents, first_span))) =
AttributeParser::parse_limited(
sess,
&krate.attrs,
sym::feature,
DUMMY_SP,
krate.id,
Some(&features),
)
{
// `feature(...)` used on non-nightly. This is definitely an error.
let mut err = errors::FeatureOnNonNightly {
span: attr.span,
span: first_span,
channel: option_env!("CFG_RELEASE_CHANNEL").unwrap_or("(unknown)"),
stable_features: vec![],
sugg: None,
};

let mut all_stable = true;
for ident in attr.meta_item_list().into_iter().flatten().flat_map(|nested| nested.ident()) {
for ident in feature_idents {
let name = ident.name;
let stable_since = features
.enabled_lang_features()
Expand All @@ -612,7 +625,7 @@ fn maybe_stage_features(sess: &Session, features: &Features, krate: &ast::Crate)
}
}
if all_stable {
err.sugg = Some(attr.span);
err.sugg = Some(first_span);
}
sess.dcx().emit_err(err);
errored = true;
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_attr_parsing/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -259,3 +259,7 @@ attr_parsing_whole_archive_needs_static =
attr_parsing_limit_invalid =
`limit` must be a non-negative integer
.label = {$error_str}

attr_parsing_feature_single_word =
rust features are always a single identifier, not paths with multiple segments
.help = did you maybe mean `{$first_segment}`?
53 changes: 53 additions & 0 deletions compiler/rustc_attr_parsing/src/attributes/crate_level.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use super::prelude::*;
use crate::session_diagnostics::{FeatureExpectedSingleWord, LimitInvalid};

pub(crate) struct CrateNameParser;

Expand Down Expand Up @@ -147,3 +148,55 @@ impl<S: Stage> NoArgsAttributeParser<S> for RustcCoherenceIsCoreParser {
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
const CREATE: fn(Span) -> AttributeKind = AttributeKind::RustcCoherenceIsCore;
}

pub(crate) struct FeatureParser;

impl<S: Stage> CombineAttributeParser<S> for FeatureParser {
const PATH: &[Symbol] = &[sym::feature];
type Item = Ident;
const CONVERT: ConvertFn<Self::Item> = AttributeKind::Feature;
const ALLOWED_TARGETS: AllowedTargets = AllowedTargets::CrateLevel;
const TEMPLATE: AttributeTemplate = template!(List: &["feature1, feature2, ..."]);

fn extend<'c>(
cx: &'c mut AcceptContext<'_, '_, S>,
args: &'c ArgParser<'_>,
) -> impl IntoIterator<Item = Self::Item> + 'c {
let ArgParser::List(list) = args else {
cx.expected_list(cx.attr_span);
return Vec::new();
};

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

let mut res = Vec::new();

for elem in list.mixed() {
let Some(elem) = elem.meta_item() else {
cx.expected_identifier(elem.span());
continue;
};
if let Err(arg_span) = elem.args().no_args() {
cx.expected_no_args(arg_span);
continue;
}

let path = elem.path();
let Some(ident) = path.word() else {
let first_segment = elem.path().segments().next().expect("at least one segment");
cx.emit_err(FeatureExpectedSingleWord {
span: path.span(),
first_segment_span: first_segment.span,
first_segment: first_segment.name,
});
continue;
};

res.push(ident);
}

res
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,15 @@ fn parse_derive_like<S: Stage>(
return None;
};

// updated if we see `attributes(...)` to keep track of the last
// argument we did accept for the final diagnostic
let mut last = trait_ident.span;

// Parse optional attributes
let mut attributes = ThinVec::new();
if let Some(attrs) = items.next() {
last = attrs.span();

let Some(attr_list) = attrs.meta_item() else {
cx.expected_list(attrs.span());
return None;
Expand Down Expand Up @@ -132,7 +138,7 @@ fn parse_derive_like<S: Stage>(

// If anything else is specified, we should reject it
if let Some(next) = items.next() {
cx.expected_no_args(next.span());
cx.expected_end_of_list(last, next.span());
}

Some((Some(trait_ident.name), attributes))
Expand Down
21 changes: 19 additions & 2 deletions compiler/rustc_attr_parsing/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ use crate::attributes::codegen_attrs::{
};
use crate::attributes::confusables::ConfusablesParser;
use crate::attributes::crate_level::{
CrateNameParser, MoveSizeLimitParser, NoCoreParser, NoStdParser, PatternComplexityLimitParser,
RecursionLimitParser, RustcCoherenceIsCoreParser, TypeLengthLimitParser,
CrateNameParser, FeatureParser, MoveSizeLimitParser, NoCoreParser, NoStdParser,
PatternComplexityLimitParser, RecursionLimitParser, RustcCoherenceIsCoreParser,
TypeLengthLimitParser,
};
use crate::attributes::debugger::DebuggerViualizerParser;
use crate::attributes::deprecation::DeprecationParser;
Expand Down Expand Up @@ -165,6 +166,7 @@ attribute_parsers!(
Combine<AllowConstFnUnstableParser>,
Combine<AllowInternalUnstableParser>,
Combine<DebuggerViualizerParser>,
Combine<FeatureParser>,
Combine<ForceTargetFeatureParser>,
Combine<LinkParser>,
Combine<ReprParser>,
Expand Down Expand Up @@ -513,6 +515,21 @@ impl<'f, 'sess: 'f, S: Stage> AcceptContext<'f, 'sess, S> {
})
}

/// Expected the end of an argument list.
///
/// Note: only useful when arguments in an attribute are ordered and we've seen the last one we expected.
/// Most attributes shouldn't care about their argument order.
pub(crate) fn expected_end_of_list(&self, last_item_span: Span, span: Span) -> ErrorGuaranteed {
self.emit_err(AttributeParseError {
span,
attr_span: self.attr_span,
template: self.template.clone(),
attribute: self.attr_path.clone(),
reason: AttributeParseErrorReason::ExpectedEnd { last: last_item_span },
attr_style: self.attr_style,
})
}

pub(crate) fn expected_single_argument(&self, span: Span) -> ErrorGuaranteed {
self.emit_err(AttributeParseError {
span,
Expand Down
18 changes: 18 additions & 0 deletions compiler/rustc_attr_parsing/src/session_diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,9 @@ pub(crate) enum AttributeParseErrorReason<'a> {
list: bool,
},
ExpectedIdentifier,
ExpectedEnd {
last: Span,
},
}

pub(crate) struct AttributeParseError<'a> {
Expand Down Expand Up @@ -744,6 +747,10 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> {
AttributeParseErrorReason::ExpectedIdentifier => {
diag.span_label(self.span, "expected a valid identifier here");
}
AttributeParseErrorReason::ExpectedEnd { last } => {
diag.span_label(last, "expected no more arguments after this");
diag.span_label(self.span, "remove this argument");
}
}

if let Some(link) = self.template.docs {
Expand Down Expand Up @@ -968,3 +975,14 @@ pub(crate) struct LimitInvalid<'a> {
pub value_span: Span,
pub error_str: &'a str,
}

#[derive(Diagnostic)]
#[diag(attr_parsing_feature_single_word)]
pub(crate) struct FeatureExpectedSingleWord {
#[primary_span]
pub span: Span,

#[help]
pub first_segment_span: Span,
pub first_segment: Symbol,
}
4 changes: 3 additions & 1 deletion compiler/rustc_error_codes/src/error_codes/E0556.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
#### Note: this error code is no longer emitted by the compiler.

The `feature` attribute was badly formed.

Erroneous code example:

```compile_fail,E0556
```compile_fail
#![feature(foo_bar_baz, foo(bar), foo = "baz", foo)] // error!
#![feature] // error!
#![feature = "foo"] // error!
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_error_codes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,7 @@ E0550: 0550,
E0551: 0551,
E0552: 0552,
E0554: 0554,
E0556: 0556,
E0556: 0556, // REMOVED: merged with other attribute error codes
E0557: 0557,
E0559: 0559,
E0560: 0560,
Expand Down
Loading
Loading