diff options
| author | Huon Wilson <dbau.pp+github@gmail.com> | 2015-03-01 14:09:28 +1100 |
|---|---|---|
| committer | Huon Wilson <dbau.pp+github@gmail.com> | 2015-03-06 00:18:28 +1100 |
| commit | 84b060ce29bf7dd65fc23e855ad7c5a8748d806c (patch) | |
| tree | 2d518fd4340c80dc8613a93bffcfee7c264fc0f4 /src/libsyntax/codemap.rs | |
| parent | 68740b405404a3f885e388c8d31722797d519c30 (diff) | |
| download | rust-84b060ce29bf7dd65fc23e855ad7c5a8748d806c.tar.gz rust-84b060ce29bf7dd65fc23e855ad7c5a8748d806c.zip | |
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.
Diffstat (limited to 'src/libsyntax/codemap.rs')
| -rw-r--r-- | src/libsyntax/codemap.rs | 70 |
1 files changed, 38 insertions, 32 deletions
diff --git a/src/libsyntax/codemap.rs b/src/libsyntax/codemap.rs index 162da2ac54c..44df8a6d3da 100644 --- a/src/libsyntax/codemap.rs +++ b/src/libsyntax/codemap.rs @@ -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. @@ -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 } } |
