Skip to content

Commit e1099aa

Browse files
bors[bot]Veykril
andauthored
Merge #11169
11169: internal: Handle macro calls better in highlighting r=Veykril a=Veykril Introduces a new semantic highlighting tag for the `!` character of macro calls. Fixes #10962 Co-authored-by: Lukas Wirth <[email protected]>
2 parents 2e7170e + 22160c4 commit e1099aa

File tree

12 files changed

+171
-173
lines changed

12 files changed

+171
-173
lines changed

crates/ide/src/syntax_highlighting.rs

Lines changed: 73 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ mod html;
1313
mod tests;
1414

1515
use hir::{InFile, Name, Semantics};
16-
use ide_db::{RootDatabase, SymbolKind};
16+
use ide_db::RootDatabase;
1717
use rustc_hash::FxHashMap;
1818
use syntax::{
1919
ast::{self, HasFormatSpecifier},
20-
match_ast, AstNode, AstToken, Direction, NodeOrToken,
20+
AstNode, AstToken, NodeOrToken,
2121
SyntaxKind::*,
2222
SyntaxNode, TextRange, WalkEvent, T,
2323
};
@@ -100,7 +100,8 @@ pub struct HlRange {
100100
// colon:: Emitted for the `:` token.
101101
// comma:: Emitted for the `,` token.
102102
// dot:: Emitted for the `.` token.
103-
// Semi:: Emitted for the `;` token.
103+
// semi:: Emitted for the `;` token.
104+
// macroBang:: Emitted for the `!` token in macro calls.
104105
//
105106
// //-
106107
//
@@ -209,107 +210,94 @@ fn traverse(
209210
// Walk all nodes, keeping track of whether we are inside a macro or not.
210211
// If in macro, expand it first and highlight the expanded code.
211212
for event in root.value.preorder_with_tokens() {
212-
let event_range = match &event {
213+
let range = match &event {
213214
WalkEvent::Enter(it) | WalkEvent::Leave(it) => it.text_range(),
214215
};
215216

216217
// Element outside of the viewport, no need to highlight
217-
if range_to_highlight.intersect(event_range).is_none() {
218+
if range_to_highlight.intersect(range).is_none() {
218219
continue;
219220
}
220221

222+
// set macro and attribute highlighting states
221223
match event.clone() {
222-
WalkEvent::Enter(NodeOrToken::Node(node)) => {
223-
match_ast! {
224-
match node {
225-
ast::MacroCall(mcall) => {
226-
if let Some(range) = macro_call_range(&mcall) {
227-
hl.add(HlRange {
228-
range,
229-
highlight: HlTag::Symbol(SymbolKind::Macro).into(),
230-
binding_hash: None,
231-
});
232-
}
233-
current_macro_call = Some(mcall);
234-
continue;
235-
},
236-
ast::Macro(mac) => {
237-
macro_highlighter.init();
238-
current_macro = Some(mac);
239-
continue;
240-
},
241-
ast::Item(item) => {
242-
if sema.is_attr_macro_call(&item) {
243-
current_attr_call = Some(item);
244-
}
245-
},
246-
ast::Attr(__) => inside_attribute = true,
247-
_ => ()
248-
}
224+
WalkEvent::Enter(NodeOrToken::Node(node)) => match ast::Item::cast(node.clone()) {
225+
Some(ast::Item::MacroCall(mcall)) => {
226+
current_macro_call = Some(mcall);
227+
continue;
249228
}
250-
}
251-
WalkEvent::Leave(NodeOrToken::Node(node)) => {
252-
match_ast! {
253-
match node {
254-
ast::MacroCall(mcall) => {
255-
assert_eq!(current_macro_call, Some(mcall));
256-
current_macro_call = None;
257-
},
258-
ast::Macro(mac) => {
259-
assert_eq!(current_macro, Some(mac));
260-
current_macro = None;
261-
macro_highlighter = MacroHighlighter::default();
262-
},
263-
ast::Item(item) => {
264-
if current_attr_call == Some(item) {
265-
current_attr_call = None;
266-
}
267-
},
268-
ast::Attr(__) => inside_attribute = false,
269-
_ => ()
270-
}
229+
Some(ast::Item::MacroRules(mac)) => {
230+
macro_highlighter.init();
231+
current_macro = Some(mac.into());
232+
continue;
271233
}
272-
}
234+
Some(ast::Item::MacroDef(mac)) => {
235+
macro_highlighter.init();
236+
current_macro = Some(mac.into());
237+
continue;
238+
}
239+
Some(item) if sema.is_attr_macro_call(&item) => current_attr_call = Some(item),
240+
None if ast::Attr::can_cast(node.kind()) => inside_attribute = true,
241+
_ => (),
242+
},
243+
WalkEvent::Leave(NodeOrToken::Node(node)) => match ast::Item::cast(node.clone()) {
244+
Some(ast::Item::MacroCall(mcall)) => {
245+
assert_eq!(current_macro_call, Some(mcall));
246+
current_macro_call = None;
247+
}
248+
Some(ast::Item::MacroRules(mac)) => {
249+
assert_eq!(current_macro, Some(mac.into()));
250+
current_macro = None;
251+
macro_highlighter = MacroHighlighter::default();
252+
}
253+
Some(ast::Item::MacroDef(mac)) => {
254+
assert_eq!(current_macro, Some(mac.into()));
255+
current_macro = None;
256+
macro_highlighter = MacroHighlighter::default();
257+
}
258+
Some(item) if current_attr_call.as_ref().map_or(false, |it| *it == item) => {
259+
current_attr_call = None
260+
}
261+
None if ast::Attr::can_cast(node.kind()) => inside_attribute = false,
262+
_ => (),
263+
},
273264
_ => (),
274265
}
275266

276267
let element = match event {
277268
WalkEvent::Enter(it) => it,
278-
WalkEvent::Leave(it) => {
279-
if let Some(node) = it.as_node() {
280-
inject::doc_comment(hl, sema, root.with_value(node));
281-
}
269+
WalkEvent::Leave(NodeOrToken::Token(_)) => continue,
270+
WalkEvent::Leave(NodeOrToken::Node(node)) => {
271+
inject::doc_comment(hl, sema, root.with_value(&node));
282272
continue;
283273
}
284274
};
285275

286-
let range = element.text_range();
287-
288276
if current_macro.is_some() {
289277
if let Some(tok) = element.as_token() {
290278
macro_highlighter.advance(tok);
291279
}
292280
}
293281

294-
let descend_token = (current_macro_call.is_some() || current_attr_call.is_some())
295-
&& element.kind() != COMMENT;
282+
// only attempt to descend if we are inside a macro call or attribute
283+
// as calling `descend_into_macros_single` gets rather expensive if done for every single token
284+
let descend_token = current_macro_call.is_some() || current_attr_call.is_some();
296285
let element_to_highlight = if descend_token {
297-
// Inside a macro -- expand it first
298-
let token = match element.clone().into_token() {
299-
Some(it) if current_macro_call.is_some() => {
300-
let not_in_tt = it.parent().map_or(true, |it| it.kind() != TOKEN_TREE);
301-
if not_in_tt {
302-
continue;
303-
}
304-
it
305-
}
306-
Some(it) => it,
307-
_ => continue,
286+
let token = match &element {
287+
NodeOrToken::Node(_) => continue,
288+
NodeOrToken::Token(tok) => tok.clone(),
289+
};
290+
let in_mcall_outside_tt = current_macro_call.is_some()
291+
&& token.parent().as_ref().map(SyntaxNode::kind) != Some(TOKEN_TREE);
292+
let token = match in_mcall_outside_tt {
293+
// not in the macros token tree, don't attempt to descend
294+
true => token,
295+
false => sema.descend_into_macros_single(token),
308296
};
309-
let token = sema.descend_into_macros_single(token);
310297
match token.parent() {
311298
Some(parent) => {
312-
// We only care Name and Name_ref
299+
// Names and NameRefs have special semantics, use them instead of the tokens
300+
// as otherwise we won't ever visit them
313301
match (token.kind(), parent.kind()) {
314302
(T![ident], NAME | NAME_REF) => parent.into(),
315303
(T![self] | T![super] | T![crate], NAME_REF) => parent.into(),
@@ -323,10 +311,14 @@ fn traverse(
323311
element.clone()
324312
};
325313

326-
if macro_highlighter.highlight(element_to_highlight.clone()).is_some() {
314+
// FIXME: do proper macro def highlighting https://github.com/rust-analyzer/rust-analyzer/issues/6232
315+
// Skip metavariables from being highlighted to prevent keyword highlighting in them
316+
if macro_highlighter.highlight(&element_to_highlight).is_some() {
327317
continue;
328318
}
329319

320+
// string highlight injections, note this does not use the descended element as proc-macros
321+
// can rewrite string literals which invalidates our indices
330322
if let (Some(token), Some(token_to_highlight)) =
331323
(element.into_token(), element_to_highlight.as_token())
332324
{
@@ -354,13 +346,15 @@ fn traverse(
354346
}
355347
}
356348

357-
if let Some((mut highlight, binding_hash)) = highlight::element(
349+
// do the normal highlighting
350+
let element = highlight::element(
358351
sema,
359352
krate,
360353
&mut bindings_shadow_count,
361354
syntactic_name_ref_highlighting,
362-
element_to_highlight.clone(),
363-
) {
355+
element_to_highlight,
356+
);
357+
if let Some((mut highlight, binding_hash)) = element {
364358
if inside_attribute {
365359
highlight |= HlMod::Attribute
366360
}
@@ -369,18 +363,3 @@ fn traverse(
369363
}
370364
}
371365
}
372-
373-
fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
374-
let path = macro_call.path()?;
375-
let name_ref = path.segment()?.name_ref()?;
376-
377-
let range_start = name_ref.syntax().text_range().start();
378-
let mut range_end = name_ref.syntax().text_range().end();
379-
for sibling in path.syntax().siblings_with_tokens(Direction::Next) {
380-
if let T![!] | T![ident] = sibling.kind() {
381-
range_end = sibling.text_range().end();
382-
}
383-
}
384-
385-
Some(TextRange::new(range_start, range_end))
386-
}

crates/ide/src/syntax_highlighting/highlight.rs

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ fn token(
8181
T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => {
8282
HlOperator::Other.into()
8383
}
84-
T![!] if parent_matches::<ast::MacroCall>(&token) => SymbolKind::Macro.into(),
84+
T![!] if parent_matches::<ast::MacroCall>(&token) => HlPunct::MacroBang.into(),
8585
T![!] if parent_matches::<ast::NeverType>(&token) => HlTag::BuiltinType.into(),
8686
T![!] if parent_matches::<ast::PrefixExpr>(&token) => HlOperator::Logical.into(),
8787
T![*] if parent_matches::<ast::PtrType>(&token) => HlTag::Keyword.into(),
@@ -665,21 +665,27 @@ fn highlight_name_ref_by_syntax(
665665
}
666666
}
667667
PATH_SEGMENT => {
668+
let name_based_fallback = || {
669+
if name.text().chars().next().unwrap_or_default().is_uppercase() {
670+
SymbolKind::Struct.into()
671+
} else {
672+
SymbolKind::Module.into()
673+
}
674+
};
668675
let path = match parent.parent().and_then(ast::Path::cast) {
669676
Some(it) => it,
670-
_ => return default.into(),
677+
_ => return name_based_fallback(),
671678
};
672-
let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
673-
Some(it) => it,
674-
_ => {
675-
// within path, decide whether it is module or adt by checking for uppercase name
676-
return if name.text().chars().next().unwrap_or_default().is_uppercase() {
677-
SymbolKind::Struct
678-
} else {
679-
SymbolKind::Module
679+
let expr = match path.syntax().parent() {
680+
Some(parent) => match_ast! {
681+
match parent {
682+
ast::PathExpr(path) => path,
683+
ast::MacroCall(_) => return SymbolKind::Macro.into(),
684+
_ => return name_based_fallback(),
680685
}
681-
.into();
682-
}
686+
},
687+
// within path, decide whether it is module or adt by checking for uppercase name
688+
None => return name_based_fallback(),
683689
};
684690
let parent = match expr.syntax().parent() {
685691
Some(it) => it,

crates/ide/src/syntax_highlighting/macro_.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ impl MacroHighlighter {
1919
}
2020
}
2121

22-
pub(super) fn highlight(&self, element: SyntaxElement) -> Option<HlRange> {
22+
pub(super) fn highlight(&self, element: &SyntaxElement) -> Option<HlRange> {
2323
if let Some(state) = self.state.as_ref() {
2424
if matches!(state.rule_state, RuleState::Matcher | RuleState::Expander) {
2525
if let Some(range) = is_metavariable(element) {
@@ -115,7 +115,7 @@ fn update_macro_state(state: &mut MacroMatcherParseState, tok: &SyntaxToken) {
115115
}
116116
}
117117

118-
fn is_metavariable(element: SyntaxElement) -> Option<TextRange> {
118+
fn is_metavariable(element: &SyntaxElement) -> Option<TextRange> {
119119
let tok = element.as_token()?;
120120
match tok.kind() {
121121
kind if kind == SyntaxKind::IDENT || kind.is_keyword() => {

crates/ide/src/syntax_highlighting/tags.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,8 @@ pub enum HlPunct {
102102
Colon,
103103
/// ;
104104
Semi,
105+
/// ! (only for macro calls)
106+
MacroBang,
105107
///
106108
Other,
107109
}
@@ -167,6 +169,7 @@ impl HlTag {
167169
HlPunct::Dot => "dot",
168170
HlPunct::Colon => "colon",
169171
HlPunct::Semi => "semicolon",
172+
HlPunct::MacroBang => "macro_bang",
170173
HlPunct::Other => "punctuation",
171174
},
172175
HlTag::NumericLiteral => "numeric_literal",

0 commit comments

Comments
 (0)