diff --git a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs index 8f93dad06e3f5..f13088cd90eff 100644 --- a/src/tools/rust-analyzer/crates/flycheck/src/lib.rs +++ b/src/tools/rust-analyzer/crates/flycheck/src/lib.rs @@ -360,13 +360,20 @@ impl FlycheckActor { } } -struct JodChild(GroupChild); +struct JodGroupChild(GroupChild); + +impl Drop for JodGroupChild { + fn drop(&mut self) { + _ = self.0.kill(); + _ = self.0.wait(); + } +} /// A handle to a cargo process used for fly-checking. struct CargoHandle { /// The handle to the actual cargo process. As we cannot cancel directly from with /// a read syscall dropping and therefore terminating the process is our best option. - child: JodChild, + child: JodGroupChild, thread: jod_thread::JoinHandle>, receiver: Receiver, } @@ -374,7 +381,7 @@ struct CargoHandle { impl CargoHandle { fn spawn(mut command: Command) -> std::io::Result { command.stdout(Stdio::piped()).stderr(Stdio::piped()).stdin(Stdio::null()); - let mut child = command.group_spawn().map(JodChild)?; + let mut child = command.group_spawn().map(JodGroupChild)?; let stdout = child.0.inner().stdout.take().unwrap(); let stderr = child.0.inner().stderr.take().unwrap(); diff --git a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs index c04cd1651926d..bb45266725c6b 100644 --- a/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-def/src/macro_expansion_tests/builtin_fn_macro.rs @@ -163,7 +163,8 @@ macro_rules! compile_error { } // This expands to nothing (since it's in item position), but emits an error. -compile_error!("error!"); +compile_error!("error, with an escaped quote: \""); +compile_error!(r"this is a raw string"); "#, expect![[r##" #[rustc_builtin_macro] @@ -172,7 +173,8 @@ macro_rules! compile_error { ($msg:expr,) => ({ /* compiler built-in */ }) } -/* error: error! */ +/* error: error, with an escaped quote: " */ +/* error: this is a raw string */ "##]], ); } diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs index 7b19518e25a84..985954d44e466 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/builtin_fn_macro.rs @@ -379,15 +379,10 @@ fn compile_error_expand( tt: &tt::Subtree, ) -> ExpandResult { let err = match &*tt.token_trees { - [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => { - let text = it.text.as_str(); - if text.starts_with('"') && text.ends_with('"') { - // FIXME: does not handle raw strings - ExpandError::Other(text[1..text.len() - 1].into()) - } else { - ExpandError::Other("`compile_error!` argument must be a string".into()) - } - } + [tt::TokenTree::Leaf(tt::Leaf::Literal(it))] => match unquote_str(it) { + Some(unquoted) => ExpandError::Other(unquoted.into()), + None => ExpandError::Other("`compile_error!` argument must be a string".into()), + }, _ => ExpandError::Other("`compile_error!` argument must be a string".into()), }; diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs index 87e4db03984ab..0096649be1460 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/db.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/db.rs @@ -240,7 +240,7 @@ fn ast_id_map(db: &dyn AstDatabase, file_id: HirFileId) -> Arc { } fn parse_or_expand(db: &dyn AstDatabase, file_id: HirFileId) -> Option { - match file_id.0 { + match file_id.repr() { HirFileIdRepr::FileId(file_id) => Some(db.parse(file_id).tree().syntax().clone()), HirFileIdRepr::MacroFile(macro_file) => { // FIXME: Note how we convert from `Parse` to `SyntaxNode` here, diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs index d60734372c0ce..df1e20256ca31 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/hygiene.rs @@ -17,7 +17,7 @@ use crate::{ db::{self, AstDatabase}, fixup, name::{AsName, Name}, - HirFileId, HirFileIdRepr, InFile, MacroCallKind, MacroCallLoc, MacroDefKind, MacroFile, + HirFileId, InFile, MacroCallKind, MacroCallLoc, MacroDefKind, MacroFile, }; #[derive(Clone, Debug)] @@ -216,9 +216,9 @@ fn make_hygiene_info( impl HygieneFrame { pub(crate) fn new(db: &dyn AstDatabase, file_id: HirFileId) -> HygieneFrame { - let (info, krate, local_inner) = match file_id.0 { - HirFileIdRepr::FileId(_) => (None, None, false), - HirFileIdRepr::MacroFile(macro_file) => { + let (info, krate, local_inner) = match file_id.macro_file() { + None => (None, None, false), + Some(macro_file) => { let loc = db.lookup_intern_macro_call(macro_file.macro_call_id); let info = make_hygiene_info(db, macro_file, &loc).map(|info| (loc.kind.file_id(), info)); diff --git a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs index 7352b003a491c..bc5f9f3b8afd4 100644 --- a/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs +++ b/src/tools/rust-analyzer/crates/hir-expand/src/lib.rs @@ -23,7 +23,11 @@ pub use mbe::{Origin, ValueResult}; use std::{fmt, hash::Hash, iter, sync::Arc}; -use base_db::{impl_intern_key, salsa, CrateId, FileId, FileRange, ProcMacroKind}; +use base_db::{ + impl_intern_key, + salsa::{self, InternId}, + CrateId, FileId, FileRange, ProcMacroKind, +}; use either::Either; use syntax::{ algo::{self, skip_trivia_token}, @@ -79,26 +83,12 @@ impl fmt::Display for ExpandError { /// finite (because everything bottoms out at the real `FileId`) and small /// (`MacroCallId` uses the location interning. You can check details here: /// ). +/// +/// The two variants are encoded in a single u32 which are differentiated by the MSB. +/// If the MSB is 0, the value represents a `FileId`, otherwise the remaining 31 bits represent a +/// `MacroCallId`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct HirFileId(HirFileIdRepr); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -enum HirFileIdRepr { - FileId(FileId), - MacroFile(MacroFile), -} - -impl From for HirFileId { - fn from(id: FileId) -> Self { - HirFileId(HirFileIdRepr::FileId(id)) - } -} - -impl From for HirFileId { - fn from(id: MacroFile) -> Self { - HirFileId(HirFileIdRepr::MacroFile(id)) - } -} +pub struct HirFileId(u32); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct MacroFile { @@ -172,13 +162,37 @@ pub enum MacroCallKind { }, } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +enum HirFileIdRepr { + FileId(FileId), + MacroFile(MacroFile), +} + +impl From for HirFileId { + fn from(FileId(id): FileId) -> Self { + assert!(id < Self::MAX_FILE_ID); + HirFileId(id) + } +} + +impl From for HirFileId { + fn from(MacroFile { macro_call_id: MacroCallId(id) }: MacroFile) -> Self { + let id = id.as_u32(); + assert!(id < Self::MAX_FILE_ID); + HirFileId(id | Self::MACRO_FILE_TAG_MASK) + } +} + impl HirFileId { + const MAX_FILE_ID: u32 = u32::MAX ^ Self::MACRO_FILE_TAG_MASK; + const MACRO_FILE_TAG_MASK: u32 = 1 << 31; + /// For macro-expansion files, returns the file original source file the /// expansion originated from. pub fn original_file(self, db: &dyn db::AstDatabase) -> FileId { let mut file_id = self; loop { - match file_id.0 { + match file_id.repr() { HirFileIdRepr::FileId(id) => break id, HirFileIdRepr::MacroFile(MacroFile { macro_call_id }) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_call_id); @@ -194,7 +208,7 @@ impl HirFileId { pub fn expansion_level(self, db: &dyn db::AstDatabase) -> u32 { let mut level = 0; let mut curr = self; - while let HirFileIdRepr::MacroFile(macro_file) = curr.0 { + while let Some(macro_file) = curr.macro_file() { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); level += 1; @@ -205,25 +219,17 @@ impl HirFileId { /// If this is a macro call, returns the syntax node of the call. pub fn call_node(self, db: &dyn db::AstDatabase) -> Option> { - match self.0 { - HirFileIdRepr::FileId(_) => None, - HirFileIdRepr::MacroFile(macro_file) => { - let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); - Some(loc.kind.to_node(db)) - } - } + let macro_file = self.macro_file()?; + let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); + Some(loc.kind.to_node(db)) } /// If this is a macro call, returns the syntax node of the very first macro call this file resides in. pub fn original_call_node(self, db: &dyn db::AstDatabase) -> Option<(FileId, SyntaxNode)> { - let mut call = match self.0 { - HirFileIdRepr::FileId(_) => return None, - HirFileIdRepr::MacroFile(MacroFile { macro_call_id }) => { - db.lookup_intern_macro_call(macro_call_id).kind.to_node(db) - } - }; + let mut call = + db.lookup_intern_macro_call(self.macro_file()?.macro_call_id).kind.to_node(db); loop { - match call.file_id.0 { + match call.file_id.repr() { HirFileIdRepr::FileId(file_id) => break Some((file_id, call.value)), HirFileIdRepr::MacroFile(MacroFile { macro_call_id }) => { call = db.lookup_intern_macro_call(macro_call_id).kind.to_node(db); @@ -234,84 +240,74 @@ impl HirFileId { /// Return expansion information if it is a macro-expansion file pub fn expansion_info(self, db: &dyn db::AstDatabase) -> Option { - match self.0 { - HirFileIdRepr::FileId(_) => None, - HirFileIdRepr::MacroFile(macro_file) => { - let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); + let macro_file = self.macro_file()?; + let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); - let arg_tt = loc.kind.arg(db)?; + let arg_tt = loc.kind.arg(db)?; - let macro_def = db.macro_def(loc.def).ok()?; - let (parse, exp_map) = db.parse_macro_expansion(macro_file).value?; - let macro_arg = db.macro_arg(macro_file.macro_call_id)?; + let macro_def = db.macro_def(loc.def).ok()?; + let (parse, exp_map) = db.parse_macro_expansion(macro_file).value?; + let macro_arg = db.macro_arg(macro_file.macro_call_id)?; - let def = loc.def.ast_id().left().and_then(|id| { - let def_tt = match id.to_node(db) { - ast::Macro::MacroRules(mac) => mac.token_tree()?, - ast::Macro::MacroDef(_) - if matches!(*macro_def, TokenExpander::BuiltinAttr(_)) => - { - return None - } - ast::Macro::MacroDef(mac) => mac.body()?, - }; - Some(InFile::new(id.file_id, def_tt)) - }); - let attr_input_or_mac_def = def.or_else(|| match loc.kind { - MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { - let tt = ast_id - .to_node(db) - .doc_comments_and_attrs() - .nth(invoc_attr_index as usize) - .and_then(Either::left)? - .token_tree()?; - Some(InFile::new(ast_id.file_id, tt)) - } - _ => None, - }); - - Some(ExpansionInfo { - expanded: InFile::new(self, parse.syntax_node()), - arg: InFile::new(loc.kind.file_id(), arg_tt), - attr_input_or_mac_def, - macro_arg_shift: mbe::Shift::new(¯o_arg.0), - macro_arg, - macro_def, - exp_map, - }) + let def = loc.def.ast_id().left().and_then(|id| { + let def_tt = match id.to_node(db) { + ast::Macro::MacroRules(mac) => mac.token_tree()?, + ast::Macro::MacroDef(_) if matches!(*macro_def, TokenExpander::BuiltinAttr(_)) => { + return None + } + ast::Macro::MacroDef(mac) => mac.body()?, + }; + Some(InFile::new(id.file_id, def_tt)) + }); + let attr_input_or_mac_def = def.or_else(|| match loc.kind { + MacroCallKind::Attr { ast_id, invoc_attr_index, .. } => { + let tt = ast_id + .to_node(db) + .doc_comments_and_attrs() + .nth(invoc_attr_index as usize) + .and_then(Either::left)? + .token_tree()?; + Some(InFile::new(ast_id.file_id, tt)) } - } + _ => None, + }); + + Some(ExpansionInfo { + expanded: InFile::new(self, parse.syntax_node()), + arg: InFile::new(loc.kind.file_id(), arg_tt), + attr_input_or_mac_def, + macro_arg_shift: mbe::Shift::new(¯o_arg.0), + macro_arg, + macro_def, + exp_map, + }) } /// Indicate it is macro file generated for builtin derive pub fn is_builtin_derive(&self, db: &dyn db::AstDatabase) -> Option> { - match self.0 { - HirFileIdRepr::FileId(_) => None, - HirFileIdRepr::MacroFile(macro_file) => { - let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); - let attr = match loc.def.kind { - MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db), - _ => return None, - }; - Some(attr.with_value(ast::Attr::cast(attr.value.clone())?)) - } - } + let macro_file = self.macro_file()?; + let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); + let attr = match loc.def.kind { + MacroDefKind::BuiltInDerive(..) => loc.kind.to_node(db), + _ => return None, + }; + Some(attr.with_value(ast::Attr::cast(attr.value.clone())?)) } pub fn is_custom_derive(&self, db: &dyn db::AstDatabase) -> bool { - match self.0 { - HirFileIdRepr::FileId(_) => false, - HirFileIdRepr::MacroFile(macro_file) => { + match self.macro_file() { + Some(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); matches!(loc.def.kind, MacroDefKind::ProcMacro(_, ProcMacroKind::CustomDerive, _)) } + None => false, } } /// Return whether this file is an include macro pub fn is_include_macro(&self, db: &dyn db::AstDatabase) -> bool { - match self.0 { - HirFileIdRepr::MacroFile(macro_file) => { + match self.macro_file() { + Some(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); matches!(loc.eager, Some(EagerCallInfo { included_file: Some(_), .. })) } @@ -321,8 +317,8 @@ impl HirFileId { /// Return whether this file is an attr macro pub fn is_attr_macro(&self, db: &dyn db::AstDatabase) -> bool { - match self.0 { - HirFileIdRepr::MacroFile(macro_file) => { + match self.macro_file() { + Some(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); matches!(loc.kind, MacroCallKind::Attr { .. }) } @@ -333,23 +329,36 @@ impl HirFileId { /// Return whether this file is the pseudo expansion of the derive attribute. /// See [`crate::builtin_attr_macro::derive_attr_expand`]. pub fn is_derive_attr_pseudo_expansion(&self, db: &dyn db::AstDatabase) -> bool { - match self.0 { - HirFileIdRepr::MacroFile(macro_file) => { + match self.macro_file() { + Some(macro_file) => { let loc: MacroCallLoc = db.lookup_intern_macro_call(macro_file.macro_call_id); matches!(loc.kind, MacroCallKind::Attr { is_derive: true, .. }) } - _ => false, + None => false, } } + #[inline] pub fn is_macro(self) -> bool { - matches!(self.0, HirFileIdRepr::MacroFile(_)) + self.0 & Self::MACRO_FILE_TAG_MASK != 0 } + #[inline] pub fn macro_file(self) -> Option { - match self.0 { - HirFileIdRepr::FileId(_) => None, - HirFileIdRepr::MacroFile(m) => Some(m), + match self.0 & Self::MACRO_FILE_TAG_MASK { + 0 => None, + _ => Some(MacroFile { + macro_call_id: MacroCallId(InternId::from(self.0 ^ Self::MACRO_FILE_TAG_MASK)), + }), + } + } + + fn repr(self) -> HirFileIdRepr { + match self.0 & Self::MACRO_FILE_TAG_MASK { + 0 => HirFileIdRepr::FileId(FileId(self.0)), + _ => HirFileIdRepr::MacroFile(MacroFile { + macro_call_id: MacroCallId(InternId::from(self.0 ^ Self::MACRO_FILE_TAG_MASK)), + }), } } } @@ -442,7 +451,7 @@ impl MacroCallKind { pub fn original_call_range_with_body(self, db: &dyn db::AstDatabase) -> FileRange { let mut kind = self; let file_id = loop { - match kind.file_id().0 { + match kind.file_id().repr() { HirFileIdRepr::MacroFile(file) => { kind = db.lookup_intern_macro_call(file.macro_call_id).kind; } @@ -467,7 +476,7 @@ impl MacroCallKind { pub fn original_call_range(self, db: &dyn db::AstDatabase) -> FileRange { let mut kind = self; let file_id = loop { - match kind.file_id().0 { + match kind.file_id().repr() { HirFileIdRepr::MacroFile(file) => { kind = db.lookup_intern_macro_call(file.macro_call_id).kind; } @@ -779,7 +788,7 @@ impl<'a> InFile<&'a SyntaxNode> { /// For attributes and derives, this will point back to the attribute only. /// For the entire item `InFile::use original_file_range_full`. pub fn original_file_range(self, db: &dyn db::AstDatabase) -> FileRange { - match self.file_id.0 { + match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() }, HirFileIdRepr::MacroFile(mac_file) => { if let Some(res) = self.original_file_range_opt(db) { @@ -846,7 +855,7 @@ impl InFile { /// Falls back to the macro call range if the node cannot be mapped up fully. pub fn original_file_range(self, db: &dyn db::AstDatabase) -> FileRange { - match self.file_id.0 { + match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => FileRange { file_id, range: self.value.text_range() }, HirFileIdRepr::MacroFile(mac_file) => { if let Some(res) = self.original_file_range_opt(db) { @@ -861,7 +870,7 @@ impl InFile { /// Attempts to map the syntax node back up its macro calls. pub fn original_file_range_opt(self, db: &dyn db::AstDatabase) -> Option { - match self.file_id.0 { + match self.file_id.repr() { HirFileIdRepr::FileId(file_id) => { Some(FileRange { file_id, range: self.value.text_range() }) } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs index 2b3793659cf7d..161bcc5c8da5f 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/add_missing_impl_members.rs @@ -107,6 +107,14 @@ fn add_missing_impl_members_inner( ) -> Option<()> { let _p = profile::span("add_missing_impl_members_inner"); let impl_def = ctx.find_node_at_offset::()?; + + if ctx.token_at_offset().all(|t| { + t.parent_ancestors() + .any(|s| ast::BlockExpr::can_cast(s.kind()) || ast::ParamList::can_cast(s.kind())) + }) { + return None; + } + let target_scope = ctx.sema.scope(impl_def.syntax())?; let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?; @@ -1343,4 +1351,95 @@ impl PartialEq for SomeStruct { "#, ); } + + #[test] + fn test_ignore_function_body() { + check_assist_not_applicable( + add_missing_default_members, + r#" +trait Trait { + type X; + fn foo(&self); + fn bar(&self) {} +} + +impl Trait for () { + type X = u8; + fn foo(&self) {$0 + let x = 5; + } +}"#, + ) + } + + #[test] + fn test_ignore_param_list() { + check_assist_not_applicable( + add_missing_impl_members, + r#" +trait Trait { + type X; + fn foo(&self); + fn bar(&self); +} + +impl Trait for () { + type X = u8; + fn foo(&self$0) { + let x = 5; + } +}"#, + ) + } + + #[test] + fn test_ignore_scope_inside_function() { + check_assist_not_applicable( + add_missing_impl_members, + r#" +trait Trait { + type X; + fn foo(&self); + fn bar(&self); +} + +impl Trait for () { + type X = u8; + fn foo(&self) { + let x = async {$0 5 }; + } +}"#, + ) + } + + #[test] + fn test_apply_outside_function() { + check_assist( + add_missing_default_members, + r#" +trait Trait { + type X; + fn foo(&self); + fn bar(&self) {} +} + +impl Trait for () { + type X = u8; + fn foo(&self)$0 {} +}"#, + r#" +trait Trait { + type X; + fn foo(&self); + fn bar(&self) {} +} + +impl Trait for () { + type X = u8; + fn foo(&self) {} + + $0fn bar(&self) {} +}"#, + ) + } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs index 92e091fca126c..b0383291e7370 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/convert_tuple_struct_to_named_struct.rs @@ -168,7 +168,7 @@ fn edit_struct_references( let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?; edit.replace( - call_expr.syntax().text_range(), + ctx.sema.original_range(&node).range, ast::make::record_expr( path, ast::make::record_expr_field_list(arg_list.args().zip(names).map( @@ -249,6 +249,24 @@ mod tests { ); check_assist_not_applicable(convert_tuple_struct_to_named_struct, r#"struct Foo$0;"#); } + #[test] + fn convert_in_macro_args() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +macro_rules! foo {($i:expr) => {$i} } +struct T$0(u8); +fn test() { + foo!(T(1)); +}"#, + r#" +macro_rules! foo {($i:expr) => {$i} } +struct T { field1: u8 } +fn test() { + foo!(T { field1: 1 }); +}"#, + ); + } #[test] fn convert_simple_struct() { @@ -554,6 +572,29 @@ where ); } + #[test] + fn convert_variant_in_macro_args() { + check_assist( + convert_tuple_struct_to_named_struct, + r#" +macro_rules! foo {($i:expr) => {$i} } +enum T { + V$0(u8) +} +fn test() { + foo!(T::V(1)); +}"#, + r#" +macro_rules! foo {($i:expr) => {$i} } +enum T { + V { field1: u8 } +} +fn test() { + foo!(T::V { field1: 1 }); +}"#, + ); + } + #[test] fn convert_simple_variant() { check_assist( diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs index c1e2f19ab18b2..0483cfdc64667 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/extract_function.rs @@ -11,7 +11,9 @@ use ide_db::{ helpers::mod_path_to_ast, imports::insert_use::{insert_use, ImportScope}, search::{FileReference, ReferenceCategory, SearchScope}, - syntax_helpers::node_ext::{preorder_expr, walk_expr, walk_pat, walk_patterns_in_expr}, + syntax_helpers::node_ext::{ + for_each_tail_expr, preorder_expr, walk_expr, walk_pat, walk_patterns_in_expr, + }, FxIndexSet, RootDatabase, }; use itertools::Itertools; @@ -78,7 +80,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op }; let body = extraction_target(&node, range)?; - let container_info = body.analyze_container(&ctx.sema)?; + let (container_info, contains_tail_expr) = body.analyze_container(&ctx.sema)?; let (locals_used, self_param) = body.analyze(&ctx.sema); @@ -119,6 +121,7 @@ pub(crate) fn extract_function(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op ret_ty, body, outliving_locals, + contains_tail_expr, mods: container_info, }; @@ -245,6 +248,8 @@ struct Function { ret_ty: RetType, body: FunctionBody, outliving_locals: Vec, + /// Whether at least one of the container's tail expr is contained in the range we're extracting. + contains_tail_expr: bool, mods: ContainerInfo, } @@ -265,7 +270,7 @@ enum ParamKind { MutRef, } -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug)] enum FunType { Unit, Single(hir::Type), @@ -294,7 +299,6 @@ struct ControlFlow { #[derive(Clone, Debug)] struct ContainerInfo { is_const: bool, - is_in_tail: bool, parent_loop: Option, /// The function's return type, const's type etc. ret_type: Option, @@ -743,7 +747,10 @@ impl FunctionBody { (res, self_param) } - fn analyze_container(&self, sema: &Semantics<'_, RootDatabase>) -> Option { + fn analyze_container( + &self, + sema: &Semantics<'_, RootDatabase>, + ) -> Option<(ContainerInfo, bool)> { let mut ancestors = self.parent()?.ancestors(); let infer_expr_opt = |expr| sema.type_of_expr(&expr?).map(TypeInfo::adjusted); let mut parent_loop = None; @@ -815,28 +822,36 @@ impl FunctionBody { } }; }; - let container_tail = match expr? { - ast::Expr::BlockExpr(block) => block.tail_expr(), - expr => Some(expr), - }; - let is_in_tail = - container_tail.zip(self.tail_expr()).map_or(false, |(container_tail, body_tail)| { - container_tail.syntax().text_range().contains_range(body_tail.syntax().text_range()) + + let expr = expr?; + let contains_tail_expr = if let Some(body_tail) = self.tail_expr() { + let mut contains_tail_expr = false; + let tail_expr_range = body_tail.syntax().text_range(); + for_each_tail_expr(&expr, &mut |e| { + if tail_expr_range.contains_range(e.syntax().text_range()) { + contains_tail_expr = true; + } }); + contains_tail_expr + } else { + false + }; let parent = self.parent()?; let parents = generic_parents(&parent); let generic_param_lists = parents.iter().filter_map(|it| it.generic_param_list()).collect(); let where_clauses = parents.iter().filter_map(|it| it.where_clause()).collect(); - Some(ContainerInfo { - is_in_tail, - is_const, - parent_loop, - ret_type: ty, - generic_param_lists, - where_clauses, - }) + Some(( + ContainerInfo { + is_const, + parent_loop, + ret_type: ty, + generic_param_lists, + where_clauses, + }, + contains_tail_expr, + )) } fn return_ty(&self, ctx: &AssistContext<'_>) -> Option { @@ -1368,7 +1383,7 @@ impl FlowHandler { None => FlowHandler::None, Some(flow_kind) => { let action = flow_kind.clone(); - if *ret_ty == FunType::Unit { + if let FunType::Unit = ret_ty { match flow_kind { FlowKind::Return(None) | FlowKind::Break(_, None) @@ -1633,7 +1648,7 @@ impl Function { fn make_ret_ty(&self, ctx: &AssistContext<'_>, module: hir::Module) -> Option { let fun_ty = self.return_type(ctx); - let handler = if self.mods.is_in_tail { + let handler = if self.contains_tail_expr { FlowHandler::None } else { FlowHandler::from_ret_ty(self, &fun_ty) @@ -1707,7 +1722,7 @@ fn make_body( fun: &Function, ) -> ast::BlockExpr { let ret_ty = fun.return_type(ctx); - let handler = if fun.mods.is_in_tail { + let handler = if fun.contains_tail_expr { FlowHandler::None } else { FlowHandler::from_ret_ty(fun, &ret_ty) @@ -1946,7 +1961,7 @@ fn update_external_control_flow(handler: &FlowHandler, syntax: &SyntaxNode) { if nested_scope.is_none() { if let Some(expr) = ast::Expr::cast(e.clone()) { match expr { - ast::Expr::ReturnExpr(return_expr) if nested_scope.is_none() => { + ast::Expr::ReturnExpr(return_expr) => { let expr = return_expr.expr(); if let Some(replacement) = make_rewritten_flow(handler, expr) { ted::replace(return_expr.syntax(), replacement.syntax()) @@ -5582,6 +5597,153 @@ impl Struct where T: Into + Copy, U: Debug { fn $0fun_name(t: T, v: V) -> i32 where T: Into + Copy, V: Into { t.into() + v.into() } +"#, + ); + } + + #[test] + fn non_tail_expr_of_tail_expr_loop() { + check_assist( + extract_function, + r#" +pub fn f() { + loop { + $0if true { + continue; + }$0 + + if false { + break; + } + } +} +"#, + r#" +pub fn f() { + loop { + if let ControlFlow::Break(_) = fun_name() { + continue; + } + + if false { + break; + } + } +} + +fn $0fun_name() -> ControlFlow<()> { + if true { + return ControlFlow::Break(()); + } + ControlFlow::Continue(()) +} +"#, + ); + } + + #[test] + fn non_tail_expr_of_tail_if_block() { + // FIXME: double semicolon + check_assist( + extract_function, + r#" +//- minicore: option, try +impl core::ops::Try for Option { + type Output = T; + type Residual = Option; +} +impl core::ops::FromResidual for Option {} + +fn f() -> Option<()> { + if true { + let a = $0if true { + Some(())? + } else { + () + }$0; + Some(a) + } else { + None + } +} +"#, + r#" +impl core::ops::Try for Option { + type Output = T; + type Residual = Option; +} +impl core::ops::FromResidual for Option {} + +fn f() -> Option<()> { + if true { + let a = fun_name()?;; + Some(a) + } else { + None + } +} + +fn $0fun_name() -> Option<()> { + Some(if true { + Some(())? + } else { + () + }) +} +"#, + ); + } + + #[test] + fn tail_expr_of_tail_block_nested() { + check_assist( + extract_function, + r#" +//- minicore: option, try +impl core::ops::Try for Option { + type Output = T; + type Residual = Option; +} +impl core::ops::FromResidual for Option {} + +fn f() -> Option<()> { + if true { + $0{ + let a = if true { + Some(())? + } else { + () + }; + Some(a) + }$0 + } else { + None + } +} +"#, + r#" +impl core::ops::Try for Option { + type Output = T; + type Residual = Option; +} +impl core::ops::FromResidual for Option {} + +fn f() -> Option<()> { + if true { + fun_name()? + } else { + None + } +} + +fn $0fun_name() -> Option<()> { + let a = if true { + Some(())? + } else { + () + }; + Some(a) +} "#, ); } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs index 9af26c04eb458..690c97e26d8c6 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/generate_impl.rs @@ -1,14 +1,17 @@ use syntax::ast::{self, AstNode, HasName}; -use crate::{utils::generate_impl_text, AssistContext, AssistId, AssistKind, Assists}; +use crate::{ + utils::{generate_impl_text, generate_trait_impl_text}, + AssistContext, AssistId, AssistKind, Assists, +}; // Assist: generate_impl // // Adds a new inherent impl for a type. // // ``` -// struct Ctx { -// data: T,$0 +// struct Ctx$0 { +// data: T, // } // ``` // -> @@ -26,6 +29,10 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio let name = nominal.name()?; let target = nominal.syntax().text_range(); + if let Some(_) = ctx.find_node_at_offset::() { + return None; + } + acc.add( AssistId("generate_impl", AssistKind::Generate), format!("Generate impl for `{name}`"), @@ -46,145 +53,393 @@ pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio ) } +// Assist: generate_trait_impl +// +// Adds a new trait impl for a type. +// +// ``` +// struct $0Ctx { +// data: T, +// } +// ``` +// -> +// ``` +// struct Ctx { +// data: T, +// } +// +// impl $0 for Ctx { +// +// } +// ``` +pub(crate) fn generate_trait_impl(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let nominal = ctx.find_node_at_offset::()?; + let name = nominal.name()?; + let target = nominal.syntax().text_range(); + + if let Some(_) = ctx.find_node_at_offset::() { + return None; + } + + acc.add( + AssistId("generate_trait_impl", AssistKind::Generate), + format!("Generate trait impl for `{name}`"), + target, + |edit| { + let start_offset = nominal.syntax().text_range().end(); + match ctx.config.snippet_cap { + Some(cap) => { + let snippet = generate_trait_impl_text(&nominal, "$0", ""); + edit.insert_snippet(cap, start_offset, snippet); + } + None => { + let text = generate_trait_impl_text(&nominal, "", ""); + edit.insert(start_offset, text); + } + } + }, + ) +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_target}; use super::*; - // FIXME: break up into separate test fns #[test] fn test_add_impl() { check_assist( generate_impl, - "struct Foo {$0}\n", - "struct Foo {}\n\nimpl Foo {\n $0\n}\n", + r#" + struct Foo$0 {} + "#, + r#" + struct Foo {} + + impl Foo { + $0 + } + "#, ); + } + + #[test] + fn test_add_impl_with_generics() { check_assist( generate_impl, - "struct Foo {$0}", - "struct Foo {}\n\nimpl Foo {\n $0\n}", + r#" + struct Foo$0 {} + "#, + r#" + struct Foo {} + + impl Foo { + $0 + } + "#, ); + } + + #[test] + fn test_add_impl_with_generics_and_lifetime_parameters() { check_assist( generate_impl, - "struct Foo<'a, T: Foo<'a>> {$0}", - "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}", + r#" + struct Foo<'a, T: Foo<'a>>$0 {} + "#, + r#" + struct Foo<'a, T: Foo<'a>> {} + + impl<'a, T: Foo<'a>> Foo<'a, T> { + $0 + } + "#, ); + } + + #[test] + fn test_add_impl_with_attributes() { check_assist( generate_impl, r#" - struct MyOwnArray {}$0"#, + #[cfg(feature = "foo")] + struct Foo<'a, T: Foo$0<'a>> {} + "#, r#" - struct MyOwnArray {} + #[cfg(feature = "foo")] + struct Foo<'a, T: Foo<'a>> {} - impl MyOwnArray { - $0 - }"#, + #[cfg(feature = "foo")] + impl<'a, T: Foo<'a>> Foo<'a, T> { + $0 + } + "#, ); + } + + #[test] + fn test_add_impl_with_default_generic() { check_assist( generate_impl, r#" - #[cfg(feature = "foo")] - struct Foo<'a, T: Foo<'a>> {$0}"#, + struct Defaulted$0 {} + "#, r#" - #[cfg(feature = "foo")] - struct Foo<'a, T: Foo<'a>> {} + struct Defaulted {} - #[cfg(feature = "foo")] - impl<'a, T: Foo<'a>> Foo<'a, T> { - $0 - }"#, + impl Defaulted { + $0 + } + "#, ); + } + #[test] + fn test_add_impl_with_constrained_default_generic() { check_assist( generate_impl, r#" - #[cfg(not(feature = "foo"))] - struct Foo<'a, T: Foo<'a>> {$0}"#, + struct Defaulted$0<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {} + "#, r#" - #[cfg(not(feature = "foo"))] - struct Foo<'a, T: Foo<'a>> {} + struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {} - #[cfg(not(feature = "foo"))] - impl<'a, T: Foo<'a>> Foo<'a, T> { - $0 - }"#, + impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> Defaulted<'a, 'b, T, S> { + $0 + } + "#, ); + } + #[test] + fn test_add_impl_with_const_defaulted_generic() { check_assist( generate_impl, r#" - struct Defaulted {}$0"#, + struct Defaulted$0 {} + "#, r#" - struct Defaulted {} + struct Defaulted {} - impl Defaulted { - $0 - }"#, + impl Defaulted { + $0 + } + "#, ); + } + #[test] + fn test_add_impl_with_trait_constraint() { check_assist( generate_impl, r#" - struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {}$0"#, + pub trait Trait {} + struct Struct$0 + where + T: Trait, + { + inner: T, + } + "#, r#" - struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {} + pub trait Trait {} + struct Struct + where + T: Trait, + { + inner: T, + } - impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> Defaulted<'a, 'b, T, S> { - $0 - }"#, + impl Struct + where + T: Trait, + { + $0 + } + "#, ); + } - check_assist( + #[test] + fn add_impl_target() { + check_assist_target( generate_impl, r#" - struct Defaulted {}$0"#, + struct SomeThingIrrelevant; + /// Has a lifetime parameter + struct Foo$0<'a, T: Foo<'a>> {} + struct EvenMoreIrrelevant; + "#, + "/// Has a lifetime parameter\nstruct Foo<'a, T: Foo<'a>> {}", + ); + } + + #[test] + fn test_add_trait_impl() { + check_assist( + generate_trait_impl, + r#" + struct Foo$0 {} + "#, r#" - struct Defaulted {} + struct Foo {} - impl Defaulted { - $0 - }"#, + impl $0 for Foo { + + } + "#, ); + } + #[test] + fn test_add_trait_impl_with_generics() { check_assist( - generate_impl, - r#"pub trait Trait {} -struct Struct$0 -where - T: Trait, -{ - inner: T, -}"#, - r#"pub trait Trait {} -struct Struct -where - T: Trait, -{ - inner: T, -} + generate_trait_impl, + r#" + struct Foo$0 {} + "#, + r#" + struct Foo {} -impl Struct -where - T: Trait, -{ - $0 -}"#, + impl $0 for Foo { + + } + "#, ); } #[test] - fn add_impl_target() { + fn test_add_trait_impl_with_generics_and_lifetime_parameters() { + check_assist( + generate_trait_impl, + r#" + struct Foo<'a, T: Foo<'a>>$0 {} + "#, + r#" + struct Foo<'a, T: Foo<'a>> {} + + impl<'a, T: Foo<'a>> $0 for Foo<'a, T> { + + } + "#, + ); + } + + #[test] + fn test_add_trait_impl_with_attributes() { + check_assist( + generate_trait_impl, + r#" + #[cfg(feature = "foo")] + struct Foo<'a, T: Foo$0<'a>> {} + "#, + r#" + #[cfg(feature = "foo")] + struct Foo<'a, T: Foo<'a>> {} + + #[cfg(feature = "foo")] + impl<'a, T: Foo<'a>> $0 for Foo<'a, T> { + + } + "#, + ); + } + + #[test] + fn test_add_trait_impl_with_default_generic() { + check_assist( + generate_trait_impl, + r#" + struct Defaulted$0 {} + "#, + r#" + struct Defaulted {} + + impl $0 for Defaulted { + + } + "#, + ); + } + + #[test] + fn test_add_trait_impl_with_constrained_default_generic() { + check_assist( + generate_trait_impl, + r#" + struct Defaulted$0<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {} + "#, + r#" + struct Defaulted<'a, 'b: 'a, T: Debug + Clone + 'a + 'b = String, const S: usize> {} + + impl<'a, 'b: 'a, T: Debug + Clone + 'a + 'b, const S: usize> $0 for Defaulted<'a, 'b, T, S> { + + } + "#, + ); + } + + #[test] + fn test_add_trait_impl_with_const_defaulted_generic() { + check_assist( + generate_trait_impl, + r#" + struct Defaulted$0 {} + "#, + r#" + struct Defaulted {} + + impl $0 for Defaulted { + + } + "#, + ); + } + + #[test] + fn test_add_trait_impl_with_trait_constraint() { + check_assist( + generate_trait_impl, + r#" + pub trait Trait {} + struct Struct$0 + where + T: Trait, + { + inner: T, + } + "#, + r#" + pub trait Trait {} + struct Struct + where + T: Trait, + { + inner: T, + } + + impl $0 for Struct + where + T: Trait, + { + + } + "#, + ); + } + + #[test] + fn add_trait_impl_target() { check_assist_target( - generate_impl, - " -struct SomeThingIrrelevant; -/// Has a lifetime parameter -struct Foo<'a, T: Foo<'a>> {$0} -struct EvenMoreIrrelevant; -", - "/// Has a lifetime parameter -struct Foo<'a, T: Foo<'a>> {}", + generate_trait_impl, + r#" + struct SomeThingIrrelevant; + /// Has a lifetime parameter + struct Foo$0<'a, T: Foo<'a>> {} + struct EvenMoreIrrelevant; + "#, + "/// Has a lifetime parameter\nstruct Foo<'a, T: Foo<'a>> {}", ); } } diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs index 387cc63142825..b12f99cc53298 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/lib.rs @@ -247,6 +247,7 @@ mod handlers { generate_from_impl_for_enum::generate_from_impl_for_enum, generate_function::generate_function, generate_impl::generate_impl, + generate_impl::generate_trait_impl, generate_is_empty_from_len::generate_is_empty_from_len, generate_new::generate_new, inline_call::inline_call, diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs index c09317572acf2..d797f077672da 100644 --- a/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs +++ b/src/tools/rust-analyzer/crates/ide-assists/src/tests/generated.rs @@ -1249,8 +1249,8 @@ fn doctest_generate_impl() { check_doc_test( "generate_impl", r#####" -struct Ctx { - data: T,$0 +struct Ctx$0 { + data: T, } "#####, r#####" @@ -1341,6 +1341,27 @@ impl Person { ) } +#[test] +fn doctest_generate_trait_impl() { + check_doc_test( + "generate_trait_impl", + r#####" +struct $0Ctx { + data: T, +} +"#####, + r#####" +struct Ctx { + data: T, +} + +impl $0 for Ctx { + +} +"#####, + ) +} + #[test] fn doctest_inline_call() { check_doc_test( diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs index 9850813a0ce1c..aa77f449530e5 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/context.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/context.rs @@ -19,7 +19,7 @@ use syntax::{ ast::{self, AttrKind, NameOrNameRef}, AstNode, SyntaxKind::{self, *}, - SyntaxToken, TextRange, TextSize, + SyntaxToken, TextRange, TextSize, T, }; use text_edit::Indel; @@ -569,6 +569,32 @@ impl<'a> CompletionContext<'a> { // completing on let original_token = original_file.syntax().token_at_offset(offset).left_biased()?; + // try to skip completions on path with invalid colons + // this approach works in normal path and inside token tree + match original_token.kind() { + T![:] => { + // return if no prev token before colon + let prev_token = original_token.prev_token()?; + + // only has a single colon + if prev_token.kind() != T![:] { + return None; + } + + // has 3 colon or 2 coloncolon in a row + // special casing this as per discussion in https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1031845205 + // and https://github.com/rust-lang/rust-analyzer/pull/13611#discussion_r1032812751 + if prev_token + .prev_token() + .map(|t| t.kind() == T![:] || t.kind() == T![::]) + .unwrap_or(false) + { + return None; + } + } + _ => {} + } + let AnalysisResult { analysis, expected: (expected_type, expected_name), diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs index 9d0044e55f598..4b48ec6bc3393 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/lib.rs @@ -164,7 +164,6 @@ pub fn completions( completions::vis::complete_vis_path(&mut completions, ctx, path_ctx, has_in_token); } } - // prevent `(` from triggering unwanted completion noise return Some(completions.into()); } diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs index 1578ba2c37712..4e60820dd6d60 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/attribute.rs @@ -607,6 +607,30 @@ fn attr_in_source_file_end() { ); } +#[test] +fn invalid_path() { + check( + r#" +//- proc_macros: identity +#[proc_macros:::$0] +struct Foo; +"#, + expect![[r#""#]], + ); + + check( + r#" +//- minicore: derive, copy +mod foo { + pub use Copy as Bar; +} +#[derive(foo:::::$0)] +struct Foo; +"#, + expect![""], + ); +} + mod cfg { use super::*; diff --git a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs index 033dc99c26cf0..cad4af4937de5 100644 --- a/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs +++ b/src/tools/rust-analyzer/crates/ide-completion/src/tests/special.rs @@ -2,13 +2,22 @@ use expect_test::{expect, Expect}; -use crate::tests::{check_edit, completion_list_no_kw}; +use crate::tests::{check_edit, completion_list_no_kw, completion_list_with_trigger_character}; fn check(ra_fixture: &str, expect: Expect) { let actual = completion_list_no_kw(ra_fixture); expect.assert_eq(&actual) } +pub(crate) fn check_with_trigger_character( + ra_fixture: &str, + trigger_character: Option, + expect: Expect, +) { + let actual = completion_list_with_trigger_character(ra_fixture, trigger_character); + expect.assert_eq(&actual) +} + #[test] fn completes_if_prefix_is_keyword() { check_edit( @@ -893,3 +902,82 @@ fn f() { "#]], ); } + +#[test] +fn completes_after_colon_with_trigger() { + check_with_trigger_character( + r#" +//- minicore: option +fn foo { ::$0 } +"#, + Some(':'), + expect![[r#" + md core + "#]], + ); + check_with_trigger_character( + r#" +//- minicore: option +fn foo { /* test */::$0 } +"#, + Some(':'), + expect![[r#" + md core + "#]], + ); + + check_with_trigger_character( + r#" +fn foo { crate::$0 } +"#, + Some(':'), + expect![[r#" + fn foo() fn() + "#]], + ); + + check_with_trigger_character( + r#" +fn foo { crate:$0 } +"#, + Some(':'), + expect![""], + ); +} + +#[test] +fn completes_after_colon_without_trigger() { + check_with_trigger_character( + r#" +fn foo { crate::$0 } +"#, + None, + expect![[r#" + fn foo() fn() + "#]], + ); + + check_with_trigger_character( + r#" +fn foo { crate:$0 } +"#, + None, + expect![""], + ); +} + +#[test] +fn no_completions_in_invalid_path() { + check( + r#" +fn foo { crate:::$0 } +"#, + expect![""], + ); + check( + r#" +fn foo { crate::::$0 } +"#, + expect![""], + ) +} diff --git a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt index 2f531ca0c709c..8c11408dec5d7 100644 --- a/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt +++ b/src/tools/rust-analyzer/crates/ide-db/src/test_data/test_symbol_index_collection.txt @@ -14,11 +14,7 @@ name: "Alias", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: TYPE_ALIAS, @@ -36,11 +32,7 @@ name: "CONST", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: CONST, @@ -58,11 +50,7 @@ name: "CONST_WITH_INNER", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: CONST, @@ -80,11 +68,7 @@ name: "Enum", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: ENUM, @@ -102,11 +86,7 @@ name: "Macro", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: MACRO_DEF, @@ -124,11 +104,7 @@ name: "STATIC", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: STATIC, @@ -146,11 +122,7 @@ name: "Struct", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: STRUCT, @@ -168,13 +140,7 @@ name: "StructFromMacro", loc: DeclarationLocation { hir_file_id: HirFileId( - MacroFile( - MacroFile { - macro_call_id: MacroCallId( - 0, - ), - }, - ), + 2147483648, ), ptr: SyntaxNodePtr { kind: STRUCT, @@ -192,11 +158,7 @@ name: "StructInFn", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: STRUCT, @@ -216,11 +178,7 @@ name: "StructInNamedConst", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: STRUCT, @@ -240,11 +198,7 @@ name: "StructInUnnamedConst", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: STRUCT, @@ -262,11 +216,7 @@ name: "Trait", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: TRAIT, @@ -284,11 +234,7 @@ name: "Union", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: UNION, @@ -306,11 +252,7 @@ name: "a_mod", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: MODULE, @@ -328,11 +270,7 @@ name: "b_mod", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: MODULE, @@ -350,11 +288,7 @@ name: "define_struct", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: MACRO_RULES, @@ -372,11 +306,7 @@ name: "impl_fn", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: FN, @@ -394,11 +324,7 @@ name: "macro_rules_macro", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: MACRO_RULES, @@ -416,11 +342,7 @@ name: "main", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: FN, @@ -438,11 +360,7 @@ name: "trait_fn", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: FN, @@ -475,11 +393,7 @@ name: "StructInModA", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 0, - ), - ), + 0, ), ptr: SyntaxNodePtr { kind: STRUCT, @@ -510,11 +424,7 @@ name: "StructInModB", loc: DeclarationLocation { hir_file_id: HirFileId( - FileId( - FileId( - 1, - ), - ), + 1, ), ptr: SyntaxNodePtr { kind: STRUCT, diff --git a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs index 926292c9b3ce1..c7130a2a4bb0a 100644 --- a/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs +++ b/src/tools/rust-analyzer/crates/ide/src/goto_declaration.rs @@ -1,18 +1,22 @@ -use hir::Semantics; +use hir::{AsAssocItem, Semantics}; use ide_db::{ defs::{Definition, NameClass, NameRefClass}, RootDatabase, }; use syntax::{ast, match_ast, AstNode, SyntaxKind::*, T}; -use crate::{FilePosition, NavigationTarget, RangeInfo}; +use crate::{ + goto_definition::goto_definition, navigation_target::TryToNav, FilePosition, NavigationTarget, + RangeInfo, +}; // Feature: Go to Declaration // // Navigates to the declaration of an identifier. // -// This is currently the same as `Go to Definition` with the exception of outline modules where it -// will navigate to the `mod name;` item declaration. +// This is the same as `Go to Definition` with the following exceptions: +// - outline modules will navigate to the `mod name;` item declaration +// - trait assoc items will navigate to the assoc item of the trait declaration opposed to the trait impl pub(crate) fn goto_declaration( db: &RootDatabase, position: FilePosition, @@ -32,25 +36,37 @@ pub(crate) fn goto_declaration( match parent { ast::NameRef(name_ref) => match NameRefClass::classify(&sema, &name_ref)? { NameRefClass::Definition(it) => Some(it), - _ => None + NameRefClass::FieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db), }, ast::Name(name) => match NameClass::classify(&sema, &name)? { - NameClass::Definition(it) => Some(it), - _ => None + NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it), + NameClass::PatFieldShorthand { field_ref, .. } => return field_ref.try_to_nav(db), }, _ => None } }; - match def? { + let assoc = match def? { Definition::Module(module) => { - Some(NavigationTarget::from_module_to_decl(db, module)) + return Some(NavigationTarget::from_module_to_decl(db, module)) } + Definition::Const(c) => c.as_assoc_item(db), + Definition::TypeAlias(ta) => ta.as_assoc_item(db), + Definition::Function(f) => f.as_assoc_item(db), _ => None, - } + }?; + + let trait_ = assoc.containing_trait_impl(db)?; + let name = Some(assoc.name(db)?); + let item = trait_.items(db).into_iter().find(|it| it.name(db) == name)?; + item.try_to_nav(db) }) .collect(); - Some(RangeInfo::new(range, info)) + if info.is_empty() { + goto_definition(db, position) + } else { + Some(RangeInfo::new(range, info)) + } } #[cfg(test)] @@ -109,4 +125,89 @@ mod foo { "#, ) } + + #[test] + fn goto_decl_goto_def_fallback() { + check( + r#" +struct Foo; + // ^^^ +impl Foo$0 {} +"#, + ); + } + + #[test] + fn goto_decl_assoc_item_no_impl_item() { + check( + r#" +trait Trait { + const C: () = (); + // ^ +} +impl Trait for () {} + +fn main() { + <()>::C$0; +} +"#, + ); + } + + #[test] + fn goto_decl_assoc_item() { + check( + r#" +trait Trait { + const C: () = (); + // ^ +} +impl Trait for () { + const C: () = (); +} + +fn main() { + <()>::C$0; +} +"#, + ); + check( + r#" +trait Trait { + const C: () = (); + // ^ +} +impl Trait for () { + const C$0: () = (); +} +"#, + ); + } + + #[test] + fn goto_decl_field_pat_shorthand() { + check( + r#" +struct Foo { field: u32 } + //^^^^^ +fn main() { + let Foo { field$0 }; +} +"#, + ); + } + + #[test] + fn goto_decl_constructor_shorthand() { + check( + r#" +struct Foo { field: u32 } + //^^^^^ +fn main() { + let field = 0; + Foo { field$0 }; +} +"#, + ); + } } diff --git a/src/tools/rust-analyzer/crates/ide/src/hover.rs b/src/tools/rust-analyzer/crates/ide/src/hover.rs index 838fb18c3d590..9cbfed4763b09 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover.rs @@ -127,6 +127,7 @@ pub(crate) fn hover( original_token.parent().and_then(ast::TokenTree::cast), Some(tt) if tt.syntax().ancestors().any(|it| ast::Meta::can_cast(it.kind())) ); + // prefer descending the same token kind in attribute expansions, in normal macros text // equivalency is more important let descended = if in_attr { @@ -135,54 +136,67 @@ pub(crate) fn hover( sema.descend_into_macros_with_same_text(original_token.clone()) }; - // FIXME: Definition should include known lints and the like instead of having this special case here - let hovered_lint = descended.iter().find_map(|token| { - let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; - render::try_for_lint(&attr, token) - }); - if let Some(res) = hovered_lint { - return Some(RangeInfo::new(original_token.text_range(), res)); - } - + // try lint hover let result = descended .iter() - .filter_map(|token| { - let node = token.parent()?; - let class = IdentClass::classify_token(sema, token)?; - if let IdentClass::Operator(OperatorClass::Await(_)) = class { - // It's better for us to fall back to the keyword hover here, - // rendering poll is very confusing - return None; - } - Some(class.definitions().into_iter().zip(iter::once(node).cycle())) + .find_map(|token| { + // FIXME: Definition should include known lints and the like instead of having this special case here + let attr = token.parent_ancestors().find_map(ast::Attr::cast)?; + render::try_for_lint(&attr, token) }) - .flatten() - .unique_by(|&(def, _)| def) - .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config)) - .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| { - acc.actions.extend(actions); - acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup)); - acc - }); + // try item definitions + .or_else(|| { + descended + .iter() + .filter_map(|token| { + let node = token.parent()?; + let class = IdentClass::classify_token(sema, token)?; + if let IdentClass::Operator(OperatorClass::Await(_)) = class { + // It's better for us to fall back to the keyword hover here, + // rendering poll is very confusing + return None; + } + Some(class.definitions().into_iter().zip(iter::once(node).cycle())) + }) + .flatten() + .unique_by(|&(def, _)| def) + .filter_map(|(def, node)| hover_for_definition(sema, file_id, def, &node, config)) + .reduce(|mut acc: HoverResult, HoverResult { markup, actions }| { + acc.actions.extend(actions); + acc.markup = Markup::from(format!("{}\n---\n{}", acc.markup, markup)); + acc + }) + }) + // try keywords + .or_else(|| descended.iter().find_map(|token| render::keyword(sema, config, token))) + // try rest item hover + .or_else(|| { + descended.iter().find_map(|token| { + if token.kind() != DOT2 { + return None; + } - if result.is_none() { - // fallbacks, show keywords or types + let rest_pat = token.parent().and_then(ast::RestPat::cast)?; + let record_pat_field_list = + rest_pat.syntax().parent().and_then(ast::RecordPatFieldList::cast)?; - let res = descended.iter().find_map(|token| render::keyword(sema, config, token)); - if let Some(res) = res { - return Some(RangeInfo::new(original_token.text_range(), res)); - } - let res = descended - .iter() - .find_map(|token| hover_type_fallback(sema, config, token, &original_token)); - if let Some(_) = res { - return res; - } - } - result.map(|mut res: HoverResult| { - res.actions = dedupe_or_merge_hover_actions(res.actions); - RangeInfo::new(original_token.text_range(), res) - }) + let record_pat = + record_pat_field_list.syntax().parent().and_then(ast::RecordPat::cast)?; + + Some(render::struct_rest_pat(sema, config, &record_pat)) + }) + }); + + result + .map(|mut res: HoverResult| { + res.actions = dedupe_or_merge_hover_actions(res.actions); + RangeInfo::new(original_token.text_range(), res) + }) + // fallback to type hover if there aren't any other suggestions + // this finds its own range instead of using the closest token's range + .or_else(|| { + descended.iter().find_map(|token| hover_type_fallback(sema, config, token, &token)) + }) } pub(crate) fn hover_for_definition( @@ -269,6 +283,7 @@ fn hover_type_fallback( }; let res = render::type_info(sema, config, &expr_or_pat)?; + let range = sema .original_range_opt(&node) .map(|frange| frange.range) diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs index d109c0769194f..fb00a40f96199 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/render.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/render.rs @@ -14,7 +14,9 @@ use ide_db::{ use itertools::Itertools; use stdx::format_to; use syntax::{ - algo, ast, match_ast, AstNode, Direction, + algo, + ast::{self, RecordPat}, + match_ast, AstNode, Direction, SyntaxKind::{LET_EXPR, LET_STMT}, SyntaxToken, T, }; @@ -250,6 +252,50 @@ pub(super) fn keyword( Some(HoverResult { markup, actions }) } +/// Returns missing types in a record pattern. +/// Only makes sense when there's a rest pattern in the record pattern. +/// i.e. `let S {a, ..} = S {a: 1, b: 2}` +pub(super) fn struct_rest_pat( + sema: &Semantics<'_, RootDatabase>, + config: &HoverConfig, + pattern: &RecordPat, +) -> HoverResult { + let missing_fields = sema.record_pattern_missing_fields(pattern); + + // if there are no missing fields, the end result is a hover that shows ".." + // should be left in to indicate that there are no more fields in the pattern + // example, S {a: 1, b: 2, ..} when struct S {a: u32, b: u32} + + let mut res = HoverResult::default(); + let mut targets: Vec = Vec::new(); + let mut push_new_def = |item: hir::ModuleDef| { + if !targets.contains(&item) { + targets.push(item); + } + }; + for (_, t) in &missing_fields { + walk_and_push_ty(sema.db, &t, &mut push_new_def); + } + + res.markup = { + let mut s = String::from(".., "); + for (f, _) in &missing_fields { + s += f.display(sema.db).to_string().as_ref(); + s += ", "; + } + // get rid of trailing comma + s.truncate(s.len() - 2); + + if config.markdown() { + Markup::fenced_block(&s) + } else { + s.into() + } + }; + res.actions.push(HoverAction::goto_type_from_targets(sema.db, targets)); + res +} + pub(super) fn try_for_lint(attr: &ast::Attr, token: &SyntaxToken) -> Option { let (path, tt) = attr.as_simple_call()?; if !tt.syntax().text_range().contains(token.text_range().start()) { diff --git a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs index eb997e6fef830..f8be4cfb04c2e 100644 --- a/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs +++ b/src/tools/rust-analyzer/crates/ide/src/hover/tests.rs @@ -5307,3 +5307,38 @@ fn main() { $0V; } "#]], ); } + +#[test] +fn hover_rest_pat() { + check( + r#" +struct Struct {a: u32, b: u32, c: u8, d: u16}; + +fn main() { + let Struct {a, c, .$0.} = Struct {a: 1, b: 2, c: 3, d: 4}; +} +"#, + expect![[r#" + *..* + ```rust + .., b: u32, d: u16 + ``` + "#]], + ); + + check( + r#" +struct Struct {a: u32, b: u32, c: u8, d: u16}; + +fn main() { + let Struct {a, b, c, d, .$0.} = Struct {a: 1, b: 2, c: 3, d: 4}; +} +"#, + expect![[r#" + *..* + ```rust + .. + ``` + "#]], + ); +} diff --git a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs index 3d199ed24afe7..aa54342e39d71 100644 --- a/src/tools/rust-analyzer/crates/project-model/src/workspace.rs +++ b/src/tools/rust-analyzer/crates/project-model/src/workspace.rs @@ -93,7 +93,7 @@ pub enum ProjectWorkspace { // // /// Project with a set of disjoint files, not belonging to any particular workspace. /// Backed by basic sysroot crates for basic completion and highlighting. - DetachedFiles { files: Vec, sysroot: Sysroot, rustc_cfg: Vec }, + DetachedFiles { files: Vec, sysroot: Option, rustc_cfg: Vec }, } impl fmt::Debug for ProjectWorkspace { @@ -133,7 +133,7 @@ impl fmt::Debug for ProjectWorkspace { ProjectWorkspace::DetachedFiles { files, sysroot, rustc_cfg } => f .debug_struct("DetachedFiles") .field("n_files", &files.len()) - .field("n_sysroot_crates", &sysroot.crates().len()) + .field("sysroot", &sysroot.is_some()) .field("n_rustc_cfg", &rustc_cfg.len()) .finish(), } @@ -191,10 +191,7 @@ impl ProjectWorkspace { let sysroot = match &config.sysroot { Some(RustcSource::Path(path)) => { Some(Sysroot::with_sysroot_dir(path.clone()).with_context(|| { - format!( - "Failed to find sysroot for Cargo.toml file {}.", - cargo_toml.display() - ) + format!("Failed to find sysroot at {}.", path.display()) })?) } Some(RustcSource::Discover) => Some( @@ -291,14 +288,29 @@ impl ProjectWorkspace { Ok(ProjectWorkspace::Json { project: project_json, sysroot, rustc_cfg }) } - pub fn load_detached_files(detached_files: Vec) -> Result { - let sysroot = Sysroot::discover( - detached_files - .first() - .and_then(|it| it.parent()) - .ok_or_else(|| format_err!("No detached files to load"))?, - &Default::default(), - )?; + pub fn load_detached_files( + detached_files: Vec, + config: &CargoConfig, + ) -> Result { + let sysroot = match &config.sysroot { + Some(RustcSource::Path(path)) => Some( + Sysroot::with_sysroot_dir(path.clone()) + .with_context(|| format!("Failed to find sysroot at {}.", path.display()))?, + ), + Some(RustcSource::Discover) => { + let dir = &detached_files + .first() + .and_then(|it| it.parent()) + .ok_or_else(|| format_err!("No detached files to load"))?; + Some(Sysroot::discover(dir, &config.extra_env).with_context(|| { + format!("Failed to find sysroot in {}. Is rust-src installed?", dir.display()) + })?) + } + None => None, + }; + if let Some(sysroot) = &sysroot { + tracing::info!(src_root = %sysroot.src_root().display(), root = %sysroot.root().display(), "Using sysroot"); + } let rustc_cfg = rustc_cfg::get(None, None, &Default::default()); Ok(ProjectWorkspace::DetachedFiles { files: detached_files, sysroot, rustc_cfg }) } @@ -479,21 +491,25 @@ impl ProjectWorkspace { include: vec![detached_file.clone()], exclude: Vec::new(), }) - .chain(mk_sysroot(Some(sysroot))) + .chain(mk_sysroot(sysroot.as_ref())) .collect(), } } pub fn n_packages(&self) -> usize { match self { - ProjectWorkspace::Json { project, .. } => project.n_crates(), + ProjectWorkspace::Json { project, sysroot, .. } => { + let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len()); + sysroot_package_len + project.n_crates() + } ProjectWorkspace::Cargo { cargo, sysroot, rustc, .. } => { let rustc_package_len = rustc.as_ref().map_or(0, |it| it.packages().len()); let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len()); cargo.packages().len() + sysroot_package_len + rustc_package_len } ProjectWorkspace::DetachedFiles { sysroot, files, .. } => { - sysroot.crates().len() + files.len() + let sysroot_package_len = sysroot.as_ref().map_or(0, |it| it.crates().len()); + sysroot_package_len + files.len() } } } @@ -805,12 +821,14 @@ fn detached_files_to_crate_graph( rustc_cfg: Vec, load: &mut dyn FnMut(&AbsPath) -> Option, detached_files: &[AbsPathBuf], - sysroot: &Sysroot, + sysroot: &Option, ) -> CrateGraph { let _p = profile::span("detached_files_to_crate_graph"); let mut crate_graph = CrateGraph::default(); - let (public_deps, _libproc_macro) = - sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load); + let (public_deps, _libproc_macro) = match sysroot { + Some(sysroot) => sysroot_to_crate_graph(&mut crate_graph, sysroot, rustc_cfg.clone(), load), + None => (SysrootPublicDeps::default(), None), + }; let mut cfg_options = CfgOptions::default(); cfg_options.extend(rustc_cfg); diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs index 6b2f22faa7178..6c0d712a4f473 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/config.rs @@ -182,7 +182,7 @@ config_data! { /// `["aarch64-apple-darwin", "x86_64-apple-darwin"]`. /// /// Aliased as `"checkOnSave.targets"`. - checkOnSave_target | checkOnSave_targets: CheckOnSaveTargets = "[]", + checkOnSave_target | checkOnSave_targets: Option = "null", /// Toggles the additional completions that automatically add imports when completed. /// Note that your client must specify the `additionalTextEdits` LSP client capability to truly have this feature enabled. @@ -1153,10 +1153,15 @@ impl Config { } Some(_) | None => FlycheckConfig::CargoCommand { command: self.data.checkOnSave_command.clone(), - target_triples: match &self.data.checkOnSave_target.0[..] { - [] => self.data.cargo_target.clone().into_iter().collect(), - targets => targets.into(), - }, + target_triples: self + .data + .checkOnSave_target + .clone() + .and_then(|targets| match &targets.0[..] { + [] => None, + targets => Some(targets.into()), + }) + .unwrap_or_else(|| self.data.cargo_target.clone().into_iter().collect()), all_targets: self.data.checkOnSave_allTargets, no_default_features: self .data @@ -2126,8 +2131,11 @@ fn field_props(field: &str, ty: &str, doc: &[&str], default: &str) -> serde_json "The command will be executed in the project root." ], }, - "CheckOnSaveTargets" => set! { + "Option" => set! { "anyOf": [ + { + "type": "null" + }, { "type": "string", }, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs index d190a9f4e2ca9..4f318f39de5f4 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/handlers.rs @@ -28,7 +28,7 @@ use lsp_types::{ use project_model::{ManifestPath, ProjectWorkspace, TargetKind}; use serde_json::json; use stdx::{format_to, never}; -use syntax::{algo, ast, AstNode, TextRange, TextSize, T}; +use syntax::{algo, ast, AstNode, TextRange, TextSize}; use vfs::AbsPathBuf; use crate::{ @@ -812,18 +812,6 @@ pub(crate) fn handle_completion( let completion_trigger_character = params.context.and_then(|ctx| ctx.trigger_character).and_then(|s| s.chars().next()); - if Some(':') == completion_trigger_character { - let source_file = snap.analysis.parse(position.file_id)?; - let left_token = source_file.syntax().token_at_offset(position.offset).left_biased(); - let completion_triggered_after_single_colon = match left_token { - Some(left_token) => left_token.kind() == T![:], - None => true, - }; - if completion_triggered_after_single_colon { - return Ok(None); - } - } - let completion_config = &snap.config.completion(); let items = match snap.analysis.completions( completion_config, diff --git a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs index fcfe4be0b8cec..748a189c203eb 100644 --- a/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs +++ b/src/tools/rust-analyzer/crates/rust-analyzer/src/reload.rs @@ -158,8 +158,10 @@ impl GlobalState { .collect::>(); if !detached_files.is_empty() { - workspaces - .push(project_model::ProjectWorkspace::load_detached_files(detached_files)); + workspaces.push(project_model::ProjectWorkspace::load_detached_files( + detached_files, + &cargo_config, + )); } tracing::info!("did fetch workspaces {:?}", workspaces); diff --git a/src/tools/rust-analyzer/docs/user/generated_config.adoc b/src/tools/rust-analyzer/docs/user/generated_config.adoc index 57f950034cbb7..db41c7bf109e2 100644 --- a/src/tools/rust-analyzer/docs/user/generated_config.adoc +++ b/src/tools/rust-analyzer/docs/user/generated_config.adoc @@ -190,7 +190,7 @@ cargo check --workspace --message-format=json --all-targets ``` . -- -[[rust-analyzer.checkOnSave.target]]rust-analyzer.checkOnSave.target (default: `[]`):: +[[rust-analyzer.checkOnSave.target]]rust-analyzer.checkOnSave.target (default: `null`):: + -- Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty. diff --git a/src/tools/rust-analyzer/editors/code/package.json b/src/tools/rust-analyzer/editors/code/package.json index c4d4e428ea07d..ad5f97bb1915f 100644 --- a/src/tools/rust-analyzer/editors/code/package.json +++ b/src/tools/rust-analyzer/editors/code/package.json @@ -640,8 +640,11 @@ }, "rust-analyzer.checkOnSave.target": { "markdownDescription": "Check for specific targets. Defaults to `#rust-analyzer.cargo.target#` if empty.\n\nCan be a single target, e.g. `\"x86_64-unknown-linux-gnu\"` or a list of targets, e.g.\n`[\"aarch64-apple-darwin\", \"x86_64-apple-darwin\"]`.\n\nAliased as `\"checkOnSave.targets\"`.", - "default": [], + "default": null, "anyOf": [ + { + "type": "null" + }, { "type": "string" }, @@ -1493,6 +1496,11 @@ "description": "Style for derives", "superType": "attribute" }, + { + "id": "deriveHelper", + "description": "Style for derive helpers", + "superType": "attribute" + }, { "id": "dot", "description": "Style for .",