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::CrateRoot).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
421        }
422    };
423
424    h.tag = match name_ref.token_kind() {
425        T![Self] => HlTag::Symbol(SymbolKind::SelfType),
426        T![self] => HlTag::Symbol(SymbolKind::SelfParam),
427        T![super] | T![crate] => HlTag::Keyword,
428        _ => h.tag,
429    };
430    h
431}
432
433fn highlight_name(
434    sema: &Semantics<'_, RootDatabase>,
435    bindings_shadow_count: Option<&mut FxHashMap<hir::Name, u32>>,
436    binding_hash: &mut Option<u64>,
437    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
438    krate: Option<hir::Crate>,
439    name: ast::Name,
440    edition: Edition,
441) -> Highlight {
442    let name_kind = NameClass::classify(sema, &name);
443    if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind
444        && let Some(bindings_shadow_count) = bindings_shadow_count
445    {
446        let name = local.name(sema.db);
447        let shadow_count = bindings_shadow_count.entry(name.clone()).or_default();
448        *shadow_count += 1;
449        *binding_hash = Some(calc_binding_hash(&name, *shadow_count))
450    };
451    match name_kind {
452        Some(NameClass::Definition(def)) => {
453            let mut h = highlight_def(sema, krate, def, edition, false) | HlMod::Definition;
454            if let Definition::Trait(trait_) = &def
455                && trait_.is_unsafe(sema.db)
456            {
457                h |= HlMod::Unsafe;
458            }
459            h
460        }
461        Some(NameClass::ConstReference(def)) => highlight_def(sema, krate, def, edition, true),
462        Some(NameClass::PatFieldShorthand { .. }) => {
463            let mut h = HlTag::Symbol(SymbolKind::Field).into();
464            let is_unsafe =
465                name.syntax().parent().and_then(ast::IdentPat::cast).is_some_and(|it| {
466                    is_unsafe_node(AstPtr::new(&ast::Pat::IdentPat(it)).wrap_right())
467                });
468            if is_unsafe {
469                h |= HlMod::Unsafe;
470            }
471            h
472        }
473        None => highlight_name_by_syntax(name) | HlMod::Definition,
474    }
475}
476
477fn calc_binding_hash(name: &hir::Name, shadow_count: u32) -> u64 {
478    hash_once::<ide_db::FxHasher>((name.as_str(), shadow_count))
479}
480
481pub(super) fn highlight_def(
482    sema: &Semantics<'_, RootDatabase>,
483    krate: Option<hir::Crate>,
484    def: Definition,
485    edition: Edition,
486    is_ref: bool,
487) -> Highlight {
488    let db = sema.db;
489    let (mut h, attrs) = match def {
490        Definition::Macro(m) => {
491            (Highlight::new(HlTag::Symbol(m.kind(sema.db).into())), Some(m.attrs(sema.db)))
492        }
493        Definition::Field(field) => {
494            (Highlight::new(HlTag::Symbol(SymbolKind::Field)), Some(field.attrs(sema.db)))
495        }
496        Definition::TupleField(_) => (Highlight::new(HlTag::Symbol(SymbolKind::Field)), None),
497        Definition::Crate(krate) => {
498            (Highlight::new(HlTag::Symbol(SymbolKind::CrateRoot)), Some(krate.attrs(sema.db)))
499        }
500        Definition::Module(module) => {
501            let h = Highlight::new(HlTag::Symbol(if module.is_crate_root(db) {
502                SymbolKind::CrateRoot
503            } else {
504                SymbolKind::Module
505            }));
506            (h, Some(module.attrs(sema.db)))
507        }
508        Definition::Function(func) => {
509            let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function));
510            if let Some(item) = func.as_assoc_item(db) {
511                h |= HlMod::Associated;
512                match func.self_param(db) {
513                    Some(sp) => {
514                        h.tag = HlTag::Symbol(SymbolKind::Method);
515                        match sp.access(db) {
516                            hir::Access::Exclusive => {
517                                h |= HlMod::Mutable;
518                                h |= HlMod::Reference;
519                            }
520                            hir::Access::Shared => h |= HlMod::Reference,
521                            hir::Access::Owned => h |= HlMod::Consuming,
522                        }
523                    }
524                    None => h |= HlMod::Static,
525                }
526
527                match item.container(db) {
528                    hir::AssocItemContainer::Impl(i) => {
529                        if i.trait_(db).is_some() {
530                            h |= HlMod::Trait;
531                        }
532                    }
533                    hir::AssocItemContainer::Trait(_t) => {
534                        h |= HlMod::Trait;
535                    }
536                }
537            }
538
539            // FIXME: Passing `None` here means not-unsafe functions with `#[target_feature]` will be
540            // highlighted as unsafe, even when the current target features set is a superset (RFC 2396).
541            // We probably should consider checking the current function, but I found no easy way to do
542            // that (also I'm worried about perf). There's also an instance below.
543            // FIXME: This should be the edition of the call.
544            if !is_ref && func.is_unsafe_to_call(db, None, edition) {
545                h |= HlMod::Unsafe;
546            }
547            if func.is_async(db) {
548                h |= HlMod::Async;
549            }
550            if func.is_const(db) {
551                h |= HlMod::Const;
552            }
553
554            (h, Some(func.attrs(sema.db)))
555        }
556        Definition::Adt(adt) => {
557            let h = match adt {
558                hir::Adt::Struct(_) => HlTag::Symbol(SymbolKind::Struct),
559                hir::Adt::Enum(_) => HlTag::Symbol(SymbolKind::Enum),
560                hir::Adt::Union(_) => HlTag::Symbol(SymbolKind::Union),
561            };
562
563            (Highlight::new(h), Some(adt.attrs(sema.db)))
564        }
565        Definition::Variant(variant) => {
566            (Highlight::new(HlTag::Symbol(SymbolKind::Variant)), Some(variant.attrs(sema.db)))
567        }
568        Definition::Const(konst) => {
569            let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const)) | HlMod::Const;
570            if let Some(item) = konst.as_assoc_item(db) {
571                h |= HlMod::Associated;
572                h |= HlMod::Static;
573                match item.container(db) {
574                    hir::AssocItemContainer::Impl(i) => {
575                        if i.trait_(db).is_some() {
576                            h |= HlMod::Trait;
577                        }
578                    }
579                    hir::AssocItemContainer::Trait(_t) => {
580                        h |= HlMod::Trait;
581                    }
582                }
583            }
584
585            (h, Some(konst.attrs(sema.db)))
586        }
587        Definition::Trait(trait_) => {
588            (Highlight::new(HlTag::Symbol(SymbolKind::Trait)), Some(trait_.attrs(sema.db)))
589        }
590        Definition::TypeAlias(type_) => {
591            let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias));
592
593            if let Some(item) = type_.as_assoc_item(db) {
594                h |= HlMod::Associated;
595                h |= HlMod::Static;
596                match item.container(db) {
597                    hir::AssocItemContainer::Impl(i) => {
598                        if i.trait_(db).is_some() {
599                            h |= HlMod::Trait;
600                        }
601                    }
602                    hir::AssocItemContainer::Trait(_t) => {
603                        h |= HlMod::Trait;
604                    }
605                }
606            }
607
608            (h, Some(type_.attrs(sema.db)))
609        }
610        Definition::BuiltinType(_) => (Highlight::new(HlTag::BuiltinType), None),
611        Definition::BuiltinLifetime(_) => {
612            (Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)), None)
613        }
614        Definition::Static(s) => {
615            let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Static));
616
617            if s.is_mut(db) {
618                h |= HlMod::Mutable;
619                if !is_ref {
620                    h |= HlMod::Unsafe;
621                }
622            }
623
624            (h, Some(s.attrs(sema.db)))
625        }
626        Definition::SelfType(_) => (Highlight::new(HlTag::Symbol(SymbolKind::Impl)), None),
627        Definition::GenericParam(it) => (
628            match it {
629                hir::GenericParam::TypeParam(_) => {
630                    Highlight::new(HlTag::Symbol(SymbolKind::TypeParam))
631                }
632                hir::GenericParam::ConstParam(_) => {
633                    Highlight::new(HlTag::Symbol(SymbolKind::ConstParam)) | HlMod::Const
634                }
635                hir::GenericParam::LifetimeParam(_) => {
636                    Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam))
637                }
638            },
639            None,
640        ),
641        Definition::Local(local) => {
642            let tag = if local.is_self(db) {
643                HlTag::Symbol(SymbolKind::SelfParam)
644            } else if local.is_param(db) {
645                HlTag::Symbol(SymbolKind::ValueParam)
646            } else {
647                HlTag::Symbol(SymbolKind::Local)
648            };
649            let mut h = Highlight::new(tag);
650            let ty = local.ty(db);
651            if local.is_mut(db) || ty.is_mutable_reference() {
652                h |= HlMod::Mutable;
653            }
654            if local.is_ref(db) || ty.is_reference() {
655                h |= HlMod::Reference;
656            }
657            if ty.as_callable(db).is_some() || ty.impls_fnonce(db) {
658                h |= HlMod::Callable;
659            }
660            (h, None)
661        }
662        Definition::ExternCrateDecl(extern_crate) => {
663            let mut highlight = Highlight::new(HlTag::Symbol(SymbolKind::CrateRoot));
664            if extern_crate.alias(db).is_none() {
665                highlight |= HlMod::Library;
666            }
667            (highlight, Some(extern_crate.attrs(sema.db)))
668        }
669        Definition::Label(_) => (Highlight::new(HlTag::Symbol(SymbolKind::Label)), None),
670        Definition::BuiltinAttr(_) => {
671            (Highlight::new(HlTag::Symbol(SymbolKind::BuiltinAttr)), None)
672        }
673        Definition::ToolModule(_) => (Highlight::new(HlTag::Symbol(SymbolKind::ToolModule)), None),
674        Definition::DeriveHelper(_) => {
675            (Highlight::new(HlTag::Symbol(SymbolKind::DeriveHelper)), None)
676        }
677        Definition::InlineAsmRegOrRegClass(_) => {
678            (Highlight::new(HlTag::Symbol(SymbolKind::InlineAsmRegOrRegClass)), None)
679        }
680        Definition::InlineAsmOperand(_) => (Highlight::new(HlTag::Symbol(SymbolKind::Local)), None),
681    };
682
683    let def_crate = def.krate(db);
684    let is_from_other_crate = def_crate != krate;
685    let is_from_builtin_crate = def_crate.is_some_and(|def_crate| def_crate.is_builtin(db));
686    let is_builtin = matches!(
687        def,
688        Definition::BuiltinType(_) | Definition::BuiltinLifetime(_) | Definition::BuiltinAttr(_)
689    );
690    match is_from_other_crate {
691        true if !is_builtin => h |= HlMod::Library,
692        false if def.visibility(db) == Some(hir::Visibility::Public) => h |= HlMod::Public,
693        _ => (),
694    }
695
696    if is_from_builtin_crate {
697        h |= HlMod::DefaultLibrary;
698    }
699
700    if let Some(attrs) = attrs
701        && attrs.is_deprecated()
702    {
703        h |= HlMod::Deprecated;
704    }
705
706    h
707}
708
709fn highlight_method_call_by_name_ref(
710    sema: &Semantics<'_, RootDatabase>,
711    krate: Option<hir::Crate>,
712    name_ref: &ast::NameRef,
713    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
714) -> Option<Highlight> {
715    let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?;
716    highlight_method_call(sema, krate, &mc, is_unsafe_node)
717}
718
719fn highlight_method_call(
720    sema: &Semantics<'_, RootDatabase>,
721    krate: Option<hir::Crate>,
722    method_call: &ast::MethodCallExpr,
723    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
724) -> Option<Highlight> {
725    let func = sema.resolve_method_call(method_call)?;
726
727    let mut h = SymbolKind::Method.into();
728
729    let is_unsafe = is_unsafe_node(AstPtr::new(method_call).upcast::<ast::Expr>().wrap_left());
730    if is_unsafe {
731        h |= HlMod::Unsafe;
732    }
733    if func.is_async(sema.db) {
734        h |= HlMod::Async;
735    }
736    if func.is_const(sema.db) {
737        h |= HlMod::Const;
738    }
739    if func
740        .as_assoc_item(sema.db)
741        .and_then(|it| it.container_or_implemented_trait(sema.db))
742        .is_some()
743    {
744        h |= HlMod::Trait;
745    }
746
747    let def_crate = func.module(sema.db).krate(sema.db);
748    let is_from_other_crate = krate.as_ref().map_or(false, |krate| def_crate != *krate);
749    let is_from_builtin_crate = def_crate.is_builtin(sema.db);
750    let is_public = func.visibility(sema.db) == hir::Visibility::Public;
751    let is_deprecated = func.attrs(sema.db).is_deprecated();
752
753    if is_from_other_crate {
754        h |= HlMod::Library;
755    } else if is_public {
756        h |= HlMod::Public;
757    }
758
759    if is_from_builtin_crate {
760        h |= HlMod::DefaultLibrary;
761    }
762
763    if is_deprecated {
764        h |= HlMod::Deprecated;
765    }
766
767    if let Some(self_param) = func.self_param(sema.db) {
768        match self_param.access(sema.db) {
769            hir::Access::Shared => h |= HlMod::Reference,
770            hir::Access::Exclusive => {
771                h |= HlMod::Mutable;
772                h |= HlMod::Reference;
773            }
774            hir::Access::Owned => {
775                if let Some(receiver_ty) =
776                    method_call.receiver().and_then(|it| sema.type_of_expr(&it))
777                    && !receiver_ty.adjusted().is_copy(sema.db)
778                {
779                    h |= HlMod::Consuming
780                }
781            }
782        }
783    }
784    Some(h)
785}
786
787fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
788    let default = HlTag::UnresolvedReference;
789
790    let parent = match name.syntax().parent() {
791        Some(it) => it,
792        _ => return default.into(),
793    };
794
795    let tag = match parent.kind() {
796        STRUCT => SymbolKind::Struct,
797        ENUM => SymbolKind::Enum,
798        VARIANT => SymbolKind::Variant,
799        UNION => SymbolKind::Union,
800        TRAIT => SymbolKind::Trait,
801        TYPE_ALIAS => SymbolKind::TypeAlias,
802        TYPE_PARAM => SymbolKind::TypeParam,
803        RECORD_FIELD => SymbolKind::Field,
804        MODULE => SymbolKind::Module,
805        EXTERN_CRATE => SymbolKind::CrateRoot,
806        FN => SymbolKind::Function,
807        CONST => SymbolKind::Const,
808        STATIC => SymbolKind::Static,
809        IDENT_PAT => SymbolKind::Local,
810        FORMAT_ARGS_ARG => SymbolKind::Local,
811        RENAME => SymbolKind::Local,
812        MACRO_RULES => SymbolKind::Macro,
813        CONST_PARAM => SymbolKind::ConstParam,
814        SELF_PARAM => SymbolKind::SelfParam,
815        ASM_OPERAND_NAMED => SymbolKind::Local,
816        _ => return default.into(),
817    };
818
819    tag.into()
820}
821
822fn highlight_name_ref_by_syntax(
823    name: ast::NameRef,
824    sema: &Semantics<'_, RootDatabase>,
825    krate: Option<hir::Crate>,
826    is_unsafe_node: &impl Fn(AstPtr<Either<ast::Expr, ast::Pat>>) -> bool,
827) -> Highlight {
828    let default = HlTag::UnresolvedReference;
829
830    let parent = match name.syntax().parent() {
831        Some(it) => it,
832        _ => return default.into(),
833    };
834
835    match parent.kind() {
836        EXTERN_CRATE => HlTag::Symbol(SymbolKind::CrateRoot).into(),
837        METHOD_CALL_EXPR => ast::MethodCallExpr::cast(parent)
838            .and_then(|it| highlight_method_call(sema, krate, &it, is_unsafe_node))
839            .unwrap_or_else(|| SymbolKind::Method.into()),
840        FIELD_EXPR => {
841            let h = HlTag::Symbol(SymbolKind::Field);
842            let is_unsafe = ast::Expr::cast(parent)
843                .is_some_and(|it| is_unsafe_node(AstPtr::new(&it).wrap_left()));
844            if is_unsafe { h | HlMod::Unsafe } else { h.into() }
845        }
846        RECORD_EXPR_FIELD | RECORD_PAT_FIELD => HlTag::Symbol(SymbolKind::Field).into(),
847        PATH_SEGMENT => {
848            let name_based_fallback = || {
849                if name.text().chars().next().unwrap_or_default().is_uppercase() {
850                    SymbolKind::Struct.into()
851                } else {
852                    SymbolKind::Module.into()
853                }
854            };
855            let path = match parent.parent().and_then(ast::Path::cast) {
856                Some(it) => it,
857                _ => return name_based_fallback(),
858            };
859            let expr = match path.syntax().parent() {
860                Some(parent) => match_ast! {
861                    match parent {
862                        ast::PathExpr(path) => path,
863                        ast::MacroCall(_) => return SymbolKind::Macro.into(),
864                        _ => return name_based_fallback(),
865                    }
866                },
867                // within path, decide whether it is module or adt by checking for uppercase name
868                None => return name_based_fallback(),
869            };
870            let parent = match expr.syntax().parent() {
871                Some(it) => it,
872                None => return default.into(),
873            };
874
875            match parent.kind() {
876                CALL_EXPR => SymbolKind::Function.into(),
877                _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
878                    SymbolKind::Struct
879                } else {
880                    SymbolKind::Const
881                }
882                .into(),
883            }
884        }
885        ASSOC_TYPE_ARG => SymbolKind::TypeAlias.into(),
886        USE_BOUND_GENERIC_ARGS => SymbolKind::TypeParam.into(),
887        _ => default.into(),
888    }
889}
890
891fn is_consumed_lvalue(node: &SyntaxNode, local: &hir::Local, db: &RootDatabase) -> bool {
892    // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming.
893    parents_match(node.clone().into(), &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST])
894        && !local.ty(db).is_copy(db)
895}
896
897/// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly.
898fn parents_match(mut node: NodeOrToken<SyntaxNode, SyntaxToken>, mut kinds: &[SyntaxKind]) -> bool {
899    while let (Some(parent), [kind, rest @ ..]) = (node.parent(), kinds) {
900        if parent.kind() != *kind {
901            return false;
902        }
903
904        node = parent.into();
905        kinds = rest;
906    }
907
908    // Only true if we matched all expected kinds
909    kinds.is_empty()
910}
911
912fn parent_matches<N: AstNode>(token: &SyntaxToken) -> bool {
913    token.parent().is_some_and(|it| N::can_cast(it.kind()))
914}