Fix syntax fixup producing invalid punctuation

Fixes #19206.
Fixes #18244.
This commit is contained in:
¨Florian 2025-02-28 17:32:21 +01:00
parent 62dea277cc
commit 5335d8cbc5
5 changed files with 105 additions and 88 deletions

View file

@ -12,7 +12,7 @@ use syntax::{
SyntaxKind::{self, *},
SyntaxNode, SyntaxToken, SyntaxTreeBuilder, TextRange, TextSize, WalkEvent, T,
};
use tt::{buffer::Cursor, token_to_literal};
use tt::{buffer::Cursor, token_to_literal, Punct};
pub mod prettify_macro_expansion;
mod to_parser_input;
@ -217,8 +217,39 @@ where
tt::TopSubtreeBuilder::new(tt::Delimiter::invisible_spanned(conv.call_site()));
while let Some((token, abs_range)) = conv.bump() {
let delimiter = builder.expected_delimiter().map(|it| it.kind);
let tt = match token.as_leaf() {
// These delimiters are not actually valid punctuation, but we produce them in syntax fixup.
// So we need to handle them specially here.
Some(&tt::Leaf::Punct(Punct {
char: char @ ('(' | ')' | '{' | '}' | '[' | ']'),
span,
spacing: _,
})) => {
let found_expected_delimiter =
builder.expected_delimiters().enumerate().find(|(_, delim)| match delim.kind {
tt::DelimiterKind::Parenthesis => char == ')',
tt::DelimiterKind::Brace => char == '}',
tt::DelimiterKind::Bracket => char == ']',
tt::DelimiterKind::Invisible => false,
});
if let Some((idx, _)) = found_expected_delimiter {
for _ in 0..=idx {
builder.close(span);
}
continue;
}
let delim = match char {
'(' => tt::DelimiterKind::Parenthesis,
'{' => tt::DelimiterKind::Brace,
'[' => tt::DelimiterKind::Bracket,
_ => panic!("unmatched closing delimiter from syntax fixup"),
};
// Start a new subtree
builder.open(delim, span);
continue;
}
Some(leaf) => leaf.clone(),
None => match token.kind(conv) {
// Desugar doc comments into doc attributes
@ -228,17 +259,24 @@ where
continue;
}
kind if kind.is_punct() && kind != UNDERSCORE => {
let expected = match delimiter {
Some(tt::DelimiterKind::Parenthesis) => Some(T![')']),
Some(tt::DelimiterKind::Brace) => Some(T!['}']),
Some(tt::DelimiterKind::Bracket) => Some(T![']']),
Some(tt::DelimiterKind::Invisible) | None => None,
};
let found_expected_delimiter =
builder.expected_delimiters().enumerate().find(|(_, delim)| {
match delim.kind {
tt::DelimiterKind::Parenthesis => kind == T![')'],
tt::DelimiterKind::Brace => kind == T!['}'],
tt::DelimiterKind::Bracket => kind == T![']'],
tt::DelimiterKind::Invisible => false,
}
});
// Current token is a closing delimiter that we expect, fix up the closing span
// and end the subtree here
if matches!(expected, Some(expected) if expected == kind) {
builder.close(conv.span_for(abs_range));
// and end the subtree here.
// We also close any open inner subtrees that might be missing their delimiter.
if let Some((idx, _)) = found_expected_delimiter {
for _ in 0..=idx {
// FIXME: record an error somewhere if we're closing more than one tree here?
builder.close(conv.span_for(abs_range));
}
continue;
}
@ -262,6 +300,7 @@ where
let Some(char) = token.to_char(conv) else {
panic!("Token from lexer must be single char: token = {token:#?}")
};
// FIXME: this might still be an unmatched closing delimiter? Maybe we should assert here
tt::Leaf::from(tt::Punct { char, spacing, span: conv.span_for(abs_range) })
}
kind => {
@ -317,11 +356,10 @@ where
builder.push(tt);
}
// If we get here, we've consumed all input tokens.
// We might have more than one subtree in the stack, if the delimiters are improperly balanced.
// Merge them so we're left with one.
builder.flatten_unclosed_subtrees();
while builder.expected_delimiters().next().is_some() {
// FIXME: record an error somewhere?
builder.close(conv.call_site());
}
builder.build_skip_top_subtree()
}