Skip to content

Commit

Permalink
Rollup merge of rust-lang#22899 - huonw:macro-stability, r=alexcrichton
Browse files Browse the repository at this point in the history
 Unstable items used in a macro expansion will now always trigger
stability warnings, *unless* the unstable items are directly inside a
macro marked with `#[allow_internal_unstable]`. IOW, the compiler warns
unless the span of the unstable item is a subspan of the definition of a
macro marked with that attribute.

E.g.

    #[allow_internal_unstable]
    macro_rules! foo {
        ($e: expr) => {{
            $e;
            unstable(); // no warning
            only_called_by_foo!();
        }}
    }

    macro_rules! only_called_by_foo {
        () => { unstable() } // warning
    }

    foo!(unstable()) // warning

The unstable inside `foo` is fine, due to the attribute. But the
`unstable` inside `only_called_by_foo` is not, since that macro doesn't
have the attribute, and the `unstable` passed into `foo` is also not
fine since it isn't contained in the macro itself (that is, even though
it is only used directly in the macro).

In the process this makes the stability tracking much more precise,
e.g. previously `println!(\"{}\", unstable())` got no warning, but now it
does. As such, this is a bug fix that may cause [breaking-change]s.

The attribute is definitely feature gated, since it explicitly allows
side-stepping the feature gating system.

---

This updates `thread_local!` macro to use the attribute, since it uses
unstable features internally (initialising a struct with unstable
fields).
  • Loading branch information
Manishearth committed Mar 6, 2015
2 parents e80fc10 + b5c6ab2 commit 9eb596c
Show file tree
Hide file tree
Showing 28 changed files with 423 additions and 84 deletions.
8 changes: 8 additions & 0 deletions src/doc/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2555,6 +2555,14 @@ The currently implemented features of the reference compiler are:
types, e.g. as the return type of a public function.
This capability may be removed in the future.

* `allow_internal_unstable` - Allows `macro_rules!` macros to be tagged with the
`#[allow_internal_unstable]` attribute, designed
to allow `std` macros to call
`#[unstable]`/feature-gated functionality
internally without imposing on callers
(i.e. making them behave like function calls in
terms of encapsulation).

If a feature is promoted to a language feature, then all existing programs will
start to receive compilation warnings about #[feature] directives which enabled
the new feature (because the directive is no longer necessary). However, if a
Expand Down
2 changes: 2 additions & 0 deletions src/libcore/num/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1238,6 +1238,7 @@ pub fn from_f64<A: FromPrimitive>(n: f64) -> Option<A> {

macro_rules! impl_from_primitive {
($T:ty, $to_ty:ident) => (
#[allow(deprecated)]
impl FromPrimitive for $T {
#[inline] fn from_int(n: int) -> Option<$T> { n.$to_ty() }
#[inline] fn from_i8(n: i8) -> Option<$T> { n.$to_ty() }
Expand Down Expand Up @@ -1299,6 +1300,7 @@ macro_rules! impl_num_cast {
($T:ty, $conv:ident) => (
impl NumCast for $T {
#[inline]
#[allow(deprecated)]
fn from<N: ToPrimitive>(n: N) -> Option<$T> {
// `$conv` could be generated using `concat_idents!`, but that
// macro seems to be broken at the moment
Expand Down
1 change: 1 addition & 0 deletions src/librustc/metadata/creader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ impl<'a> CrateReader<'a> {
// overridden in plugin/load.rs
export: false,
use_locally: false,
allow_internal_unstable: false,

body: body,
});
Expand Down
3 changes: 3 additions & 0 deletions src/librustc/metadata/macro_import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ impl<'a> MacroLoader<'a> {
Some(sel) => sel.contains_key(&name),
};
def.export = reexport.contains_key(&name);
def.allow_internal_unstable = attr::contains_name(&def.attrs,
"allow_internal_unstable");
debug!("load_macros: loaded: {:?}", def);
self.macros.push(def);
}

Expand Down
5 changes: 2 additions & 3 deletions src/librustc/middle/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -362,8 +362,6 @@ pub fn check_item(tcx: &ty::ctxt, item: &ast::Item, warn_about_defns: bool,
/// Helper for discovering nodes to check for stability
pub fn check_expr(tcx: &ty::ctxt, e: &ast::Expr,
cb: &mut FnMut(ast::DefId, Span, &Option<Stability>)) {
if is_internal(tcx, e.span) { return; }

let span;
let id = match e.node {
ast::ExprMethodCall(i, _, _) => {
Expand Down Expand Up @@ -527,12 +525,13 @@ pub fn check_pat(tcx: &ty::ctxt, pat: &ast::Pat,
fn maybe_do_stability_check(tcx: &ty::ctxt, id: ast::DefId, span: Span,
cb: &mut FnMut(ast::DefId, Span, &Option<Stability>)) {
if !is_staged_api(tcx, id) { return }
if is_internal(tcx, span) { return }
let ref stability = lookup(tcx, id);
cb(id, span, stability);
}

fn is_internal(tcx: &ty::ctxt, span: Span) -> bool {
tcx.sess.codemap().span_is_internal(span)
tcx.sess.codemap().span_allows_unstable(span)
}

fn is_staged_api(tcx: &ty::ctxt, id: DefId) -> bool {
Expand Down
11 changes: 8 additions & 3 deletions src/librustc/plugin/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,12 @@ impl<'a> Registry<'a> {
/// This is the most general hook into `libsyntax`'s expansion behavior.
pub fn register_syntax_extension(&mut self, name: ast::Name, extension: SyntaxExtension) {
self.syntax_exts.push((name, match extension {
NormalTT(ext, _) => NormalTT(ext, Some(self.krate_span)),
IdentTT(ext, _) => IdentTT(ext, Some(self.krate_span)),
NormalTT(ext, _, allow_internal_unstable) => {
NormalTT(ext, Some(self.krate_span), allow_internal_unstable)
}
IdentTT(ext, _, allow_internal_unstable) => {
IdentTT(ext, Some(self.krate_span), allow_internal_unstable)
}
Decorator(ext) => Decorator(ext),
Modifier(ext) => Modifier(ext),
MultiModifier(ext) => MultiModifier(ext),
Expand All @@ -99,7 +103,8 @@ impl<'a> Registry<'a> {
/// It builds for you a `NormalTT` that calls `expander`,
/// and also takes care of interning the macro's name.
pub fn register_macro(&mut self, name: &str, expander: MacroExpanderFn) {
self.register_syntax_extension(token::intern(name), NormalTT(Box::new(expander), None));
self.register_syntax_extension(token::intern(name),
NormalTT(Box::new(expander), None, false));
}

/// Register a compiler lint pass.
Expand Down
25 changes: 21 additions & 4 deletions src/librustc_driver/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,12 +493,16 @@ pub fn phase_2_configure_and_expand(sess: &Session,
}
);

// Needs to go *after* expansion to be able to check the results of macro expansion.
time(time_passes, "complete gated feature checking", (), |_| {
// Needs to go *after* expansion to be able to check the results
// of macro expansion. This runs before #[cfg] to try to catch as
// much as possible (e.g. help the programmer avoid platform
// specific differences)
time(time_passes, "complete gated feature checking 1", (), |_| {
let features =
syntax::feature_gate::check_crate(sess.codemap(),
&sess.parse_sess.span_diagnostic,
&krate);
&sess.parse_sess.span_diagnostic,
&krate,
true);
*sess.features.borrow_mut() = features;
sess.abort_if_errors();
});
Expand All @@ -521,6 +525,19 @@ pub fn phase_2_configure_and_expand(sess: &Session,
time(time_passes, "checking that all macro invocations are gone", &krate, |krate|
syntax::ext::expand::check_for_macros(&sess.parse_sess, krate));

// One final feature gating of the true AST that gets compiled
// later, to make sure we've got everything (e.g. configuration
// can insert new attributes via `cfg_attr`)
time(time_passes, "complete gated feature checking 2", (), |_| {
let features =
syntax::feature_gate::check_crate(sess.codemap(),
&sess.parse_sess.span_diagnostic,
&krate,
false);
*sess.features.borrow_mut() = features;
sess.abort_if_errors();
});

Some(krate)
}

Expand Down
1 change: 1 addition & 0 deletions src/libstd/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@
#![feature(hash)]
#![feature(int_uint)]
#![feature(unique)]
#![feature(allow_internal_unstable)]
#![cfg_attr(test, feature(test, rustc_private))]

// Don't link to std. We are std.
Expand Down
6 changes: 1 addition & 5 deletions src/libstd/sys/common/thread_local.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
//! ```
#![allow(non_camel_case_types)]
#![unstable(feature = "thread_local_internals")]

use prelude::v1::*;

Expand Down Expand Up @@ -84,17 +85,14 @@ use sys::thread_local as imp;
/// KEY.set(1 as *mut u8);
/// }
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub struct StaticKey {
/// Inner static TLS key (internals), created with by `INIT_INNER` in this
/// module.
#[stable(feature = "rust1", since = "1.0.0")]
pub inner: StaticKeyInner,
/// Destructor for the TLS value.
///
/// See `Key::new` for information about when the destructor runs and how
/// it runs.
#[stable(feature = "rust1", since = "1.0.0")]
pub dtor: Option<unsafe extern fn(*mut u8)>,
}

Expand Down Expand Up @@ -131,7 +129,6 @@ pub struct Key {
/// Constant initialization value for static TLS keys.
///
/// This value specifies no destructor by default.
#[stable(feature = "rust1", since = "1.0.0")]
pub const INIT: StaticKey = StaticKey {
inner: INIT_INNER,
dtor: None,
Expand All @@ -140,7 +137,6 @@ pub const INIT: StaticKey = StaticKey {
/// Constant initialization value for the inner part of static TLS keys.
///
/// This value allows specific configuration of the destructor for a TLS key.
#[stable(feature = "rust1", since = "1.0.0")]
pub const INIT_INNER: StaticKeyInner = StaticKeyInner {
key: atomic::ATOMIC_USIZE_INIT,
};
Expand Down
22 changes: 12 additions & 10 deletions src/libstd/thread_local/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub mod scoped;

// Sure wish we had macro hygiene, no?
#[doc(hidden)]
#[stable(feature = "rust1", since = "1.0.0")]
#[unstable(feature = "thread_local_internals")]
pub mod __impl {
pub use super::imp::Key as KeyInner;
pub use super::imp::destroy_value;
Expand Down Expand Up @@ -117,6 +117,7 @@ pub struct Key<T> {
/// Declare a new thread local storage key of type `std::thread_local::Key`.
#[macro_export]
#[stable(feature = "rust1", since = "1.0.0")]
#[allow_internal_unstable]
macro_rules! thread_local {
(static $name:ident: $t:ty = $init:expr) => (
static $name: ::std::thread_local::Key<$t> = {
Expand Down Expand Up @@ -176,6 +177,7 @@ macro_rules! thread_local {

#[macro_export]
#[doc(hidden)]
#[allow_internal_unstable]
macro_rules! __thread_local_inner {
(static $name:ident: $t:ty = $init:expr) => (
#[cfg_attr(all(any(target_os = "macos", target_os = "linux"),
Expand Down Expand Up @@ -337,22 +339,22 @@ mod imp {
use ptr;

#[doc(hidden)]
#[stable(since = "1.0.0", feature = "rust1")]
#[unstable(feature = "thread_local_internals")]
pub struct Key<T> {
// Place the inner bits in an `UnsafeCell` to currently get around the
// "only Sync statics" restriction. This allows any type to be placed in
// the cell.
//
// Note that all access requires `T: 'static` so it can't be a type with
// any borrowed pointers still.
#[stable(since = "1.0.0", feature = "rust1")]
#[unstable(feature = "thread_local_internals")]
pub inner: UnsafeCell<T>,

// Metadata to keep track of the state of the destructor. Remember that
// these variables are thread-local, not global.
#[stable(since = "1.0.0", feature = "rust1")]
#[unstable(feature = "thread_local_internals")]
pub dtor_registered: UnsafeCell<bool>, // should be Cell
#[stable(since = "1.0.0", feature = "rust1")]
#[unstable(feature = "thread_local_internals")]
pub dtor_running: UnsafeCell<bool>, // should be Cell
}

Expand Down Expand Up @@ -455,7 +457,7 @@ mod imp {
}

#[doc(hidden)]
#[stable(feature = "rust1", since = "1.0.0")]
#[unstable(feature = "thread_local_internals")]
pub unsafe extern fn destroy_value<T>(ptr: *mut u8) {
let ptr = ptr as *mut Key<T>;
// Right before we run the user destructor be sure to flag the
Expand All @@ -477,15 +479,15 @@ mod imp {
use sys_common::thread_local::StaticKey as OsStaticKey;

#[doc(hidden)]
#[stable(since = "1.0.0", feature = "rust1")]
#[unstable(feature = "thread_local_internals")]
pub struct Key<T> {
// Statically allocated initialization expression, using an `UnsafeCell`
// for the same reasons as above.
#[stable(since = "1.0.0", feature = "rust1")]
#[unstable(feature = "thread_local_internals")]
pub inner: UnsafeCell<T>,

// OS-TLS key that we'll use to key off.
#[stable(since = "1.0.0", feature = "rust1")]
#[unstable(feature = "thread_local_internals")]
pub os: OsStaticKey,
}

Expand Down Expand Up @@ -528,7 +530,7 @@ mod imp {
}

#[doc(hidden)]
#[stable(feature = "rust1", since = "1.0.0")]
#[unstable(feature = "thread_local_internals")]
pub unsafe extern fn destroy_value<T: 'static>(ptr: *mut u8) {
// The OS TLS ensures that this key contains a NULL value when this
// destructor starts to run. We set it back to a sentinel value of 1 to
Expand Down
2 changes: 2 additions & 0 deletions src/libstd/thread_local/scoped.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ pub struct Key<T> { #[doc(hidden)] pub inner: __impl::KeyInner<T> }
/// This macro declares a `static` item on which methods are used to get and
/// set the value stored within.
#[macro_export]
#[allow_internal_unstable]
macro_rules! scoped_thread_local {
(static $name:ident: $t:ty) => (
__scoped_thread_local_inner!(static $name: $t);
Expand All @@ -76,6 +77,7 @@ macro_rules! scoped_thread_local {

#[macro_export]
#[doc(hidden)]
#[allow_internal_unstable]
macro_rules! __scoped_thread_local_inner {
(static $name:ident: $t:ty) => (
#[cfg_attr(not(any(windows,
Expand Down
1 change: 1 addition & 0 deletions src/libsyntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1743,6 +1743,7 @@ pub struct MacroDef {
pub imported_from: Option<Ident>,
pub export: bool,
pub use_locally: bool,
pub allow_internal_unstable: bool,
pub body: Vec<TokenTree>,
}

Expand Down
70 changes: 38 additions & 32 deletions src/libsyntax/codemap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ pub struct NameAndSpan {
pub name: String,
/// The format with which the macro was invoked.
pub format: MacroFormat,
/// Whether the macro is allowed to use #[unstable]/feature-gated
/// features internally without forcing the whole crate to opt-in
/// to them.
pub allow_internal_unstable: bool,
/// The span of the macro definition itself. The macro may not
/// have a sensible definition span (e.g. something defined
/// completely inside libsyntax) in which case this is None.
Expand Down Expand Up @@ -830,41 +834,43 @@ impl CodeMap {
}
}

/// Check if a span is "internal" to a macro. This means that it is entirely generated by a
/// macro expansion and contains no code that was passed in as an argument.
pub fn span_is_internal(&self, span: Span) -> bool {
// first, check if the given expression was generated by a macro or not
// we need to go back the expn_info tree to check only the arguments
// of the initial macro call, not the nested ones.
let mut is_internal = false;
let mut expnid = span.expn_id;
while self.with_expn_info(expnid, |expninfo| {
match expninfo {
Some(ref info) => {
// save the parent expn_id for next loop iteration
expnid = info.call_site.expn_id;
if info.callee.name == "format_args" {
// This is a hack because the format_args builtin calls unstable APIs.
// I spent like 6 hours trying to solve this more generally but am stupid.
is_internal = true;
false
} else if info.callee.span.is_none() {
// it's a compiler built-in, we *really* don't want to mess with it
// so we skip it, unless it was called by a regular macro, in which case
// we will handle the caller macro next turn
is_internal = true;
true // continue looping
/// Check if a span is "internal" to a macro in which #[unstable]
/// items can be used (that is, a macro marked with
/// `#[allow_internal_unstable]`).
pub fn span_allows_unstable(&self, span: Span) -> bool {
debug!("span_allows_unstable(span = {:?})", span);
let mut allows_unstable = false;
let mut expn_id = span.expn_id;
loop {
let quit = self.with_expn_info(expn_id, |expninfo| {
debug!("span_allows_unstable: expninfo = {:?}", expninfo);
expninfo.map_or(/* hit the top level */ true, |info| {

let span_comes_from_this_expansion =
info.callee.span.map_or(span == info.call_site, |mac_span| {
mac_span.lo <= span.lo && span.hi < mac_span.hi
});

debug!("span_allows_unstable: from this expansion? {}, allows unstable? {}",
span_comes_from_this_expansion,
info.callee.allow_internal_unstable);
if span_comes_from_this_expansion {
allows_unstable = info.callee.allow_internal_unstable;
// we've found the right place, stop looking
true
} else {
// was this expression from the current macro arguments ?
is_internal = !( span.lo > info.call_site.lo &&
span.hi < info.call_site.hi );
true // continue looping
// not the right place, keep looking
expn_id = info.call_site.expn_id;
false
}
},
_ => false // stop looping
})
});
if quit {
break
}
}) { /* empty while loop body */ }
return is_internal;
}
debug!("span_allows_unstable? {}", allows_unstable);
allows_unstable
}
}

Expand Down
1 change: 1 addition & 0 deletions src/libsyntax/ext/asm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ pub fn expand_asm<'cx>(cx: &'cx mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
name: "asm".to_string(),
format: codemap::MacroBang,
span: None,
allow_internal_unstable: false,
},
});

Expand Down
Loading

0 comments on commit 9eb596c

Please sign in to comment.