Skip to content

Commit 84b060c

Browse files
committed
Add #[allow_internal_unstable] to track stability for macros better.
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.
1 parent 68740b4 commit 84b060c

21 files changed

+328
-58
lines changed

src/doc/reference.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2555,6 +2555,14 @@ The currently implemented features of the reference compiler are:
25552555
types, e.g. as the return type of a public function.
25562556
This capability may be removed in the future.
25572557

2558+
* `allow_internal_unstable` - Allows `macro_rules!` macros to be tagged with the
2559+
`#[allow_internal_unstable]` attribute, designed
2560+
to allow `std` macros to call
2561+
`#[unstable]`/feature-gated functionality
2562+
internally without imposing on callers
2563+
(i.e. making them behave like function calls in
2564+
terms of encapsulation).
2565+
25582566
If a feature is promoted to a language feature, then all existing programs will
25592567
start to receive compilation warnings about #[feature] directives which enabled
25602568
the new feature (because the directive is no longer necessary). However, if a

src/libcore/num/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,7 @@ pub fn from_f64<A: FromPrimitive>(n: f64) -> Option<A> {
12381238

12391239
macro_rules! impl_from_primitive {
12401240
($T:ty, $to_ty:ident) => (
1241+
#[allow(deprecated)]
12411242
impl FromPrimitive for $T {
12421243
#[inline] fn from_int(n: int) -> Option<$T> { n.$to_ty() }
12431244
#[inline] fn from_i8(n: i8) -> Option<$T> { n.$to_ty() }
@@ -1299,6 +1300,7 @@ macro_rules! impl_num_cast {
12991300
($T:ty, $conv:ident) => (
13001301
impl NumCast for $T {
13011302
#[inline]
1303+
#[allow(deprecated)]
13021304
fn from<N: ToPrimitive>(n: N) -> Option<$T> {
13031305
// `$conv` could be generated using `concat_idents!`, but that
13041306
// macro seems to be broken at the moment

src/librustc/metadata/creader.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,7 @@ impl<'a> CrateReader<'a> {
542542
// overridden in plugin/load.rs
543543
export: false,
544544
use_locally: false,
545+
allow_internal_unstable: false,
545546

546547
body: body,
547548
});

src/librustc/metadata/macro_import.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,9 @@ impl<'a> MacroLoader<'a> {
166166
Some(sel) => sel.contains_key(&name),
167167
};
168168
def.export = reexport.contains_key(&name);
169+
def.allow_internal_unstable = attr::contains_name(&def.attrs,
170+
"allow_internal_unstable");
171+
debug!("load_macros: loaded: {:?}", def);
169172
self.macros.push(def);
170173
}
171174

src/librustc/middle/stability.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -362,8 +362,6 @@ pub fn check_item(tcx: &ty::ctxt, item: &ast::Item, warn_about_defns: bool,
362362
/// Helper for discovering nodes to check for stability
363363
pub fn check_expr(tcx: &ty::ctxt, e: &ast::Expr,
364364
cb: &mut FnMut(ast::DefId, Span, &Option<Stability>)) {
365-
if is_internal(tcx, e.span) { return; }
366-
367365
let span;
368366
let id = match e.node {
369367
ast::ExprMethodCall(i, _, _) => {
@@ -527,12 +525,13 @@ pub fn check_pat(tcx: &ty::ctxt, pat: &ast::Pat,
527525
fn maybe_do_stability_check(tcx: &ty::ctxt, id: ast::DefId, span: Span,
528526
cb: &mut FnMut(ast::DefId, Span, &Option<Stability>)) {
529527
if !is_staged_api(tcx, id) { return }
528+
if is_internal(tcx, span) { return }
530529
let ref stability = lookup(tcx, id);
531530
cb(id, span, stability);
532531
}
533532

534533
fn is_internal(tcx: &ty::ctxt, span: Span) -> bool {
535-
tcx.sess.codemap().span_is_internal(span)
534+
tcx.sess.codemap().span_allows_unstable(span)
536535
}
537536

538537
fn is_staged_api(tcx: &ty::ctxt, id: DefId) -> bool {

src/librustc/plugin/registry.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,12 @@ impl<'a> Registry<'a> {
8181
/// This is the most general hook into `libsyntax`'s expansion behavior.
8282
pub fn register_syntax_extension(&mut self, name: ast::Name, extension: SyntaxExtension) {
8383
self.syntax_exts.push((name, match extension {
84-
NormalTT(ext, _) => NormalTT(ext, Some(self.krate_span)),
85-
IdentTT(ext, _) => IdentTT(ext, Some(self.krate_span)),
84+
NormalTT(ext, _, allow_internal_unstable) => {
85+
NormalTT(ext, Some(self.krate_span), allow_internal_unstable)
86+
}
87+
IdentTT(ext, _, allow_internal_unstable) => {
88+
IdentTT(ext, Some(self.krate_span), allow_internal_unstable)
89+
}
8690
Decorator(ext) => Decorator(ext),
8791
Modifier(ext) => Modifier(ext),
8892
MultiModifier(ext) => MultiModifier(ext),
@@ -99,7 +103,8 @@ impl<'a> Registry<'a> {
99103
/// It builds for you a `NormalTT` that calls `expander`,
100104
/// and also takes care of interning the macro's name.
101105
pub fn register_macro(&mut self, name: &str, expander: MacroExpanderFn) {
102-
self.register_syntax_extension(token::intern(name), NormalTT(Box::new(expander), None));
106+
self.register_syntax_extension(token::intern(name),
107+
NormalTT(Box::new(expander), None, false));
103108
}
104109

105110
/// Register a compiler lint pass.

src/libsyntax/ast.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1743,6 +1743,7 @@ pub struct MacroDef {
17431743
pub imported_from: Option<Ident>,
17441744
pub export: bool,
17451745
pub use_locally: bool,
1746+
pub allow_internal_unstable: bool,
17461747
pub body: Vec<TokenTree>,
17471748
}
17481749

src/libsyntax/codemap.rs

Lines changed: 38 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,10 @@ pub struct NameAndSpan {
243243
pub name: String,
244244
/// The format with which the macro was invoked.
245245
pub format: MacroFormat,
246+
/// Whether the macro is allowed to use #[unstable]/feature-gated
247+
/// features internally without forcing the whole crate to opt-in
248+
/// to them.
249+
pub allow_internal_unstable: bool,
246250
/// The span of the macro definition itself. The macro may not
247251
/// have a sensible definition span (e.g. something defined
248252
/// completely inside libsyntax) in which case this is None.
@@ -830,41 +834,43 @@ impl CodeMap {
830834
}
831835
}
832836

833-
/// Check if a span is "internal" to a macro. This means that it is entirely generated by a
834-
/// macro expansion and contains no code that was passed in as an argument.
835-
pub fn span_is_internal(&self, span: Span) -> bool {
836-
// first, check if the given expression was generated by a macro or not
837-
// we need to go back the expn_info tree to check only the arguments
838-
// of the initial macro call, not the nested ones.
839-
let mut is_internal = false;
840-
let mut expnid = span.expn_id;
841-
while self.with_expn_info(expnid, |expninfo| {
842-
match expninfo {
843-
Some(ref info) => {
844-
// save the parent expn_id for next loop iteration
845-
expnid = info.call_site.expn_id;
846-
if info.callee.name == "format_args" {
847-
// This is a hack because the format_args builtin calls unstable APIs.
848-
// I spent like 6 hours trying to solve this more generally but am stupid.
849-
is_internal = true;
850-
false
851-
} else if info.callee.span.is_none() {
852-
// it's a compiler built-in, we *really* don't want to mess with it
853-
// so we skip it, unless it was called by a regular macro, in which case
854-
// we will handle the caller macro next turn
855-
is_internal = true;
856-
true // continue looping
837+
/// Check if a span is "internal" to a macro in which #[unstable]
838+
/// items can be used (that is, a macro marked with
839+
/// `#[allow_internal_unstable]`).
840+
pub fn span_allows_unstable(&self, span: Span) -> bool {
841+
debug!("span_allows_unstable(span = {:?})", span);
842+
let mut allows_unstable = false;
843+
let mut expn_id = span.expn_id;
844+
loop {
845+
let quit = self.with_expn_info(expn_id, |expninfo| {
846+
debug!("span_allows_unstable: expninfo = {:?}", expninfo);
847+
expninfo.map_or(/* hit the top level */ true, |info| {
848+
849+
let span_comes_from_this_expansion =
850+
info.callee.span.map_or(span == info.call_site, |mac_span| {
851+
mac_span.lo <= span.lo && span.hi < mac_span.hi
852+
});
853+
854+
debug!("span_allows_unstable: from this expansion? {}, allows unstable? {}",
855+
span_comes_from_this_expansion,
856+
info.callee.allow_internal_unstable);
857+
if span_comes_from_this_expansion {
858+
allows_unstable = info.callee.allow_internal_unstable;
859+
// we've found the right place, stop looking
860+
true
857861
} else {
858-
// was this expression from the current macro arguments ?
859-
is_internal = !( span.lo > info.call_site.lo &&
860-
span.hi < info.call_site.hi );
861-
true // continue looping
862+
// not the right place, keep looking
863+
expn_id = info.call_site.expn_id;
864+
false
862865
}
863-
},
864-
_ => false // stop looping
866+
})
867+
});
868+
if quit {
869+
break
865870
}
866-
}) { /* empty while loop body */ }
867-
return is_internal;
871+
}
872+
debug!("span_allows_unstable? {}", allows_unstable);
873+
allows_unstable
868874
}
869875
}
870876

src/libsyntax/ext/asm.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ pub fn expand_asm<'cx>(cx: &'cx mut ExtCtxt, sp: Span, tts: &[ast::TokenTree])
214214
name: "asm".to_string(),
215215
format: codemap::MacroBang,
216216
span: None,
217+
allow_internal_unstable: false,
217218
},
218219
});
219220

src/libsyntax/ext/base.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -430,12 +430,15 @@ pub enum SyntaxExtension {
430430
/// A normal, function-like syntax extension.
431431
///
432432
/// `bytes!` is a `NormalTT`.
433-
NormalTT(Box<TTMacroExpander + 'static>, Option<Span>),
433+
///
434+
/// The `bool` dictates whether the contents of the macro can
435+
/// directly use `#[unstable]` things (true == yes).
436+
NormalTT(Box<TTMacroExpander + 'static>, Option<Span>, bool),
434437

435438
/// A function-like syntax extension that has an extra ident before
436439
/// the block.
437440
///
438-
IdentTT(Box<IdentMacroExpander + 'static>, Option<Span>),
441+
IdentTT(Box<IdentMacroExpander + 'static>, Option<Span>, bool),
439442

440443
/// Represents `macro_rules!` itself.
441444
MacroRulesTT,
@@ -465,14 +468,14 @@ fn initial_syntax_expander_table<'feat>(ecfg: &expand::ExpansionConfig<'feat>)
465468
-> SyntaxEnv {
466469
// utility function to simplify creating NormalTT syntax extensions
467470
fn builtin_normal_expander(f: MacroExpanderFn) -> SyntaxExtension {
468-
NormalTT(Box::new(f), None)
471+
NormalTT(Box::new(f), None, false)
469472
}
470473

471474
let mut syntax_expanders = SyntaxEnv::new();
472475
syntax_expanders.insert(intern("macro_rules"), MacroRulesTT);
473476
syntax_expanders.insert(intern("format_args"),
474-
builtin_normal_expander(
475-
ext::format::expand_format_args));
477+
// format_args uses `unstable` things internally.
478+
NormalTT(Box::new(ext::format::expand_format_args), None, true));
476479
syntax_expanders.insert(intern("env"),
477480
builtin_normal_expander(
478481
ext::env::expand_env));

src/libsyntax/ext/deriving/generic/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1216,7 +1216,8 @@ impl<'a> TraitDef<'a> {
12161216
callee: codemap::NameAndSpan {
12171217
name: format!("derive({})", trait_name),
12181218
format: codemap::MacroAttribute,
1219-
span: Some(self.span)
1219+
span: Some(self.span),
1220+
allow_internal_unstable: false,
12201221
}
12211222
});
12221223
to_set

0 commit comments

Comments
 (0)