aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/syntax_highlighting/highlight.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/syntax_highlighting/highlight.rs')
-rw-r--r--crates/ide/src/syntax_highlighting/highlight.rs509
1 files changed, 509 insertions, 0 deletions
diff --git a/crates/ide/src/syntax_highlighting/highlight.rs b/crates/ide/src/syntax_highlighting/highlight.rs
new file mode 100644
index 000000000..8625ef5df
--- /dev/null
+++ b/crates/ide/src/syntax_highlighting/highlight.rs
@@ -0,0 +1,509 @@
1//! Computes color for a single element.
2
3use hir::{AsAssocItem, Semantics, VariantDef};
4use ide_db::{
5 defs::{Definition, NameClass, NameRefClass},
6 RootDatabase,
7};
8use rustc_hash::FxHashMap;
9use syntax::{
10 ast, AstNode, AstToken, NodeOrToken, SyntaxElement,
11 SyntaxKind::{self, *},
12 SyntaxNode, SyntaxToken, T,
13};
14
15use crate::{syntax_highlighting::tags::HlPunct, Highlight, HlMod, HlTag, SymbolKind};
16
17pub(super) fn element(
18 sema: &Semantics<RootDatabase>,
19 bindings_shadow_count: &mut FxHashMap<hir::Name, u32>,
20 syntactic_name_ref_highlighting: bool,
21 element: SyntaxElement,
22) -> Option<(Highlight, Option<u64>)> {
23 let db = sema.db;
24 let mut binding_hash = None;
25 let highlight: Highlight = match element.kind() {
26 FN => {
27 bindings_shadow_count.clear();
28 return None;
29 }
30
31 // Highlight definitions depending on the "type" of the definition.
32 NAME => {
33 let name = element.into_node().and_then(ast::Name::cast).unwrap();
34 let name_kind = NameClass::classify(sema, &name);
35
36 if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind {
37 if let Some(name) = local.name(db) {
38 let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
39 *shadow_count += 1;
40 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
41 }
42 };
43
44 match name_kind {
45 Some(NameClass::ExternCrate(_)) => HlTag::Symbol(SymbolKind::Module).into(),
46 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition,
47 Some(NameClass::ConstReference(def)) => highlight_def(db, def),
48 Some(NameClass::PatFieldShorthand { field_ref, .. }) => {
49 let mut h = HlTag::Symbol(SymbolKind::Field).into();
50 if let Definition::Field(field) = field_ref {
51 if let VariantDef::Union(_) = field.parent_def(db) {
52 h |= HlMod::Unsafe;
53 }
54 }
55
56 h
57 }
58 None => highlight_name_by_syntax(name) | HlMod::Definition,
59 }
60 }
61
62 // Highlight references like the definitions they resolve to
63 NAME_REF if element.ancestors().any(|it| it.kind() == ATTR) => {
64 // even though we track whether we are in an attribute or not we still need this special case
65 // as otherwise we would emit unresolved references for name refs inside attributes
66 Highlight::from(HlTag::Symbol(SymbolKind::Function))
67 }
68 NAME_REF => {
69 let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
70 highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| {
71 let is_self = name_ref.self_token().is_some();
72 let h = match NameRefClass::classify(sema, &name_ref) {
73 Some(name_kind) => match name_kind {
74 NameRefClass::ExternCrate(_) => HlTag::Symbol(SymbolKind::Module).into(),
75 NameRefClass::Definition(def) => {
76 if let Definition::Local(local) = &def {
77 if let Some(name) = local.name(db) {
78 let shadow_count =
79 bindings_shadow_count.entry(name.clone()).or_default();
80 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
81 }
82 };
83
84 let mut h = highlight_def(db, def);
85
86 if let Definition::Local(local) = &def {
87 if is_consumed_lvalue(name_ref.syntax().clone().into(), local, db) {
88 h |= HlMod::Consuming;
89 }
90 }
91
92 if let Some(parent) = name_ref.syntax().parent() {
93 if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) {
94 if let Definition::Field(field) = def {
95 if let VariantDef::Union(_) = field.parent_def(db) {
96 h |= HlMod::Unsafe;
97 }
98 }
99 }
100 }
101
102 h
103 }
104 NameRefClass::FieldShorthand { .. } => {
105 HlTag::Symbol(SymbolKind::Field).into()
106 }
107 },
108 None if syntactic_name_ref_highlighting => {
109 highlight_name_ref_by_syntax(name_ref, sema)
110 }
111 None => HlTag::UnresolvedReference.into(),
112 };
113 if h.tag == HlTag::Symbol(SymbolKind::Module) && is_self {
114 HlTag::Symbol(SymbolKind::SelfParam).into()
115 } else {
116 h
117 }
118 })
119 }
120
121 // Simple token-based highlighting
122 COMMENT => {
123 let comment = element.into_token().and_then(ast::Comment::cast)?;
124 let h = HlTag::Comment;
125 match comment.kind().doc {
126 Some(_) => h | HlMod::Documentation,
127 None => h.into(),
128 }
129 }
130 STRING | BYTE_STRING => HlTag::StringLiteral.into(),
131 ATTR => HlTag::Attribute.into(),
132 INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
133 BYTE => HlTag::ByteLiteral.into(),
134 CHAR => HlTag::CharLiteral.into(),
135 QUESTION => Highlight::new(HlTag::Operator) | HlMod::ControlFlow,
136 LIFETIME => {
137 let lifetime = element.into_node().and_then(ast::Lifetime::cast).unwrap();
138
139 match NameClass::classify_lifetime(sema, &lifetime) {
140 Some(NameClass::Definition(def)) => highlight_def(db, def) | HlMod::Definition,
141 None => match NameRefClass::classify_lifetime(sema, &lifetime) {
142 Some(NameRefClass::Definition(def)) => highlight_def(db, def),
143 _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)),
144 },
145 _ => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)) | HlMod::Definition,
146 }
147 }
148 p if p.is_punct() => match p {
149 T![&] => {
150 let h = HlTag::Operator.into();
151 let is_unsafe = element
152 .parent()
153 .and_then(ast::RefExpr::cast)
154 .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr))
155 .unwrap_or(false);
156 if is_unsafe {
157 h | HlMod::Unsafe
158 } else {
159 h
160 }
161 }
162 T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] | T![.] => HlTag::Operator.into(),
163 T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
164 HlTag::Symbol(SymbolKind::Macro).into()
165 }
166 T![!] if element.parent().and_then(ast::NeverType::cast).is_some() => {
167 HlTag::BuiltinType.into()
168 }
169 T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => {
170 HlTag::Keyword.into()
171 }
172 T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
173 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
174
175 let expr = prefix_expr.expr()?;
176 let ty = sema.type_of_expr(&expr)?;
177 if ty.is_raw_ptr() {
178 HlTag::Operator | HlMod::Unsafe
179 } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() {
180 HlTag::Operator.into()
181 } else {
182 HlTag::Punctuation(HlPunct::Other).into()
183 }
184 }
185 T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
186 let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
187
188 let expr = prefix_expr.expr()?;
189 match expr {
190 ast::Expr::Literal(_) => HlTag::NumericLiteral,
191 _ => HlTag::Operator,
192 }
193 .into()
194 }
195 _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
196 HlTag::Operator.into()
197 }
198 _ if element.parent().and_then(ast::BinExpr::cast).is_some() => HlTag::Operator.into(),
199 _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => {
200 HlTag::Operator.into()
201 }
202 _ if element.parent().and_then(ast::RangePat::cast).is_some() => HlTag::Operator.into(),
203 _ if element.parent().and_then(ast::RestPat::cast).is_some() => HlTag::Operator.into(),
204 _ if element.parent().and_then(ast::Attr::cast).is_some() => HlTag::Attribute.into(),
205 kind => HlTag::Punctuation(match kind {
206 T!['['] | T![']'] => HlPunct::Bracket,
207 T!['{'] | T!['}'] => HlPunct::Brace,
208 T!['('] | T![')'] => HlPunct::Parenthesis,
209 T![<] | T![>] => HlPunct::Angle,
210 T![,] => HlPunct::Comma,
211 T![:] => HlPunct::Colon,
212 T![;] => HlPunct::Semi,
213 T![.] => HlPunct::Dot,
214 _ => HlPunct::Other,
215 })
216 .into(),
217 },
218
219 k if k.is_keyword() => {
220 let h = Highlight::new(HlTag::Keyword);
221 match k {
222 T![break]
223 | T![continue]
224 | T![else]
225 | T![if]
226 | T![loop]
227 | T![match]
228 | T![return]
229 | T![while]
230 | T![in] => h | HlMod::ControlFlow,
231 T![for] if !is_child_of_impl(&element) => h | HlMod::ControlFlow,
232 T![unsafe] => h | HlMod::Unsafe,
233 T![true] | T![false] => HlTag::BoolLiteral.into(),
234 // self is handled as either a Name or NameRef already
235 T![self] => return None,
236 T![ref] => element
237 .parent()
238 .and_then(ast::IdentPat::cast)
239 .and_then(|ident_pat| {
240 if sema.is_unsafe_ident_pat(&ident_pat) {
241 Some(HlMod::Unsafe)
242 } else {
243 None
244 }
245 })
246 .map(|modifier| h | modifier)
247 .unwrap_or(h),
248 _ => h,
249 }
250 }
251
252 _ => return None,
253 };
254
255 return Some((highlight, binding_hash));
256
257 fn calc_binding_hash(name: &hir::Name, shadow_count: u32) -> u64 {
258 fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
259 use std::{collections::hash_map::DefaultHasher, hash::Hasher};
260
261 let mut hasher = DefaultHasher::new();
262 x.hash(&mut hasher);
263 hasher.finish()
264 }
265
266 hash((name, shadow_count))
267 }
268}
269
270fn highlight_def(db: &RootDatabase, def: Definition) -> Highlight {
271 match def {
272 Definition::Macro(_) => HlTag::Symbol(SymbolKind::Macro),
273 Definition::Field(_) => HlTag::Symbol(SymbolKind::Field),
274 Definition::ModuleDef(def) => match def {
275 hir::ModuleDef::Module(_) => HlTag::Symbol(SymbolKind::Module),
276 hir::ModuleDef::Function(func) => {
277 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function));
278 if func.as_assoc_item(db).is_some() {
279 h |= HlMod::Associated;
280 if func.self_param(db).is_none() {
281 h |= HlMod::Static
282 }
283 }
284 if func.is_unsafe(db) {
285 h |= HlMod::Unsafe;
286 }
287 return h;
288 }
289 hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HlTag::Symbol(SymbolKind::Struct),
290 hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HlTag::Symbol(SymbolKind::Enum),
291 hir::ModuleDef::Adt(hir::Adt::Union(_)) => HlTag::Symbol(SymbolKind::Union),
292 hir::ModuleDef::Variant(_) => HlTag::Symbol(SymbolKind::Variant),
293 hir::ModuleDef::Const(konst) => {
294 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const));
295 if konst.as_assoc_item(db).is_some() {
296 h |= HlMod::Associated
297 }
298 return h;
299 }
300 hir::ModuleDef::Trait(_) => HlTag::Symbol(SymbolKind::Trait),
301 hir::ModuleDef::TypeAlias(type_) => {
302 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias));
303 if type_.as_assoc_item(db).is_some() {
304 h |= HlMod::Associated
305 }
306 return h;
307 }
308 hir::ModuleDef::BuiltinType(_) => HlTag::BuiltinType,
309 hir::ModuleDef::Static(s) => {
310 let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Static));
311 if s.is_mut(db) {
312 h |= HlMod::Mutable;
313 h |= HlMod::Unsafe;
314 }
315 return h;
316 }
317 },
318 Definition::SelfType(_) => HlTag::Symbol(SymbolKind::Impl),
319 Definition::GenericParam(it) => match it {
320 hir::GenericParam::TypeParam(_) => HlTag::Symbol(SymbolKind::TypeParam),
321 hir::GenericParam::ConstParam(_) => HlTag::Symbol(SymbolKind::ConstParam),
322 hir::GenericParam::LifetimeParam(_) => HlTag::Symbol(SymbolKind::LifetimeParam),
323 },
324 Definition::Local(local) => {
325 let tag = if local.is_self(db) {
326 HlTag::Symbol(SymbolKind::SelfParam)
327 } else if local.is_param(db) {
328 HlTag::Symbol(SymbolKind::ValueParam)
329 } else {
330 HlTag::Symbol(SymbolKind::Local)
331 };
332 let mut h = Highlight::new(tag);
333 if local.is_mut(db) || local.ty(db).is_mutable_reference() {
334 h |= HlMod::Mutable;
335 }
336 if local.ty(db).as_callable(db).is_some() || local.ty(db).impls_fnonce(db) {
337 h |= HlMod::Callable;
338 }
339 return h;
340 }
341 Definition::Label(_) => HlTag::Symbol(SymbolKind::Label),
342 }
343 .into()
344}
345
346fn highlight_func_by_name_ref(
347 sema: &Semantics<RootDatabase>,
348 name_ref: &ast::NameRef,
349) -> Option<Highlight> {
350 let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
351 highlight_method_call(sema, &mc)
352}
353
354fn highlight_method_call(
355 sema: &Semantics<RootDatabase>,
356 method_call: &ast::MethodCallExpr,
357) -> Option<Highlight> {
358 let func = sema.resolve_method_call(&method_call)?;
359 let mut h = HlTag::Symbol(SymbolKind::Function).into();
360 h |= HlMod::Associated;
361 if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) {
362 h |= HlMod::Unsafe;
363 }
364 if let Some(self_param) = func.self_param(sema.db) {
365 match self_param.access(sema.db) {
366 hir::Access::Shared => (),
367 hir::Access::Exclusive => h |= HlMod::Mutable,
368 hir::Access::Owned => {
369 if let Some(receiver_ty) =
370 method_call.receiver().and_then(|it| sema.type_of_expr(&it))
371 {
372 if !receiver_ty.is_copy(sema.db) {
373 h |= HlMod::Consuming
374 }
375 }
376 }
377 }
378 }
379 Some(h)
380}
381
382fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
383 let default = HlTag::UnresolvedReference;
384
385 let parent = match name.syntax().parent() {
386 Some(it) => it,
387 _ => return default.into(),
388 };
389
390 let tag = match parent.kind() {
391 STRUCT => HlTag::Symbol(SymbolKind::Struct),
392 ENUM => HlTag::Symbol(SymbolKind::Enum),
393 VARIANT => HlTag::Symbol(SymbolKind::Variant),
394 UNION => HlTag::Symbol(SymbolKind::Union),
395 TRAIT => HlTag::Symbol(SymbolKind::Trait),
396 TYPE_ALIAS => HlTag::Symbol(SymbolKind::TypeAlias),
397 TYPE_PARAM => HlTag::Symbol(SymbolKind::TypeParam),
398 RECORD_FIELD => HlTag::Symbol(SymbolKind::Field),
399 MODULE => HlTag::Symbol(SymbolKind::Module),
400 FN => HlTag::Symbol(SymbolKind::Function),
401 CONST => HlTag::Symbol(SymbolKind::Const),
402 STATIC => HlTag::Symbol(SymbolKind::Static),
403 IDENT_PAT => HlTag::Symbol(SymbolKind::Local),
404 _ => default,
405 };
406
407 tag.into()
408}
409
410fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight {
411 let default = HlTag::UnresolvedReference;
412
413 let parent = match name.syntax().parent() {
414 Some(it) => it,
415 _ => return default.into(),
416 };
417
418 match parent.kind() {
419 METHOD_CALL_EXPR => {
420 return ast::MethodCallExpr::cast(parent)
421 .and_then(|it| highlight_method_call(sema, &it))
422 .unwrap_or_else(|| HlTag::Symbol(SymbolKind::Function).into());
423 }
424 FIELD_EXPR => {
425 let h = HlTag::Symbol(SymbolKind::Field);
426 let is_union = ast::FieldExpr::cast(parent)
427 .and_then(|field_expr| {
428 let field = sema.resolve_field(&field_expr)?;
429 Some(if let VariantDef::Union(_) = field.parent_def(sema.db) {
430 true
431 } else {
432 false
433 })
434 })
435 .unwrap_or(false);
436 if is_union {
437 h | HlMod::Unsafe
438 } else {
439 h.into()
440 }
441 }
442 PATH_SEGMENT => {
443 let path = match parent.parent().and_then(ast::Path::cast) {
444 Some(it) => it,
445 _ => return default.into(),
446 };
447 let expr = match path.syntax().parent().and_then(ast::PathExpr::cast) {
448 Some(it) => it,
449 _ => {
450 // within path, decide whether it is module or adt by checking for uppercase name
451 return if name.text().chars().next().unwrap_or_default().is_uppercase() {
452 HlTag::Symbol(SymbolKind::Struct)
453 } else {
454 HlTag::Symbol(SymbolKind::Module)
455 }
456 .into();
457 }
458 };
459 let parent = match expr.syntax().parent() {
460 Some(it) => it,
461 None => return default.into(),
462 };
463
464 match parent.kind() {
465 CALL_EXPR => HlTag::Symbol(SymbolKind::Function).into(),
466 _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
467 HlTag::Symbol(SymbolKind::Struct)
468 } else {
469 HlTag::Symbol(SymbolKind::Const)
470 }
471 .into(),
472 }
473 }
474 _ => default.into(),
475 }
476}
477
478fn is_consumed_lvalue(
479 node: NodeOrToken<SyntaxNode, SyntaxToken>,
480 local: &hir::Local,
481 db: &RootDatabase,
482) -> bool {
483 // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming.
484 parents_match(node, &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db)
485}
486
487/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly.
488fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool {
489 while let (Some(parent), [kind, rest @ ..]) = (&node.parent(), kinds) {
490 if parent.kind() != *kind {
491 return false;
492 }
493
494 // FIXME: Would be nice to get parent out of the match, but binding by-move and by-value
495 // in the same pattern is unstable: rust-lang/rust#68354.
496 node = node.parent().unwrap().into();
497 kinds = rest;
498 }
499
500 // Only true if we matched all expected kinds
501 kinds.len() == 0
502}
503
504fn is_child_of_impl(element: &SyntaxElement) -> bool {
505 match element.parent() {
506 Some(e) => e.kind() == IMPL,
507 _ => false,
508 }
509}