Auto merge of #73461 - calebzulawski:validate-attribute-placement, r=matthewjasper
Validate built-in attribute placement Closes #54584, closes #47725, closes #54044. I've changed silently ignoring some incorrectly placed attributes to errors. I'm not sure what the policy is since this can theoretically break code (should they be warnings instead? does it warrant a crater run?).
This commit is contained in:
commit
dbb73f8f79
18 changed files with 1167 additions and 473 deletions
|
@ -66,12 +66,26 @@ impl CheckAttrVisitor<'tcx> {
|
|||
} else if self.tcx.sess.check_name(attr, sym::marker) {
|
||||
self.check_marker(attr, span, target)
|
||||
} else if self.tcx.sess.check_name(attr, sym::target_feature) {
|
||||
self.check_target_feature(attr, span, target)
|
||||
self.check_target_feature(hir_id, attr, span, target)
|
||||
} else if self.tcx.sess.check_name(attr, sym::track_caller) {
|
||||
self.check_track_caller(&attr.span, attrs, span, target)
|
||||
} else if self.tcx.sess.check_name(attr, sym::doc) {
|
||||
self.check_doc_alias(attr, hir_id, target)
|
||||
} else if self.tcx.sess.check_name(attr, sym::no_link) {
|
||||
self.check_no_link(&attr, span, target)
|
||||
} else if self.tcx.sess.check_name(attr, sym::export_name) {
|
||||
self.check_export_name(&attr, span, target)
|
||||
} else {
|
||||
// lint-only checks
|
||||
if self.tcx.sess.check_name(attr, sym::cold) {
|
||||
self.check_cold(hir_id, attr, span, target);
|
||||
} else if self.tcx.sess.check_name(attr, sym::link_name) {
|
||||
self.check_link_name(hir_id, attr, span, target);
|
||||
} else if self.tcx.sess.check_name(attr, sym::link_section) {
|
||||
self.check_link_section(hir_id, attr, span, target);
|
||||
} else if self.tcx.sess.check_name(attr, sym::no_mangle) {
|
||||
self.check_no_mangle(hir_id, attr, span, target);
|
||||
}
|
||||
true
|
||||
};
|
||||
}
|
||||
|
@ -109,12 +123,12 @@ impl CheckAttrVisitor<'tcx> {
|
|||
lint.build("`#[inline]` is ignored on constants")
|
||||
.warn(
|
||||
"this was previously accepted by the compiler but is \
|
||||
being phased out; it will become a hard error in \
|
||||
a future release!",
|
||||
being phased out; it will become a hard error in \
|
||||
a future release!",
|
||||
)
|
||||
.note(
|
||||
"see issue #65833 <https://github.com/rust-lang/rust/issues/65833> \
|
||||
for more information",
|
||||
for more information",
|
||||
)
|
||||
.emit();
|
||||
});
|
||||
|
@ -153,7 +167,7 @@ impl CheckAttrVisitor<'tcx> {
|
|||
.emit();
|
||||
false
|
||||
}
|
||||
Target::Fn | Target::Method(..) | Target::ForeignFn => true,
|
||||
Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => true,
|
||||
_ => {
|
||||
struct_span_err!(
|
||||
self.tcx.sess,
|
||||
|
@ -202,10 +216,31 @@ impl CheckAttrVisitor<'tcx> {
|
|||
}
|
||||
|
||||
/// Checks if the `#[target_feature]` attribute on `item` is valid. Returns `true` if valid.
|
||||
fn check_target_feature(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
|
||||
fn check_target_feature(
|
||||
&self,
|
||||
hir_id: HirId,
|
||||
attr: &Attribute,
|
||||
span: &Span,
|
||||
target: Target,
|
||||
) -> bool {
|
||||
match target {
|
||||
Target::Fn
|
||||
| Target::Method(MethodKind::Trait { body: true } | MethodKind::Inherent) => true,
|
||||
// FIXME: #[target_feature] was previously erroneously allowed on statements and some
|
||||
// crates used this, so only emit a warning.
|
||||
Target::Statement => {
|
||||
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
|
||||
lint.build("attribute should be applied to a function")
|
||||
.warn(
|
||||
"this was previously accepted by the compiler but is \
|
||||
being phased out; it will become a hard error in \
|
||||
a future release!",
|
||||
)
|
||||
.span_label(*span, "not a function")
|
||||
.emit();
|
||||
});
|
||||
true
|
||||
}
|
||||
_ => {
|
||||
self.tcx
|
||||
.sess
|
||||
|
@ -277,6 +312,136 @@ impl CheckAttrVisitor<'tcx> {
|
|||
true
|
||||
}
|
||||
|
||||
/// Checks if `#[cold]` is applied to a non-function. Returns `true` if valid.
|
||||
fn check_cold(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
|
||||
match target {
|
||||
Target::Fn | Target::Method(..) | Target::ForeignFn | Target::Closure => {}
|
||||
_ => {
|
||||
// FIXME: #[cold] was previously allowed on non-functions and some crates used
|
||||
// this, so only emit a warning.
|
||||
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
|
||||
lint.build("attribute should be applied to a function")
|
||||
.warn(
|
||||
"this was previously accepted by the compiler but is \
|
||||
being phased out; it will become a hard error in \
|
||||
a future release!",
|
||||
)
|
||||
.span_label(*span, "not a function")
|
||||
.emit();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if `#[link_name]` is applied to an item other than a foreign function or static.
|
||||
fn check_link_name(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
|
||||
match target {
|
||||
Target::ForeignFn | Target::ForeignStatic => {}
|
||||
_ => {
|
||||
// FIXME: #[cold] was previously allowed on non-functions/statics and some crates
|
||||
// used this, so only emit a warning.
|
||||
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
|
||||
let mut diag =
|
||||
lint.build("attribute should be applied to a foreign function or static");
|
||||
diag.warn(
|
||||
"this was previously accepted by the compiler but is \
|
||||
being phased out; it will become a hard error in \
|
||||
a future release!",
|
||||
);
|
||||
|
||||
// See issue #47725
|
||||
if let Target::ForeignMod = target {
|
||||
if let Some(value) = attr.value_str() {
|
||||
diag.span_help(
|
||||
attr.span,
|
||||
&format!(r#"try `#[link(name = "{}")]` instead"#, value),
|
||||
);
|
||||
} else {
|
||||
diag.span_help(attr.span, r#"try `#[link(name = "...")]` instead"#);
|
||||
}
|
||||
}
|
||||
|
||||
diag.span_label(*span, "not a foreign function or static");
|
||||
diag.emit();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if `#[no_link]` is applied to an `extern crate`. Returns `true` if valid.
|
||||
fn check_no_link(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
|
||||
if target == Target::ExternCrate {
|
||||
true
|
||||
} else {
|
||||
self.tcx
|
||||
.sess
|
||||
.struct_span_err(attr.span, "attribute should be applied to an `extern crate` item")
|
||||
.span_label(*span, "not an `extern crate` item")
|
||||
.emit();
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if `#[export_name]` is applied to a function or static. Returns `true` if valid.
|
||||
fn check_export_name(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
|
||||
match target {
|
||||
Target::Static | Target::Fn | Target::Method(..) => true,
|
||||
_ => {
|
||||
self.tcx
|
||||
.sess
|
||||
.struct_span_err(
|
||||
attr.span,
|
||||
"attribute should be applied to a function or static",
|
||||
)
|
||||
.span_label(*span, "not a function or static")
|
||||
.emit();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if `#[link_section]` is applied to a function or static.
|
||||
fn check_link_section(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
|
||||
match target {
|
||||
Target::Static | Target::Fn | Target::Method(..) => {}
|
||||
_ => {
|
||||
// FIXME: #[link_section] was previously allowed on non-functions/statics and some
|
||||
// crates used this, so only emit a warning.
|
||||
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
|
||||
lint.build("attribute should be applied to a function or static")
|
||||
.warn(
|
||||
"this was previously accepted by the compiler but is \
|
||||
being phased out; it will become a hard error in \
|
||||
a future release!",
|
||||
)
|
||||
.span_label(*span, "not a function or static")
|
||||
.emit();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if `#[no_mangle]` is applied to a function or static.
|
||||
fn check_no_mangle(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
|
||||
match target {
|
||||
Target::Static | Target::Fn | Target::Method(..) => {}
|
||||
_ => {
|
||||
// FIXME: #[no_mangle] was previously allowed on non-functions/statics and some
|
||||
// crates used this, so only emit a warning.
|
||||
self.tcx.struct_span_lint_hir(UNUSED_ATTRIBUTES, hir_id, attr.span, |lint| {
|
||||
lint.build("attribute should be applied to a function or static")
|
||||
.warn(
|
||||
"this was previously accepted by the compiler but is \
|
||||
being phased out; it will become a hard error in \
|
||||
a future release!",
|
||||
)
|
||||
.span_label(*span, "not a function or static")
|
||||
.emit();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the `#[repr]` attributes on `item` are valid.
|
||||
fn check_repr(
|
||||
&self,
|
||||
|
@ -321,7 +486,11 @@ impl CheckAttrVisitor<'tcx> {
|
|||
}
|
||||
sym::simd => {
|
||||
is_simd = true;
|
||||
if target != Target::Struct { ("a", "struct") } else { continue }
|
||||
if target != Target::Struct {
|
||||
("a", "struct")
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
sym::transparent => {
|
||||
is_transparent = true;
|
||||
|
@ -358,7 +527,11 @@ impl CheckAttrVisitor<'tcx> {
|
|||
| sym::isize
|
||||
| sym::usize => {
|
||||
int_reprs += 1;
|
||||
if target != Target::Enum { ("an", "enum") } else { continue }
|
||||
if target != Target::Enum {
|
||||
("an", "enum")
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_ => continue,
|
||||
};
|
||||
|
@ -421,10 +594,8 @@ impl CheckAttrVisitor<'tcx> {
|
|||
fn check_stmt_attributes(&self, stmt: &hir::Stmt<'_>) {
|
||||
// When checking statements ignore expressions, they will be checked later
|
||||
if let hir::StmtKind::Local(ref l) = stmt.kind {
|
||||
self.check_attributes(l.hir_id, &l.attrs, &stmt.span, Target::Statement, None);
|
||||
for attr in l.attrs.iter() {
|
||||
if self.tcx.sess.check_name(attr, sym::inline) {
|
||||
self.check_inline(l.hir_id, attr, &stmt.span, Target::Statement);
|
||||
}
|
||||
if self.tcx.sess.check_name(attr, sym::repr) {
|
||||
self.emit_repr_error(
|
||||
attr.span,
|
||||
|
@ -442,10 +613,8 @@ impl CheckAttrVisitor<'tcx> {
|
|||
hir::ExprKind::Closure(..) => Target::Closure,
|
||||
_ => Target::Expression,
|
||||
};
|
||||
self.check_attributes(expr.hir_id, &expr.attrs, &expr.span, target, None);
|
||||
for attr in expr.attrs.iter() {
|
||||
if self.tcx.sess.check_name(attr, sym::inline) {
|
||||
self.check_inline(expr.hir_id, attr, &expr.span, target);
|
||||
}
|
||||
if self.tcx.sess.check_name(attr, sym::repr) {
|
||||
self.emit_repr_error(
|
||||
attr.span,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue