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:
bors[bot] 2020-10-12 14:44:34 +00:00 committed by GitHub
commit 44df0e2a9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 124 additions and 4 deletions

View File

@ -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,
}
}

View File

@ -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">&gt;</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]

View File

@ -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">&gt;</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>

View File

@ -89,6 +89,10 @@ macro_rules! noop {
}
}
macro_rules! keyword_frag {
($type:ty) => ($type)
}
// comment
fn main() {
println!("Hello, {}!", 92);