7802: Fix builtin macros split exprs on comma r=edwin0cheng a=edwin0cheng

Fixes #7640

bors r+

Co-authored-by: Edwin Cheng <edwin0cheng@gmail.com>
This commit is contained in:
bors[bot] 2021-02-28 05:07:09 +00:00 committed by GitHub
commit e0437f899c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 144 additions and 89 deletions

View File

@ -6,7 +6,7 @@ use crate::{
use base_db::{AnchoredPath, FileId};
use either::Either;
use mbe::{parse_to_token_tree, ExpandResult};
use mbe::{parse_exprs_with_sep, parse_to_token_tree, ExpandResult};
use parser::FragmentKind;
use syntax::ast::{self, AstToken};
@ -238,35 +238,21 @@ fn format_args_expand(
// ])
// ```,
// which is still not really correct, but close enough for now
let mut args = Vec::new();
let mut current = Vec::new();
for tt in tt.token_trees.iter().cloned() {
match tt {
tt::TokenTree::Leaf(tt::Leaf::Punct(p)) if p.char == ',' => {
args.push(current);
current = Vec::new();
}
_ => {
current.push(tt);
}
}
}
if !current.is_empty() {
args.push(current);
}
let mut args = parse_exprs_with_sep(tt, ',');
if args.is_empty() {
return ExpandResult::only_err(mbe::ExpandError::NoMatchingRule);
}
for arg in &mut args {
// Remove `key =`.
if matches!(arg.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=' && p.spacing != tt::Spacing::Joint)
if matches!(arg.token_trees.get(1), Some(tt::TokenTree::Leaf(tt::Leaf::Punct(p))) if p.char == '=' && p.spacing != tt::Spacing::Joint)
{
arg.drain(..2);
arg.token_trees.drain(..2);
}
}
let _format_string = args.remove(0);
let arg_tts = args.into_iter().flat_map(|arg| {
quote! { std::fmt::ArgumentV1::new(&(##arg), std::fmt::Display::fmt), }
quote! { std::fmt::ArgumentV1::new(&(#arg), std::fmt::Display::fmt), }
}.token_trees).collect::<Vec<_>>();
let expanded = quote! {
std::fmt::Arguments::new_v1(&[], &[##arg_tts])
@ -719,6 +705,25 @@ mod tests {
);
}
#[test]
fn test_format_args_expand_with_comma_exprs() {
let expanded = expand_builtin_macro(
r#"
#[rustc_builtin_macro]
macro_rules! format_args {
($fmt:expr) => ({ /* compiler built-in */ });
($fmt:expr, $($args:tt)*) => ({ /* compiler built-in */ })
}
format_args!("{} {:?}", a::<A,B>(), b);
"#,
);
assert_eq!(
expanded,
r#"std::fmt::Arguments::new_v1(&[], &[std::fmt::ArgumentV1::new(&(a::<A,B>()),std::fmt::Display::fmt),std::fmt::ArgumentV1::new(&(b),std::fmt::Display::fmt),])"#
);
}
#[test]
fn test_include_bytes_expand() {
let expanded = expand_builtin_macro(

View File

@ -3,15 +3,13 @@
use crate::{
expander::{Binding, Bindings, Fragment},
parser::{Op, RepeatKind, Separator},
subtree_source::SubtreeTokenSource,
tt_iter::TtIter,
ExpandError, MetaTemplate,
};
use super::ExpandResult;
use parser::{FragmentKind::*, TreeSink};
use syntax::{SmolStr, SyntaxKind};
use tt::buffer::{Cursor, TokenBuffer};
use parser::FragmentKind::*;
use syntax::SmolStr;
impl Bindings {
fn push_optional(&mut self, name: &SmolStr) {
@ -409,68 +407,6 @@ impl<'a> TtIter<'a> {
.into())
}
fn expect_fragment(
&mut self,
fragment_kind: parser::FragmentKind,
) -> ExpandResult<Option<tt::TokenTree>> {
struct OffsetTokenSink<'a> {
cursor: Cursor<'a>,
error: bool,
}
impl<'a> TreeSink for OffsetTokenSink<'a> {
fn token(&mut self, kind: SyntaxKind, mut n_tokens: u8) {
if kind == SyntaxKind::LIFETIME_IDENT {
n_tokens = 2;
}
for _ in 0..n_tokens {
self.cursor = self.cursor.bump_subtree();
}
}
fn start_node(&mut self, _kind: SyntaxKind) {}
fn finish_node(&mut self) {}
fn error(&mut self, _error: parser::ParseError) {
self.error = true;
}
}
let buffer = TokenBuffer::from_tokens(&self.inner.as_slice());
let mut src = SubtreeTokenSource::new(&buffer);
let mut sink = OffsetTokenSink { cursor: buffer.begin(), error: false };
parser::parse_fragment(&mut src, &mut sink, fragment_kind);
let mut err = None;
if !sink.cursor.is_root() || sink.error {
err = Some(err!("expected {:?}", fragment_kind));
}
let mut curr = buffer.begin();
let mut res = vec![];
if sink.cursor.is_root() {
while curr != sink.cursor {
if let Some(token) = curr.token_tree() {
res.push(token);
}
curr = curr.bump();
}
}
self.inner = self.inner.as_slice()[res.len()..].iter();
if res.len() == 0 && err.is_none() {
err = Some(err!("no tokens consumed"));
}
let res = match res.len() {
1 => Some(res[0].cloned()),
0 => None,
_ => Some(tt::TokenTree::Subtree(tt::Subtree {
delimiter: None,
token_trees: res.into_iter().map(|it| it.cloned()).collect(),
})),
};
ExpandResult { value: res, err }
}
fn eat_vis(&mut self) -> Option<tt::TokenTree> {
let mut fork = self.clone();
match fork.expect_fragment(Visibility) {

View File

@ -65,8 +65,8 @@ impl fmt::Display for ExpandError {
}
pub use crate::syntax_bridge::{
ast_to_token_tree, parse_to_token_tree, syntax_node_to_token_tree, token_tree_to_syntax_node,
TokenMap,
ast_to_token_tree, parse_exprs_with_sep, parse_to_token_tree, syntax_node_to_token_tree,
token_tree_to_syntax_node, TokenMap,
};
/// This struct contains AST for a single `macro_rules` definition. What might

View File

@ -10,8 +10,8 @@ use syntax::{
};
use tt::buffer::{Cursor, TokenBuffer};
use crate::subtree_source::SubtreeTokenSource;
use crate::ExpandError;
use crate::{subtree_source::SubtreeTokenSource, tt_iter::TtIter};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum TokenTextRange {
@ -112,6 +112,43 @@ pub fn parse_to_token_tree(text: &str) -> Option<(tt::Subtree, TokenMap)> {
Some((subtree, conv.id_alloc.map))
}
/// Split token tree with seperate expr: $($e:expr)SEP*
pub fn parse_exprs_with_sep(tt: &tt::Subtree, sep: char) -> Vec<tt::Subtree> {
if tt.token_trees.is_empty() {
return Vec::new();
}
let mut iter = TtIter::new(tt);
let mut res = Vec::new();
while iter.peek_n(0).is_some() {
let expanded = iter.expect_fragment(FragmentKind::Expr);
if expanded.err.is_some() {
break;
}
res.push(match expanded.value {
None => break,
Some(tt @ tt::TokenTree::Leaf(_)) => {
tt::Subtree { delimiter: None, token_trees: vec![tt.into()] }
}
Some(tt::TokenTree::Subtree(tt)) => tt,
});
let mut fork = iter.clone();
if fork.expect_char(sep).is_err() {
break;
}
iter = fork;
}
if iter.peek_n(0).is_some() {
res.push(tt::Subtree { delimiter: None, token_trees: iter.into_iter().cloned().collect() });
}
res
}
impl TokenMap {
pub fn token_by_range(&self, relative_range: TextRange) -> Option<tt::TokenId> {
let &(token_id, _) = self.entries.iter().find(|(_, range)| match range {

View File

@ -1,5 +1,20 @@
//! FIXME: write short doc here
use crate::{subtree_source::SubtreeTokenSource, ExpandError, ExpandResult};
use parser::TreeSink;
use syntax::SyntaxKind;
use tt::buffer::{Cursor, TokenBuffer};
macro_rules! err {
() => {
ExpandError::BindingError(format!(""))
};
($($tt:tt)*) => {
ExpandError::BindingError(format!($($tt)*))
};
}
#[derive(Debug, Clone)]
pub(crate) struct TtIter<'a> {
pub(crate) inner: std::slice::Iter<'a, tt::TokenTree>,
@ -56,6 +71,68 @@ impl<'a> TtIter<'a> {
}
}
pub(crate) fn expect_fragment(
&mut self,
fragment_kind: parser::FragmentKind,
) -> ExpandResult<Option<tt::TokenTree>> {
struct OffsetTokenSink<'a> {
cursor: Cursor<'a>,
error: bool,
}
impl<'a> TreeSink for OffsetTokenSink<'a> {
fn token(&mut self, kind: SyntaxKind, mut n_tokens: u8) {
if kind == SyntaxKind::LIFETIME_IDENT {
n_tokens = 2;
}
for _ in 0..n_tokens {
self.cursor = self.cursor.bump_subtree();
}
}
fn start_node(&mut self, _kind: SyntaxKind) {}
fn finish_node(&mut self) {}
fn error(&mut self, _error: parser::ParseError) {
self.error = true;
}
}
let buffer = TokenBuffer::from_tokens(&self.inner.as_slice());
let mut src = SubtreeTokenSource::new(&buffer);
let mut sink = OffsetTokenSink { cursor: buffer.begin(), error: false };
parser::parse_fragment(&mut src, &mut sink, fragment_kind);
let mut err = None;
if !sink.cursor.is_root() || sink.error {
err = Some(err!("expected {:?}", fragment_kind));
}
let mut curr = buffer.begin();
let mut res = vec![];
if sink.cursor.is_root() {
while curr != sink.cursor {
if let Some(token) = curr.token_tree() {
res.push(token);
}
curr = curr.bump();
}
}
self.inner = self.inner.as_slice()[res.len()..].iter();
if res.len() == 0 && err.is_none() {
err = Some(err!("no tokens consumed"));
}
let res = match res.len() {
1 => Some(res[0].cloned()),
0 => None,
_ => Some(tt::TokenTree::Subtree(tt::Subtree {
delimiter: None,
token_trees: res.into_iter().map(|it| it.cloned()).collect(),
})),
};
ExpandResult { value: res, err }
}
pub(crate) fn peek_n(&self, n: usize) -> Option<&tt::TokenTree> {
self.inner.as_slice().get(n)
}