rust/src/librustc/hir/check_attr.rs

394 lines
14 KiB
Rust
Raw Normal View History

2015-09-25 15:25:59 +09:00
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
2017-06-03 18:14:29 +02:00
//! This module implements some validity checks for attributes.
//! In particular it verifies that `#[inline]` and `#[repr]` attributes are
//! attached to items that actually support them and if there are
//! conflicts between multiple such attributes attached to the same
//! item.
use hir;
use hir::intravisit::{self, Visitor, NestedVisitorMap};
use ty::TyCtxt;
use std::fmt::{self, Display};
use syntax_pos::Span;
2015-09-25 15:25:59 +09:00
#[derive(Copy, Clone, PartialEq)]
pub(crate) enum Target {
ExternCrate,
Use,
Static,
Const,
2015-09-25 15:25:59 +09:00
Fn,
Closure,
Mod,
ForeignMod,
GlobalAsm,
Ty,
Existential,
Enum,
2015-09-25 15:25:59 +09:00
Struct,
Union,
Trait,
TraitAlias,
Impl,
Expression,
Statement,
}
impl Display for Target {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", match *self {
Target::ExternCrate => "extern crate",
Target::Use => "use",
Target::Static => "static item",
Target::Const => "constant item",
Target::Fn => "function",
Target::Closure => "closure",
Target::Mod => "module",
Target::ForeignMod => "foreign module",
Target::GlobalAsm => "global asm",
Target::Ty => "type alias",
Target::Existential => "existential type",
Target::Enum => "enum",
Target::Struct => "struct",
Target::Union => "union",
Target::Trait => "trait",
Target::TraitAlias => "trait alias",
Target::Impl => "item",
Target::Expression => "expression",
Target::Statement => "statement",
})
}
2015-09-25 15:25:59 +09:00
}
impl Target {
pub(crate) fn from_item(item: &hir::Item) -> Target {
2015-09-25 15:25:59 +09:00
match item.node {
hir::ItemKind::ExternCrate(..) => Target::ExternCrate,
hir::ItemKind::Use(..) => Target::Use,
hir::ItemKind::Static(..) => Target::Static,
hir::ItemKind::Const(..) => Target::Const,
2018-07-11 23:36:06 +08:00
hir::ItemKind::Fn(..) => Target::Fn,
hir::ItemKind::Mod(..) => Target::Mod,
hir::ItemKind::ForeignMod(..) => Target::ForeignMod,
hir::ItemKind::GlobalAsm(..) => Target::GlobalAsm,
hir::ItemKind::Ty(..) => Target::Ty,
hir::ItemKind::Existential(..) => Target::Existential,
hir::ItemKind::Enum(..) => Target::Enum,
2018-07-11 23:36:06 +08:00
hir::ItemKind::Struct(..) => Target::Struct,
hir::ItemKind::Union(..) => Target::Union,
hir::ItemKind::Trait(..) => Target::Trait,
hir::ItemKind::TraitAlias(..) => Target::TraitAlias,
hir::ItemKind::Impl(..) => Target::Impl,
2015-09-25 15:25:59 +09:00
}
}
}
struct CheckAttrVisitor<'a, 'tcx: 'a> {
tcx: TyCtxt<'a, 'tcx, 'tcx>,
2015-09-25 15:25:59 +09:00
}
impl<'a, 'tcx> CheckAttrVisitor<'a, 'tcx> {
2017-06-03 18:14:29 +02:00
/// Check any attribute.
fn check_attributes(&self, item: &hir::Item, target: Target) {
Upgrade to LLVM's master branch (LLVM 7) This commit upgrades the main LLVM submodule to LLVM's current master branch. The LLD submodule is updated in tandem as well as compiler-builtins. Along the way support was also added for LLVM 7's new features. This primarily includes the support for custom section concatenation natively in LLD so we now add wasm custom sections in LLVM IR rather than having custom support in rustc itself for doing so. Some other miscellaneous changes are: * We now pass `--gc-sections` to `wasm-ld` * The optimization level is now passed to `wasm-ld` * A `--stack-first` option is passed to LLD to have stack overflow always cause a trap instead of corrupting static data * The wasm target for LLVM switched to `wasm32-unknown-unknown`. * The syntax for aligned pointers has changed in LLVM IR and tests are updated to reflect this. * The `thumbv6m-none-eabi` target is disabled due to an [LLVM bug][llbug] Nowadays we've been mostly only upgrading whenever there's a major release of LLVM but enough changes have been happening on the wasm target that there's been growing motivation for quite some time now to upgrade out version of LLD. To upgrade LLD, however, we need to upgrade LLVM to avoid needing to build yet another version of LLVM on the builders. The revision of LLVM in use here is arbitrarily chosen. We will likely need to continue to update it over time if and when we discover bugs. Once LLVM 7 is fully released we can switch to that channel as well. [llbug]: https://bugs.llvm.org/show_bug.cgi?id=37382
2018-06-01 10:20:00 -07:00
if target == Target::Fn || target == Target::Const {
2018-05-08 16:10:16 +03:00
self.tcx.codegen_fn_attrs(self.tcx.hir.local_def_id(item.id));
} else if let Some(a) = item.attrs.iter().find(|a| a.check_name("target_feature")) {
self.tcx.sess.struct_span_err(a.span, "attribute should be applied to a function")
.span_label(item.span, "not a function")
.emit();
}
for attr in &item.attrs {
rustc: Add a `#[wasm_import_module]` attribute This commit adds a new attribute to the Rust compiler specific to the wasm target (and no other targets). The `#[wasm_import_module]` attribute is used to specify the module that a name is imported from, and is used like so: #[wasm_import_module = "./foo.js"] extern { fn some_js_function(); } Here the import of the symbol `some_js_function` is tagged with the `./foo.js` module in the wasm output file. Wasm-the-format includes two fields on all imports, a module and a field. The field is the symbol name (`some_js_function` above) and the module has historically unconditionally been `"env"`. I'm not sure if this `"env"` convention has asm.js or LLVM roots, but regardless we'd like the ability to configure it! The proposed ES module integration with wasm (aka a wasm module is "just another ES module") requires that the import module of wasm imports is interpreted as an ES module import, meaning that you'll need to encode paths, NPM packages, etc. As a result, we'll need this to be something other than `"env"`! Unfortunately neither our version of LLVM nor LLD supports custom import modules (aka anything not `"env"`). My hope is that by the time LLVM 7 is released both will have support, but in the meantime this commit adds some primitive encoding/decoding of wasm files to the compiler. This way rustc postprocesses the wasm module that LLVM emits to ensure it's got all the imports we'd like to have in it. Eventually I'd ideally like to unconditionally require this attribute to be placed on all `extern { ... }` blocks. For now though it seemed prudent to add it as an unstable attribute, so for now it's not required (as that'd force usage of a feature gate). Hopefully it doesn't take too long to "stabilize" this! cc rust-lang-nursery/rust-wasm#29
2018-02-10 14:28:17 -08:00
if attr.check_name("inline") {
self.check_inline(attr, &item.span, target)
} else if attr.check_name("non_exhaustive") {
self.check_non_exhaustive(attr, item, target)
} else if attr.check_name("marker") {
self.check_marker(attr, item, target)
2017-06-03 18:14:29 +02:00
}
}
self.check_repr(item, target);
self.check_used(item, target);
2017-06-03 18:14:29 +02:00
}
/// Check if an `#[inline]` is applied to a function or a closure.
fn check_inline(&self, attr: &hir::Attribute, span: &Span, target: Target) {
if target != Target::Fn && target != Target::Closure {
struct_span_err!(self.tcx.sess,
attr.span,
E0518,
"attribute should be applied to function or closure")
.span_label(*span, "not a function or closure")
.emit();
2015-09-25 15:25:59 +09:00
}
}
/// Check if the `#[non_exhaustive]` attribute on an `item` is valid.
fn check_non_exhaustive(&self, attr: &hir::Attribute, item: &hir::Item, target: Target) {
match target {
Target::Struct | Target::Enum => { /* Valid */ },
_ => {
struct_span_err!(self.tcx.sess,
attr.span,
2018-06-10 14:04:48 +02:00
E0701,
"attribute can only be applied to a struct or enum")
.span_label(item.span, "not a struct or enum")
.emit();
return;
}
}
if attr.meta_item_list().is_some() || attr.value_str().is_some() {
struct_span_err!(self.tcx.sess,
attr.span,
2018-06-10 14:04:48 +02:00
E0702,
"attribute should be empty")
.span_label(item.span, "not empty")
.emit();
}
}
/// Check if the `#[marker]` attribute on an `item` is valid.
fn check_marker(&self, attr: &hir::Attribute, item: &hir::Item, target: Target) {
match target {
Target::Trait => { /* Valid */ },
_ => {
self.tcx.sess
.struct_span_err(attr.span, "attribute can only be applied to a trait")
.span_label(item.span, "not a trait")
.emit();
return;
}
}
if !attr.is_word() {
self.tcx.sess
.struct_span_err(attr.span, "attribute should be empty")
.emit();
}
}
/// Check if the `#[repr]` attributes on `item` are valid.
fn check_repr(&self, item: &hir::Item, target: Target) {
// Extract the names of all repr hints, e.g., [foo, bar, align] for:
// ```
// #[repr(foo)]
// #[repr(bar, align(8))]
// ```
let hints: Vec<_> = item.attrs
.iter()
.filter(|attr| attr.name() == "repr")
.filter_map(|attr| attr.meta_item_list())
2018-09-12 12:31:11 +02:00
.flatten()
.collect();
let mut int_reprs = 0;
let mut is_c = false;
let mut is_simd = false;
2018-01-03 17:43:30 +01:00
let mut is_transparent = false;
for hint in &hints {
let name = if let Some(name) = hint.name() {
name
} else {
// Invalid repr hint like repr(42). We don't check for unrecognized hints here
// (libsyntax does that), so just ignore it.
continue;
};
let (article, allowed_targets) = match &*name.as_str() {
2015-09-25 15:25:59 +09:00
"C" => {
is_c = true;
if target != Target::Struct &&
target != Target::Union &&
target != Target::Enum {
("a", "struct, enum or union")
2015-11-09 22:13:55 +05:30
} else {
continue
2015-09-25 15:25:59 +09:00
}
}
"packed" => {
if target != Target::Struct &&
target != Target::Union {
("a", "struct or union")
} else {
continue
}
}
"simd" => {
is_simd = true;
2015-09-25 15:25:59 +09:00
if target != Target::Struct {
("a", "struct")
2015-11-09 22:13:55 +05:30
} else {
continue
2015-09-25 15:25:59 +09:00
}
}
"align" => {
2017-07-17 00:44:13 +10:00
if target != Target::Struct &&
target != Target::Union {
("a", "struct or union")
} else {
continue
}
}
2018-01-03 17:43:30 +01:00
"transparent" => {
is_transparent = true;
if target != Target::Struct {
("a", "struct")
} else {
continue
}
}
2018-09-12 12:31:11 +02:00
"i8" | "u8" | "i16" | "u16" |
2015-09-25 15:25:59 +09:00
"i32" | "u32" | "i64" | "u64" |
"isize" | "usize" => {
int_reprs += 1;
2015-09-25 15:25:59 +09:00
if target != Target::Enum {
("an", "enum")
2015-11-09 22:13:55 +05:30
} else {
continue
2015-09-25 15:25:59 +09:00
}
}
2015-11-09 22:13:55 +05:30
_ => continue,
};
self.emit_repr_error(
hint.span,
item.span,
&format!("attribute should be applied to {}", allowed_targets),
&format!("not {} {}", article, allowed_targets),
)
2015-09-25 15:25:59 +09:00
}
2018-01-03 17:43:30 +01:00
// Just point at all repr hints if there are any incompatibilities.
// This is not ideal, but tracking precisely which ones are at fault is a huge hassle.
let hint_spans = hints.iter().map(|hint| hint.span);
// Error on repr(transparent, <anything else>).
if is_transparent && hints.len() > 1 {
let hint_spans: Vec<_> = hint_spans.clone().collect();
span_err!(self.tcx.sess, hint_spans, E0692,
"transparent struct cannot have other repr hints");
}
// Warn on repr(u8, u16), repr(C, simd), and c-like-enum-repr(C, u8)
if (int_reprs > 1)
|| (is_simd && is_c)
|| (int_reprs == 1 && is_c && is_c_like_enum(item)) {
2018-01-03 17:43:30 +01:00
let hint_spans: Vec<_> = hint_spans.collect();
span_warn!(self.tcx.sess, hint_spans, E0566,
"conflicting representation hints");
}
2015-09-25 15:25:59 +09:00
}
fn emit_repr_error(
&self,
hint_span: Span,
label_span: Span,
hint_message: &str,
label_message: &str,
) {
struct_span_err!(self.tcx.sess, hint_span, E0517, "{}", hint_message)
.span_label(label_span, label_message)
.emit();
}
fn check_stmt_attributes(&self, stmt: &hir::Stmt) {
// When checking statements ignore expressions, they will be checked later
2018-07-11 18:54:37 +08:00
if let hir::StmtKind::Decl(_, _) = stmt.node {
for attr in stmt.node.attrs() {
if attr.check_name("inline") {
self.check_inline(attr, &stmt.span, Target::Statement);
}
if attr.check_name("repr") {
self.emit_repr_error(
attr.span,
stmt.span,
2018-07-28 14:40:32 +02:00
"attribute should not be applied to a statement",
"not a struct, enum or union",
);
}
}
}
}
fn check_expr_attributes(&self, expr: &hir::Expr) {
let target = match expr.node {
2018-07-11 20:05:29 +08:00
hir::ExprKind::Closure(..) => Target::Closure,
_ => Target::Expression,
};
for attr in expr.attrs.iter() {
if attr.check_name("inline") {
self.check_inline(attr, &expr.span, target);
}
if attr.check_name("repr") {
self.emit_repr_error(
attr.span,
expr.span,
2018-07-28 14:40:32 +02:00
"attribute should not be applied to an expression",
"not defining a struct, enum or union",
);
}
}
}
fn check_used(&self, item: &hir::Item, target: Target) {
for attr in &item.attrs {
2018-04-17 15:33:39 +02:00
if attr.name() == "used" && target != Target::Static {
self.tcx.sess
.span_err(attr.span, "attribute must be applied to a `static` variable");
}
}
}
2015-09-25 15:25:59 +09:00
}
impl<'a, 'tcx> Visitor<'tcx> for CheckAttrVisitor<'a, 'tcx> {
fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
NestedVisitorMap::OnlyBodies(&self.tcx.hir)
}
fn visit_item(&mut self, item: &'tcx hir::Item) {
2015-09-25 15:25:59 +09:00
let target = Target::from_item(item);
self.check_attributes(item, target);
intravisit::walk_item(self, item)
}
fn visit_stmt(&mut self, stmt: &'tcx hir::Stmt) {
self.check_stmt_attributes(stmt);
intravisit::walk_stmt(self, stmt)
}
fn visit_expr(&mut self, expr: &'tcx hir::Expr) {
self.check_expr_attributes(expr);
intravisit::walk_expr(self, expr)
2015-09-25 15:25:59 +09:00
}
}
pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>) {
let mut checker = CheckAttrVisitor { tcx };
tcx.hir.krate().visit_all_item_likes(&mut checker.as_deep_visitor());
2015-09-25 15:25:59 +09:00
}
fn is_c_like_enum(item: &hir::Item) -> bool {
2018-07-11 23:36:06 +08:00
if let hir::ItemKind::Enum(ref def, _) = item.node {
for variant in &def.variants {
match variant.node.data {
hir::VariantData::Unit(_) => { /* continue */ }
_ => { return false; }
}
}
true
} else {
false
}
}