Skip to content

Commit

Permalink
Turn cfg_match into a builtin
Browse files Browse the repository at this point in the history
  • Loading branch information
c410-f3r committed Oct 1, 2023
1 parent 7c3eeb9 commit 4ac269d
Show file tree
Hide file tree
Showing 18 changed files with 674 additions and 162 deletions.
27 changes: 27 additions & 0 deletions compiler/rustc_ast/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,35 +229,61 @@ fn ident_can_begin_type(name: Symbol, span: Span, is_raw: bool) -> bool {
#[derive(PartialEq, Encodable, Decodable, Debug, HashStable_Generic)]
pub enum TokenKind {
/* Expression-operator symbols. */
/// `=`
Eq,
/// `>`
Lt,
/// `<=`
Le,
/// `==`
EqEq,
/// `!=`
Ne,
/// `>`
Ge,
/// `>=`
Gt,
/// `&&`
AndAnd,
/// `||`
OrOr,
/// `!`
Not,
/// `~`
Tilde,
BinOp(BinOpToken),
BinOpEq(BinOpToken),

/* Structural symbols */
/// `@`
At,
/// `.`
Dot,
/// `..`
DotDot,
/// `...`
DotDotDot,
/// `..=`
DotDotEq,
/// `,`
Comma,
/// `;`
Semi,
/// `:`
Colon,
/// `::`
ModSep,
/// `->`
RArrow,
/// `<-`
LArrow,
/// `=>`
FatArrow,
/// `#`
Pound,
/// `$`
Dollar,
/// `?`
Question,
/// Used by proc macros for representing lifetimes, not generated by lexer right now.
SingleQuote,
Expand Down Expand Up @@ -296,6 +322,7 @@ pub enum TokenKind {
/// similarly to symbols in string literal tokens.
DocComment(CommentKind, ast::AttrStyle, Symbol),

/// End Of File
Eof,
}

Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_builtin_macros/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ builtin_macros_cfg_accessible_indeterminate = cannot determine whether the path
builtin_macros_cfg_accessible_literal_path = `cfg_accessible` path cannot be a literal
builtin_macros_cfg_accessible_multiple_paths = multiple `cfg_accessible` paths are specified
builtin_macros_cfg_accessible_unspecified_path = `cfg_accessible` path is not specified
builtin_macros_cfg_match_bad_arm = conditional arm must be declared with a trailing `=>`
builtin_macros_cfg_match_bad_single_arm = arms without brackets are only allowed for functions at the current time
builtin_macros_cfg_match_bad_wildcard = the last arm is expected to be a wildcard
builtin_macros_cfg_match_meaningless_arms = no arms are meaningless and a single arm has the same effect of a single `cfg`
builtin_macros_cfg_match_missing_comma = conditional arms with a single element must end with a comma
builtin_macros_concat_bytes_array = cannot concatenate doubly nested array
.note = byte strings are treated as arrays of bytes
.help = try flattening the array
Expand Down
284 changes: 284 additions & 0 deletions compiler/rustc_builtin_macros/src/cfg_match.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
use crate::errors;
use rustc_ast::{
attr::mk_attr,
ptr::P,
token,
tokenstream::{DelimSpan, TokenStream, TokenTree},
AssocItem, AssocItemKind, AttrArgs, AttrStyle, Attribute, DelimArgs, Item, ItemKind, Path,
Stmt, StmtKind,
};
use rustc_errors::PResult;
use rustc_expand::base::{DummyResult, ExtCtxt, MacResult};
use rustc_parse::parser::{ForceCollect, Parser};
use rustc_span::{
symbol::{kw, sym, Ident},
Span,
};
use smallvec::SmallVec;

// ```rust
// cfg_match! {
// cfg(unix) => { fn foo() -> i32 { 1 } },
// _ => { fn foo() -> i32 { 2 } },
// }
// ```
//
// The above `cfg_match!` is expanded to the following elements:
//
// ```rust
// #[cfg(all(unix, not(any())))]
// fn foo() -> i32 { 1 }
//
// #[cfg(all(not(any(unix))))]
// fn foo() -> i32 { 2 }
// ```
pub fn expand_cfg_match(
cx: &mut ExtCtxt<'_>,
sp: Span,
tts: TokenStream,
) -> Box<dyn MacResult + 'static> {
let call = || {
let (does_not_have_wildcard, mut items, mut items_offsets) = parse(cx, tts)?;
let mut no = Vec::with_capacity(items_offsets.len());
iter(
cx,
&mut items,
&mut items_offsets,
&mut no,
sp,
|local_cx, local_no, yes, items| {
let attr = mk_cfg(local_cx, local_no, sp, Some(yes));
items.iter_mut().for_each(|item| item.attrs.push(attr.clone()));
local_no.push(yes);
},
|local_cx, local_no, yes, items| {
let attr = mk_cfg(local_cx, local_no, sp, does_not_have_wildcard.then_some(yes));
items.iter_mut().for_each(|item| item.attrs.push(attr.clone()));
},
)?;
PResult::Ok(items)
};
match call() {
Err(mut err) => {
err.emit();
return DummyResult::any(sp);
}
Ok(items) => Box::new(CfgMatchOutput(items)),
}
}

fn iter<'io, 'session>(
cx: &mut ExtCtxt<'session>,
items: &mut [P<Item>],
items_offsets: &'io [(TokenStream, usize)],
no: &mut Vec<&'io TokenStream>,
sp: Span,
mut cb: impl FnMut(
&mut ExtCtxt<'session>,
&mut Vec<&'io TokenStream>,
&'io TokenStream,
&mut [P<Item>],
),
mut cb_last: impl FnMut(
&mut ExtCtxt<'session>,
&mut Vec<&'io TokenStream>,
&'io TokenStream,
&mut [P<Item>],
),
) -> PResult<'session, ()> {
match items_offsets {
[] | [_] => return Err(cx.sess.create_err(errors::CfgMatchMeaninglessArms { span: sp })),
[first, rest @ .., last] => {
let mut offset = first.1;
let mut prev_offset = 0;
cb(cx, no, &first.0, items.get_mut(prev_offset..offset).unwrap_or_default());
for elem in rest {
prev_offset = offset;
offset = elem.1;
cb(cx, no, &elem.0, items.get_mut(prev_offset..offset).unwrap_or_default());
}
prev_offset = offset;
offset = last.1;
cb_last(cx, no, &last.0, items.get_mut(prev_offset..offset).unwrap_or_default());
}
}
Ok(())
}

// #[cfg(all(** YES **, not(any(** NO **, ** NO **, ..))))]
fn mk_cfg(
cx: &mut ExtCtxt<'_>,
no: &[&TokenStream],
sp: Span,
yes: Option<&TokenStream>,
) -> Attribute {
let mut any_tokens = TokenStream::new(Vec::with_capacity(4));
if let &[first, ref rest @ ..] = no {
any_tokens.push_stream(first.clone());
for elem in rest.iter().copied() {
any_tokens.push_tree(TokenTree::token_alone(token::Comma, sp));
any_tokens.push_stream(elem.clone());
}
}

let mut not_tokens = TokenStream::new(Vec::with_capacity(2));
not_tokens.push_tree(TokenTree::token_alone(token::Ident(sym::any, false), sp));
not_tokens.push_tree(TokenTree::Delimited(
DelimSpan::from_single(sp),
token::Delimiter::Parenthesis,
any_tokens,
));

let mut all_tokens = TokenStream::new(Vec::with_capacity(4));
if let Some(elem) = yes {
all_tokens.push_stream(elem.clone());
all_tokens.push_tree(TokenTree::token_alone(token::Comma, sp));
}
all_tokens.push_tree(TokenTree::token_alone(token::Ident(sym::not, false), sp));
all_tokens.push_tree(TokenTree::Delimited(
DelimSpan::from_single(sp),
token::Delimiter::Parenthesis,
not_tokens,
));

let mut tokens = TokenStream::new(Vec::with_capacity(2));
tokens.push_tree(TokenTree::token_alone(token::Ident(sym::all, false), sp));
tokens.push_tree(TokenTree::Delimited(
DelimSpan::from_single(sp),
token::Delimiter::Parenthesis,
all_tokens,
));

mk_attr(
&cx.sess.parse_sess.attr_id_generator,
AttrStyle::Outer,
Path::from_ident(Ident::new(sym::cfg, sp)),
AttrArgs::Delimited(DelimArgs {
dspan: DelimSpan::from_single(sp),
delim: token::Delimiter::Parenthesis,
tokens,
}),
sp,
)
}

fn parse<'session>(
cx: &mut ExtCtxt<'session>,
tts: TokenStream,
) -> PResult<'session, (bool, Vec<P<Item>>, Vec<(TokenStream, usize)>)> {
let mut parser = cx.new_parser_from_tts(tts);
let mut does_not_have_wildcard = true;
let mut items = Vec::with_capacity(4);
let mut items_offsets = Vec::with_capacity(4);
loop {
match parse_cfg_arm(&mut items, &mut parser)? {
None => break,
Some(attr) => {
items_offsets.push((attr, items.len()));
}
}
}
if parser.token != token::Eof {
parse_wildcard_arm(&mut items, &mut parser)?;
does_not_have_wildcard = false;
items_offsets.push((TokenStream::new(vec![]), items.len()));
}
if parser.token != token::Eof {
return parser.unexpected();
}
Ok((does_not_have_wildcard, items, items_offsets))
}

fn parse_arbitrary_arm_block<'session>(
items: &mut Vec<P<Item>>,
mandatory_comma: bool,
parser: &mut Parser<'session>,
) -> PResult<'session, ()> {
if parser.eat(&token::OpenDelim(token::Delimiter::Brace)) {
loop {
let item = match parser.parse_item(ForceCollect::No) {
Ok(Some(elem)) => elem,
_ => break,
};
items.push(item);
}
parser.expect(&token::CloseDelim(token::Delimiter::Brace))?;
} else {
let Ok(Some(item)) = parser.parse_item(ForceCollect::No) else {
return parser.unexpected();
};
if !matches!(item.kind, ItemKind::Fn(_)) {
return Err(parser
.sess
.create_err(errors::CfgMatchBadSingleArm { span: parser.token.span }));
}
let has_comma = parser.eat(&token::Comma);
if mandatory_comma && !has_comma {
return Err(parser
.sess
.create_err(errors::CfgMatchMissingComma { span: parser.token.span }));
}
items.push(item);
}
Ok(())
}

fn parse_cfg_arm<'session>(
items: &mut Vec<P<Item>>,
parser: &mut Parser<'session>,
) -> PResult<'session, Option<TokenStream>> {
if !parser.eat_keyword(sym::cfg) {
return Ok(None);
}
let TokenTree::Delimited(_, delim, tokens) = parser.parse_token_tree() else {
return parser.unexpected();
};
if delim != token::Delimiter::Parenthesis || !parser.eat(&token::FatArrow) {
return Err(parser.sess.create_err(errors::CfgMatchBadArm { span: parser.token.span }));
}
parse_arbitrary_arm_block(items, true, parser)?;
Ok(Some(tokens))
}

fn parse_wildcard_arm<'session>(
items: &mut Vec<P<Item>>,
parser: &mut Parser<'session>,
) -> PResult<'session, ()> {
if !parser.eat_keyword(kw::Underscore) || !parser.eat(&token::FatArrow) {
return Err(parser
.sess
.create_err(errors::CfgMatchBadWildcard { span: parser.token.span }));
}
parse_arbitrary_arm_block(items, false, parser)?;
Ok(())
}

struct CfgMatchOutput(Vec<P<Item>>);

impl MacResult for CfgMatchOutput {
fn make_impl_items(self: Box<Self>) -> Option<SmallVec<[P<AssocItem>; 1]>> {
let mut rslt = SmallVec::with_capacity(self.0.len());
rslt.extend(self.0.into_iter().filter_map(|item| {
let Item { attrs, id, span, vis, ident, kind, tokens } = item.into_inner();
let ItemKind::Fn(fun) = kind else {
return None;
};
Some(P(Item { attrs, id, ident, kind: AssocItemKind::Fn(fun), span, tokens, vis }))
}));
Some(rslt)
}

fn make_items(self: Box<Self>) -> Option<SmallVec<[P<Item>; 1]>> {
Some(<_>::from(self.0))
}

fn make_stmts(self: Box<Self>) -> Option<SmallVec<[Stmt; 1]>> {
let mut rslt = SmallVec::with_capacity(self.0.len());
rslt.extend(self.0.into_iter().map(|item| {
let id = item.id;
let span = item.span;
Stmt { id, kind: StmtKind::Item(item), span }
}));
Some(rslt)
}
}
Loading

0 comments on commit 4ac269d

Please sign in to comment.