ide/syntax_highlighting/
highlight.rs

1//! Computes color for a single element.
2
3use std::ops::ControlFlow;
4
5use either::Either;
6use hir::{AsAssocItem, HasAttrs, HasVisibility, Semantics};
7use ide_db::{
8    FxHashMap, RootDatabase, SymbolKind,
9    defs::{Definition, IdentClass, NameClass, NameRefClass},
10    syntax_helpers::node_ext::walk_pat,
11};
12use span::Edition;
13use stdx::hash_once;
14use syntax::{
15    AstNode, AstPtr, AstToken, NodeOrToken,
16    SyntaxKind::{self, *},
17    SyntaxNode, SyntaxNodePtr, SyntaxToken, T, ast, match_ast,
18};
19
20use crate::{
21    Highlight, HlMod, HlTag,
22    syntax_highlighting::tags::{HlOperator, HlPunct},
23};
24
25pub(super) fn token(
26    sema: &Semantics<'_, RootDatabase>,
27    token: SyntaxToken,
28    edition: Edition,
29    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
30    in_tt: bool,
31) -> Option<Highlight> {
32    if let Some(comment) = ast::Comment::cast(token.clone()) {
33        let h = HlTag::Comment;
34        return Some(match comment.kind().doc {
35            Some(_) => h | HlMod::Documentation,
36            None => h.into(),
37        });
38    }
39
40    let h = match token.kind() {
41        STRING | BYTE_STRING | C_STRING => HlTag::StringLiteral.into(),
42        INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(),
43        BYTE => HlTag::ByteLiteral.into(),
44        CHAR => HlTag::CharLiteral.into(),
45        IDENT if in_tt => {
46            // from this point on we are inside a token tree, this only happens for identifiers
47            // that were not mapped down into macro invocations
48            HlTag::None.into()
49        }
50        p if p.is_punct() => punctuation(sema, token, p, is_unsafe_node),
51        k if k.is_keyword(edition) => {
52            if in_tt && token.prev_token().is_some_and(|t| t.kind() == T![$]) {
53                // we are likely within a macro definition where our keyword is a fragment name
54                HlTag::None.into()
55            } else {
56                keyword(token, k)
57            }
58        }
59        _ => return None,
60    };
61    Some(h)
62}
63
64pub(super) fn name_like(
65    sema: &Semantics<'_, RootDatabase>,
66    krate: Option<hir::Crate>,
67    bindings_shadow_count: Option<&mut FxHashMap<hir::Name, u32>>,
68    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
69    syntactic_name_ref_highlighting: bool,
70    name_like: ast::NameLike,
71    edition: Edition,
72) -> Option<(Highlight, Option<u64>)> {
73    let mut binding_hash = None;
74    let highlight = match name_like {
75        ast::NameLike::NameRef(name_ref) => highlight_name_ref(
76            sema,
77            krate,
78            bindings_shadow_count,
79            &mut binding_hash,
80            is_unsafe_node,
81            syntactic_name_ref_highlighting,
82            name_ref,
83            edition,
84        ),
85        ast::NameLike::Name(name) => highlight_name(
86            sema,
87            bindings_shadow_count,
88            &mut binding_hash,
89            is_unsafe_node,
90            krate,
91            name,
92            edition,
93        ),
94        ast::NameLike::Lifetime(lifetime) => match IdentClass::classify_lifetime(sema, &lifetime) {
95            Some(IdentClass::NameClass(NameClass::Definition(def))) => {
96                highlight_def(sema, krate, def, edition, false) | HlMod::Definition
97            }
98            Some(IdentClass::NameRefClass(NameRefClass::Definition(def, _))) => {
99                highlight_def(sema, krate, def, edition, true)
100            }
101            // FIXME: Fallback for '_, as we do not resolve these yet
102            _ => SymbolKind::LifetimeParam.into(),
103        },
104    };
105    Some((highlight, binding_hash))
106}
107
108fn punctuation(
109    sema: &Semantics<'_, RootDatabase>,
110    token: SyntaxToken,
111    kind: SyntaxKind,
112    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
113) -> Highlight {
114    let operator_parent = token.parent();
115    let parent_kind = operator_parent.as_ref().map_or(EOF, SyntaxNode::kind);
116
117    match (kind, parent_kind) {
118        (T![?], TRY_EXPR) => HlTag::Operator(HlOperator::Other) | HlMod::ControlFlow,
119        (T![&], BIN_EXPR) => HlOperator::Bitwise.into(),
120        (T![&], REF_EXPR | REF_PAT) => HlTag::Operator(HlOperator::Other).into(),
121        (T![..] | T![..=], _) => match token.parent().and_then(ast::Pat::cast) {
122            Some(pat) if is_unsafe_node(AstPtr::new(&pat).wrap_right()) => {
123                Highlight::from(HlOperator::Other) | HlMod::Unsafe
124            }
125            _ => HlOperator::Other.into(),
126        },
127        (T![::] | T![->] | T![=>] | T![=] | T![@] | T![.], _) => HlOperator::Other.into(),
128        (T![!], MACRO_CALL) => {
129            if operator_parent
130                .and_then(ast::MacroCall::cast)
131                .is_some_and(|macro_call| sema.is_unsafe_macro_call(&macro_call))
132            {
133                Highlight::from(HlPunct::MacroBang) | HlMod::Unsafe
134            } else {
135                HlPunct::MacroBang.into()
136            }
137        }
138        (T![!], MACRO_RULES) => HlPunct::MacroBang.into(),
139        (T![!], NEVER_TYPE) => HlTag::BuiltinType.into(),
140        (T![!], PREFIX_EXPR) => HlOperator::Negation.into(),
141        (T![*], PTR_TYPE) => HlTag::Keyword.into(),
142        (T![*], PREFIX_EXPR) => {
143            let h = HlTag::Operator(HlOperator::Other).into();
144            let ptr = operator_parent
145                .as_ref()
146                .and_then(|it| AstPtr::try_from_raw(SyntaxNodePtr::new(it)));
147            if ptr.is_some_and(is_unsafe_node) { h | HlMod::Unsafe } else { h }
148        }
149        (T![-], PREFIX_EXPR) => {
150            let prefix_expr =
151                operator_parent.and_then(ast::PrefixExpr::cast).and_then(|e| e.expr());
152            match prefix_expr {
153                Some(ast::Expr::Literal(_)) => HlTag::NumericLiteral,
154                _ => HlTag::Operator(HlOperator::Other),
155            }
156            .into()
157        }
158        (T![+] | T![-] | T![*] | T![/] | T![%], BIN_EXPR) => HlOperator::Arithmetic.into(),
159        (T![+=] | T![-=] | T![*=] | T![/=] | T![%=], BIN_EXPR) => {
160            Highlight::from(HlOperator::Arithmetic) | HlMod::Mutable
161        }
162        (T![|] | T![&] | T![^] | T![>>] | T![<<], BIN_EXPR) => HlOperator::Bitwise.into(),
163        (T![|=] | T![&=] | T![^=] | T![>>=] | T![<<=], BIN_EXPR) => {
164            Highlight::from(HlOperator::Bitwise) | HlMod::Mutable
165        }
166        (T![&&] | T![||], BIN_EXPR) => HlOperator::Logical.into(),
167        (T![>] | T![<] | T![==] | T![>=] | T![<=] | T![!=], BIN_EXPR) => {
168            HlOperator::Comparison.into()
169        }
170        (_, ATTR) => HlTag::AttributeBracket.into(),
171        (T![>], _)
172            if operator_parent
173                .as_ref()
174                .and_then(SyntaxNode::parent)
175                .is_some_and(|it| it.kind() == MACRO_RULES) =>
176        {
177            HlOperator::Other.into()
178        }
179        (kind, _) => match kind {
180            T!['['] | T![']'] => {
181                let is_unsafe_macro = operator_parent
182                    .as_ref()
183                    .and_then(|it| ast::TokenTree::cast(it.clone())?.syntax().parent())
184                    .and_then(ast::MacroCall::cast)
185                    .is_some_and(|macro_call| sema.is_unsafe_macro_call(&macro_call));
186                let is_unsafe = is_unsafe_macro
187                    || operator_parent
188                        .as_ref()
189                        .and_then(|it| AstPtr::try_from_raw(SyntaxNodePtr::new(it)))
190                        .is_some_and(is_unsafe_node);
191                if is_unsafe {
192                    return Highlight::from(HlPunct::Bracket) | HlMod::Unsafe;
193                } else {
194                    HlPunct::Bracket
195                }
196            }
197            T!['{'] | T!['}'] => {
198                let is_unsafe_macro = operator_parent
199                    .as_ref()
200                    .and_then(|it| ast::TokenTree::cast(it.clone())?.syntax().parent())
201                    .and_then(ast::MacroCall::cast)
202                    .is_some_and(|macro_call| sema.is_unsafe_macro_call(&macro_call));
203                let is_unsafe = is_unsafe_macro
204                    || operator_parent
205                        .as_ref()
206                        .and_then(|it| AstPtr::try_from_raw(SyntaxNodePtr::new(it)))
207                        .is_some_and(is_unsafe_node);
208                if is_unsafe {
209                    return Highlight::from(HlPunct::Brace) | HlMod::Unsafe;
210                } else {
211                    HlPunct::Brace
212                }
213            }
214            T!['('] | T![')'] => {
215                let is_unsafe_macro = operator_parent
216                    .as_ref()
217                    .and_then(|it| ast::TokenTree::cast(it.clone())?.syntax().parent())
218                    .and_then(ast::MacroCall::cast)
219                    .is_some_and(|macro_call| sema.is_unsafe_macro_call(&macro_call));
220                let is_unsafe = is_unsafe_macro
221                    || operator_parent
222                        .and_then(|it| {
223                            if ast::ArgList::can_cast(it.kind()) { it.parent() } else { Some(it) }
224                        })
225                        .and_then(|it| AstPtr::try_from_raw(SyntaxNodePtr::new(&it)))
226                        .is_some_and(is_unsafe_node);
227
228                if is_unsafe {
229                    return Highlight::from(HlPunct::Parenthesis) | HlMod::Unsafe;
230                } else {
231                    HlPunct::Parenthesis
232                }
233            }
234            T![<] | T![>] => HlPunct::Angle,
235            // Early return as otherwise we'd highlight these in
236            // asm expressions
237            T![,] => return HlPunct::Comma.into(),
238            T![:] => HlPunct::Colon,
239            T![;] => HlPunct::Semi,
240            T![.] => HlPunct::Dot,
241            _ => HlPunct::Other,
242        }
243        .into(),
244    }
245}
246
247fn keyword(token: SyntaxToken, kind: SyntaxKind) -> Highlight {
248    let h = Highlight::new(HlTag::Keyword);
249    match kind {
250        T![await] => h | HlMod::Async | HlMod::ControlFlow,
251        T![async] => h | HlMod::Async,
252        T![break]
253        | T![continue]
254        | T![else]
255        | T![if]
256        | T![in]
257        | T![loop]
258        | T![match]
259        | T![return]
260        | T![while]
261        | T![yield] => h | HlMod::ControlFlow,
262        T![do] | T![yeet] if parent_matches::<ast::YeetExpr>(&token) => h | HlMod::ControlFlow,
263        T![for] if parent_matches::<ast::ForExpr>(&token) => h | HlMod::ControlFlow,
264        T![unsafe] => h | HlMod::Unsafe,
265        T![const] => h | HlMod::Const,
266        T![true] | T![false] => HlTag::BoolLiteral.into(),
267        // crate is handled just as a token if it's in an `extern crate`
268        T![crate] if parent_matches::<ast::ExternCrate>(&token) => h,
269        _ => h,
270    }
271}
272
273fn highlight_name_ref(
274    sema: &Semantics<'_, RootDatabase>,
275    krate: Option<hir::Crate>,
276    bindings_shadow_count: Option<&mut FxHashMap<hir::Name, u32>>,
277    binding_hash: &mut Option<u64>,
278    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
279    syntactic_name_ref_highlighting: bool,
280    name_ref: ast::NameRef,
281    edition: Edition,
282) -> Highlight {
283    let db = sema.db;
284    if let Some(res) = highlight_method_call_by_name_ref(sema, krate, &name_ref, is_unsafe_node) {
285        return res;
286    }
287
288    let name_class = match NameRefClass::classify(sema, &name_ref) {
289        Some(name_kind) => name_kind,
290        None if syntactic_name_ref_highlighting => {
291            return highlight_name_ref_by_syntax(name_ref, sema, krate, is_unsafe_node);
292        }
293        // FIXME: This is required for helper attributes used by proc-macros, as those do not map down
294        // to anything when used.
295        // We can fix this for derive attributes since derive helpers are recorded, but not for
296        // general attributes.
297        None if name_ref.syntax().ancestors().any(|it| it.kind() == ATTR)
298            && !sema
299                .hir_file_for(name_ref.syntax())
300                .macro_file()
301                .is_some_and(|it| it.is_derive_attr_pseudo_expansion(sema.db)) =>
302        {
303            return HlTag::Symbol(SymbolKind::Attribute).into();
304        }
305        None => return HlTag::UnresolvedReference.into(),
306    };
307    let mut h = match name_class {
308        NameRefClass::Definition(def, _) => {
309            if let Definition::Local(local) = &def
310                && let Some(bindings_shadow_count) = bindings_shadow_count
311            {
312                let name = local.name(sema.db);
313                let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
314                *binding_hash = Some(calc_binding_hash(&name, *shadow_count))
315            };
316
317            let mut h = highlight_def(sema, krate, def, edition, true);
318
319            match def {
320                Definition::Local(local) if is_consumed_lvalue(name_ref.syntax(), &local, db) => {
321                    h |= HlMod::Consuming;
322                }
323                // highlight unsafe traits as unsafe only in their implementations
324                Definition::Trait(trait_) if trait_.is_unsafe(db) => {
325                    if ast::Impl::for_trait_name_ref(&name_ref)
326                        .is_some_and(|impl_| impl_.unsafe_token().is_some())
327                    {
328                        h |= HlMod::Unsafe;
329                    }
330                }
331                Definition::Function(_) => {
332                    let is_unsafe = name_ref
333                        .syntax()
334                        .parent()
335                        .and_then(|it| ast::PathSegment::cast(it)?.parent_path().syntax().parent())
336                        .and_then(ast::PathExpr::cast)
337                        .and_then(|it| it.syntax().parent())
338                        .and_then(ast::CallExpr::cast)
339                        .is_some_and(|it| {
340                            is_unsafe_node(AstPtr::new(&ast::Expr::CallExpr(it)).wrap_left())
341                        });
342                    if is_unsafe {
343                        h |= HlMod::Unsafe;
344                    }
345                }
346                Definition::Macro(_) => {
347                    let is_unsafe = name_ref
348                        .syntax()
349                        .parent()
350                        .and_then(|it| ast::PathSegment::cast(it)?.parent_path().syntax().parent())
351                        .and_then(ast::MacroCall::cast)
352                        .is_some_and(|macro_call| sema.is_unsafe_macro_call(&macro_call));
353                    if is_unsafe {
354                        h |= HlMod::Unsafe;
355                    }
356                }
357                Definition::Field(_) => {
358                    let is_unsafe = name_ref
359                        .syntax()
360                        .parent()
361                        .and_then(|it| {
362                            match_ast! { match it {
363                                ast::FieldExpr(expr) => Some(is_unsafe_node(AstPtr::new(&Either::Left(expr.into())))),
364                                ast::RecordPatField(pat) => {
365                                    walk_pat(&pat.pat()?, &mut |pat| {
366                                        if is_unsafe_node(AstPtr::new(&Either::Right(pat))) {
367                                            ControlFlow::Break(true)
368                                        }
369                                         else {ControlFlow::Continue(())}
370                                    }).break_value()
371                                },
372                                _ => None,
373                            }}
374                        })
375                        .unwrap_or(false);
376                    if is_unsafe {
377                        h |= HlMod::Unsafe;
378                    }
379                }
380                Definition::Static(_) => {
381                    let is_unsafe = name_ref
382                        .syntax()
383                        .parent()
384                        .and_then(|it| ast::PathSegment::cast(it)?.parent_path().syntax().parent())
385                        .and_then(ast::PathExpr::cast)
386                        .is_some_and(|it| {
387                            is_unsafe_node(AstPtr::new(&ast::Expr::PathExpr(it)).wrap_left())
388                        });
389                    if is_unsafe {
390                        h |= HlMod::Unsafe;
391                    }
392                }
393                _ => (),
394            }
395
396            h
397        }
398        NameRefClass::FieldShorthand { field_ref, .. } => {
399            highlight_def(sema, krate, field_ref.into(), edition, true)
400        }
401        NameRefClass::ExternCrateShorthand { decl, krate: resolved_krate } => {
402            let mut h = HlTag::Symbol(SymbolKind::Module).into();
403
404            if krate.as_ref().is_some_and(|krate| resolved_krate != *krate) {
405                h |= HlMod::Library;
406            }
407
408            let is_public = decl.visibility(db) == hir::Visibility::Public;
409            if is_public {
410                h |= HlMod::Public
411            }
412            let is_from_builtin_crate = resolved_krate.is_builtin(db);
413            if is_from_builtin_crate {
414                h |= HlMod::DefaultLibrary;
415            }
416            let is_deprecated = resolved_krate.attrs(sema.db).is_deprecated();
417            if is_deprecated {
418                h |= HlMod::Deprecated;
419            }
420            h |= HlMod::CrateRoot;
421            h
422        }
423    };
424
425    h.tag = match name_ref.token_kind() {
426        T![Self] => HlTag::Symbol(SymbolKind::SelfType),
427        T![self] => HlTag::Symbol(SymbolKind::SelfParam),
428        T![super] | T![crate] => HlTag::Keyword,
429        _ => h.tag,
430    };
431    h
432}
433
434fn highlight_name(
435    sema: &Semantics<'_, RootDatabase>,
436    bindings_shadow_count: Option<&mut FxHashMap<hir::Name, u32>>,
437    binding_hash: &mut Option<u64>,
438    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
439    krate: Option<hir::Crate>,
440    name: ast::Name,
441    edition: Edition,
442) -> Highlight {
443    let name_kind = NameClass::classify(sema, &name);
444    if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind
445        && let Some(bindings_shadow_count) = bindings_shadow_count
446    {
447        let name = local.name(sema.db);
448        let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
449        *shadow_count += 1;
450        *binding_hash = Some(calc_binding_hash(&name, *shadow_count))
451    };
452    match name_kind {
453        Some(NameClass::Definition(def)) => {
454            let mut h = highlight_def(sema, krate, def, edition, false) | HlMod::Definition;
455            if let Definition::Trait(trait_) = &def
456                && trait_.is_unsafe(sema.db)
457            {
458                h |= HlMod::Unsafe;
459            }
460            h
461        }
462        Some(NameClass::ConstReference(def)) => highlight_def(sema, krate, def, edition, true),
463        Some(NameClass::PatFieldShorthand { .. }) => {
464            let mut h = HlTag::Symbol(SymbolKind::Field).into();
465            let is_unsafe =
466                name.syntax().parent().and_then(ast::IdentPat::cast).is_some_and(|it| {
467                    is_unsafe_node(AstPtr::new(&ast::Pat::IdentPat(it)).wrap_right())
468                });
469            if is_unsafe {
470                h |= HlMod::Unsafe;
471            }
472            h
473        }
474        None => highlight_name_by_syntax(name) | HlMod::Definition,
475    }
476}
477
478fn calc_binding_hash(name: &hir::Name, shadow_count: u32) -> u64 {
479    hash_once::<ide_db::FxHasher>((name.as_str(), shadow_count))
480}
481
482pub(super) fn highlight_def(
483    sema: &Semantics<'_, RootDatabase>,
484    krate: Option<hir::Crate>,
485    def: Definition,
486    edition: Edition,
487    is_ref: bool,
488) -> Highlight {
489    let db = sema.db;
490    let (mut h, attrs) = match def {
491        Definition::Macro(m) => {
492            (Highlight::new(HlTag::Symbol(m.kind(sema.db).into())), Some(m.attrs(sema.db)))
493        }
494        Definition::Field(field) => {
495            (Highlight::new(HlTag::Symbol(SymbolKind::Field)), Some(field.attrs(sema.db)))
496        }
497        Definition::TupleField(_) => (Highlight::new(HlTag::Symbol(SymbolKind::Field)), None),
498        Definition::Crate(krate) => (
499            Highlight::new(HlTag::Symbol(SymbolKind::Module)) | HlMod::CrateRoot,
500            Some(krate.attrs(sema.db)),
501        ),
502        Definition::Module(module) => {
503            let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Module));
504            if module.is_crate_root(db) {
505                h |= HlMod::CrateRoot;
506            }
507
508            (h, Some(module.attrs(sema.db)))
509        }
510        Definition::Function(func) => {
511            let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function));
512            if let Some(item) = func.as_assoc_item(db) {
513                h |= HlMod::Associated;
514                match func.self_param(db) {
515                    Some(sp) => {
516                        h.tag = HlTag::Symbol(SymbolKind::Method);
517                        match sp.access(db) {
518                            hir::Access::Exclusive => {
519                                h |= HlMod::Mutable;
520                                h |= HlMod::Reference;
521                            }
522                            hir::Access::Shared => h |= HlMod::Reference,
523                            hir::Access::Owned => h |= HlMod::Consuming,
524                        }
525                    }
526                    None => h |= HlMod::Static,
527                }
528
529                match item.container(db) {
530                    hir::AssocItemContainer::Impl(i) => {
531                        if i.trait_(db).is_some() {
532                            h |= HlMod::Trait;
533                        }
534                    }
535                    hir::AssocItemContainer::Trait(_t) => {
536                        h |= HlMod::Trait;
537                    }
538                }
539            }
540
541            // FIXME: Passing `None` here means not-unsafe functions with `#[target_feature]` will be
542            // highlighted as unsafe, even when the current target features set is a superset (RFC 2396).
543            // We probably should consider checking the current function, but I found no easy way to do
544            // that (also I'm worried about perf). There's also an instance below.
545            // FIXME: This should be the edition of the call.
546            if !is_ref && func.is_unsafe_to_call(db, None, edition) {
547                h |= HlMod::Unsafe;
548            }
549            if func.is_async(db) {
550                h |= HlMod::Async;
551            }
552            if func.is_const(db) {
553                h |= HlMod::Const;
554            }
555
556            (h, Some(func.attrs(sema.db)))
557        }
558        Definition::Adt(adt) => {
559            let h = match adt {
560                hir::Adt::Struct(_) => HlTag::Symbol(SymbolKind::Struct),
561                hir::Adt::Enum(_) => HlTag::Symbol(SymbolKind::Enum),
562                hir::Adt::Union(_) => HlTag::Symbol(SymbolKind::Union),
563            };
564
565            (Highlight::new(h), Some(adt.attrs(sema.db)))
566        }
567        Definition::Variant(variant) => {
568            (Highlight::new(HlTag::Symbol(SymbolKind::Variant)), Some(variant.attrs(sema.db)))
569        }
570        Definition::Const(konst) => {
571            let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const)) | HlMod::Const;
572            if let Some(item) = konst.as_assoc_item(db) {
573                h |= HlMod::Associated;
574                h |= HlMod::Static;
575                match item.container(db) {
576                    hir::AssocItemContainer::Impl(i) => {
577                        if i.trait_(db).is_some() {
578                            h |= HlMod::Trait;
579                        }
580                    }
581                    hir::AssocItemContainer::Trait(_t) => {
582                        h |= HlMod::Trait;
583                    }
584                }
585            }
586
587            (h, Some(konst.attrs(sema.db)))
588        }
589        Definition::Trait(trait_) => {
590            (Highlight::new(HlTag::Symbol(SymbolKind::Trait)), Some(trait_.attrs(sema.db)))
591        }
592        Definition::TypeAlias(type_) => {
593            let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias));
594
595            if let Some(item) = type_.as_assoc_item(db) {
596                h |= HlMod::Associated;
597                h |= HlMod::Static;
598                match item.container(db) {
599                    hir::AssocItemContainer::Impl(i) => {
600                        if i.trait_(db).is_some() {
601                            h |= HlMod::Trait;
602                        }
603                    }
604                    hir::AssocItemContainer::Trait(_t) => {
605                        h |= HlMod::Trait;
606                    }
607                }
608            }
609
610            (h, Some(type_.attrs(sema.db)))
611        }
612        Definition::BuiltinType(_) => (Highlight::new(HlTag::BuiltinType), None),
613        Definition::BuiltinLifetime(_) => {
614            (Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)), None)
615        }
616        Definition::Static(s) => {
617            let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Static));
618
619            if s.is_mut(db) {
620                h |= HlMod::Mutable;
621                if !is_ref {
622                    h |= HlMod::Unsafe;
623                }
624            }
625
626            (h, Some(s.attrs(sema.db)))
627        }
628        Definition::SelfType(_) => (Highlight::new(HlTag::Symbol(SymbolKind::Impl)), None),
629        Definition::GenericParam(it) => (
630            match it {
631                hir::GenericParam::TypeParam(_) => {
632                    Highlight::new(HlTag::Symbol(SymbolKind::TypeParam))
633                }
634                hir::GenericParam::ConstParam(_) => {
635                    Highlight::new(HlTag::Symbol(SymbolKind::ConstParam)) | HlMod::Const
636                }
637                hir::GenericParam::LifetimeParam(_) => {
638                    Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam))
639                }
640            },
641            None,
642        ),
643        Definition::Local(local) => {
644            let tag = if local.is_self(db) {
645                HlTag::Symbol(SymbolKind::SelfParam)
646            } else if local.is_param(db) {
647                HlTag::Symbol(SymbolKind::ValueParam)
648            } else {
649                HlTag::Symbol(SymbolKind::Local)
650            };
651            let mut h = Highlight::new(tag);
652            let ty = local.ty(db);
653            if local.is_mut(db) || ty.is_mutable_reference() {
654                h |= HlMod::Mutable;
655            }
656            if local.is_ref(db) || ty.is_reference() {
657                h |= HlMod::Reference;
658            }
659            if ty.as_callable(db).is_some() || ty.impls_fnonce(db) {
660                h |= HlMod::Callable;
661            }
662            (h, None)
663        }
664        Definition::ExternCrateDecl(extern_crate) => {
665            let mut highlight =
666                Highlight::new(HlTag::Symbol(SymbolKind::Module)) | HlMod::CrateRoot;
667            if extern_crate.alias(db).is_none() {
668                highlight |= HlMod::Library;
669            }
670            (highlight, Some(extern_crate.attrs(sema.db)))
671        }
672        Definition::Label(_) => (Highlight::new(HlTag::Symbol(SymbolKind::Label)), None),
673        Definition::BuiltinAttr(_) => {
674            (Highlight::new(HlTag::Symbol(SymbolKind::BuiltinAttr)), None)
675        }
676        Definition::ToolModule(_) => (Highlight::new(HlTag::Symbol(SymbolKind::ToolModule)), None),
677        Definition::DeriveHelper(_) => {
678            (Highlight::new(HlTag::Symbol(SymbolKind::DeriveHelper)), None)
679        }
680        Definition::InlineAsmRegOrRegClass(_) => {
681            (Highlight::new(HlTag::Symbol(SymbolKind::InlineAsmRegOrRegClass)), None)
682        }
683        Definition::InlineAsmOperand(_) => (Highlight::new(HlTag::Symbol(SymbolKind::Local)), None),
684    };
685
686    let def_crate = def.krate(db);
687    let is_from_other_crate = def_crate != krate;
688    let is_from_builtin_crate = def_crate.is_some_and(|def_crate| def_crate.is_builtin(db));
689    let is_builtin = matches!(
690        def,
691        Definition::BuiltinType(_) | Definition::BuiltinLifetime(_) | Definition::BuiltinAttr(_)
692    );
693    match is_from_other_crate {
694        true if !is_builtin => h |= HlMod::Library,
695        false if def.visibility(db) == Some(hir::Visibility::Public) => h |= HlMod::Public,
696        _ => (),
697    }
698
699    if is_from_builtin_crate {
700        h |= HlMod::DefaultLibrary;
701    }
702
703    if let Some(attrs) = attrs
704        && attrs.is_deprecated()
705    {
706        h |= HlMod::Deprecated;
707    }
708
709    h
710}
711
712fn highlight_method_call_by_name_ref(
713    sema: &Semantics<'_, RootDatabase>,
714    krate: Option<hir::Crate>,
715    name_ref: &ast::NameRef,
716    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
717) -> Option<Highlight> {
718    let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
719    highlight_method_call(sema, krate, &mc, is_unsafe_node)
720}
721
722fn highlight_method_call(
723    sema: &Semantics<'_, RootDatabase>,
724    krate: Option<hir::Crate>,
725    method_call: &ast::MethodCallExpr,
726    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
727) -> Option<Highlight> {
728    let func = sema.resolve_method_call(method_call)?;
729
730    let mut h = SymbolKind::Method.into();
731
732    let is_unsafe = is_unsafe_node(AstPtr::new(method_call).upcast::<ast::Expr>().wrap_left());
733    if is_unsafe {
734        h |= HlMod::Unsafe;
735    }
736    if func.is_async(sema.db) {
737        h |= HlMod::Async;
738    }
739    if func.is_const(sema.db) {
740        h |= HlMod::Const;
741    }
742    if func
743        .as_assoc_item(sema.db)
744        .and_then(|it| it.container_or_implemented_trait(sema.db))
745        .is_some()
746    {
747        h |= HlMod::Trait;
748    }
749
750    let def_crate = func.module(sema.db).krate(sema.db);
751    let is_from_other_crate = krate.as_ref().map_or(false, |krate| def_crate != *krate);
752    let is_from_builtin_crate = def_crate.is_builtin(sema.db);
753    let is_public = func.visibility(sema.db) == hir::Visibility::Public;
754    let is_deprecated = func.attrs(sema.db).is_deprecated();
755
756    if is_from_other_crate {
757        h |= HlMod::Library;
758    } else if is_public {
759        h |= HlMod::Public;
760    }
761
762    if is_from_builtin_crate {
763        h |= HlMod::DefaultLibrary;
764    }
765
766    if is_deprecated {
767        h |= HlMod::Deprecated;
768    }
769
770    if let Some(self_param) = func.self_param(sema.db) {
771        match self_param.access(sema.db) {
772            hir::Access::Shared => h |= HlMod::Reference,
773            hir::Access::Exclusive => {
774                h |= HlMod::Mutable;
775                h |= HlMod::Reference;
776            }
777            hir::Access::Owned => {
778                if let Some(receiver_ty) =
779                    method_call.receiver().and_then(|it| sema.type_of_expr(&it))
780                    && !receiver_ty.adjusted().is_copy(sema.db)
781                {
782                    h |= HlMod::Consuming
783                }
784            }
785        }
786    }
787    Some(h)
788}
789
790fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
791    let default = HlTag::UnresolvedReference;
792
793    let parent = match name.syntax().parent() {
794        Some(it) => it,
795        _ => return default.into(),
796    };
797
798    let tag = match parent.kind() {
799        STRUCT => SymbolKind::Struct,
800        ENUM => SymbolKind::Enum,
801        VARIANT => SymbolKind::Variant,
802        UNION => SymbolKind::Union,
803        TRAIT => SymbolKind::Trait,
804        TYPE_ALIAS => SymbolKind::TypeAlias,
805        TYPE_PARAM => SymbolKind::TypeParam,
806        RECORD_FIELD => SymbolKind::Field,
807        MODULE => SymbolKind::Module,
808        FN => SymbolKind::Function,
809        CONST => SymbolKind::Const,
810        STATIC => SymbolKind::Static,
811        IDENT_PAT => SymbolKind::Local,
812        FORMAT_ARGS_ARG => SymbolKind::Local,
813        RENAME => SymbolKind::Local,
814        MACRO_RULES => SymbolKind::Macro,
815        CONST_PARAM => SymbolKind::ConstParam,
816        SELF_PARAM => SymbolKind::SelfParam,
817        ASM_OPERAND_NAMED => SymbolKind::Local,
818        _ => return default.into(),
819    };
820
821    tag.into()
822}
823
824fn highlight_name_ref_by_syntax(
825    name: ast::NameRef,
826    sema: &Semantics<'_, RootDatabase>,
827    krate: Option<hir::Crate>,
828    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
829) -> Highlight {
830    let default = HlTag::UnresolvedReference;
831
832    let parent = match name.syntax().parent() {
833        Some(it) => it,
834        _ => return default.into(),
835    };
836
837    match parent.kind() {
838        EXTERN_CRATE => HlTag::Symbol(SymbolKind::Module) | HlMod::CrateRoot,
839        METHOD_CALL_EXPR => ast::MethodCallExpr::cast(parent)
840            .and_then(|it| highlight_method_call(sema, krate, &it, is_unsafe_node))
841            .unwrap_or_else(|| SymbolKind::Method.into()),
842        FIELD_EXPR => {
843            let h = HlTag::Symbol(SymbolKind::Field);
844            let is_unsafe = ast::Expr::cast(parent)
845                .is_some_and(|it| is_unsafe_node(AstPtr::new(&it).wrap_left()));
846            if is_unsafe { h | HlMod::Unsafe } else { h.into() }
847        }
848        RECORD_EXPR_FIELD | RECORD_PAT_FIELD => HlTag::Symbol(SymbolKind::Field).into(),
849        PATH_SEGMENT => {
850            let name_based_fallback = || {
851                if name.text().chars().next().unwrap_or_default().is_uppercase() {
852                    SymbolKind::Struct.into()
853                } else {
854                    SymbolKind::Module.into()
855                }
856            };
857            let path = match parent.parent().and_then(ast::Path::cast) {
858                Some(it) => it,
859                _ => return name_based_fallback(),
860            };
861            let expr = match path.syntax().parent() {
862                Some(parent) => match_ast! {
863                    match parent {
864                        ast::PathExpr(path) => path,
865                        ast::MacroCall(_) => return SymbolKind::Macro.into(),
866                        _ => return name_based_fallback(),
867                    }
868                },
869                // within path, decide whether it is module or adt by checking for uppercase name
870                None => return name_based_fallback(),
871            };
872            let parent = match expr.syntax().parent() {
873                Some(it) => it,
874                None => return default.into(),
875            };
876
877            match parent.kind() {
878                CALL_EXPR => SymbolKind::Function.into(),
879                _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
880                    SymbolKind::Struct
881                } else {
882                    SymbolKind::Const
883                }
884                .into(),
885            }
886        }
887        ASSOC_TYPE_ARG => SymbolKind::TypeAlias.into(),
888        USE_BOUND_GENERIC_ARGS => SymbolKind::TypeParam.into(),
889        _ => default.into(),
890    }
891}
892
893fn is_consumed_lvalue(node: &SyntaxNode, local: &hir::Local, db: &RootDatabase) -> bool {
894    // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming.
895    parents_match(node.clone().into(), &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST])
896        && !local.ty(db).is_copy(db)
897}
898
899/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly.
900fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool {
901    while let (Some(parent), [kind, rest @ ..]) = (node.parent(), kinds) {
902        if parent.kind() != *kind {
903            return false;
904        }
905
906        node = parent.into();
907        kinds = rest;
908    }
909
910    // Only true if we matched all expected kinds
911    kinds.is_empty()
912}
913
914fn parent_matches<N: AstNode>(token: &SyntaxToken) -> bool {
915    token.parent().is_some_and(|it| N::can_cast(it.kind()))
916}