mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-29 13:25:09 +00:00
Merge pull request #2672 from Speedy37/master
fix #2520: change expand_repeat loop stop condition
This commit is contained in:
commit
c3a86325da
2 changed files with 83 additions and 37 deletions
|
@ -14,21 +14,24 @@ impl Bindings {
|
||||||
self.inner.contains_key(name)
|
self.inner.contains_key(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, name: &str, nesting: &[usize]) -> Result<&Fragment, ExpandError> {
|
fn get(&self, name: &str, nesting: &mut [NestingState]) -> Result<&Fragment, ExpandError> {
|
||||||
let mut b = self.inner.get(name).ok_or_else(|| {
|
let mut b = self.inner.get(name).ok_or_else(|| {
|
||||||
ExpandError::BindingError(format!("could not find binding `{}`", name))
|
ExpandError::BindingError(format!("could not find binding `{}`", name))
|
||||||
})?;
|
})?;
|
||||||
for &idx in nesting.iter() {
|
for nesting_state in nesting.iter_mut() {
|
||||||
|
nesting_state.hit = true;
|
||||||
b = match b {
|
b = match b {
|
||||||
Binding::Fragment(_) => break,
|
Binding::Fragment(_) => break,
|
||||||
Binding::Nested(bs) => bs.get(idx).ok_or_else(|| {
|
Binding::Nested(bs) => bs.get(nesting_state.idx).ok_or_else(|| {
|
||||||
|
nesting_state.at_end = true;
|
||||||
ExpandError::BindingError(format!("could not find nested binding `{}`", name))
|
ExpandError::BindingError(format!("could not find nested binding `{}`", name))
|
||||||
})?,
|
})?,
|
||||||
Binding::Empty => {
|
Binding::Empty => {
|
||||||
|
nesting_state.at_end = true;
|
||||||
return Err(ExpandError::BindingError(format!(
|
return Err(ExpandError::BindingError(format!(
|
||||||
"could not find empty binding `{}`",
|
"could not find empty binding `{}`",
|
||||||
name
|
name
|
||||||
)))
|
)));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -51,15 +54,25 @@ pub(super) fn transcribe(
|
||||||
bindings: &Bindings,
|
bindings: &Bindings,
|
||||||
) -> Result<tt::Subtree, ExpandError> {
|
) -> Result<tt::Subtree, ExpandError> {
|
||||||
assert!(template.delimiter == None);
|
assert!(template.delimiter == None);
|
||||||
let mut ctx = ExpandCtx { bindings: &bindings, nesting: Vec::new(), var_expanded: false };
|
let mut ctx = ExpandCtx { bindings: &bindings, nesting: Vec::new() };
|
||||||
expand_subtree(&mut ctx, template)
|
expand_subtree(&mut ctx, template)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct NestingState {
|
||||||
|
idx: usize,
|
||||||
|
/// `hit` is currently necessary to tell `expand_repeat` if it should stop
|
||||||
|
/// because there is no variable in use by the current repetition
|
||||||
|
hit: bool,
|
||||||
|
/// `at_end` is currently necessary to tell `expand_repeat` if it should stop
|
||||||
|
/// because there is no more value avaible for the current repetition
|
||||||
|
at_end: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct ExpandCtx<'a> {
|
struct ExpandCtx<'a> {
|
||||||
bindings: &'a Bindings,
|
bindings: &'a Bindings,
|
||||||
nesting: Vec<usize>,
|
nesting: Vec<NestingState>,
|
||||||
var_expanded: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expand_subtree(ctx: &mut ExpandCtx, template: &tt::Subtree) -> Result<tt::Subtree, ExpandError> {
|
fn expand_subtree(ctx: &mut ExpandCtx, template: &tt::Subtree) -> Result<tt::Subtree, ExpandError> {
|
||||||
|
@ -121,9 +134,7 @@ fn expand_var(ctx: &mut ExpandCtx, v: &SmolStr) -> Result<Fragment, ExpandError>
|
||||||
.into();
|
.into();
|
||||||
Fragment::Tokens(tt)
|
Fragment::Tokens(tt)
|
||||||
} else {
|
} else {
|
||||||
let fragment = ctx.bindings.get(&v, &ctx.nesting)?.clone();
|
ctx.bindings.get(&v, &mut ctx.nesting)?.clone()
|
||||||
ctx.var_expanded = true;
|
|
||||||
fragment
|
|
||||||
};
|
};
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
@ -135,37 +146,24 @@ fn expand_repeat(
|
||||||
separator: Option<Separator>,
|
separator: Option<Separator>,
|
||||||
) -> Result<Fragment, ExpandError> {
|
) -> Result<Fragment, ExpandError> {
|
||||||
let mut buf: Vec<tt::TokenTree> = Vec::new();
|
let mut buf: Vec<tt::TokenTree> = Vec::new();
|
||||||
ctx.nesting.push(0);
|
ctx.nesting.push(NestingState { idx: 0, at_end: false, hit: false });
|
||||||
// Dirty hack to make macro-expansion terminate.
|
// Dirty hack to make macro-expansion terminate.
|
||||||
// This should be replaced by a propper macro-by-example implementation
|
// This should be replaced by a propper macro-by-example implementation
|
||||||
let mut limit = 65536;
|
let limit = 65536;
|
||||||
let mut has_seps = 0;
|
let mut has_seps = 0;
|
||||||
let mut counter = 0;
|
let mut counter = 0;
|
||||||
|
|
||||||
// We store the old var expanded value, and restore it later
|
loop {
|
||||||
// It is because before this `$repeat`,
|
let res = expand_subtree(ctx, template);
|
||||||
// it is possible some variables already expanad in the same subtree
|
let nesting_state = ctx.nesting.last_mut().unwrap();
|
||||||
//
|
if nesting_state.at_end || !nesting_state.hit {
|
||||||
// `some_var_expanded` keep check if the deeper subtree has expanded variables
|
|
||||||
let mut some_var_expanded = false;
|
|
||||||
let old_var_expanded = ctx.var_expanded;
|
|
||||||
ctx.var_expanded = false;
|
|
||||||
|
|
||||||
while let Ok(mut t) = expand_subtree(ctx, template) {
|
|
||||||
t.delimiter = None;
|
|
||||||
// if no var expanded in the child, we count it as a fail
|
|
||||||
if !ctx.var_expanded {
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
nesting_state.idx += 1;
|
||||||
// Reset `ctx.var_expandeded` to see if there is other expanded variable
|
nesting_state.hit = false;
|
||||||
// in the next matching
|
|
||||||
some_var_expanded = true;
|
|
||||||
ctx.var_expanded = false;
|
|
||||||
|
|
||||||
counter += 1;
|
counter += 1;
|
||||||
limit -= 1;
|
if counter == limit {
|
||||||
if limit == 0 {
|
|
||||||
log::warn!(
|
log::warn!(
|
||||||
"expand_tt excced in repeat pattern exceed limit => {:#?}\n{:#?}",
|
"expand_tt excced in repeat pattern exceed limit => {:#?}\n{:#?}",
|
||||||
template,
|
template,
|
||||||
|
@ -174,8 +172,11 @@ fn expand_repeat(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let idx = ctx.nesting.pop().unwrap();
|
let mut t = match res {
|
||||||
ctx.nesting.push(idx + 1);
|
Ok(t) => t,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
t.delimiter = None;
|
||||||
push_subtree(&mut buf, t);
|
push_subtree(&mut buf, t);
|
||||||
|
|
||||||
if let Some(ref sep) = separator {
|
if let Some(ref sep) = separator {
|
||||||
|
@ -203,9 +204,6 @@ fn expand_repeat(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore the `var_expanded` by combining old one and the new one
|
|
||||||
ctx.var_expanded = some_var_expanded || old_var_expanded;
|
|
||||||
|
|
||||||
ctx.nesting.pop().unwrap();
|
ctx.nesting.pop().unwrap();
|
||||||
for _ in 0..has_seps {
|
for _ in 0..has_seps {
|
||||||
buf.pop();
|
buf.pop();
|
||||||
|
|
|
@ -1491,3 +1491,51 @@ fn debug_dump_ignore_spaces(node: &ra_syntax::SyntaxNode) -> String {
|
||||||
|
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_issue_2520() {
|
||||||
|
let macro_fixture = parse_macro(
|
||||||
|
r#"
|
||||||
|
macro_rules! my_macro {
|
||||||
|
{
|
||||||
|
( $(
|
||||||
|
$( [] $sname:ident : $stype:ty )?
|
||||||
|
$( [$expr:expr] $nname:ident : $ntype:ty )?
|
||||||
|
),* )
|
||||||
|
} => {
|
||||||
|
Test {
|
||||||
|
$(
|
||||||
|
$( $sname, )?
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
|
||||||
|
macro_fixture.assert_expand_items(
|
||||||
|
r#"my_macro ! {
|
||||||
|
([] p1 : u32 , [|_| S0K0] s : S0K0 , [] k0 : i32)
|
||||||
|
}"#,
|
||||||
|
"Test {p1 , k0 ,}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_repeat_bad_var() {
|
||||||
|
// FIXME: the second rule of the macro should be removed and an error about
|
||||||
|
// `$( $c )+` raised
|
||||||
|
parse_macro(
|
||||||
|
r#"
|
||||||
|
macro_rules! foo {
|
||||||
|
($( $b:ident )+) => {
|
||||||
|
$( $c )+
|
||||||
|
};
|
||||||
|
($( $b:ident )+) => {
|
||||||
|
$( $b )+
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.assert_expand_items("foo!(b0 b1);", "b0 b1");
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue