//! Computes color for a single element. use either::Either; use hir::{AsAssocItem, HasVisibility, MacroFileIdExt, Semantics}; use ide_db::{ defs::{Definition, IdentClass, NameClass, NameRefClass}, FxHashMap, RootDatabase, SymbolKind, }; use stdx::hash_once; use syntax::{ ast, match_ast, AstNode, AstToken, NodeOrToken, SyntaxKind::{self, *}, SyntaxNode, SyntaxToken, T, }; use crate::{ syntax_highlighting::tags::{HlOperator, HlPunct}, Highlight, HlMod, HlTag, }; pub(super) fn token(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Option { if let Some(comment) = ast::Comment::cast(token.clone()) { let h = HlTag::Comment; return Some(match comment.kind().doc { Some(_) => h | HlMod::Documentation, None => h.into(), }); } let highlight: Highlight = match token.kind() { STRING | BYTE_STRING | C_STRING => HlTag::StringLiteral.into(), INT_NUMBER if token.parent_ancestors().nth(1).map(|it| it.kind()) == Some(FIELD_EXPR) => { SymbolKind::Field.into() } INT_NUMBER | FLOAT_NUMBER => HlTag::NumericLiteral.into(), BYTE => HlTag::ByteLiteral.into(), CHAR => HlTag::CharLiteral.into(), IDENT if token.parent().and_then(ast::TokenTree::cast).is_some() => { // from this point on we are inside a token tree, this only happens for identifiers // that were not mapped down into macro invocations HlTag::None.into() } p if p.is_punct() => punctuation(sema, token, p), k if k.is_keyword() => keyword(sema, token, k)?, _ => return None, }; Some(highlight) } pub(super) fn name_like( sema: &Semantics<'_, RootDatabase>, krate: hir::Crate, bindings_shadow_count: &mut FxHashMap, syntactic_name_ref_highlighting: bool, name_like: ast::NameLike, ) -> Option<(Highlight, Option)> { let mut binding_hash = None; let highlight = match name_like { ast::NameLike::NameRef(name_ref) => highlight_name_ref( sema, krate, bindings_shadow_count, &mut binding_hash, syntactic_name_ref_highlighting, name_ref, ), ast::NameLike::Name(name) => { highlight_name(sema, bindings_shadow_count, &mut binding_hash, krate, name) } ast::NameLike::Lifetime(lifetime) => match IdentClass::classify_lifetime(sema, &lifetime) { Some(IdentClass::NameClass(NameClass::Definition(def))) => { highlight_def(sema, krate, def) | HlMod::Definition } Some(IdentClass::NameRefClass(NameRefClass::Definition(def))) => { highlight_def(sema, krate, def) } // FIXME: Fallback for 'static and '_, as we do not resolve these yet _ => SymbolKind::LifetimeParam.into(), }, }; Some((highlight, binding_hash)) } fn punctuation( sema: &Semantics<'_, RootDatabase>, token: SyntaxToken, kind: SyntaxKind, ) -> Highlight { let parent = token.parent(); let parent_kind = parent.as_ref().map_or(EOF, SyntaxNode::kind); match (kind, parent_kind) { (T![?], TRY_EXPR) => HlTag::Operator(HlOperator::Other) | HlMod::ControlFlow, (T![&], BIN_EXPR) => HlOperator::Bitwise.into(), (T![&], REF_EXPR) => { let h = HlTag::Operator(HlOperator::Other).into(); let is_unsafe = parent .and_then(ast::RefExpr::cast) .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr)); if let Some(true) = is_unsafe { h | HlMod::Unsafe } else { h } } (T![::] | T![->] | T![=>] | T![..] | T![..=] | T![=] | T![@] | T![.], _) => { HlOperator::Other.into() } (T![!], MACRO_CALL | MACRO_RULES) => HlPunct::MacroBang.into(), (T![!], NEVER_TYPE) => HlTag::BuiltinType.into(), (T![!], PREFIX_EXPR) => HlOperator::Logical.into(), (T![*], PTR_TYPE) => HlTag::Keyword.into(), (T![*], PREFIX_EXPR) => { let is_raw_ptr = (|| { let prefix_expr = parent.and_then(ast::PrefixExpr::cast)?; let expr = prefix_expr.expr()?; sema.type_of_expr(&expr)?.original.is_raw_ptr().then_some(()) })(); if let Some(()) = is_raw_ptr { HlTag::Operator(HlOperator::Other) | HlMod::Unsafe } else { HlOperator::Other.into() } } (T![-], PREFIX_EXPR) => { let prefix_expr = parent.and_then(ast::PrefixExpr::cast).and_then(|e| e.expr()); match prefix_expr { Some(ast::Expr::Literal(_)) => HlTag::NumericLiteral, _ => HlTag::Operator(HlOperator::Other), } .into() } (T![+] | T![-] | T![*] | T![/] | T![%], BIN_EXPR) => HlOperator::Arithmetic.into(), (T![+=] | T![-=] | T![*=] | T![/=] | T![%=], BIN_EXPR) => { Highlight::from(HlOperator::Arithmetic) | HlMod::Mutable } (T![|] | T![&] | T![^] | T![>>] | T![<<], BIN_EXPR) => HlOperator::Bitwise.into(), (T![|=] | T![&=] | T![^=] | T![>>=] | T![<<=], BIN_EXPR) => { Highlight::from(HlOperator::Bitwise) | HlMod::Mutable } (T![&&] | T![||], BIN_EXPR) => HlOperator::Logical.into(), (T![>] | T![<] | T![==] | T![>=] | T![<=] | T![!=], BIN_EXPR) => { HlOperator::Comparison.into() } (_, ATTR) => HlTag::AttributeBracket.into(), (kind, _) => match kind { T!['['] | T![']'] => HlPunct::Bracket, T!['{'] | T!['}'] => HlPunct::Brace, T!['('] | T![')'] => HlPunct::Parenthesis, T![<] | T![>] => HlPunct::Angle, T![,] => HlPunct::Comma, T![:] => HlPunct::Colon, T![;] => HlPunct::Semi, T![.] => HlPunct::Dot, _ => HlPunct::Other, } .into(), } } fn keyword( sema: &Semantics<'_, RootDatabase>, token: SyntaxToken, kind: SyntaxKind, ) -> Option { let h = Highlight::new(HlTag::Keyword); let h = match kind { T![await] => h | HlMod::Async | HlMod::ControlFlow, T![async] => h | HlMod::Async, T![break] | T![continue] | T![else] | T![if] | T![in] | T![loop] | T![match] | T![return] | T![while] | T![yield] => h | HlMod::ControlFlow, T![do] | T![yeet] if parent_matches::(&token) => h | HlMod::ControlFlow, T![for] if parent_matches::(&token) => h | HlMod::ControlFlow, T![unsafe] => h | HlMod::Unsafe, T![const] if token.parent().map_or(false, |it| { matches!( it.kind(), SyntaxKind::CONST | SyntaxKind::FN | SyntaxKind::IMPL | SyntaxKind::BLOCK_EXPR | SyntaxKind::CLOSURE_EXPR | SyntaxKind::FN_PTR_TYPE | SyntaxKind::TYPE_BOUND | SyntaxKind::CONST_BLOCK_PAT ) }) => { h | HlMod::Const } T![true] | T![false] => HlTag::BoolLiteral.into(), // crate is handled just as a token if it's in an `extern crate` T![crate] if parent_matches::(&token) => h, // self, crate, super and `Self` are handled as either a Name or NameRef already, unless they // are inside unmapped token trees T![self] | T![crate] | T![super] | T![Self] if parent_matches::(&token) => { return None } T![self] if parent_matches::(&token) => return None, T![ref] => match token.parent().and_then(ast::IdentPat::cast) { Some(ident) if sema.is_unsafe_ident_pat(&ident) => h | HlMod::Unsafe, _ => h, }, _ => h, }; Some(h) } fn highlight_name_ref( sema: &Semantics<'_, RootDatabase>, krate: hir::Crate, bindings_shadow_count: &mut FxHashMap, binding_hash: &mut Option, syntactic_name_ref_highlighting: bool, name_ref: ast::NameRef, ) -> Highlight { let db = sema.db; if let Some(res) = highlight_method_call_by_name_ref(sema, krate, &name_ref) { return res; } let name_class = match NameRefClass::classify(sema, &name_ref) { Some(name_kind) => name_kind, None if syntactic_name_ref_highlighting => { return highlight_name_ref_by_syntax(name_ref, sema, krate) } // FIXME: This is required for helper attributes used by proc-macros, as those do not map down // to anything when used. // We can fix this for derive attributes since derive helpers are recorded, but not for // general attributes. None if name_ref.syntax().ancestors().any(|it| it.kind() == ATTR) && !sema .hir_file_for(name_ref.syntax()) .macro_file() .map_or(false, |it| it.is_derive_attr_pseudo_expansion(sema.db)) => { return HlTag::Symbol(SymbolKind::Attribute).into(); } None => return HlTag::UnresolvedReference.into(), }; let mut h = match name_class { NameRefClass::Definition(def) => { if let Definition::Local(local) = &def { let name = local.name(db); let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); *binding_hash = Some(calc_binding_hash(&name, *shadow_count)) }; let mut h = highlight_def(sema, krate, def); match def { Definition::Local(local) if is_consumed_lvalue(name_ref.syntax(), &local, db) => { h |= HlMod::Consuming; } Definition::Trait(trait_) if trait_.is_unsafe(db) => { if ast::Impl::for_trait_name_ref(&name_ref) .map_or(false, |impl_| impl_.unsafe_token().is_some()) { h |= HlMod::Unsafe; } } Definition::Field(field) => { if let Some(parent) = name_ref.syntax().parent() { if matches!(parent.kind(), FIELD_EXPR | RECORD_PAT_FIELD) { if let hir::VariantDef::Union(_) = field.parent_def(db) { h |= HlMod::Unsafe; } } } } Definition::Macro(_) => { if let Some(macro_call) = ide_db::syntax_helpers::node_ext::full_path_of_name_ref(&name_ref) .and_then(|it| it.syntax().parent().and_then(ast::MacroCall::cast)) { if sema.is_unsafe_macro_call(¯o_call) { h |= HlMod::Unsafe; } } } _ => (), } h } NameRefClass::FieldShorthand { field_ref, .. } => { highlight_def(sema, krate, field_ref.into()) } NameRefClass::ExternCrateShorthand { decl, krate: resolved_krate } => { let mut h = HlTag::Symbol(SymbolKind::Module).into(); if resolved_krate != krate { h |= HlMod::Library } let is_public = decl.visibility(db) == hir::Visibility::Public; if is_public { h |= HlMod::Public } let is_from_builtin_crate = resolved_krate.is_builtin(db); if is_from_builtin_crate { h |= HlMod::DefaultLibrary; } h |= HlMod::CrateRoot; h } }; h.tag = match name_ref.token_kind() { T![Self] => HlTag::Symbol(SymbolKind::SelfType), T![self] => HlTag::Symbol(SymbolKind::SelfParam), T![super] | T![crate] => HlTag::Keyword, _ => h.tag, }; h } fn highlight_name( sema: &Semantics<'_, RootDatabase>, bindings_shadow_count: &mut FxHashMap, binding_hash: &mut Option, krate: hir::Crate, name: ast::Name, ) -> Highlight { let name_kind = NameClass::classify(sema, &name); if let Some(NameClass::Definition(Definition::Local(local))) = &name_kind { let name = local.name(sema.db); let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); *shadow_count += 1; *binding_hash = Some(calc_binding_hash(&name, *shadow_count)) }; match name_kind { Some(NameClass::Definition(def)) => { let mut h = highlight_def(sema, krate, def) | HlMod::Definition; if let Definition::Trait(trait_) = &def { if trait_.is_unsafe(sema.db) { h |= HlMod::Unsafe; } } h } Some(NameClass::ConstReference(def)) => highlight_def(sema, krate, def), Some(NameClass::PatFieldShorthand { field_ref, .. }) => { let mut h = HlTag::Symbol(SymbolKind::Field).into(); if let hir::VariantDef::Union(_) = field_ref.parent_def(sema.db) { h |= HlMod::Unsafe; } h } None => highlight_name_by_syntax(name) | HlMod::Definition, } } fn calc_binding_hash(name: &hir::Name, shadow_count: u32) -> u64 { hash_once::((name.as_str(), shadow_count)) } pub(super) fn highlight_def( sema: &Semantics<'_, RootDatabase>, krate: hir::Crate, def: Definition, ) -> Highlight { let db = sema.db; let mut h = match def { Definition::Macro(m) => Highlight::new(HlTag::Symbol(m.kind(sema.db).into())), Definition::Field(_) | Definition::TupleField(_) => { Highlight::new(HlTag::Symbol(SymbolKind::Field)) } Definition::Module(module) => { let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Module)); if module.is_crate_root() { h |= HlMod::CrateRoot; } h } Definition::Function(func) => { let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Function)); if let Some(item) = func.as_assoc_item(db) { h |= HlMod::Associated; match func.self_param(db) { Some(sp) => { h.tag = HlTag::Symbol(SymbolKind::Method); match sp.access(db) { hir::Access::Exclusive => { h |= HlMod::Mutable; h |= HlMod::Reference; } hir::Access::Shared => h |= HlMod::Reference, hir::Access::Owned => h |= HlMod::Consuming, } } None => h |= HlMod::Static, } match item.container(db) { hir::AssocItemContainer::Impl(i) => { if i.trait_(db).is_some() { h |= HlMod::Trait; } } hir::AssocItemContainer::Trait(_t) => { h |= HlMod::Trait; } } } if func.is_unsafe_to_call(db) { h |= HlMod::Unsafe; } if func.is_async(db) { h |= HlMod::Async; } if func.is_const(db) { h |= HlMod::Const; } h } Definition::Adt(adt) => { let h = match adt { hir::Adt::Struct(_) => HlTag::Symbol(SymbolKind::Struct), hir::Adt::Enum(_) => HlTag::Symbol(SymbolKind::Enum), hir::Adt::Union(_) => HlTag::Symbol(SymbolKind::Union), }; Highlight::new(h) } Definition::Variant(_) => Highlight::new(HlTag::Symbol(SymbolKind::Variant)), Definition::Const(konst) => { let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Const)) | HlMod::Const; if let Some(item) = konst.as_assoc_item(db) { h |= HlMod::Associated; h |= HlMod::Static; match item.container(db) { hir::AssocItemContainer::Impl(i) => { if i.trait_(db).is_some() { h |= HlMod::Trait; } } hir::AssocItemContainer::Trait(_t) => { h |= HlMod::Trait; } } } h } Definition::Trait(_) => Highlight::new(HlTag::Symbol(SymbolKind::Trait)), Definition::TraitAlias(_) => Highlight::new(HlTag::Symbol(SymbolKind::TraitAlias)), Definition::TypeAlias(type_) => { let mut h = Highlight::new(HlTag::Symbol(SymbolKind::TypeAlias)); if let Some(item) = type_.as_assoc_item(db) { h |= HlMod::Associated; h |= HlMod::Static; match item.container(db) { hir::AssocItemContainer::Impl(i) => { if i.trait_(db).is_some() { h |= HlMod::Trait; } } hir::AssocItemContainer::Trait(_t) => { h |= HlMod::Trait; } } } h } Definition::BuiltinType(_) => Highlight::new(HlTag::BuiltinType), Definition::BuiltinLifetime(_) => Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)), Definition::Static(s) => { let mut h = Highlight::new(HlTag::Symbol(SymbolKind::Static)); if s.is_mut(db) { h |= HlMod::Mutable; h |= HlMod::Unsafe; } h } Definition::SelfType(_) => Highlight::new(HlTag::Symbol(SymbolKind::Impl)), Definition::GenericParam(it) => match it { hir::GenericParam::TypeParam(_) => Highlight::new(HlTag::Symbol(SymbolKind::TypeParam)), hir::GenericParam::ConstParam(_) => { Highlight::new(HlTag::Symbol(SymbolKind::ConstParam)) | HlMod::Const } hir::GenericParam::LifetimeParam(_) => { Highlight::new(HlTag::Symbol(SymbolKind::LifetimeParam)) } }, Definition::Local(local) => { let tag = if local.is_self(db) { HlTag::Symbol(SymbolKind::SelfParam) } else if local.is_param(db) { HlTag::Symbol(SymbolKind::ValueParam) } else { HlTag::Symbol(SymbolKind::Local) }; let mut h = Highlight::new(tag); let ty = local.ty(db); if local.is_mut(db) || ty.is_mutable_reference() { h |= HlMod::Mutable; } if local.is_ref(db) || ty.is_reference() { h |= HlMod::Reference; } if ty.as_callable(db).is_some() || ty.impls_fnonce(db) { h |= HlMod::Callable; } h } Definition::ExternCrateDecl(extern_crate) => { let mut highlight = Highlight::new(HlTag::Symbol(SymbolKind::Module)) | HlMod::CrateRoot; if extern_crate.alias(db).is_none() { highlight |= HlMod::Library; } highlight } Definition::Label(_) => Highlight::new(HlTag::Symbol(SymbolKind::Label)), Definition::BuiltinAttr(_) => Highlight::new(HlTag::Symbol(SymbolKind::BuiltinAttr)), Definition::ToolModule(_) => Highlight::new(HlTag::Symbol(SymbolKind::ToolModule)), Definition::DeriveHelper(_) => Highlight::new(HlTag::Symbol(SymbolKind::DeriveHelper)), }; let def_crate = def.krate(db); let is_from_other_crate = def_crate != Some(krate); let is_from_builtin_crate = def_crate.map_or(false, |def_crate| def_crate.is_builtin(db)); let is_builtin = matches!( def, Definition::BuiltinType(_) | Definition::BuiltinLifetime(_) | Definition::BuiltinAttr(_) ); match is_from_other_crate { true if !is_builtin => h |= HlMod::Library, false if def.visibility(db) == Some(hir::Visibility::Public) => h |= HlMod::Public, _ => (), } if is_from_builtin_crate { h |= HlMod::DefaultLibrary; } h } fn highlight_method_call_by_name_ref( sema: &Semantics<'_, RootDatabase>, krate: hir::Crate, name_ref: &ast::NameRef, ) -> Option { let mc = name_ref.syntax().parent().and_then(ast::MethodCallExpr::cast)?; highlight_method_call(sema, krate, &mc) } fn highlight_method_call( sema: &Semantics<'_, RootDatabase>, krate: hir::Crate, method_call: &ast::MethodCallExpr, ) -> Option { let func = sema.resolve_method_call(method_call)?; let mut h = SymbolKind::Method.into(); if func.is_unsafe_to_call(sema.db) || sema.is_unsafe_method_call(method_call) { h |= HlMod::Unsafe; } if func.is_async(sema.db) { h |= HlMod::Async; } if func.is_const(sema.db) { h |= HlMod::Const; } if func .as_assoc_item(sema.db) .and_then(|it| it.container_or_implemented_trait(sema.db)) .is_some() { h |= HlMod::Trait; } let def_crate = func.module(sema.db).krate(); let is_from_other_crate = def_crate != krate; let is_from_builtin_crate = def_crate.is_builtin(sema.db); let is_public = func.visibility(sema.db) == hir::Visibility::Public; if is_from_other_crate { h |= HlMod::Library; } else if is_public { h |= HlMod::Public; } if is_from_builtin_crate { h |= HlMod::DefaultLibrary; } if let Some(self_param) = func.self_param(sema.db) { match self_param.access(sema.db) { hir::Access::Shared => h |= HlMod::Reference, hir::Access::Exclusive => { h |= HlMod::Mutable; h |= HlMod::Reference; } hir::Access::Owned => { if let Some(receiver_ty) = method_call.receiver().and_then(|it| sema.type_of_expr(&it)) { if !receiver_ty.adjusted().is_copy(sema.db) { h |= HlMod::Consuming } } } } } Some(h) } fn highlight_name_by_syntax(name: ast::Name) -> Highlight { let default = HlTag::UnresolvedReference; let parent = match name.syntax().parent() { Some(it) => it, _ => return default.into(), }; let tag = match parent.kind() { STRUCT => SymbolKind::Struct, ENUM => SymbolKind::Enum, VARIANT => SymbolKind::Variant, UNION => SymbolKind::Union, TRAIT => SymbolKind::Trait, TYPE_ALIAS => SymbolKind::TypeAlias, TYPE_PARAM => SymbolKind::TypeParam, RECORD_FIELD => SymbolKind::Field, MODULE => SymbolKind::Module, FN => SymbolKind::Function, CONST => SymbolKind::Const, STATIC => SymbolKind::Static, IDENT_PAT => SymbolKind::Local, FORMAT_ARGS_ARG => SymbolKind::Local, _ => return default.into(), }; tag.into() } fn highlight_name_ref_by_syntax( name: ast::NameRef, sema: &Semantics<'_, RootDatabase>, krate: hir::Crate, ) -> Highlight { let default = HlTag::UnresolvedReference; let parent = match name.syntax().parent() { Some(it) => it, _ => return default.into(), }; match parent.kind() { METHOD_CALL_EXPR => ast::MethodCallExpr::cast(parent) .and_then(|it| highlight_method_call(sema, krate, &it)) .unwrap_or_else(|| SymbolKind::Method.into()), FIELD_EXPR => { let h = HlTag::Symbol(SymbolKind::Field); let is_union = ast::FieldExpr::cast(parent) .and_then(|field_expr| sema.resolve_field(&field_expr)) .map_or(false, |field| match field { Either::Left(field) => { matches!(field.parent_def(sema.db), hir::VariantDef::Union(_)) } Either::Right(_) => false, }); if is_union { h | HlMod::Unsafe } else { h.into() } } PATH_SEGMENT => { let name_based_fallback = || { if name.text().chars().next().unwrap_or_default().is_uppercase() { SymbolKind::Struct.into() } else { SymbolKind::Module.into() } }; let path = match parent.parent().and_then(ast::Path::cast) { Some(it) => it, _ => return name_based_fallback(), }; let expr = match path.syntax().parent() { Some(parent) => match_ast! { match parent { ast::PathExpr(path) => path, ast::MacroCall(_) => return SymbolKind::Macro.into(), _ => return name_based_fallback(), } }, // within path, decide whether it is module or adt by checking for uppercase name None => return name_based_fallback(), }; let parent = match expr.syntax().parent() { Some(it) => it, None => return default.into(), }; match parent.kind() { CALL_EXPR => SymbolKind::Function.into(), _ => if name.text().chars().next().unwrap_or_default().is_uppercase() { SymbolKind::Struct } else { SymbolKind::Const } .into(), } } _ => default.into(), } } fn is_consumed_lvalue(node: &SyntaxNode, local: &hir::Local, db: &RootDatabase) -> bool { // When lvalues are passed as arguments and they're not Copy, then mark them as Consuming. parents_match(node.clone().into(), &[PATH_SEGMENT, PATH, PATH_EXPR, ARG_LIST]) && !local.ty(db).is_copy(db) } /// Returns true if the parent nodes of `node` all match the `SyntaxKind`s in `kinds` exactly. fn parents_match(mut node: NodeOrToken, mut kinds: &[SyntaxKind]) -> bool { while let (Some(parent), [kind, rest @ ..]) = (node.parent(), kinds) { if parent.kind() != *kind { return false; } node = parent.into(); kinds = rest; } // Only true if we matched all expected kinds kinds.is_empty() } fn parent_matches(token: &SyntaxToken) -> bool { token.parent().map_or(false, |it| N::can_cast(it.kind())) }