]> git.lizzy.rs Git - rust.git/blobdiff - crates/ra_ide/src/syntax_highlighting.rs
Remove token tree from ReprKind::Other variant, expose ReprKind higher, remove debug...
[rust.git] / crates / ra_ide / src / syntax_highlighting.rs
index 854b6cc6d618412b050df7914e2749e0f80313ee..b82b51efda5b419215c631c0081771acdca58d29 100644 (file)
@@ -4,7 +4,7 @@
 #[cfg(test)]
 mod tests;
 
-use hir::{Name, Semantics};
+use hir::{Name, Semantics, VariantDef};
 use ra_ide_db::{
     defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass},
     RootDatabase,
@@ -121,7 +121,6 @@ pub(crate) fn highlight(
                 assert!(current_macro_call == Some(mc));
                 current_macro_call = None;
                 format_string = None;
-                continue;
             }
             _ => (),
         }
@@ -150,7 +149,7 @@ pub(crate) fn highlight(
 
         let range = element.text_range();
 
-        let element_to_highlight = if current_macro_call.is_some() {
+        let element_to_highlight = if current_macro_call.is_some() && element.kind() != COMMENT {
             // Inside a macro -- expand it first
             let token = match element.clone().into_token() {
                 Some(it) if it.parent().kind() == TOKEN_TREE => it,
@@ -237,7 +236,7 @@ pub(crate) fn highlight(
                             });
                         }
                     }
-                    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)
@@ -325,16 +324,27 @@ fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> Highlig
         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());
@@ -344,26 +354,45 @@ fn pop_and_inject(&mut self, delete: bool) {
             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");
+                    }
+                }
             }
         }
     }
@@ -426,6 +455,18 @@ fn macro_call_range(macro_call: &ast::MacroCall) -> Option<TextRange> {
     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>,
@@ -435,7 +476,7 @@ fn highlight_element(
     let db = sema.db;
     let mut binding_hash = None;
     let highlight: Highlight = match element.kind() {
-        FN_DEF => {
+        FN => {
             bindings_shadow_count.clear();
             return None;
         }
@@ -454,11 +495,21 @@ fn highlight_element(
             };
 
             match name_kind {
+                Some(NameClass::ExternCrate(_)) => HighlightTag::Module.into(),
                 Some(NameClass::Definition(def)) => {
-                    highlight_name(db, def) | HighlightModifier::Definition
+                    highlight_name(db, def, false) | HighlightModifier::Definition
+                }
+                Some(NameClass::ConstReference(def)) => highlight_name(db, def, 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,
             }
         }
@@ -469,8 +520,10 @@ fn highlight_element(
         }
         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) {
@@ -479,17 +532,26 @@ fn highlight_element(
                                 binding_hash = Some(calc_binding_hash(&name, *shadow_count))
                             }
                         };
-                        highlight_name(db, def)
+                        highlight_name(db, def, 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(),
             }
         }
 
         // Simple token-based highlighting
-        COMMENT => HighlightTag::Comment.into(),
+        COMMENT => {
+            let comment = element.into_token().and_then(ast::Comment::cast)?;
+            let h = HighlightTag::Comment;
+            match comment.kind().doc {
+                Some(_) => h | HighlightModifier::Documentation,
+                None => h.into(),
+            }
+        }
         STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => HighlightTag::StringLiteral.into(),
         ATTR => HighlightTag::Attribute.into(),
         INT_NUMBER | FLOAT_NUMBER => HighlightTag::NumericLiteral.into(),
@@ -503,22 +565,70 @@ fn highlight_element(
                 _ => h,
             }
         }
-        T![*] => {
-            let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?;
+        REF_EXPR => {
+            let ref_expr = element.into_node().and_then(ast::RefExpr::cast)?;
+            let expr = ref_expr.expr()?;
+            let field_expr = match expr {
+                ast::Expr::FieldExpr(fe) => fe,
+                _ => return None,
+            };
 
-            let expr = prefix_expr.expr()?;
+            let expr = field_expr.expr()?;
             let ty = sema.type_of_expr(&expr)?;
-            if !ty.is_raw_ptr() {
+            if !ty.is_packed(db) {
                 return None;
             }
 
-            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)
+            // FIXME account for alignment... somehow
+
+            Highlight::new(HighlightTag::Operator) | HighlightModifier::Unsafe
         }
+        p if p.is_punct() => match p {
+            T![::] | 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);
@@ -532,10 +642,31 @@ fn highlight_element(
                 | 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()
+                    }
+                }
                 _ => h,
             }
         }
@@ -558,17 +689,26 @@ fn hash<T: std::hash::Hash + std::fmt::Debug>(x: T) -> u64 {
     }
 }
 
-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(db: &RootDatabase, def: Definition, 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) => {
@@ -590,15 +730,17 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight {
                 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;
             }
@@ -617,26 +759,26 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight {
     };
 
     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() {
@@ -646,7 +788,20 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef) -> Highlight {
 
     let tag = match parent.kind() {
         METHOD_CALL_EXPR => HighlightTag::Function,
-        FIELD_EXPR => HighlightTag::Field,
+        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);
+            return if is_union { h | HighlightModifier::Unsafe } else { h.into() };
+        }
         PATH_SEGMENT => {
             let path = match parent.parent().and_then(ast::Path::cast) {
                 Some(it) => it,