diff --git a/crates/hir/src/code_model.rs b/crates/hir/src/code_model.rs index c442654dd0..31d5276b0f 100644 --- a/crates/hir/src/code_model.rs +++ b/crates/hir/src/code_model.rs @@ -670,6 +670,21 @@ impl Function { db.function_data(self.id).has_self_param } + pub fn mutability_of_self_param(self, db: &dyn HirDatabase) -> Option { + let func_data = db.function_data(self.id); + if !func_data.has_self_param { + return None; + } + + func_data.params.first().and_then(|param| { + if let TypeRef::Reference(_, mutability) = param { + Some(*mutability) + } else { + None + } + }) + } + pub fn params(self, db: &dyn HirDatabase) -> Vec { db.function_data(self.id).params.clone() } diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 8961ba8fd6..fc1c1ccd3d 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -38,7 +38,7 @@ pub use crate::{ Static, Struct, Trait, Type, TypeAlias, TypeParam, Union, VariantDef, Visibility, }, has_source::HasSource, - semantics::{original_range, PathResolution, Semantics, SemanticsScope}, + semantics::{original_range, PathResolution, SelfKind, Semantics, SemanticsScope}, }; pub use hir_def::{ diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index c693176fa4..aff0e73da7 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -6,8 +6,10 @@ use std::{cell::RefCell, fmt, iter::successors}; use base_db::{FileId, FileRange}; use hir_def::{ + lang_item::LangItemTarget, resolver::{self, HasResolver, Resolver, TypeNs}, - AsMacroCall, FunctionId, TraitId, VariantId, + src::HasSource, + AsMacroCall, FunctionId, Lookup, TraitId, VariantId, }; use hir_expand::{hygiene::Hygiene, name::AsName, ExpansionInfo}; use hir_ty::associated_type_shorthand_candidates; @@ -15,7 +17,7 @@ use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet}; use syntax::{ algo::{find_node_at_offset, skip_trivia_token}, - ast, AstNode, Direction, SyntaxNode, SyntaxToken, TextRange, TextSize, + ast, AstNode, Direction, SmolStr, SyntaxNode, SyntaxToken, TextRange, TextSize, }; use crate::{ @@ -79,6 +81,13 @@ impl PathResolution { } } +pub enum SelfKind { + Shared, + Mutable, + Consuming, + Copied, +} + /// Primary API to get semantic information, like types, from syntax trees. pub struct Semantics<'db, DB> { pub db: &'db DB, @@ -188,6 +197,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.type_of_self(param) } + pub fn method_reciever_kind(&self, call: &ast::MethodCallExpr) -> Option { + self.imp.method_receiver_kind(call) + } + pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option { self.imp.resolve_method_call(call).map(Function::from) } @@ -267,7 +280,7 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { self.imp.assert_contains_node(node) } - pub fn is_unsafe_method_call(&self, method_call_expr: ast::MethodCallExpr) -> bool { + pub fn is_unsafe_method_call(&self, method_call_expr: &ast::MethodCallExpr) -> bool { self.imp.is_unsafe_method_call(method_call_expr) } @@ -410,6 +423,35 @@ impl<'db> SemanticsImpl<'db> { self.analyze(param.syntax()).type_of_self(self.db, ¶m) } + fn method_receiver_kind(&self, call: &ast::MethodCallExpr) -> Option { + self.resolve_method_call(call).and_then(|func| { + let lookup = func.lookup(self.db.upcast()); + let src = lookup.source(self.db.upcast()); + let param_list = src.value.param_list()?; + let self_param = param_list.self_param()?; + if self_param.amp_token().is_some() { + return Some(if self_param.mut_token().is_some() { + SelfKind::Mutable + } else { + SelfKind::Shared + }); + } + + let ty = self.type_of_expr(&call.expr()?)?; + let krate = Function::from(func).krate(self.db)?; + let lang_item = self.db.lang_item(krate.id, SmolStr::new("copy")); + let copy_trait = match lang_item? { + LangItemTarget::TraitId(copy_trait) => Trait::from(copy_trait), + _ => return None, + }; + Some(if ty.impls_trait(self.db, copy_trait, &[]) { + SelfKind::Copied + } else { + SelfKind::Consuming + }) + }) + } + fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option { self.analyze(call.syntax()).resolve_method_call(self.db, call) } @@ -571,7 +613,7 @@ impl<'db> SemanticsImpl<'db> { InFile::new(file_id, node) } - pub fn is_unsafe_method_call(&self, method_call_expr: ast::MethodCallExpr) -> bool { + pub fn is_unsafe_method_call(&self, method_call_expr: &ast::MethodCallExpr) -> bool { method_call_expr .expr() .and_then(|expr| { diff --git a/crates/ide/src/syntax_highlighting.rs b/crates/ide/src/syntax_highlighting.rs index 5d7c7e8d02..9827c68afb 100644 --- a/crates/ide/src/syntax_highlighting.rs +++ b/crates/ide/src/syntax_highlighting.rs @@ -4,7 +4,7 @@ mod injection; #[cfg(test)] mod tests; -use hir::{Name, Semantics, VariantDef}; +use hir::{Mutability, Name, SelfKind, Semantics, VariantDef}; use ide_db::{ defs::{classify_name, classify_name_ref, Definition, NameClass, NameRefClass}, RootDatabase, @@ -519,27 +519,29 @@ 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) { - let shadow_count = - bindings_shadow_count.entry(name.clone()).or_default(); - binding_hash = Some(calc_binding_hash(&name, *shadow_count)) - } - }; - highlight_name(sema, db, def, Some(name_ref), possibly_unsafe) + highlight_func_by_name_ref(sema, &name_ref).unwrap_or_else(|| { + 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) { + let shadow_count = + bindings_shadow_count.entry(name.clone()).or_default(); + binding_hash = Some(calc_binding_hash(&name, *shadow_count)) + } + }; + 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, sema) } - NameRefClass::FieldShorthand { .. } => HighlightTag::Field.into(), - }, - None if syntactic_name_ref_highlighting => { - highlight_name_ref_by_syntax(name_ref, sema) + None => HighlightTag::UnresolvedReference.into(), } - None => HighlightTag::UnresolvedReference.into(), - } + }) } // Simple token-based highlighting @@ -700,6 +702,35 @@ fn is_child_of_impl(element: &SyntaxElement) -> bool { } } +fn highlight_func_by_name_ref( + sema: &Semantics, + name_ref: &ast::NameRef, +) -> Option { + let parent = name_ref.syntax().parent()?; + let method_call = ast::MethodCallExpr::cast(parent)?; + highlight_method_call(sema, &method_call) +} + +fn highlight_method_call( + sema: &Semantics, + method_call: &ast::MethodCallExpr, +) -> Option { + let func = sema.resolve_method_call(&method_call)?; + let mut h = HighlightTag::Function.into(); + if func.is_unsafe(sema.db) || sema.is_unsafe_method_call(&method_call) { + h |= HighlightModifier::Unsafe; + } + + sema.method_reciever_kind(&method_call) + .map(|self_kind| match self_kind { + SelfKind::Shared => h, + SelfKind::Mutable => h | HighlightModifier::Mutable, + SelfKind::Consuming => h | HighlightModifier::Consuming, + SelfKind::Copied => h, + }) + .or_else(|| Some(h)) +} + fn highlight_name( sema: &Semantics, db: &RootDatabase, @@ -722,20 +753,26 @@ fn highlight_name( 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; + return name_ref + .and_then(|name_ref| highlight_func_by_name_ref(sema, &name_ref)) + .unwrap_or_else(|| { + let mut h = HighlightTag::Function.into(); + if func.is_unsafe(db) { + h |= HighlightModifier::Unsafe; + } + + return if func.has_self_param(db) { + match func.mutability_of_self_param(db) { + Some(mutability) => match mutability { + Mutability::Mut => h | HighlightModifier::Mutable, + Mutability::Shared => h, + }, + None => h, + } + } else { + h + }; + }); } hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, @@ -807,15 +844,9 @@ fn highlight_name_ref_by_syntax(name: ast::NameRef, sema: &Semantics { - 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 + return ast::MethodCallExpr::cast(parent) + .and_then(|method_call| highlight_method_call(sema, &method_call)) + .unwrap_or_else(|| HighlightTag::Function.into()); } FIELD_EXPR => { let h = HighlightTag::Field; diff --git a/crates/ide/src/syntax_highlighting/tags.rs b/crates/ide/src/syntax_highlighting/tags.rs index 49ec94bdcd..c1b817f06c 100644 --- a/crates/ide/src/syntax_highlighting/tags.rs +++ b/crates/ide/src/syntax_highlighting/tags.rs @@ -62,6 +62,7 @@ pub enum HighlightModifier { Documentation, Injected, Mutable, + Consuming, Unsafe, } @@ -119,6 +120,7 @@ impl HighlightModifier { HighlightModifier::Documentation, HighlightModifier::Injected, HighlightModifier::Mutable, + HighlightModifier::Consuming, HighlightModifier::Unsafe, ]; @@ -130,6 +132,7 @@ impl HighlightModifier { HighlightModifier::Documentation => "documentation", HighlightModifier::Injected => "injected", HighlightModifier::Mutable => "mutable", + HighlightModifier::Consuming => "consuming", HighlightModifier::Unsafe => "unsafe", } } diff --git a/crates/ide/src/syntax_highlighting/tests.rs b/crates/ide/src/syntax_highlighting/tests.rs index 94f37d773f..ccb76f5529 100644 --- a/crates/ide/src/syntax_highlighting/tests.rs +++ b/crates/ide/src/syntax_highlighting/tests.rs @@ -12,6 +12,12 @@ fn test_highlighting() { use inner::{self as inner_mod}; mod inner {} +// Needed for function consuming vs normal +pub mod marker { + #[lang = "copy"] + pub trait Copy {} +} + #[derive(Clone, Debug)] struct Foo { pub x: i32, @@ -36,6 +42,29 @@ impl Foo { fn qux(&mut self) { self.x = 0; } + + fn quop(&self) -> i32 { + self.x + } +} + +#[derive(Copy, Clone)] +struct FooCopy { + x: u32, +} + +impl FooCopy { + fn baz(self) -> u32 { + self.x + } + + fn qux(&mut self) { + self.x = 0; + } + + fn quop(&self) -> u32 { + self.x + } } static mut STATIC_MUT: i32 = 0; @@ -87,6 +116,16 @@ fn main() { let Foo { x: z, y } = Foo { x: z, y }; y; + + let mut foo = Foo { x, y: x }; + foo.quop(); + foo.qux(); + foo.baz(); + + let mut copy = FooCopy { x }; + copy.quop(); + copy.qux(); + copy.baz(); } enum Option { diff --git a/crates/ide/test_data/highlighting.html b/crates/ide/test_data/highlighting.html index 8e0160eee5..a6b79589bf 100644 --- a/crates/ide/test_data/highlighting.html +++ b/crates/ide/test_data/highlighting.html @@ -38,6 +38,12 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
use inner::{self as inner_mod};
 mod inner {}
 
+// Needed for function consuming vs normal
+pub mod marker {
+    #[lang = "copy"]
+    pub trait Copy {}
+}
+
 #[derive(Clone, Debug)]
 struct Foo {
     pub x: i32,
@@ -59,9 +65,32 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
         self.x
     }
 
-    fn qux(&mut self) {
+    fn qux(&mut self) {
         self.x = 0;
     }
+
+    fn quop(&self) -> i32 {
+        self.x
+    }
+}
+
+#[derive(Copy, Clone)]
+struct FooCopy {
+    x: u32,
+}
+
+impl FooCopy {
+    fn baz(self) -> u32 {
+        self.x
+    }
+
+    fn qux(&mut self) {
+        self.x = 0;
+    }
+
+    fn quop(&self) -> u32 {
+        self.x
+    }
 }
 
 static mut STATIC_MUT: i32 = 0;
@@ -113,6 +142,16 @@ pre                 { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd
     let Foo { x: z, y } = Foo { x: z, y };
 
     y;
+
+    let mut foo = Foo { x, y: x };
+    foo.quop();
+    foo.qux();
+    foo.baz();
+
+    let mut copy = FooCopy { x };
+    copy.quop();
+    copy.qux();
+    copy.baz();
 }
 
 enum Option<T> {
diff --git a/crates/rust-analyzer/src/semantic_tokens.rs b/crates/rust-analyzer/src/semantic_tokens.rs
index afc38fb4e8..9db7b8af58 100644
--- a/crates/rust-analyzer/src/semantic_tokens.rs
+++ b/crates/rust-analyzer/src/semantic_tokens.rs
@@ -75,6 +75,7 @@ define_semantic_token_modifiers![
     (CONTROL_FLOW, "controlFlow"),
     (INJECTED, "injected"),
     (MUTABLE, "mutable"),
+    (CONSUMING, "consuming"),
     (UNSAFE, "unsafe"),
     (ATTRIBUTE_MODIFIER, "attribute"),
 ];
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 643dcb4fcb..a5191c16e7 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -400,6 +400,7 @@ fn semantic_token_type_and_modifiers(
             HighlightModifier::Injected => semantic_tokens::INJECTED,
             HighlightModifier::ControlFlow => semantic_tokens::CONTROL_FLOW,
             HighlightModifier::Mutable => semantic_tokens::MUTABLE,
+            HighlightModifier::Consuming => semantic_tokens::CONSUMING,
             HighlightModifier::Unsafe => semantic_tokens::UNSAFE,
         };
         mods |= modifier;