Skip to main content

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