Merge #6198
6198: Skip macro matcher fragment name semantic highlighting r=matklad a=Veykril Implements a small state-machine for macro_rules! highlighting to separate out the matcher part of its rules. This skips semantically highlighting names of metavariables in the matcher and expander. This might even allow for more fun macro highlighting things in the future. Fixes #4380. Co-authored-by: Lukas Wirth <lukastw97@gmail.com>
This commit is contained in:
commit
44df0e2a9f
@ -68,7 +68,7 @@ pub(crate) fn highlight(
|
||||
// When we leave a node, the we use it to flatten the highlighted ranges.
|
||||
let mut stack = HighlightedRangeStack::new();
|
||||
|
||||
let mut current_macro_call: Option<ast::MacroCall> = None;
|
||||
let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None;
|
||||
let mut format_string: Option<SyntaxElement> = None;
|
||||
|
||||
// Walk all nodes, keeping track of whether we are inside a macro or not.
|
||||
@ -92,7 +92,6 @@ pub(crate) fn highlight(
|
||||
// Track "inside macro" state
|
||||
match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) {
|
||||
WalkEvent::Enter(Some(mc)) => {
|
||||
current_macro_call = Some(mc.clone());
|
||||
if let Some(range) = macro_call_range(&mc) {
|
||||
stack.add(HighlightedRange {
|
||||
range,
|
||||
@ -100,7 +99,9 @@ pub(crate) fn highlight(
|
||||
binding_hash: None,
|
||||
});
|
||||
}
|
||||
let mut is_macro_rules = None;
|
||||
if let Some(name) = mc.is_macro_rules() {
|
||||
is_macro_rules = Some(MacroMatcherParseState::new());
|
||||
if let Some((highlight, binding_hash)) = highlight_element(
|
||||
&sema,
|
||||
&mut bindings_shadow_count,
|
||||
@ -114,10 +115,11 @@ pub(crate) fn highlight(
|
||||
});
|
||||
}
|
||||
}
|
||||
current_macro_call = Some((mc.clone(), is_macro_rules));
|
||||
continue;
|
||||
}
|
||||
WalkEvent::Leave(Some(mc)) => {
|
||||
assert!(current_macro_call == Some(mc));
|
||||
assert!(current_macro_call.map(|it| it.0) == Some(mc));
|
||||
current_macro_call = None;
|
||||
format_string = None;
|
||||
}
|
||||
@ -146,6 +148,20 @@ pub(crate) fn highlight(
|
||||
WalkEvent::Leave(_) => continue,
|
||||
};
|
||||
|
||||
// check if in matcher part of a macro_rules rule
|
||||
if let Some((_, Some(ref mut state))) = current_macro_call {
|
||||
if let Some(tok) = element.as_token() {
|
||||
if matches!(
|
||||
update_macro_rules_state(tok, state),
|
||||
RuleState::Matcher | RuleState::Expander
|
||||
) {
|
||||
if skip_metavariables(element.clone()) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let range = element.text_range();
|
||||
|
||||
let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT {
|
||||
@ -918,3 +934,99 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabas
|
||||
_ => default.into(),
|
||||
}
|
||||
}
|
||||
|
||||
struct MacroMatcherParseState {
|
||||
/// Opening and corresponding closing bracket of the matcher or expander of the current rule
|
||||
paren_ty: Option<(SyntaxKind, SyntaxKind)>,
|
||||
paren_level: usize,
|
||||
rule_state: RuleState,
|
||||
/// Whether we are inside the outer `{` `}` macro block that holds the rules
|
||||
in_invoc_body: bool,
|
||||
}
|
||||
|
||||
impl MacroMatcherParseState {
|
||||
fn new() -> Self {
|
||||
MacroMatcherParseState {
|
||||
paren_ty: None,
|
||||
paren_level: 0,
|
||||
in_invoc_body: false,
|
||||
rule_state: RuleState::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq)]
|
||||
enum RuleState {
|
||||
Matcher,
|
||||
Expander,
|
||||
Between,
|
||||
None,
|
||||
}
|
||||
|
||||
impl RuleState {
|
||||
fn transition(&mut self) {
|
||||
*self = match self {
|
||||
RuleState::Matcher => RuleState::Between,
|
||||
RuleState::Expander => RuleState::None,
|
||||
RuleState::Between => RuleState::Expander,
|
||||
RuleState::None => RuleState::Matcher,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState {
|
||||
if !state.in_invoc_body {
|
||||
if tok.kind() == T!['{'] {
|
||||
state.in_invoc_body = true;
|
||||
}
|
||||
return state.rule_state;
|
||||
}
|
||||
|
||||
match state.paren_ty {
|
||||
Some((open, close)) => {
|
||||
if tok.kind() == open {
|
||||
state.paren_level += 1;
|
||||
} else if tok.kind() == close {
|
||||
state.paren_level -= 1;
|
||||
if state.paren_level == 0 {
|
||||
let res = state.rule_state;
|
||||
state.rule_state.transition();
|
||||
state.paren_ty = None;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
match tok.kind() {
|
||||
T!['('] => {
|
||||
state.paren_ty = Some((T!['('], T![')']));
|
||||
}
|
||||
T!['{'] => {
|
||||
state.paren_ty = Some((T!['{'], T!['}']));
|
||||
}
|
||||
T!['['] => {
|
||||
state.paren_ty = Some((T!['['], T![']']));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
if state.paren_ty.is_some() {
|
||||
state.paren_level = 1;
|
||||
state.rule_state.transition();
|
||||
}
|
||||
}
|
||||
}
|
||||
state.rule_state
|
||||
}
|
||||
|
||||
fn skip_metavariables(element: SyntaxElement) -> bool {
|
||||
let tok = match element.as_token() {
|
||||
Some(tok) => tok,
|
||||
None => return false,
|
||||
};
|
||||
let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]);
|
||||
match tok.kind() {
|
||||
IDENT if is_fragment() => true,
|
||||
kind if kind.is_keyword() && is_fragment() => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
</style>
|
||||
<pre><code><span class="macro">macro_rules!</span> <span class="macro declaration">println</span> <span class="punctuation">{</span>
|
||||
<span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">:</span>tt<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">></span> <span class="punctuation">(</span><span class="punctuation">{</span>
|
||||
<span class="punctuation">$</span><span class="keyword">crate</span><span class="punctuation">:</span><span class="punctuation">:</span>io<span class="punctuation">:</span><span class="punctuation">:</span>_print<span class="punctuation">(</span><span class="punctuation">$</span><span class="keyword">crate</span><span class="punctuation">:</span><span class="punctuation">:</span>format_args_nl<span class="punctuation">!</span><span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">;</span>
|
||||
<span class="punctuation">$</span>crate<span class="punctuation">:</span><span class="punctuation">:</span>io<span class="punctuation">:</span><span class="punctuation">:</span>_print<span class="punctuation">(</span><span class="punctuation">$</span>crate<span class="punctuation">:</span><span class="punctuation">:</span>format_args_nl<span class="punctuation">!</span><span class="punctuation">(</span><span class="punctuation">$</span><span class="punctuation">(</span><span class="punctuation">$</span>arg<span class="punctuation">)</span><span class="punctuation">*</span><span class="punctuation">)</span><span class="punctuation">)</span><span class="punctuation">;</span>
|
||||
<span class="punctuation">}</span><span class="punctuation">)</span>
|
||||
<span class="punctuation">}</span>
|
||||
#[rustc_builtin_macro]
|
||||
|
@ -115,6 +115,10 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
|
||||
<span class="punctuation">}</span>
|
||||
<span class="punctuation">}</span>
|
||||
|
||||
<span class="macro">macro_rules!</span> <span class="macro declaration">keyword_frag</span> <span class="punctuation">{</span>
|
||||
<span class="punctuation">(</span><span class="punctuation">$</span>type<span class="punctuation">:</span>ty<span class="punctuation">)</span> <span class="operator">=</span><span class="punctuation">></span> <span class="punctuation">(</span><span class="punctuation">$</span>type<span class="punctuation">)</span>
|
||||
<span class="punctuation">}</span>
|
||||
|
||||
<span class="comment">// comment</span>
|
||||
<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
|
||||
<span class="macro">println!</span><span class="punctuation">(</span><span class="string_literal">"Hello, {}!"</span><span class="punctuation">,</span> <span class="numeric_literal">92</span><span class="punctuation">)</span><span class="punctuation">;</span>
|
||||
|
@ -89,6 +89,10 @@ macro_rules! noop {
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! keyword_frag {
|
||||
($type:ty) => ($type)
|
||||
}
|
||||
|
||||
// comment
|
||||
fn main() {
|
||||
println!("Hello, {}!", 92);
|
||||
|
Loading…
Reference in New Issue
Block a user