-
Notifications
You must be signed in to change notification settings - Fork 13.5k
make cfg_select
a builtin macro
#143461
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
make cfg_select
a builtin macro
#143461
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
use rustc_ast::tokenstream::TokenStream; | ||
use rustc_attr_parsing as attr; | ||
use rustc_expand::base::{ExtCtxt, MacroExpanderResult, *}; | ||
use rustc_parse::parser::cfg_select::{CfgSelectBranches, CfgSelectRule, parse_cfg_select}; | ||
use rustc_span::{Ident, Span, sym}; | ||
|
||
use crate::errors::{CfgSelectNoMatches, CfgSelectUnreachable}; | ||
|
||
/// Selects the first arm whose rule evaluates to true. | ||
fn select_arm(ecx: &ExtCtxt<'_>, branches: CfgSelectBranches) -> Option<(TokenStream, Span)> { | ||
for (cfg, tt, arm_span) in branches.reachable { | ||
if attr::cfg_matches( | ||
&cfg, | ||
&ecx.sess, | ||
ecx.current_expansion.lint_node_id, | ||
Some(ecx.ecfg.features), | ||
) { | ||
return Some((tt, arm_span)); | ||
} | ||
} | ||
|
||
branches.wildcard.map(|(_, tt, span)| (tt, span)) | ||
} | ||
|
||
pub(super) fn expand_cfg_select<'cx>( | ||
ecx: &'cx mut ExtCtxt<'_>, | ||
sp: Span, | ||
tts: TokenStream, | ||
) -> MacroExpanderResult<'cx> { | ||
ExpandResult::Ready(match parse_cfg_select(&mut ecx.new_parser_from_tts(tts)) { | ||
Ok(branches) => { | ||
if let Some((underscore, _, _)) = branches.wildcard { | ||
// Warn for every unreachable rule. | ||
for (rule, _, _) in &branches.unreachable { | ||
let span = match rule { | ||
CfgSelectRule::Wildcard(underscore) => underscore.span, | ||
CfgSelectRule::Cfg(cfg) => cfg.span(), | ||
}; | ||
let err = CfgSelectUnreachable { span, wildcard_span: underscore.span }; | ||
ecx.dcx().emit_warn(err); | ||
} | ||
} | ||
|
||
if let Some((tts, arm_span)) = select_arm(ecx, branches) { | ||
rustc_expand::expand_token_stream( | ||
ecx, | ||
sp, | ||
arm_span, | ||
ecx.current_expansion.lint_node_id, | ||
Ident::with_dummy_span(sym::cfg_select), | ||
tts, | ||
) | ||
} else { | ||
// Emit a compiler error when none of the rules matched. | ||
let guar = ecx.dcx().emit_err(CfgSelectNoMatches { span: sp }); | ||
DummyResult::any(sp, guar) | ||
} | ||
} | ||
Err(err) => { | ||
let guar = err.emit(); | ||
DummyResult::any(sp, guar) | ||
} | ||
}) | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the relevant file for A fn print() {
println!(cfg_select! {
unix => { "unix" }
_ => { "not unix" }
});
} where the right-hand side of the arrow is a So the formatter heeds to handle this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #[derive(Default)]
pub struct CfgSelectBranches {
pub reachable: Vec<(MetaItemInner, TokenStream, Span)>,
pub wildcard: Option<(Token, TokenStream, Span)>,
pub unreachable: Vec<(CfgSelectRule, TokenStream, Span)>,
} Question, what's the difference between Also, will the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For formatting there is really no difference. The goal of this structure is to be able to emit warnings for unreachable branches. The cfg rules on the left-hand side are evaluated from top to bottom, and the first one that evaluates to true is picked. The wildcard
I don't think so. It is like the right-hand side of a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you provide examples of more complicated RHS that aren't valid rust? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Something like this
the rhs is a valid token tree, but when expanded it's not valid rust code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. rustfmt would definitely fail to handle that. Is there a more realistic example you can think of? In the example above things fail to compile, but is there a case where the RHS is composed of valid tokens that don't parse as valid rust, but still get expanded to valid rust? Technically, rustfmt doesn't care what the tokens get expanded to as long as the tokens themselves can be parsed as valid rust, otherwise there's no hope to format them. Would you say that the typical case is going to be a RHS that parses as valid rust? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, in valid programs the rhs will generally be valid rust. The only tricky thing is that it's unclear what kind (could be a sequence of items, or an expression, or a statement). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now I think it should be enough to call |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
use rustc_ast::token::Token; | ||
use rustc_ast::tokenstream::{TokenStream, TokenTree}; | ||
use rustc_ast::{MetaItemInner, token}; | ||
use rustc_errors::PResult; | ||
use rustc_span::{Span, kw}; | ||
|
||
use crate::exp; | ||
use crate::parser::Parser; | ||
|
||
pub enum CfgSelectRule { | ||
Cfg(MetaItemInner), | ||
Wildcard(Token), | ||
} | ||
|
||
#[derive(Default)] | ||
pub struct CfgSelectBranches { | ||
/// All the conditional branches. | ||
pub reachable: Vec<(MetaItemInner, TokenStream, Span)>, | ||
/// The first wildcard `_ => { ... }` branch. | ||
pub wildcard: Option<(Token, TokenStream, Span)>, | ||
/// All branches after the first wildcard, including further wildcards. | ||
pub unreachable: Vec<(CfgSelectRule, TokenStream, Span)>, | ||
} | ||
|
||
/// Parses a `TokenTree` that must be of the form `{ /* ... */ }`, and returns a `TokenStream` where | ||
/// the surrounding braces are stripped. | ||
fn parse_token_tree<'a>(p: &mut Parser<'a>) -> PResult<'a, TokenStream> { | ||
// Generate an error if the `=>` is not followed by `{`. | ||
if p.token != token::OpenBrace { | ||
p.expect(exp!(OpenBrace))?; | ||
} | ||
|
||
// Strip the outer '{' and '}'. | ||
match p.parse_token_tree() { | ||
TokenTree::Token(..) => unreachable!("because of the expect above"), | ||
TokenTree::Delimited(.., tts) => Ok(tts), | ||
} | ||
} | ||
|
||
pub fn parse_cfg_select<'a>(p: &mut Parser<'a>) -> PResult<'a, CfgSelectBranches> { | ||
let mut branches = CfgSelectBranches::default(); | ||
|
||
while p.token != token::Eof { | ||
if p.token.is_keyword(kw::Underscore) { | ||
let underscore = p.token; | ||
p.bump(); | ||
p.expect(exp!(FatArrow))?; | ||
|
||
let tts = parse_token_tree(p)?; | ||
let span = underscore.span.to(p.token.span); | ||
|
||
match branches.wildcard { | ||
None => branches.wildcard = Some((underscore, tts, span)), | ||
Some(_) => { | ||
branches.unreachable.push((CfgSelectRule::Wildcard(underscore), tts, span)) | ||
} | ||
} | ||
} else { | ||
let meta_item = p.parse_meta_item_inner()?; | ||
p.expect(exp!(FatArrow))?; | ||
|
||
let tts = parse_token_tree(p)?; | ||
let span = meta_item.span().to(p.token.span); | ||
|
||
match branches.wildcard { | ||
None => branches.reachable.push((meta_item, tts, span)), | ||
Some(_) => branches.unreachable.push((CfgSelectRule::Cfg(meta_item), tts, span)), | ||
} | ||
} | ||
} | ||
|
||
Ok(branches) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
pub mod asm; | ||
pub mod attr; | ||
mod attr_wrapper; | ||
pub mod cfg_select; | ||
mod diagnostics; | ||
mod expr; | ||
mod generics; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd love to know if there is already a standard way to expand a token stream. I couldn't find one, so I did some refactoring here.