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
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() }
($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
// overridden in plugin/load.rs
export: false,
use_locally: false,
+ allow_internal_unstable: false,
body: body,
});
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);
}
/// 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, _, _) => {
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 {
/// 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),
/// 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.
pub imported_from: Option<Ident>,
pub export: bool,
pub use_locally: bool,
+ pub allow_internal_unstable: bool,
pub body: Vec<TokenTree>,
}
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.
}
}
- /// 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
}
}
name: "asm".to_string(),
format: codemap::MacroBang,
span: None,
+ allow_internal_unstable: false,
},
});
/// A normal, function-like syntax extension.
///
/// `bytes!` is a `NormalTT`.
- NormalTT(Box<TTMacroExpander + 'static>, Option<Span>),
+ ///
+ /// The `bool` dictates whether the contents of the macro can
+ /// directly use `#[unstable]` things (true == yes).
+ NormalTT(Box<TTMacroExpander + 'static>, Option<Span>, bool),
/// A function-like syntax extension that has an extra ident before
/// the block.
///
- IdentTT(Box<IdentMacroExpander + 'static>, Option<Span>),
+ IdentTT(Box<IdentMacroExpander + 'static>, Option<Span>, bool),
/// Represents `macro_rules!` itself.
MacroRulesTT,
-> SyntaxEnv {
// utility function to simplify creating NormalTT syntax extensions
fn builtin_normal_expander(f: MacroExpanderFn) -> SyntaxExtension {
- NormalTT(Box::new(f), None)
+ NormalTT(Box::new(f), None, false)
}
let mut syntax_expanders = SyntaxEnv::new();
syntax_expanders.insert(intern("macro_rules"), MacroRulesTT);
syntax_expanders.insert(intern("format_args"),
- builtin_normal_expander(
- ext::format::expand_format_args));
+ // format_args uses `unstable` things internally.
+ NormalTT(Box::new(ext::format::expand_format_args), None, true));
syntax_expanders.insert(intern("env"),
builtin_normal_expander(
ext::env::expand_env));
callee: codemap::NameAndSpan {
name: format!("derive({})", trait_name),
format: codemap::MacroAttribute,
- span: Some(self.span)
+ span: Some(self.span),
+ allow_internal_unstable: false,
}
});
to_set
use codemap;
use codemap::{Span, Spanned, ExpnInfo, NameAndSpan, MacroBang, MacroAttribute};
use ext::base::*;
-use feature_gate::{Features};
+use feature_gate::{self, Features};
use fold;
use fold::*;
use parse;
None
}
Some(rc) => match *rc {
- NormalTT(ref expandfun, exp_span) => {
+ NormalTT(ref expandfun, exp_span, allow_internal_unstable) => {
fld.cx.bt_push(ExpnInfo {
call_site: span,
callee: NameAndSpan {
name: extnamestr.to_string(),
format: MacroBang,
span: exp_span,
+ allow_internal_unstable: allow_internal_unstable,
},
});
let fm = fresh_mark();
name: mname.to_string(),
format: MacroAttribute,
span: None,
+ // attributes can do whatever they like,
+ // for now
+ allow_internal_unstable: true,
}
});
it = mac.expand(fld.cx, attr.span, &*attr.node.value, it);
}
Some(rc) => match *rc {
- NormalTT(ref expander, span) => {
+ NormalTT(ref expander, span, allow_internal_unstable) => {
if it.ident.name != parse::token::special_idents::invalid.name {
fld.cx
.span_err(path_span,
callee: NameAndSpan {
name: extnamestr.to_string(),
format: MacroBang,
- span: span
+ span: span,
+ allow_internal_unstable: allow_internal_unstable,
}
});
// mark before expansion:
let marked_before = mark_tts(&tts[..], fm);
expander.expand(fld.cx, it.span, &marked_before[..])
}
- IdentTT(ref expander, span) => {
+ IdentTT(ref expander, span, allow_internal_unstable) => {
if it.ident.name == parse::token::special_idents::invalid.name {
fld.cx.span_err(path_span,
&format!("macro {}! expects an ident argument",
callee: NameAndSpan {
name: extnamestr.to_string(),
format: MacroBang,
- span: span
+ span: span,
+ allow_internal_unstable: allow_internal_unstable,
}
});
// mark before expansion:
);
return SmallVector::zero();
}
+
fld.cx.bt_push(ExpnInfo {
call_site: it.span,
callee: NameAndSpan {
name: extnamestr.to_string(),
format: MacroBang,
span: None,
+ // `macro_rules!` doesn't directly allow
+ // unstable (this is orthogonal to whether
+ // the macro it creates allows it)
+ allow_internal_unstable: false,
}
});
// DON'T mark before expansion.
+ let allow_internal_unstable = attr::contains_name(&it.attrs,
+ "allow_internal_unstable");
+
+ // ensure any #[allow_internal_unstable]s are
+ // detected (including nested macro definitions
+ // etc.)
+ if allow_internal_unstable && !fld.cx.ecfg.enable_allow_internal_unstable() {
+ feature_gate::emit_feature_err(
+ &fld.cx.parse_sess.span_diagnostic,
+ "allow_internal_unstable",
+ it.span,
+ feature_gate::EXPLAIN_ALLOW_INTERNAL_UNSTABLE)
+ }
+
let def = ast::MacroDef {
ident: it.ident,
attrs: it.attrs.clone(),
imported_from: None,
export: attr::contains_name(&it.attrs, "macro_export"),
use_locally: true,
+ allow_internal_unstable: allow_internal_unstable,
body: tts,
};
fld.cx.insert_macro(def);
}
Some(rc) => match *rc {
- NormalTT(ref expander, tt_span) => {
+ NormalTT(ref expander, tt_span, allow_internal_unstable) => {
fld.cx.bt_push(ExpnInfo {
call_site: span,
callee: NameAndSpan {
name: extnamestr.to_string(),
format: MacroBang,
- span: tt_span
+ span: tt_span,
+ allow_internal_unstable: allow_internal_unstable,
}
});
callee: NameAndSpan {
name: mname.to_string(),
format: MacroAttribute,
- span: None
+ span: None,
+ // attributes can do whatever they like,
+ // for now.
+ allow_internal_unstable: true,
}
});
name: mname.to_string(),
format: MacroAttribute,
span: None,
+ // attributes can do whatever they like,
+ // for now
+ allow_internal_unstable: true,
}
});
it = mac.expand(fld.cx, attr.span, &*attr.node.value, it);
_ => false,
}
}
+
+ pub fn enable_allow_internal_unstable(&self) -> bool {
+ match self.features {
+ Some(&Features { allow_internal_unstable: true, .. }) => true,
+ _ => false
+ }
+ }
}
pub fn expand_crate<'feat>(parse_sess: &parse::ParseSess,
rhses: rhses,
};
- NormalTT(exp, Some(def.span))
+ NormalTT(exp, Some(def.span), def.allow_internal_unstable)
}
fn check_lhs_nt_follows(cx: &mut ExtCtxt, lhs: &NamedMatch, sp: Span) {
// Allows the use of `static_assert`
("static_assert", "1.0.0", Active),
+
+ // Allows the use of #[allow_internal_unstable]. This is an
+ // attribute on macro_rules! and can't use the attribute handling
+ // below (it has to be checked before expansion possibly makes
+ // macros disappear).
+ ("allow_internal_unstable", "1.0.0", Active),
];
// (changing above list without updating src/doc/reference.md makes @cmr sad)
pub allow_log_syntax: bool,
pub allow_concat_idents: bool,
pub allow_trace_macros: bool,
+ pub allow_internal_unstable: bool,
pub old_orphan_check: bool,
pub simd_ffi: bool,
pub unmarked_api: bool,
allow_log_syntax: false,
allow_concat_idents: false,
allow_trace_macros: false,
+ allow_internal_unstable: false,
old_orphan_check: false,
simd_ffi: false,
unmarked_api: false,
pub const EXPLAIN_TRACE_MACROS: &'static str =
"`trace_macros` is not stable enough for use and is subject to change";
+pub const EXPLAIN_ALLOW_INTERNAL_UNSTABLE: &'static str =
+ "allow_internal_unstable side-steps feature gating and stability checks";
struct MacroVisitor<'a> {
context: &'a Context<'a>
self.context.gate_feature("concat_idents", path.span, EXPLAIN_CONCAT_IDENTS);
}
}
+
+ fn visit_attribute(&mut self, attr: &'v ast::Attribute) {
+ if attr.name() == "allow_internal_unstable" {
+ self.context.gate_feature("allow_internal_unstable", attr.span,
+ EXPLAIN_ALLOW_INTERNAL_UNSTABLE)
+ }
+ }
}
struct PostExpansionVisitor<'a> {
impl<'a> PostExpansionVisitor<'a> {
fn gate_feature(&self, feature: &str, span: Span, explain: &str) {
- if !self.context.cm.span_is_internal(span) {
+ if !self.context.cm.span_allows_unstable(span) {
self.context.gate_feature(feature, span, explain)
}
}
allow_log_syntax: cx.has_feature("log_syntax"),
allow_concat_idents: cx.has_feature("concat_idents"),
allow_trace_macros: cx.has_feature("trace_macros"),
+ allow_internal_unstable: cx.has_feature("allow_internal_unstable"),
old_orphan_check: cx.has_feature("old_orphan_check"),
simd_ffi: cx.has_feature("simd_ffi"),
unmarked_api: cx.has_feature("unmarked_api"),
callee: NameAndSpan {
name: "test".to_string(),
format: MacroAttribute,
- span: None
+ span: None,
+ allow_internal_unstable: false,
}
});
callee: NameAndSpan {
name: "test".to_string(),
format: MacroAttribute,
- span: None
+ span: None,
+ allow_internal_unstable: true,
}
};
let expn_id = cx.sess.span_diagnostic.cm.record_expansion(info);
--- /dev/null
+// 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.
+
+#![feature(staged_api, allow_internal_unstable)]
+#![staged_api]
+#![stable(feature = "stable", since = "1.0.0")]
+
+#[unstable(feature = "function")]
+pub fn unstable() {}
+
+
+#[stable(feature = "stable", since = "1.0.0")]
+pub struct Foo {
+ #[unstable(feature = "struct_field")]
+ pub x: u8
+}
+
+#[allow_internal_unstable]
+#[macro_export]
+macro_rules! call_unstable_allow {
+ () => { $crate::unstable() }
+}
+
+#[allow_internal_unstable]
+#[macro_export]
+macro_rules! construct_unstable_allow {
+ ($e: expr) => {
+ $crate::Foo { x: $e }
+ }
+}
+
+#[allow_internal_unstable]
+#[macro_export]
+macro_rules! pass_through_allow {
+ ($e: expr) => { $e }
+}
+
+#[macro_export]
+macro_rules! call_unstable_noallow {
+ () => { $crate::unstable() }
+}
+
+#[macro_export]
+macro_rules! construct_unstable_noallow {
+ ($e: expr) => {
+ $crate::Foo { x: $e }
+ }
+}
+
+#[macro_export]
+macro_rules! pass_through_noallow {
+ ($e: expr) => { $e }
+}
let args = reg.args().clone();
reg.register_syntax_extension(token::intern("plugin_args"),
// FIXME (#22405): Replace `Box::new` with `box` here when/if possible.
- NormalTT(Box::new(Expander { args: args, }), None));
+ NormalTT(Box::new(Expander { args: args, }), None, false));
}
--- /dev/null
+// 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.
+
+macro_rules! bar {
+ () => {
+ // more layers don't help:
+ #[allow_internal_unstable]
+ macro_rules! baz { //~ ERROR allow_internal_unstable side-steps
+ () => {}
+ }
+ }
+}
+
+bar!();
+
+fn main() {}
--- /dev/null
+// 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.
+
+#[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps
+macro_rules! foo {
+ () => {}
+}
+
+fn main() {}
--- /dev/null
+// 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.
+
+// this has to be separate to internal-unstable.rs because these tests
+// have error messages pointing deep into the internals of the
+// cross-crate macros, and hence need to use error-pattern instead of
+// the // ~ form.
+
+// aux-build:internal_unstable.rs
+// error-pattern:use of unstable library feature 'function'
+// error-pattern:use of unstable library feature 'struct_field'
+// error-pattern:compilation successful
+#![feature(rustc_attrs)]
+
+#[macro_use]
+extern crate internal_unstable;
+
+#[rustc_error]
+fn main() {
+ call_unstable_noallow!();
+
+ construct_unstable_noallow!(0);
+}
--- /dev/null
+// 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.
+
+// aux-build:internal_unstable.rs
+
+#![feature(rustc_attrs, allow_internal_unstable)]
+
+#[macro_use]
+extern crate internal_unstable;
+
+macro_rules! foo {
+ ($e: expr, $f: expr) => {{
+ $e;
+ $f;
+ internal_unstable::unstable(); //~ WARN use of unstable
+ }}
+}
+
+#[allow_internal_unstable]
+macro_rules! bar {
+ ($e: expr) => {{
+ foo!($e,
+ internal_unstable::unstable());
+ internal_unstable::unstable();
+ }}
+}
+
+#[rustc_error]
+fn main() { //~ ERROR
+ // ok, the instability is contained.
+ call_unstable_allow!();
+ construct_unstable_allow!(0);
+
+ // bad.
+ pass_through_allow!(internal_unstable::unstable()); //~ WARN use of unstable
+
+ pass_through_noallow!(internal_unstable::unstable()); //~ WARN use of unstable
+
+
+
+ println!("{:?}", internal_unstable::unstable()); //~ WARN use of unstable
+
+ bar!(internal_unstable::unstable()); //~ WARN use of unstable
+}