Auto merge of #33706 - jseyfried:refactor_cfg, r=nrc

Perform `cfg` attribute processing during macro expansion and fix bugs

This PR refactors `cfg` attribute processing and fixes bugs. More specifically:
 - It merges gated feature checking for stmt/expr attributes, `cfg_attr` processing, and `cfg` processing into a single fold.
  - This allows feature gated `cfg` variables to be used in `cfg_attr` on unconfigured items. All other feature gated attributes can already be used on unconfigured items.
 - It performs `cfg` attribute processing during macro expansion instead of after expansion so that macro-expanded items are configured the same as ordinary items. In particular, to match their non-expanded counterparts,
  - macro-expanded unconfigured macro invocations are no longer expanded,
  - macro-expanded unconfigured macro definitions are no longer usable, and
  - feature gated `cfg` variables on macro-expanded macro definitions/invocations are now errors.

This is a [breaking-change]. For example, the following would break:
```rust
macro_rules! m {
    () => {
        #[cfg(attr)]
        macro_rules! foo { () => {} }
        foo!(); // This will be an error

        macro_rules! bar { () => { fn f() {} } }
        #[cfg(attr)] bar!(); // This will no longer be expanded ...
        fn g() { f(); } // ... so that `f` will be unresolved.

        #[cfg(target_thread_local)] // This will be a gated feature error
        macro_rules! baz { () => {} }
    }
}

m!();
```

r? @nrc
This commit is contained in:
bors 2016-05-27 17:46:14 -07:00
commit 8b012ed142
8 changed files with 381 additions and 639 deletions

View file

@ -720,16 +720,7 @@ pub fn phase_2_configure_and_expand(sess: &Session,
ret
});
// JBC: make CFG processing part of expansion to avoid this problem:
// strip again, in case expansion added anything with a #[cfg].
krate = sess.track_errors(|| {
let krate = time(time_passes, "configuration 2", || {
syntax::config::strip_unconfigured_items(sess.diagnostic(),
krate,
&mut feature_gated_cfgs)
});
time(time_passes, "gated configuration checking", || {
let features = sess.features.borrow();
feature_gated_cfgs.sort();

View file

@ -15,7 +15,7 @@ pub use self::UnsafeSource::*;
pub use self::ViewPath_::*;
pub use self::PathParameters::*;
use attr::ThinAttributes;
use attr::{ThinAttributes, HasAttrs};
use codemap::{mk_sp, respan, Span, Spanned, DUMMY_SP, ExpnId};
use abi::Abi;
use errors;
@ -831,13 +831,7 @@ impl StmtKind {
}
pub fn attrs(&self) -> &[Attribute] {
match *self {
StmtKind::Decl(ref d, _) => d.attrs(),
StmtKind::Expr(ref e, _) |
StmtKind::Semi(ref e, _) => e.attrs(),
StmtKind::Mac(_, _, Some(ref b)) => b,
StmtKind::Mac(_, _, None) => &[],
}
HasAttrs::attrs(self)
}
}
@ -870,10 +864,7 @@ pub struct Local {
impl Local {
pub fn attrs(&self) -> &[Attribute] {
match self.attrs {
Some(ref b) => b,
None => &[],
}
HasAttrs::attrs(self)
}
}
@ -889,10 +880,7 @@ pub enum DeclKind {
impl Decl {
pub fn attrs(&self) -> &[Attribute] {
match self.node {
DeclKind::Local(ref l) => l.attrs(),
DeclKind::Item(ref i) => i.attrs(),
}
HasAttrs::attrs(self)
}
}
@ -937,10 +925,7 @@ pub struct Expr {
impl Expr {
pub fn attrs(&self) -> &[Attribute] {
match self.attrs {
Some(ref b) => b,
None => &[],
}
HasAttrs::attrs(self)
}
}

View file

@ -884,82 +884,109 @@ impl AttributesExt for Vec<Attribute> {
}
}
pub trait HasAttrs: Sized {
fn attrs(&self) -> &[ast::Attribute];
fn map_attrs<F: FnOnce(Vec<ast::Attribute>) -> Vec<ast::Attribute>>(self, f: F) -> Self;
}
/// A cheap way to add Attributes to an AST node.
pub trait WithAttrs {
// FIXME: Could be extended to anything IntoIter<Item=Attribute>
fn with_attrs(self, attrs: ThinAttributes) -> Self;
}
impl WithAttrs for P<Expr> {
impl<T: HasAttrs> WithAttrs for T {
fn with_attrs(self, attrs: ThinAttributes) -> Self {
self.map(|mut e| {
e.attrs.update(|a| a.append(attrs));
e
self.map_attrs(|mut orig_attrs| {
orig_attrs.extend(attrs.into_attr_vec());
orig_attrs
})
}
}
impl WithAttrs for P<Item> {
fn with_attrs(self, attrs: ThinAttributes) -> Self {
self.map(|Item { ident, attrs: mut ats, id, node, vis, span }| {
ats.extend(attrs.into_attr_vec());
Item {
ident: ident,
attrs: ats,
id: id,
node: node,
vis: vis,
span: span,
}
})
impl HasAttrs for Vec<Attribute> {
fn attrs(&self) -> &[Attribute] {
&self
}
fn map_attrs<F: FnOnce(Vec<Attribute>) -> Vec<Attribute>>(self, f: F) -> Self {
f(self)
}
}
impl WithAttrs for P<Local> {
fn with_attrs(self, attrs: ThinAttributes) -> Self {
self.map(|Local { pat, ty, init, id, span, attrs: mut ats }| {
ats.update(|a| a.append(attrs));
Local {
pat: pat,
ty: ty,
init: init,
id: id,
span: span,
attrs: ats,
}
})
impl HasAttrs for ThinAttributes {
fn attrs(&self) -> &[Attribute] {
self.as_attr_slice()
}
fn map_attrs<F: FnOnce(Vec<Attribute>) -> Vec<Attribute>>(self, f: F) -> Self {
self.map_thin_attrs(f)
}
}
impl WithAttrs for P<Decl> {
fn with_attrs(self, attrs: ThinAttributes) -> Self {
self.map(|Spanned { span, node }| {
Spanned {
span: span,
node: match node {
DeclKind::Local(local) => DeclKind::Local(local.with_attrs(attrs)),
DeclKind::Item(item) => DeclKind::Item(item.with_attrs(attrs)),
}
}
})
impl<T: HasAttrs + 'static> HasAttrs for P<T> {
fn attrs(&self) -> &[Attribute] {
(**self).attrs()
}
fn map_attrs<F: FnOnce(Vec<Attribute>) -> Vec<Attribute>>(self, f: F) -> Self {
self.map(|t| t.map_attrs(f))
}
}
impl WithAttrs for P<Stmt> {
fn with_attrs(self, attrs: ThinAttributes) -> Self {
self.map(|Spanned { span, node }| {
Spanned {
span: span,
node: match node {
StmtKind::Decl(decl, id) => StmtKind::Decl(decl.with_attrs(attrs), id),
StmtKind::Expr(expr, id) => StmtKind::Expr(expr.with_attrs(attrs), id),
StmtKind::Semi(expr, id) => StmtKind::Semi(expr.with_attrs(attrs), id),
StmtKind::Mac(mac, style, mut ats) => {
ats.update(|a| a.append(attrs));
StmtKind::Mac(mac, style, ats)
}
},
}
})
impl HasAttrs for DeclKind {
fn attrs(&self) -> &[Attribute] {
match *self {
DeclKind::Local(ref local) => local.attrs(),
DeclKind::Item(ref item) => item.attrs(),
}
}
fn map_attrs<F: FnOnce(Vec<Attribute>) -> Vec<Attribute>>(self, f: F) -> Self {
match self {
DeclKind::Local(local) => DeclKind::Local(local.map_attrs(f)),
DeclKind::Item(item) => DeclKind::Item(item.map_attrs(f)),
}
}
}
impl HasAttrs for StmtKind {
fn attrs(&self) -> &[Attribute] {
match *self {
StmtKind::Decl(ref decl, _) => decl.attrs(),
StmtKind::Expr(ref expr, _) | StmtKind::Semi(ref expr, _) => expr.attrs(),
StmtKind::Mac(_, _, ref attrs) => attrs.attrs(),
}
}
fn map_attrs<F: FnOnce(Vec<Attribute>) -> Vec<Attribute>>(self, f: F) -> Self {
match self {
StmtKind::Decl(decl, id) => StmtKind::Decl(decl.map_attrs(f), id),
StmtKind::Expr(expr, id) => StmtKind::Expr(expr.map_attrs(f), id),
StmtKind::Semi(expr, id) => StmtKind::Semi(expr.map_attrs(f), id),
StmtKind::Mac(mac, style, attrs) =>
StmtKind::Mac(mac, style, attrs.map_attrs(f)),
}
}
}
macro_rules! derive_has_attrs_from_field {
($($ty:path),*) => { derive_has_attrs_from_field!($($ty: .attrs),*); };
($($ty:path : $(.$field:ident)*),*) => { $(
impl HasAttrs for $ty {
fn attrs(&self) -> &[Attribute] {
self $(.$field)* .attrs()
}
fn map_attrs<F>(mut self, f: F) -> Self
where F: FnOnce(Vec<Attribute>) -> Vec<Attribute>,
{
self $(.$field)* = self $(.$field)* .map_attrs(f);
self
}
}
)* }
}
derive_has_attrs_from_field! {
Item, Expr, Local, ast::ForeignItem, ast::StructField, ast::ImplItem, ast::TraitItem, ast::Arm
}
derive_has_attrs_from_field! { Decl: .node, Stmt: .node, ast::Variant: .node.attrs }

View file

@ -1258,31 +1258,6 @@ impl CodeMap {
return a;
}
/// Check if the backtrace `subtrace` contains `suptrace` as a prefix.
pub fn more_specific_trace(&self,
mut subtrace: ExpnId,
suptrace: ExpnId)
-> bool {
loop {
if subtrace == suptrace {
return true;
}
let stop = self.with_expn_info(subtrace, |opt_expn_info| {
if let Some(expn_info) = opt_expn_info {
subtrace = expn_info.call_site.expn_id;
false
} else {
true
}
});
if stop {
return false;
}
}
}
pub fn record_expansion(&self, expn_info: ExpnInfo) -> ExpnId {
let mut expansions = self.expansions.borrow_mut();
expansions.push(expn_info);

View file

@ -8,326 +8,76 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use attr::AttrMetaMethods;
use attr::{AttrMetaMethods, HasAttrs};
use errors::Handler;
use feature_gate::GatedCfgAttr;
use fold::Folder;
use {ast, fold, attr};
use visit;
use codemap::{Spanned, respan};
use ptr::P;
use util::small_vector::SmallVector;
pub trait CfgFolder: fold::Folder {
// Check if a node with the given attributes is in this configuration.
fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool;
// Update a node before checking if it is in this configuration (used to implement `cfg_attr`).
fn process_attrs<T: HasAttrs>(&mut self, node: T) -> T { node }
// Visit attributes on expression and statements (but not attributes on items in blocks).
fn visit_stmt_or_expr_attrs(&mut self, _attrs: &[ast::Attribute]) {}
// Visit unremovable (non-optional) expressions -- c.f. `fold_expr` vs `fold_opt_expr`.
fn visit_unremovable_expr(&mut self, _expr: &ast::Expr) {}
fn configure<T: HasAttrs>(&mut self, node: T) -> Option<T> {
let node = self.process_attrs(node);
if self.in_cfg(node.attrs()) { Some(node) } else { None }
}
}
/// A folder that strips out items that do not belong in the current
/// configuration.
struct Context<'a, F> where F: FnMut(&[ast::Attribute]) -> bool {
in_cfg: F,
diagnostic: &'a Handler,
}
// Support conditional compilation by transforming the AST, stripping out
// any items that do not belong in the current configuration
pub fn strip_unconfigured_items(diagnostic: &Handler, krate: ast::Crate,
feature_gated_cfgs: &mut Vec<GatedCfgAttr>)
-> ast::Crate
{
// Need to do this check here because cfg runs before feature_gates
check_for_gated_stmt_expr_attributes(&krate, feature_gated_cfgs);
let krate = process_cfg_attr(diagnostic, krate, feature_gated_cfgs);
let config = krate.config.clone();
strip_items(diagnostic,
krate,
|attrs| {
let mut diag = CfgDiagReal {
diag: diagnostic,
feature_gated_cfgs: feature_gated_cfgs,
};
in_cfg(&config, attrs, &mut diag)
})
}
impl<'a, F> fold::Folder for Context<'a, F> where F: FnMut(&[ast::Attribute]) -> bool {
fn fold_foreign_mod(&mut self, foreign_mod: ast::ForeignMod) -> ast::ForeignMod {
fold_foreign_mod(self, foreign_mod)
}
fn fold_item_kind(&mut self, item: ast::ItemKind) -> ast::ItemKind {
fold_item_kind(self, item)
}
fn fold_expr(&mut self, expr: P<ast::Expr>) -> P<ast::Expr> {
// If an expr is valid to cfg away it will have been removed by the
// outer stmt or expression folder before descending in here.
// Anything else is always required, and thus has to error out
// in case of a cfg attr.
//
// NB: This is intentionally not part of the fold_expr() function
// in order for fold_opt_expr() to be able to avoid this check
if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a)) {
self.diagnostic.span_err(attr.span,
"removing an expression is not supported in this position");
}
fold_expr(self, expr)
}
fn fold_opt_expr(&mut self, expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
fold_opt_expr(self, expr)
}
fn fold_stmt(&mut self, stmt: ast::Stmt) -> SmallVector<ast::Stmt> {
fold_stmt(self, stmt)
}
fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac {
fold::noop_fold_mac(mac, self)
}
fn fold_item(&mut self, item: P<ast::Item>) -> SmallVector<P<ast::Item>> {
fold_item(self, item)
}
}
pub fn strip_items<'a, F>(diagnostic: &'a Handler,
krate: ast::Crate, in_cfg: F) -> ast::Crate where
F: FnMut(&[ast::Attribute]) -> bool,
{
let mut ctxt = Context {
in_cfg: in_cfg,
diagnostic: diagnostic,
};
ctxt.fold_crate(krate)
}
fn filter_foreign_item<F>(cx: &mut Context<F>,
item: ast::ForeignItem)
-> Option<ast::ForeignItem> where
F: FnMut(&[ast::Attribute]) -> bool
{
if foreign_item_in_cfg(cx, &item) {
Some(item)
} else {
None
}
}
fn fold_foreign_mod<F>(cx: &mut Context<F>,
ast::ForeignMod {abi, items}: ast::ForeignMod)
-> ast::ForeignMod where
F: FnMut(&[ast::Attribute]) -> bool
{
ast::ForeignMod {
abi: abi,
items: items.into_iter()
.filter_map(|a| filter_foreign_item(cx, a))
.collect()
}
}
fn fold_item<F>(cx: &mut Context<F>, item: P<ast::Item>) -> SmallVector<P<ast::Item>> where
F: FnMut(&[ast::Attribute]) -> bool
{
if item_in_cfg(cx, &item) {
SmallVector::one(item.map(|i| cx.fold_item_simple(i)))
} else {
SmallVector::zero()
}
}
fn fold_item_kind<F>(cx: &mut Context<F>, item: ast::ItemKind) -> ast::ItemKind where
F: FnMut(&[ast::Attribute]) -> bool
{
let item = match item {
ast::ItemKind::Impl(u, o, a, b, c, impl_items) => {
let impl_items = impl_items.into_iter()
.filter(|ii| (cx.in_cfg)(&ii.attrs))
.collect();
ast::ItemKind::Impl(u, o, a, b, c, impl_items)
}
ast::ItemKind::Trait(u, a, b, methods) => {
let methods = methods.into_iter()
.filter(|ti| (cx.in_cfg)(&ti.attrs))
.collect();
ast::ItemKind::Trait(u, a, b, methods)
}
ast::ItemKind::Struct(def, generics) => {
ast::ItemKind::Struct(fold_struct(cx, def), generics)
}
ast::ItemKind::Enum(def, generics) => {
let variants = def.variants.into_iter().filter_map(|v| {
if !(cx.in_cfg)(&v.node.attrs) {
None
} else {
Some(Spanned {
node: ast::Variant_ {
name: v.node.name,
attrs: v.node.attrs,
data: fold_struct(cx, v.node.data),
disr_expr: v.node.disr_expr,
},
span: v.span
})
}
});
ast::ItemKind::Enum(ast::EnumDef {
variants: variants.collect(),
}, generics)
}
item => item,
};
fold::noop_fold_item_kind(item, cx)
}
fn fold_struct<F>(cx: &mut Context<F>, vdata: ast::VariantData) -> ast::VariantData where
F: FnMut(&[ast::Attribute]) -> bool
{
match vdata {
ast::VariantData::Struct(fields, id) => {
ast::VariantData::Struct(fields.into_iter().filter(|m| {
(cx.in_cfg)(&m.attrs)
}).collect(), id)
}
ast::VariantData::Tuple(fields, id) => {
ast::VariantData::Tuple(fields.into_iter().filter(|m| {
(cx.in_cfg)(&m.attrs)
}).collect(), id)
}
ast::VariantData::Unit(id) => ast::VariantData::Unit(id)
}
}
fn fold_opt_expr<F>(cx: &mut Context<F>, expr: P<ast::Expr>) -> Option<P<ast::Expr>>
where F: FnMut(&[ast::Attribute]) -> bool
{
if expr_in_cfg(cx, &expr) {
Some(fold_expr(cx, expr))
} else {
None
}
}
fn fold_expr<F>(cx: &mut Context<F>, expr: P<ast::Expr>) -> P<ast::Expr> where
F: FnMut(&[ast::Attribute]) -> bool
{
expr.map(|ast::Expr {id, span, node, attrs}| {
fold::noop_fold_expr(ast::Expr {
id: id,
node: match node {
ast::ExprKind::Match(m, arms) => {
ast::ExprKind::Match(m, arms.into_iter()
.filter(|a| (cx.in_cfg)(&a.attrs))
.collect())
}
_ => node
},
span: span,
attrs: attrs,
}, cx)
})
}
fn fold_stmt<F>(cx: &mut Context<F>, stmt: ast::Stmt) -> SmallVector<ast::Stmt>
where F: FnMut(&[ast::Attribute]) -> bool
{
if stmt_in_cfg(cx, &stmt) {
fold::noop_fold_stmt(stmt, cx)
} else {
SmallVector::zero()
}
}
fn stmt_in_cfg<F>(cx: &mut Context<F>, stmt: &ast::Stmt) -> bool where
F: FnMut(&[ast::Attribute]) -> bool
{
(cx.in_cfg)(stmt.node.attrs())
}
fn expr_in_cfg<F>(cx: &mut Context<F>, expr: &ast::Expr) -> bool where
F: FnMut(&[ast::Attribute]) -> bool
{
(cx.in_cfg)(expr.attrs())
}
fn item_in_cfg<F>(cx: &mut Context<F>, item: &ast::Item) -> bool where
F: FnMut(&[ast::Attribute]) -> bool
{
return (cx.in_cfg)(&item.attrs);
}
fn foreign_item_in_cfg<F>(cx: &mut Context<F>, item: &ast::ForeignItem) -> bool where
F: FnMut(&[ast::Attribute]) -> bool
{
return (cx.in_cfg)(&item.attrs);
}
fn is_cfg(attr: &ast::Attribute) -> bool {
attr.check_name("cfg")
}
// Determine if an item should be translated in the current crate
// configuration based on the item's attributes
fn in_cfg<T: CfgDiag>(cfg: &[P<ast::MetaItem>],
attrs: &[ast::Attribute],
diag: &mut T) -> bool {
attrs.iter().all(|attr| {
let mis = match attr.node.value.node {
ast::MetaItemKind::List(_, ref mis) if is_cfg(&attr) => mis,
_ => return true
};
if mis.len() != 1 {
diag.emit_error(|diagnostic| {
diagnostic.span_err(attr.span, "expected 1 cfg-pattern");
});
return true;
}
attr::cfg_matches(cfg, &mis[0], diag)
})
}
struct CfgAttrFolder<'a, T> {
diag: T,
pub struct StripUnconfigured<'a> {
diag: CfgDiagReal<'a, 'a>,
config: &'a ast::CrateConfig,
}
// Process `#[cfg_attr]`.
fn process_cfg_attr(diagnostic: &Handler, krate: ast::Crate,
feature_gated_cfgs: &mut Vec<GatedCfgAttr>) -> ast::Crate {
let mut fld = CfgAttrFolder {
diag: CfgDiagReal {
diag: diagnostic,
feature_gated_cfgs: feature_gated_cfgs,
},
config: &krate.config.clone(),
};
fld.fold_crate(krate)
}
impl<'a> StripUnconfigured<'a> {
pub fn new(config: &'a ast::CrateConfig,
diagnostic: &'a Handler,
feature_gated_cfgs: &'a mut Vec<GatedCfgAttr>)
-> Self {
StripUnconfigured {
config: config,
diag: CfgDiagReal { diag: diagnostic, feature_gated_cfgs: feature_gated_cfgs },
}
}
impl<'a, T: CfgDiag> fold::Folder for CfgAttrFolder<'a, T> {
fn fold_attribute(&mut self, attr: ast::Attribute) -> Option<ast::Attribute> {
fn process_cfg_attr(&mut self, attr: ast::Attribute) -> Option<ast::Attribute> {
if !attr.check_name("cfg_attr") {
return fold::noop_fold_attribute(attr, self);
return Some(attr);
}
let attr_list = match attr.meta_item_list() {
Some(attr_list) => attr_list,
None => {
self.diag.emit_error(|diag| {
diag.span_err(attr.span,
"expected `#[cfg_attr(<cfg pattern>, <attr>)]`");
});
let msg = "expected `#[cfg_attr(<cfg pattern>, <attr>)]`";
self.diag.diag.span_err(attr.span, msg);
return None;
}
};
let (cfg, mi) = match (attr_list.len(), attr_list.get(0), attr_list.get(1)) {
(2, Some(cfg), Some(mi)) => (cfg, mi),
_ => {
self.diag.emit_error(|diag| {
diag.span_err(attr.span,
"expected `#[cfg_attr(<cfg pattern>, <attr>)]`");
});
let msg = "expected `#[cfg_attr(<cfg pattern>, <attr>)]`";
self.diag.diag.span_err(attr.span, msg);
return None;
}
};
if attr::cfg_matches(&self.config[..], &cfg, &mut self.diag) {
if attr::cfg_matches(self.config, &cfg, &mut self.diag) {
Some(respan(mi.span, ast::Attribute_ {
id: attr::mk_attr_id(),
style: attr.node.style,
@ -338,125 +88,185 @@ impl<'a, T: CfgDiag> fold::Folder for CfgAttrFolder<'a, T> {
None
}
}
}
impl<'a> CfgFolder for StripUnconfigured<'a> {
// Determine if an item should be translated in the current crate
// configuration based on the item's attributes
fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool {
attrs.iter().all(|attr| {
let mis = match attr.node.value.node {
ast::MetaItemKind::List(_, ref mis) if is_cfg(&attr) => mis,
_ => return true
};
if mis.len() != 1 {
self.diag.emit_error(|diagnostic| {
diagnostic.span_err(attr.span, "expected 1 cfg-pattern");
});
return true;
}
attr::cfg_matches(self.config, &mis[0], &mut self.diag)
})
}
fn process_attrs<T: HasAttrs>(&mut self, node: T) -> T {
node.map_attrs(|attrs| {
attrs.into_iter().filter_map(|attr| self.process_cfg_attr(attr)).collect()
})
}
fn visit_stmt_or_expr_attrs(&mut self, attrs: &[ast::Attribute]) {
// flag the offending attributes
for attr in attrs.iter() {
self.diag.feature_gated_cfgs.push(GatedCfgAttr::GatedAttr(attr.span));
}
}
fn visit_unremovable_expr(&mut self, expr: &ast::Expr) {
if let Some(attr) = expr.attrs().iter().find(|a| is_cfg(a)) {
let msg = "removing an expression is not supported in this position";
self.diag.diag.span_err(attr.span, msg);
}
}
}
// Support conditional compilation by transforming the AST, stripping out
// any items that do not belong in the current configuration
pub fn strip_unconfigured_items(diagnostic: &Handler, krate: ast::Crate,
feature_gated_cfgs: &mut Vec<GatedCfgAttr>)
-> ast::Crate
{
let config = &krate.config.clone();
StripUnconfigured::new(config, diagnostic, feature_gated_cfgs).fold_crate(krate)
}
impl<T: CfgFolder> fold::Folder for T {
fn fold_foreign_mod(&mut self, foreign_mod: ast::ForeignMod) -> ast::ForeignMod {
ast::ForeignMod {
abi: foreign_mod.abi,
items: foreign_mod.items.into_iter().filter_map(|item| {
self.configure(item).map(|item| fold::noop_fold_foreign_item(item, self))
}).collect(),
}
}
fn fold_item_kind(&mut self, item: ast::ItemKind) -> ast::ItemKind {
let fold_struct = |this: &mut Self, vdata| match vdata {
ast::VariantData::Struct(fields, id) => {
let fields = fields.into_iter().filter_map(|field| this.configure(field));
ast::VariantData::Struct(fields.collect(), id)
}
ast::VariantData::Tuple(fields, id) => {
let fields = fields.into_iter().filter_map(|field| this.configure(field));
ast::VariantData::Tuple(fields.collect(), id)
}
ast::VariantData::Unit(id) => ast::VariantData::Unit(id)
};
let item = match item {
ast::ItemKind::Impl(u, o, a, b, c, items) => {
let items = items.into_iter().filter_map(|item| self.configure(item)).collect();
ast::ItemKind::Impl(u, o, a, b, c, items)
}
ast::ItemKind::Trait(u, a, b, items) => {
let items = items.into_iter().filter_map(|item| self.configure(item)).collect();
ast::ItemKind::Trait(u, a, b, items)
}
ast::ItemKind::Struct(def, generics) => {
ast::ItemKind::Struct(fold_struct(self, def), generics)
}
ast::ItemKind::Enum(def, generics) => {
let variants = def.variants.into_iter().filter_map(|v| {
self.configure(v).map(|v| {
Spanned {
node: ast::Variant_ {
name: v.node.name,
attrs: v.node.attrs,
data: fold_struct(self, v.node.data),
disr_expr: v.node.disr_expr,
},
span: v.span
}
})
});
ast::ItemKind::Enum(ast::EnumDef {
variants: variants.collect(),
}, generics)
}
item => item,
};
fold::noop_fold_item_kind(item, self)
}
fn fold_expr(&mut self, expr: P<ast::Expr>) -> P<ast::Expr> {
self.visit_stmt_or_expr_attrs(expr.attrs());
// If an expr is valid to cfg away it will have been removed by the
// outer stmt or expression folder before descending in here.
// Anything else is always required, and thus has to error out
// in case of a cfg attr.
//
// NB: This is intentionally not part of the fold_expr() function
// in order for fold_opt_expr() to be able to avoid this check
self.visit_unremovable_expr(&expr);
let expr = self.process_attrs(expr);
fold_expr(self, expr)
}
fn fold_opt_expr(&mut self, expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
self.configure(expr).map(|expr| fold_expr(self, expr))
}
fn fold_stmt(&mut self, stmt: ast::Stmt) -> SmallVector<ast::Stmt> {
let is_item = match stmt.node {
ast::StmtKind::Decl(ref decl, _) => match decl.node {
ast::DeclKind::Item(_) => true,
_ => false,
},
_ => false,
};
// avoid calling `visit_stmt_or_expr_attrs` on items
if !is_item {
self.visit_stmt_or_expr_attrs(stmt.attrs());
}
self.configure(stmt).map(|stmt| fold::noop_fold_stmt(stmt, self))
.unwrap_or(SmallVector::zero())
}
// Need the ability to run pre-expansion.
fn fold_mac(&mut self, mac: ast::Mac) -> ast::Mac {
fold::noop_fold_mac(mac, self)
}
fn fold_item(&mut self, item: P<ast::Item>) -> SmallVector<P<ast::Item>> {
self.configure(item).map(|item| SmallVector::one(item.map(|i| self.fold_item_simple(i))))
.unwrap_or(SmallVector::zero())
}
}
fn check_for_gated_stmt_expr_attributes(krate: &ast::Crate,
discovered: &mut Vec<GatedCfgAttr>) {
let mut v = StmtExprAttrFeatureVisitor {
config: &krate.config,
discovered: discovered,
};
visit::walk_crate(&mut v, krate);
}
/// To cover this feature, we need to discover all attributes
/// so we need to run before cfg.
struct StmtExprAttrFeatureVisitor<'a, 'b> {
config: &'a ast::CrateConfig,
discovered: &'b mut Vec<GatedCfgAttr>,
}
// Runs the cfg_attr and cfg folders locally in "silent" mode
// to discover attribute use on stmts or expressions ahead of time
impl<'v, 'a, 'b> visit::Visitor<'v> for StmtExprAttrFeatureVisitor<'a, 'b> {
fn visit_stmt(&mut self, s: &'v ast::Stmt) {
// check if there even are any attributes on this node
let stmt_attrs = s.node.attrs();
if stmt_attrs.len() > 0 {
// attributes on items are fine
if let ast::StmtKind::Decl(ref decl, _) = s.node {
if let ast::DeclKind::Item(_) = decl.node {
visit::walk_stmt(self, s);
return;
fn fold_expr<F: CfgFolder>(folder: &mut F, expr: P<ast::Expr>) -> P<ast::Expr> {
expr.map(|ast::Expr {id, span, node, attrs}| {
fold::noop_fold_expr(ast::Expr {
id: id,
node: match node {
ast::ExprKind::Match(m, arms) => {
ast::ExprKind::Match(m, arms.into_iter()
.filter_map(|a| folder.configure(a))
.collect())
}
}
_ => node
},
span: span,
attrs: attrs,
}, folder)
})
}
// flag the offending attributes
for attr in stmt_attrs {
self.discovered.push(GatedCfgAttr::GatedAttr(attr.span));
}
// if the node does not end up being cfg-d away, walk down
if node_survives_cfg(stmt_attrs, self.config) {
visit::walk_stmt(self, s);
}
} else {
visit::walk_stmt(self, s);
}
}
fn visit_expr(&mut self, ex: &'v ast::Expr) {
// check if there even are any attributes on this node
let expr_attrs = ex.attrs();
if expr_attrs.len() > 0 {
// flag the offending attributes
for attr in expr_attrs {
self.discovered.push(GatedCfgAttr::GatedAttr(attr.span));
}
// if the node does not end up being cfg-d away, walk down
if node_survives_cfg(expr_attrs, self.config) {
visit::walk_expr(self, ex);
}
} else {
visit::walk_expr(self, ex);
}
}
fn visit_foreign_item(&mut self, i: &'v ast::ForeignItem) {
if node_survives_cfg(&i.attrs, self.config) {
visit::walk_foreign_item(self, i);
}
}
fn visit_item(&mut self, i: &'v ast::Item) {
if node_survives_cfg(&i.attrs, self.config) {
visit::walk_item(self, i);
}
}
fn visit_impl_item(&mut self, ii: &'v ast::ImplItem) {
if node_survives_cfg(&ii.attrs, self.config) {
visit::walk_impl_item(self, ii);
}
}
fn visit_trait_item(&mut self, ti: &'v ast::TraitItem) {
if node_survives_cfg(&ti.attrs, self.config) {
visit::walk_trait_item(self, ti);
}
}
fn visit_struct_field(&mut self, s: &'v ast::StructField) {
if node_survives_cfg(&s.attrs, self.config) {
visit::walk_struct_field(self, s);
}
}
fn visit_variant(&mut self, v: &'v ast::Variant,
g: &'v ast::Generics, item_id: ast::NodeId) {
if node_survives_cfg(&v.node.attrs, self.config) {
visit::walk_variant(self, v, g, item_id);
}
}
fn visit_arm(&mut self, a: &'v ast::Arm) {
if node_survives_cfg(&a.attrs, self.config) {
visit::walk_arm(self, a);
}
}
// This visitor runs pre expansion, so we need to prevent
// the default panic here
fn visit_mac(&mut self, mac: &'v ast::Mac) {
visit::walk_mac(self, mac)
}
fn is_cfg(attr: &ast::Attribute) -> bool {
attr.check_name("cfg")
}
pub trait CfgDiag {
@ -477,41 +287,3 @@ impl<'a, 'b> CfgDiag for CfgDiagReal<'a, 'b> {
f(self.feature_gated_cfgs)
}
}
struct CfgDiagSilent {
error: bool,
}
impl CfgDiag for CfgDiagSilent {
fn emit_error<F>(&mut self, _: F) where F: FnMut(&Handler) {
self.error = true;
}
fn flag_gated<F>(&mut self, _: F) where F: FnMut(&mut Vec<GatedCfgAttr>) {}
}
fn node_survives_cfg(attrs: &[ast::Attribute],
config: &ast::CrateConfig) -> bool {
let mut survives_cfg = true;
for attr in attrs {
let mut fld = CfgAttrFolder {
diag: CfgDiagSilent { error: false },
config: config,
};
let attr = fld.fold_attribute(attr.clone());
// In case of error we can just return true,
// since the actual cfg folders will end compilation anyway.
if fld.diag.error { return true; }
survives_cfg &= attr.map(|attr| {
let mut diag = CfgDiagSilent { error: false };
let r = in_cfg(config, &[attr], &mut diag);
if diag.error { return true; }
r
}).unwrap_or(true)
}
survives_cfg
}

View file

@ -18,7 +18,8 @@ use ext::build::AstBuilder;
use attr;
use attr::{AttrMetaMethods, WithAttrs, ThinAttributesExt};
use codemap;
use codemap::{Span, Spanned, ExpnInfo, NameAndSpan, MacroBang, MacroAttribute};
use codemap::{Span, Spanned, ExpnInfo, ExpnId, NameAndSpan, MacroBang, MacroAttribute};
use config::StripUnconfigured;
use ext::base::*;
use feature_gate::{self, Features};
use fold;
@ -33,7 +34,6 @@ use visit::Visitor;
use std_inject;
use std::collections::HashSet;
use std::env;
// A trait for AST nodes and AST node lists into which macro invocations may expand.
trait MacroGenerable: Sized {
@ -77,25 +77,35 @@ impl_macro_generable! {
"statement", .make_stmts, lift .fold_stmt, |_span| SmallVector::zero();
}
pub fn expand_expr(e: P<ast::Expr>, fld: &mut MacroExpander) -> P<ast::Expr> {
return e.and_then(|ast::Expr {id, node, span, attrs}| match node {
impl MacroGenerable for Option<P<ast::Expr>> {
fn kind_name() -> &'static str { "expression" }
fn dummy(_span: Span) -> Self { None }
fn make_with<'a>(result: Box<MacResult + 'a>) -> Option<Self> {
result.make_expr().map(Some)
}
fn fold_with<F: Folder>(self, folder: &mut F) -> Self {
self.and_then(|expr| folder.fold_opt_expr(expr))
}
}
pub fn expand_expr(expr: ast::Expr, fld: &mut MacroExpander) -> P<ast::Expr> {
match expr.node {
// expr_mac should really be expr_ext or something; it's the
// entry-point for all syntax extensions.
ast::ExprKind::Mac(mac) => {
expand_mac_invoc(mac, None, attrs.into_attr_vec(), span, fld)
expand_mac_invoc(mac, None, expr.attrs.into_attr_vec(), expr.span, fld)
}
ast::ExprKind::While(cond, body, opt_ident) => {
let cond = fld.fold_expr(cond);
let (body, opt_ident) = expand_loop_block(body, opt_ident, fld);
fld.cx.expr(span, ast::ExprKind::While(cond, body, opt_ident))
.with_attrs(fold_thin_attrs(attrs, fld))
fld.cx.expr(expr.span, ast::ExprKind::While(cond, body, opt_ident))
.with_attrs(fold_thin_attrs(expr.attrs, fld))
}
ast::ExprKind::WhileLet(pat, expr, body, opt_ident) => {
ast::ExprKind::WhileLet(pat, cond, body, opt_ident) => {
let pat = fld.fold_pat(pat);
let expr = fld.fold_expr(expr);
let cond = fld.fold_expr(cond);
// Hygienic renaming of the body.
let ((body, opt_ident), mut rewritten_pats) =
@ -107,14 +117,14 @@ pub fn expand_expr(e: P<ast::Expr>, fld: &mut MacroExpander) -> P<ast::Expr> {
});
assert!(rewritten_pats.len() == 1);
let wl = ast::ExprKind::WhileLet(rewritten_pats.remove(0), expr, body, opt_ident);
fld.cx.expr(span, wl).with_attrs(fold_thin_attrs(attrs, fld))
let wl = ast::ExprKind::WhileLet(rewritten_pats.remove(0), cond, body, opt_ident);
fld.cx.expr(expr.span, wl).with_attrs(fold_thin_attrs(expr.attrs, fld))
}
ast::ExprKind::Loop(loop_block, opt_ident) => {
let (loop_block, opt_ident) = expand_loop_block(loop_block, opt_ident, fld);
fld.cx.expr(span, ast::ExprKind::Loop(loop_block, opt_ident))
.with_attrs(fold_thin_attrs(attrs, fld))
fld.cx.expr(expr.span, ast::ExprKind::Loop(loop_block, opt_ident))
.with_attrs(fold_thin_attrs(expr.attrs, fld))
}
ast::ExprKind::ForLoop(pat, head, body, opt_ident) => {
@ -132,7 +142,7 @@ pub fn expand_expr(e: P<ast::Expr>, fld: &mut MacroExpander) -> P<ast::Expr> {
let head = fld.fold_expr(head);
let fl = ast::ExprKind::ForLoop(rewritten_pats.remove(0), head, body, opt_ident);
fld.cx.expr(span, fl).with_attrs(fold_thin_attrs(attrs, fld))
fld.cx.expr(expr.span, fl).with_attrs(fold_thin_attrs(expr.attrs, fld))
}
ast::ExprKind::IfLet(pat, sub_expr, body, else_opt) => {
@ -151,7 +161,7 @@ pub fn expand_expr(e: P<ast::Expr>, fld: &mut MacroExpander) -> P<ast::Expr> {
let else_opt = else_opt.map(|else_opt| fld.fold_expr(else_opt));
let sub_expr = fld.fold_expr(sub_expr);
let il = ast::ExprKind::IfLet(rewritten_pats.remove(0), sub_expr, body, else_opt);
fld.cx.expr(span, il).with_attrs(fold_thin_attrs(attrs, fld))
fld.cx.expr(expr.span, il).with_attrs(fold_thin_attrs(expr.attrs, fld))
}
ast::ExprKind::Closure(capture_clause, fn_decl, block, fn_decl_span) => {
@ -160,22 +170,15 @@ pub fn expand_expr(e: P<ast::Expr>, fld: &mut MacroExpander) -> P<ast::Expr> {
let new_node = ast::ExprKind::Closure(capture_clause,
rewritten_fn_decl,
rewritten_block,
fld.new_span(fn_decl_span));
P(ast::Expr{ id:id,
fn_decl_span);
P(ast::Expr{ id: expr.id,
node: new_node,
span: fld.new_span(span),
attrs: fold_thin_attrs(attrs, fld) })
span: expr.span,
attrs: fold_thin_attrs(expr.attrs, fld) })
}
_ => {
P(noop_fold_expr(ast::Expr {
id: id,
node: node,
span: span,
attrs: attrs
}, fld))
}
});
_ => P(noop_fold_expr(expr, fld)),
}
}
/// Expand a macro invocation. Returns the result of expansion.
@ -322,8 +325,9 @@ fn expand_mac_invoc<T>(mac: ast::Mac, ident: Option<Ident>, attrs: Vec<ast::Attr
return T::dummy(span);
};
let marked = expanded.fold_with(&mut Marker { mark: mark });
let fully_expanded = marked.fold_with(fld);
let marked = expanded.fold_with(&mut Marker { mark: mark, expn_id: Some(fld.cx.backtrace()) });
let configured = marked.fold_with(&mut fld.strip_unconfigured());
let fully_expanded = configured.fold_with(fld);
fld.cx.bt_pop();
fully_expanded
}
@ -699,12 +703,12 @@ impl<'a> Folder for PatIdentRenamer<'a> {
mtwt::apply_renames(self.renames, ident.ctxt));
let new_node =
PatKind::Ident(binding_mode,
Spanned{span: self.new_span(sp), node: new_ident},
Spanned{span: sp, node: new_ident},
sub.map(|p| self.fold_pat(p)));
ast::Pat {
id: id,
node: new_node,
span: self.new_span(span)
span: span,
}
},
_ => unreachable!()
@ -774,7 +778,7 @@ fn expand_annotatable(a: Annotatable,
}
_ => unreachable!()
},
span: fld.new_span(ti.span)
span: ti.span,
})
}
_ => fold::noop_fold_trait_item(it.unwrap(), fld)
@ -914,7 +918,7 @@ fn expand_impl_item(ii: ast::ImplItem, fld: &mut MacroExpander)
}
_ => unreachable!()
},
span: fld.new_span(ii.span)
span: ii.span,
}),
ast::ImplItemKind::Macro(mac) => {
expand_mac_invoc(mac, None, ii.attrs, ii.span, fld)
@ -987,6 +991,12 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
pub fn new(cx: &'a mut ExtCtxt<'b>) -> MacroExpander<'a, 'b> {
MacroExpander { cx: cx }
}
fn strip_unconfigured(&mut self) -> StripUnconfigured {
StripUnconfigured::new(&self.cx.cfg,
&self.cx.parse_sess.span_diagnostic,
self.cx.feature_gated_cfgs)
}
}
impl<'a, 'b> Folder for MacroExpander<'a, 'b> {
@ -996,7 +1006,15 @@ impl<'a, 'b> Folder for MacroExpander<'a, 'b> {
}
fn fold_expr(&mut self, expr: P<ast::Expr>) -> P<ast::Expr> {
expand_expr(expr, self)
expr.and_then(|expr| expand_expr(expr, self))
}
fn fold_opt_expr(&mut self, expr: P<ast::Expr>) -> Option<P<ast::Expr>> {
expr.and_then(|expr| match expr.node {
ast::ExprKind::Mac(mac) =>
expand_mac_invoc(mac, None, expr.attrs.into_attr_vec(), expr.span, self),
_ => Some(expand_expr(expr, self)),
})
}
fn fold_pat(&mut self, pat: P<ast::Pat>) -> P<ast::Pat> {
@ -1059,10 +1077,6 @@ impl<'a, 'b> Folder for MacroExpander<'a, 'b> {
fn fold_ty(&mut self, ty: P<ast::Ty>) -> P<ast::Ty> {
expand_type(ty, self)
}
fn new_span(&mut self, span: Span) -> Span {
new_span(self.cx, span)
}
}
impl<'a, 'b> MacroExpander<'a, 'b> {
@ -1080,45 +1094,6 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
}
}
fn new_span(cx: &ExtCtxt, sp: Span) -> Span {
debug!("new_span(sp={:?})", sp);
if cx.codemap().more_specific_trace(sp.expn_id, cx.backtrace()) {
// If the span we are looking at has a backtrace that has more
// detail than our current backtrace, then we keep that
// backtrace. Honestly, I have no idea if this makes sense,
// because I have no idea why we are stripping the backtrace
// below. But the reason I made this change is because, in
// deriving, we were generating attributes with a specific
// backtrace, which was essential for `#[structural_match]` to
// be properly supported, but these backtraces were being
// stripped and replaced with a null backtrace. Sort of
// unclear why this is the case. --nmatsakis
debug!("new_span: keeping trace from {:?} because it is more specific",
sp.expn_id);
sp
} else {
// This discards information in the case of macro-defining macros.
//
// The comment above was originally added in
// b7ec2488ff2f29681fe28691d20fd2c260a9e454 in Feb 2012. I
// *THINK* the reason we are doing this is because we want to
// replace the backtrace of the macro contents with the
// backtrace that contains the macro use. But it's pretty
// unclear to me. --nmatsakis
let sp1 = Span {
lo: sp.lo,
hi: sp.hi,
expn_id: cx.backtrace(),
};
debug!("new_span({:?}) = {:?}", sp, sp1);
if sp.expn_id.into_u32() == 0 && env::var_os("NDM").is_some() {
panic!("NDM");
}
sp1
}
}
pub struct ExpansionConfig<'feat> {
pub crate_name: String,
pub features: Option<&'feat Features>,
@ -1205,8 +1180,9 @@ pub fn expand_crate(mut cx: ExtCtxt,
// the ones defined here include:
// Marker - add a mark to a context
// A Marker adds the given mark to the syntax context
struct Marker { mark: Mrk }
// A Marker adds the given mark to the syntax context and
// sets spans' `expn_id` to the given expn_id (unless it is `None`).
struct Marker { mark: Mrk, expn_id: Option<ExpnId> }
impl Folder for Marker {
fn fold_ident(&mut self, id: Ident) -> Ident {
@ -1219,14 +1195,21 @@ impl Folder for Marker {
tts: self.fold_tts(&node.tts),
ctxt: mtwt::apply_mark(self.mark, node.ctxt),
},
span: span,
span: self.new_span(span),
}
}
fn new_span(&mut self, mut span: Span) -> Span {
if let Some(expn_id) = self.expn_id {
span.expn_id = expn_id;
}
span
}
}
// apply a given mark to the given token trees. Used prior to expansion of a macro.
fn mark_tts(tts: &[TokenTree], m: Mrk) -> Vec<TokenTree> {
noop_fold_tts(tts, &mut Marker{mark:m})
noop_fold_tts(tts, &mut Marker{mark:m, expn_id: None})
}
/// Check that there are no macro invocations left in the AST:

View file

@ -87,7 +87,7 @@ pub fn modify_for_testing(sess: &ParseSess,
if should_test {
generate_test_harness(sess, reexport_test_harness_main, krate, cfg, span_diagnostic)
} else {
strip_test_functions(span_diagnostic, krate)
strip_test_functions(krate)
}
}
@ -312,14 +312,17 @@ fn generate_test_harness(sess: &ParseSess,
return res;
}
fn strip_test_functions(diagnostic: &errors::Handler, krate: ast::Crate)
-> ast::Crate {
fn strip_test_functions(krate: ast::Crate) -> ast::Crate {
// When not compiling with --test we should not compile the
// #[test] functions
config::strip_items(diagnostic, krate, |attrs| {
!attr::contains_name(&attrs[..], "test") &&
!attr::contains_name(&attrs[..], "bench")
})
struct StripTests;
impl config::CfgFolder for StripTests {
fn in_cfg(&mut self, attrs: &[ast::Attribute]) -> bool {
!attr::contains_name(attrs, "test") && !attr::contains_name(attrs, "bench")
}
}
StripTests.fold_crate(krate)
}
/// Craft a span that will be ignored by the stability lint's

View file

@ -8,7 +8,7 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(rustc_attrs)]
#![feature(custom_attribute, rustc_attrs)]
macro_rules! mac {
{} => {
@ -16,7 +16,13 @@ macro_rules! mac {
mod m {
#[lang_item]
fn f() {}
#[cfg_attr(target_thread_local, custom)]
fn g() {}
}
#[cfg(attr)]
unconfigured_invocation!();
}
}