Skip to content

Commit 44df0e2

Browse files
bors[bot]Veykril
andauthored
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 <[email protected]>
2 parents 93de491 + 1416413 commit 44df0e2

File tree

4 files changed

+124
-4
lines changed

4 files changed

+124
-4
lines changed

crates/ide/src/syntax_highlighting.rs

Lines changed: 115 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub(crate) fn highlight(
6868
// When we leave a node, the we use it to flatten the highlighted ranges.
6969
let mut stack = HighlightedRangeStack::new();
7070

71-
let mut current_macro_call: Option<ast::MacroCall> = None;
71+
let mut current_macro_call: Option<(ast::MacroCall, Option<MacroMatcherParseState>)> = None;
7272
let mut format_string: Option<SyntaxElement> = None;
7373

7474
// Walk all nodes, keeping track of whether we are inside a macro or not.
@@ -92,15 +92,16 @@ pub(crate) fn highlight(
9292
// Track "inside macro" state
9393
match event.clone().map(|it| it.into_node().and_then(ast::MacroCall::cast)) {
9494
WalkEvent::Enter(Some(mc)) => {
95-
current_macro_call = Some(mc.clone());
9695
if let Some(range) = macro_call_range(&mc) {
9796
stack.add(HighlightedRange {
9897
range,
9998
highlight: HighlightTag::Macro.into(),
10099
binding_hash: None,
101100
});
102101
}
102+
let mut is_macro_rules = None;
103103
if let Some(name) = mc.is_macro_rules() {
104+
is_macro_rules = Some(MacroMatcherParseState::new());
104105
if let Some((highlight, binding_hash)) = highlight_element(
105106
&sema,
106107
&mut bindings_shadow_count,
@@ -114,10 +115,11 @@ pub(crate) fn highlight(
114115
});
115116
}
116117
}
118+
current_macro_call = Some((mc.clone(), is_macro_rules));
117119
continue;
118120
}
119121
WalkEvent::Leave(Some(mc)) => {
120-
assert!(current_macro_call == Some(mc));
122+
assert!(current_macro_call.map(|it| it.0) == Some(mc));
121123
current_macro_call = None;
122124
format_string = None;
123125
}
@@ -146,6 +148,20 @@ pub(crate) fn highlight(
146148
WalkEvent::Leave(_) => continue,
147149
};
148150

151+
// check if in matcher part of a macro_rules rule
152+
if let Some((_, Some(ref mut state))) = current_macro_call {
153+
if let Some(tok) = element.as_token() {
154+
if matches!(
155+
update_macro_rules_state(tok, state),
156+
RuleState::Matcher | RuleState::Expander
157+
) {
158+
if skip_metavariables(element.clone()) {
159+
continue;
160+
}
161+
}
162+
}
163+
}
164+
149165
let range = element.text_range();
150166

151167
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
918934
_ => default.into(),
919935
}
920936
}
937+
938+
struct MacroMatcherParseState {
939+
/// Opening and corresponding closing bracket of the matcher or expander of the current rule
940+
paren_ty: Option<(SyntaxKind, SyntaxKind)>,
941+
paren_level: usize,
942+
rule_state: RuleState,
943+
/// Whether we are inside the outer `{` `}` macro block that holds the rules
944+
in_invoc_body: bool,
945+
}
946+
947+
impl MacroMatcherParseState {
948+
fn new() -> Self {
949+
MacroMatcherParseState {
950+
paren_ty: None,
951+
paren_level: 0,
952+
in_invoc_body: false,
953+
rule_state: RuleState::None,
954+
}
955+
}
956+
}
957+
958+
#[derive(Copy, Clone, PartialEq)]
959+
enum RuleState {
960+
Matcher,
961+
Expander,
962+
Between,
963+
None,
964+
}
965+
966+
impl RuleState {
967+
fn transition(&mut self) {
968+
*self = match self {
969+
RuleState::Matcher => RuleState::Between,
970+
RuleState::Expander => RuleState::None,
971+
RuleState::Between => RuleState::Expander,
972+
RuleState::None => RuleState::Matcher,
973+
};
974+
}
975+
}
976+
977+
fn update_macro_rules_state(tok: &SyntaxToken, state: &mut MacroMatcherParseState) -> RuleState {
978+
if !state.in_invoc_body {
979+
if tok.kind() == T!['{'] {
980+
state.in_invoc_body = true;
981+
}
982+
return state.rule_state;
983+
}
984+
985+
match state.paren_ty {
986+
Some((open, close)) => {
987+
if tok.kind() == open {
988+
state.paren_level += 1;
989+
} else if tok.kind() == close {
990+
state.paren_level -= 1;
991+
if state.paren_level == 0 {
992+
let res = state.rule_state;
993+
state.rule_state.transition();
994+
state.paren_ty = None;
995+
return res;
996+
}
997+
}
998+
}
999+
None => {
1000+
match tok.kind() {
1001+
T!['('] => {
1002+
state.paren_ty = Some((T!['('], T![')']));
1003+
}
1004+
T!['{'] => {
1005+
state.paren_ty = Some((T!['{'], T!['}']));
1006+
}
1007+
T!['['] => {
1008+
state.paren_ty = Some((T!['['], T![']']));
1009+
}
1010+
_ => (),
1011+
}
1012+
if state.paren_ty.is_some() {
1013+
state.paren_level = 1;
1014+
state.rule_state.transition();
1015+
}
1016+
}
1017+
}
1018+
state.rule_state
1019+
}
1020+
1021+
fn skip_metavariables(element: SyntaxElement) -> bool {
1022+
let tok = match element.as_token() {
1023+
Some(tok) => tok,
1024+
None => return false,
1025+
};
1026+
let is_fragment = || tok.prev_token().map(|tok| tok.kind()) == Some(T![$]);
1027+
match tok.kind() {
1028+
IDENT if is_fragment() => true,
1029+
kind if kind.is_keyword() && is_fragment() => true,
1030+
_ => false,
1031+
}
1032+
}

crates/ide/src/syntax_highlighting/test_data/highlight_strings.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
</style>
3838
<pre><code><span class="macro">macro_rules!</span> <span class="macro declaration">println</span> <span class="punctuation">{</span>
3939
<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>
40-
<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>
40+
<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>
4141
<span class="punctuation">}</span><span class="punctuation">)</span>
4242
<span class="punctuation">}</span>
4343
#[rustc_builtin_macro]

crates/ide/src/syntax_highlighting/test_data/highlighting.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@
115115
<span class="punctuation">}</span>
116116
<span class="punctuation">}</span>
117117

118+
<span class="macro">macro_rules!</span> <span class="macro declaration">keyword_frag</span> <span class="punctuation">{</span>
119+
<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>
120+
<span class="punctuation">}</span>
121+
118122
<span class="comment">// comment</span>
119123
<span class="keyword">fn</span> <span class="function declaration">main</span><span class="punctuation">(</span><span class="punctuation">)</span> <span class="punctuation">{</span>
120124
<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>

crates/ide/src/syntax_highlighting/tests.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ macro_rules! noop {
8989
}
9090
}
9191
92+
macro_rules! keyword_frag {
93+
($type:ty) => ($type)
94+
}
95+
9296
// comment
9397
fn main() {
9498
println!("Hello, {}!", 92);

0 commit comments

Comments
 (0)