#[cfg(test)]
mod tests;
-use hir::{Name, Semantics};
+use hir::{Name, Semantics, TypeRef, VariantDef};
use ra_ide_db::{
defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
RootDatabase,
});
}
}
- stack.pop_and_inject(false);
+ stack.pop_and_inject(None);
}
} else if let Some(string) =
element_to_highlight.as_token().cloned().and_then(ast::RawString::cast)
cloned
}
+ /// Remove the `HighlightRange` of `parent` that's currently covered by `child`.
+ fn intersect_partial(parent: &mut HighlightedRange, child: &HighlightedRange) {
+ assert!(
+ parent.range.start() <= child.range.start()
+ && parent.range.end() >= child.range.start()
+ && child.range.end() > parent.range.end()
+ );
+
+ parent.range = TextRange::new(parent.range.start(), child.range.start());
+ }
+
/// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`)
/// can only modify the last range currently on the stack.
/// Can be used to do injections that span multiple ranges, like the
/// doctest injection below.
- /// If `delete` is set to true, the parent range is deleted instead of
- /// intersected.
+ /// If `overwrite_parent` is non-optional, the highlighting of the parent range
+ /// is overwritten with the argument.
///
/// Note that `pop` can be simulated by `pop_and_inject(false)` but the
/// latter is computationally more expensive.
- fn pop_and_inject(&mut self, delete: bool) {
+ fn pop_and_inject(&mut self, overwrite_parent: Option<Highlight>) {
let mut children = self.stack.pop().unwrap();
let prev = self.stack.last_mut().unwrap();
children.sort_by_key(|range| range.range.start());
if let Some(idx) =
prev.iter().position(|parent| parent.range.contains_range(child.range))
{
+ if let Some(tag) = overwrite_parent {
+ prev[idx].highlight = tag;
+ }
+
let cloned = Self::intersect(&mut prev[idx], &child);
- let insert_idx = if delete || prev[idx].range.is_empty() {
+ let insert_idx = if prev[idx].range.is_empty() {
prev.remove(idx);
idx
} else {
idx + 1
};
prev.insert(insert_idx, child);
- if !delete && !cloned.range.is_empty() {
+ if !cloned.range.is_empty() {
prev.insert(insert_idx + 1, cloned);
}
- } else if let Some(_idx) =
- prev.iter().position(|parent| parent.range.contains(child.range.start()))
- {
- unreachable!("child range should be completely contained in parent range");
} else {
- let idx = prev
- .binary_search_by_key(&child.range.start(), |range| range.range.start())
- .unwrap_or_else(|x| x);
- prev.insert(idx, child);
+ let maybe_idx =
+ prev.iter().position(|parent| parent.range.contains(child.range.start()));
+ match (overwrite_parent, maybe_idx) {
+ (Some(_), Some(idx)) => {
+ Self::intersect_partial(&mut prev[idx], &child);
+ let insert_idx = if prev[idx].range.is_empty() {
+ prev.remove(idx);
+ idx
+ } else {
+ idx + 1
+ };
+ prev.insert(insert_idx, child);
+ }
+ (_, None) => {
+ let idx = prev
+ .binary_search_by_key(&child.range.start(), |range| range.range.start())
+ .unwrap_or_else(|x| x);
+ prev.insert(idx, child);
+ }
+ _ => {
+ unreachable!("child range should be completely contained in parent range");
+ }
+ }
}
}
}
Some(TextRange::new(range_start, range_end))
}
+fn is_possibly_unsafe(name_ref: &ast::NameRef) -> bool {
+ name_ref
+ .syntax()
+ .parent()
+ .and_then(|parent| {
+ ast::FieldExpr::cast(parent.clone())
+ .map(|_| true)
+ .or_else(|| ast::RecordPatField::cast(parent).map(|_| true))
+ })
+ .unwrap_or(false)
+}
+
fn highlight_element(
sema: &Semantics<RootDatabase>,
bindings_shadow_count: &mut FxHashMap<Name, u32>,
let db = sema.db;
let mut binding_hash = None;
let highlight: Highlight = match element.kind() {
- FN_DEF => {
+ FN => {
bindings_shadow_count.clear();
return None;
}
};
match name_kind {
+ Some(NameClass::ExternCrate(_)) => HighlightTag::Module.into(),
Some(NameClass::Definition(def)) => {
- highlight_name(db, def) | HighlightModifier::Definition
+ highlight_name(sema, db, def, None, false) | HighlightModifier::Definition
+ }
+ Some(NameClass::ConstReference(def)) => highlight_name(sema, db, def, None, false),
+ Some(NameClass::FieldShorthand { field, .. }) => {
+ let mut h = HighlightTag::Field.into();
+ if let Definition::Field(field) = field {
+ if let VariantDef::Union(_) = field.parent_def(db) {
+ h |= HighlightModifier::Unsafe;
+ }
+ }
+
+ h
}
- Some(NameClass::ConstReference(def)) => highlight_name(db, def),
- Some(NameClass::FieldShorthand { .. }) => HighlightTag::Field.into(),
None => highlight_name_by_syntax(name) | HighlightModifier::Definition,
}
}
}
NAME_REF => {
let name_ref = element.into_node().and_then(ast::NameRef::cast).unwrap();
+ let possibly_unsafe = is_possibly_unsafe(&name_ref);
match classify_name_ref(sema, &name_ref) {
Some(name_kind) => match name_kind {
+ NameRefClass::ExternCrate(_) => HighlightTag::Module.into(),
NameRefClass::Definition(def) => {
if let Definition::Local(local) = &def {
if let Some(name) = local.name(db) {
binding_hash = Some(calc_binding_hash(&name, *shadow_count))
}
};
- highlight_name(db, def)
+ highlight_name(sema, db, def, Some(name_ref), possibly_unsafe)
}
NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(),
},
- None if syntactic_name_ref_highlighting => highlight_name_ref_by_syntax(name_ref),
+ None if syntactic_name_ref_highlighting => {
+ highlight_name_ref_by_syntax(name_ref, sema)
+ }
None => HighlightTag::UnresolvedReference.into(),
}
}
_ => h,
}
}
- T![*] => {
- let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
-
- let expr = prefix_expr.expr()?;
- let ty = sema.type_of_expr(&expr)?;
- if !ty.is_raw_ptr() {
- return None;
+ p if p.is_punct() => match p {
+ T![&] => {
+ let h = HighlightTag::Operator.into();
+ let is_unsafe = element
+ .parent()
+ .and_then(ast::RefExpr::cast)
+ .map(|ref_expr| sema.is_unsafe_ref_expr(&ref_expr))
+ .unwrap_or(false);
+ if is_unsafe {
+ h | HighlightModifier::Unsafe
+ } else {
+ h
+ }
}
-
- let mut h = Highlight::new(HighlightTag::Operator);
- h |= HighlightModifier::Unsafe;
- h
- }
- T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
- Highlight::new(HighlightTag::Macro)
- }
+ T![::] | T![->] | T![=>] | T![..] | T![=] | T![@] => HighlightTag::Operator.into(),
+ T![!] if element.parent().and_then(ast::MacroCall::cast).is_some() => {
+ HighlightTag::Macro.into()
+ }
+ T![*] if element.parent().and_then(ast::PtrType::cast).is_some() => {
+ HighlightTag::Keyword.into()
+ }
+ T![*] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
+ let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
+
+ let expr = prefix_expr.expr()?;
+ let ty = sema.type_of_expr(&expr)?;
+ if ty.is_raw_ptr() {
+ HighlightTag::Operator | HighlightModifier::Unsafe
+ } else if let Some(ast::PrefixOp::Deref) = prefix_expr.op_kind() {
+ HighlightTag::Operator.into()
+ } else {
+ HighlightTag::Punctuation.into()
+ }
+ }
+ T![-] if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
+ HighlightTag::NumericLiteral.into()
+ }
+ _ if element.parent().and_then(ast::PrefixExpr::cast).is_some() => {
+ HighlightTag::Operator.into()
+ }
+ _ if element.parent().and_then(ast::BinExpr::cast).is_some() => {
+ HighlightTag::Operator.into()
+ }
+ _ if element.parent().and_then(ast::RangeExpr::cast).is_some() => {
+ HighlightTag::Operator.into()
+ }
+ _ if element.parent().and_then(ast::RangePat::cast).is_some() => {
+ HighlightTag::Operator.into()
+ }
+ _ if element.parent().and_then(ast::RestPat::cast).is_some() => {
+ HighlightTag::Operator.into()
+ }
+ _ if element.parent().and_then(ast::Attr::cast).is_some() => {
+ HighlightTag::Attribute.into()
+ }
+ _ => HighlightTag::Punctuation.into(),
+ },
k if k.is_keyword() => {
let h = Highlight::new(HighlightTag::Keyword);
| T![return]
| T![while]
| T![in] => h | HighlightModifier::ControlFlow,
- T![for] if !is_child_of_impl(element) => h | HighlightModifier::ControlFlow,
+ T![for] if !is_child_of_impl(&element) => h | HighlightModifier::ControlFlow,
T![unsafe] => h | HighlightModifier::Unsafe,
T![true] | T![false] => HighlightTag::BoolLiteral.into(),
- T![self] => HighlightTag::SelfKeyword.into(),
+ T![self] => {
+ let self_param_is_mut = element
+ .parent()
+ .and_then(ast::SelfParam::cast)
+ .and_then(|p| p.mut_token())
+ .is_some();
+ // closure to enforce lazyness
+ let self_path = || {
+ sema.resolve_path(&element.parent()?.parent().and_then(ast::Path::cast)?)
+ };
+ if self_param_is_mut
+ || matches!(self_path(),
+ Some(hir::PathResolution::Local(local))
+ if local.is_self(db)
+ && (local.is_mut(db) || local.ty(db).is_mutable_reference())
+ )
+ {
+ HighlightTag::SelfKeyword | HighlightModifier::Mutable
+ } else {
+ HighlightTag::SelfKeyword.into()
+ }
+ }
+ T![ref] => element
+ .parent()
+ .and_then(ast::IdentPat::cast)
+ .and_then(|ident_pat| {
+ if sema.is_unsafe_ident_pat(&ident_pat) {
+ Some(HighlightModifier::Unsafe)
+ } else {
+ None
+ }
+ })
+ .map(|modifier| h | modifier)
+ .unwrap_or(h),
_ => h,
}
}
}
}
-fn is_child_of_impl(element: SyntaxElement) -> bool {
+fn is_child_of_impl(element: &SyntaxElement) -> bool {
match element.parent() {
- Some(e) => e.kind() == IMPL_DEF,
+ Some(e) => e.kind() == IMPL,
_ => false,
}
}
-fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
+fn highlight_name(
+ sema: &Semantics<RootDatabase>,
+ db: &RootDatabase,
+ def: Definition,
+ name_ref: Option<ast::NameRef>,
+ possibly_unsafe: bool,
+) -> Highlight {
match def {
Definition::Macro(_) => HighlightTag::Macro,
- Definition::Field(_) => HighlightTag::Field,
+ Definition::Field(field) => {
+ let mut h = HighlightTag::Field.into();
+ if possibly_unsafe {
+ if let VariantDef::Union(_) = field.parent_def(db) {
+ h |= HighlightModifier::Unsafe;
+ }
+ }
+
+ return h;
+ }
Definition::ModuleDef(def) => match def {
hir::ModuleDef::Module(_) => HighlightTag::Module,
hir::ModuleDef::Function(func) => {
let mut h = HighlightTag::Function.into();
if func.is_unsafe(db) {
h |= HighlightModifier::Unsafe;
+ } else {
+ let is_unsafe = name_ref
+ .and_then(|name_ref| name_ref.syntax().parent())
+ .and_then(ast::MethodCallExpr::cast)
+ .map(|method_call_expr| sema.is_unsafe_method_call(method_call_expr))
+ .unwrap_or(false);
+ if is_unsafe {
+ h |= HighlightModifier::Unsafe;
+ }
}
return h;
}
let mut h = Highlight::new(HighlightTag::Static);
if s.is_mut(db) {
h |= HighlightModifier::Mutable;
+ h |= HighlightModifier::Unsafe;
}
return h;
}
},
Definition::SelfType(_) => HighlightTag::SelfType,
Definition::TypeParam(_) => HighlightTag::TypeParam,
- // FIXME: distinguish between locals and parameters
Definition::Local(local) => {
- let mut h = Highlight::new(HighlightTag::Local);
+ let tag =
+ if local.is_param(db) { HighlightTag::ValueParam } else { HighlightTag::Local };
+ let mut h = Highlight::new(tag);
if local.is_mut(db) || local.ty(db).is_mutable_reference() {
h |= HighlightModifier::Mutable;
}
};
let tag = match parent.kind() {
- STRUCT_DEF => HighlightTag::Struct,
- ENUM_DEF => HighlightTag::Enum,
- UNION_DEF => HighlightTag::Union,
- TRAIT_DEF => HighlightTag::Trait,
- TYPE_ALIAS_DEF => HighlightTag::TypeAlias,
+ STRUCT => HighlightTag::Struct,
+ ENUM => HighlightTag::Enum,
+ UNION => HighlightTag::Union,
+ TRAIT => HighlightTag::Trait,
+ TYPE_ALIAS => HighlightTag::TypeAlias,
TYPE_PARAM => HighlightTag::TypeParam,
- RECORD_FIELD_DEF => HighlightTag::Field,
+ RECORD_FIELD => HighlightTag::Field,
MODULE => HighlightTag::Module,
- FN_DEF => HighlightTag::Function,
- CONST_DEF => HighlightTag::Constant,
- STATIC_DEF => HighlightTag::Static,
- ENUM_VARIANT => HighlightTag::EnumVariant,
- BIND_PAT => HighlightTag::Local,
+ FN => HighlightTag::Function,
+ CONST => HighlightTag::Constant,
+ STATIC => HighlightTag::Static,
+ VARIANT => HighlightTag::EnumVariant,
+ IDENT_PAT => HighlightTag::Local,
_ => default,
};
tag.into()
}
-fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight {
+fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics<RootDatabase>) -> Highlight {
let default = HighlightTag::UnresolvedReference;
let parent = match name.syntax().parent() {
_ => return default.into(),
};
- let tag = match parent.kind() {
- METHOD_CALL_EXPR => HighlightTag::Function,
- FIELD_EXPR => HighlightTag::Field,
+ match parent.kind() {
+ METHOD_CALL_EXPR => {
+ let mut h = Highlight::new(HighlightTag::Function);
+ let is_unsafe = ast::MethodCallExpr::cast(parent)
+ .map(|method_call_expr| sema.is_unsafe_method_call(method_call_expr))
+ .unwrap_or(false);
+ if is_unsafe {
+ h |= HighlightModifier::Unsafe;
+ }
+
+ h
+ }
+ FIELD_EXPR => {
+ let h = HighlightTag::Field;
+ let is_union = ast::FieldExpr::cast(parent)
+ .and_then(|field_expr| {
+ let field = sema.resolve_field(&field_expr)?;
+ Some(if let VariantDef::Union(_) = field.parent_def(sema.db) {
+ true
+ } else {
+ false
+ })
+ })
+ .unwrap_or(false);
+ if is_union {
+ h | HighlightModifier::Unsafe
+ } else {
+ h.into()
+ }
+ }
PATH_SEGMENT => {
let path = match parent.parent().and_then(ast::Path::cast) {
Some(it) => it,
};
match parent.kind() {
- CALL_EXPR => HighlightTag::Function,
- _ => {
- if name.text().chars().next().unwrap_or_default().is_uppercase() {
- HighlightTag::Struct
- } else {
- HighlightTag::Constant
- }
+ CALL_EXPR => HighlightTag::Function.into(),
+ _ => if name.text().chars().next().unwrap_or_default().is_uppercase() {
+ HighlightTag::Struct.into()
+ } else {
+ HighlightTag::Constant
}
+ .into(),
}
}
- _ => default,
- };
-
- tag.into()
+ _ => default.into(),
+ }
}