diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index ca4a3f5217cf..3486928e4146 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -6,13 +6,14 @@ pub mod scope; #[cfg(test)] mod tests; -use std::ops::Index; +use std::ops::{Deref, Index}; use base_db::CrateId; use cfg::{CfgExpr, CfgOptions}; use hir_expand::{name::Name, InFile}; use la_arena::{Arena, ArenaMap}; use rustc_hash::FxHashMap; +use smallvec::SmallVec; use span::MacroFileId; use syntax::{ast, AstPtr, SyntaxNodePtr}; use triomphe::Arc; @@ -91,6 +92,7 @@ pub struct BodySourceMap { label_map_back: ArenaMap, self_param: Option>>, + binding_definitions: FxHashMap>, /// We don't create explicit nodes for record fields (`S { record_field: 92 }`). /// Instead, we use id of expression (`92`) to identify the field. @@ -377,6 +379,10 @@ impl BodySourceMap { self.label_map_back[label] } + pub fn patterns_for_binding(&self, binding: BindingId) -> &[PatId] { + self.binding_definitions.get(&binding).map_or(&[], Deref::deref) + } + pub fn node_label(&self, node: InFile<&ast::Label>) -> Option { let src = node.map(AstPtr::new); self.label_map.get(&src).cloned() @@ -428,6 +434,7 @@ impl BodySourceMap { expansions, format_args_template_map, diagnostics, + binding_definitions, } = self; format_args_template_map.shrink_to_fit(); expr_map.shrink_to_fit(); @@ -440,5 +447,6 @@ impl BodySourceMap { pat_field_map_back.shrink_to_fit(); expansions.shrink_to_fit(); diagnostics.shrink_to_fit(); + binding_definitions.shrink_to_fit(); } } diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index e2a5f353136a..fe5264674a6a 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -4,13 +4,13 @@ use std::mem; use base_db::CrateId; +use either::Either; use hir_expand::{ name::{AsName, Name}, ExpandError, InFile, }; use intern::{sym, Interned, Symbol}; use rustc_hash::FxHashMap; -use smallvec::SmallVec; use span::AstIdMap; use stdx::never; use syntax::{ @@ -1436,15 +1436,14 @@ impl ExprCollector<'_> { args: AstChildren, has_leading_comma: bool, binding_list: &mut BindingList, - ) -> (Box<[PatId]>, Option) { + ) -> (Box<[PatId]>, Option) { + let args: Vec<_> = args.map(|p| self.collect_pat_possibly_rest(p, binding_list)).collect(); // Find the location of the `..`, if there is one. Note that we do not // consider the possibility of there being multiple `..` here. - let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_))); + let ellipsis = args.iter().position(|p| p.is_right()).map(|it| it as u32); + // We want to skip the `..` pattern here, since we account for it above. - let mut args: Vec<_> = args - .filter(|p| !matches!(p, ast::Pat::RestPat(_))) - .map(|p| self.collect_pat(p, binding_list)) - .collect(); + let mut args: Vec<_> = args.into_iter().filter_map(Either::left).collect(); // if there is a leading comma, the user is most likely to type out a leading pattern // so we insert a missing pattern at the beginning for IDE features if has_leading_comma { @@ -1454,6 +1453,41 @@ impl ExprCollector<'_> { (args.into_boxed_slice(), ellipsis) } + // `collect_pat` rejects `ast::Pat::RestPat`, but it should be handled in some cases that + // it is the macro expansion result of an arg sub-pattern in a slice or tuple pattern. + fn collect_pat_possibly_rest( + &mut self, + pat: ast::Pat, + binding_list: &mut BindingList, + ) -> Either { + match &pat { + ast::Pat::RestPat(_) => Either::Right(()), + ast::Pat::MacroPat(mac) => match mac.macro_call() { + Some(call) => { + let macro_ptr = AstPtr::new(&call); + let src = self.expander.in_file(AstPtr::new(&pat)); + let pat = + self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| { + if let Some(expanded_pat) = expanded_pat { + this.collect_pat_possibly_rest(expanded_pat, binding_list) + } else { + Either::Left(this.missing_pat()) + } + }); + if let Some(pat) = pat.left() { + self.source_map.pat_map.insert(src, pat); + } + pat + } + None => { + let ptr = AstPtr::new(&pat); + Either::Left(self.alloc_pat(Pat::Missing, ptr)) + } + }, + _ => Either::Left(self.collect_pat(pat, binding_list)), + } + } + // endregion: patterns /// Returns `None` (and emits diagnostics) when `owner` if `#[cfg]`d out, and `Some(())` when @@ -1478,7 +1512,7 @@ impl ExprCollector<'_> { } fn add_definition_to_binding(&mut self, binding_id: BindingId, pat_id: PatId) { - self.body.bindings[binding_id].definitions.push(pat_id); + self.source_map.binding_definitions.entry(binding_id).or_default().push(pat_id); } // region: labels @@ -2024,12 +2058,7 @@ impl ExprCollector<'_> { } fn alloc_binding(&mut self, name: Name, mode: BindingAnnotation) -> BindingId { - let binding = self.body.bindings.alloc(Binding { - name, - mode, - definitions: SmallVec::new(), - problems: None, - }); + let binding = self.body.bindings.alloc(Binding { name, mode, problems: None }); if let Some(owner) = self.current_binding_owner { self.body.binding_owners.insert(binding, owner); } diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index c48d16d05304..edaee60937d1 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -517,7 +517,7 @@ impl Printer<'_> { if i != 0 { w!(self, ", "); } - if *ellipsis == Some(i) { + if *ellipsis == Some(i as u32) { w!(self, ".., "); } self.print_pat(*pat); @@ -595,7 +595,7 @@ impl Printer<'_> { if i != 0 { w!(self, ", "); } - if *ellipsis == Some(i) { + if *ellipsis == Some(i as u32) { w!(self, ", .."); } self.print_pat(*arg); diff --git a/crates/hir-def/src/body/scope.rs b/crates/hir-def/src/body/scope.rs index 404e5ba69c60..bf201ca83479 100644 --- a/crates/hir-def/src/body/scope.rs +++ b/crates/hir-def/src/body/scope.rs @@ -484,7 +484,7 @@ fn foo() { let function = find_function(&db, file_id.file_id()); let scopes = db.expr_scopes(function.into()); - let (body, source_map) = db.body_with_source_map(function.into()); + let (_, source_map) = db.body_with_source_map(function.into()); let expr_scope = { let expr_ast = name_ref.syntax().ancestors().find_map(ast::Expr::cast).unwrap(); @@ -495,7 +495,7 @@ fn foo() { let resolved = scopes.resolve_name_in_scope(expr_scope, &name_ref.as_name()).unwrap(); let pat_src = source_map - .pat_syntax(*body.bindings[resolved.binding()].definitions.first().unwrap()) + .pat_syntax(*source_map.binding_definitions[&resolved.binding()].first().unwrap()) .unwrap(); let local_name = pat_src.value.syntax_node_ptr().to_node(file.syntax()); diff --git a/crates/hir-def/src/db.rs b/crates/hir-def/src/db.rs index 1b83808b3867..56feb0163e13 100644 --- a/crates/hir-def/src/db.rs +++ b/crates/hir-def/src/db.rs @@ -177,6 +177,7 @@ pub trait DefDatabase: InternDatabase + ExpandDatabase + Upcast (Arc, Arc); #[salsa::invoke(Body::body_query)] diff --git a/crates/hir-def/src/hir.rs b/crates/hir-def/src/hir.rs index d7eb80a88bfa..86fd092603ff 100644 --- a/crates/hir-def/src/hir.rs +++ b/crates/hir-def/src/hir.rs @@ -21,7 +21,6 @@ use hir_expand::name::Name; use intern::{Interned, Symbol}; use la_arena::{Idx, RawIdx}; use rustc_apfloat::ieee::{Half as f16, Quad as f128}; -use smallvec::SmallVec; use syntax::ast; use crate::{ @@ -525,7 +524,6 @@ pub enum BindingProblems { pub struct Binding { pub name: Name, pub mode: BindingAnnotation, - pub definitions: SmallVec<[PatId; 1]>, pub problems: Option, } @@ -540,7 +538,7 @@ pub struct RecordFieldPat { pub enum Pat { Missing, Wild, - Tuple { args: Box<[PatId]>, ellipsis: Option }, + Tuple { args: Box<[PatId]>, ellipsis: Option }, Or(Box<[PatId]>), Record { path: Option>, args: Box<[RecordFieldPat]>, ellipsis: bool }, Range { start: Option>, end: Option> }, @@ -548,7 +546,7 @@ pub enum Pat { Path(Box), Lit(ExprId), Bind { id: BindingId, subpat: Option }, - TupleStruct { path: Option>, args: Box<[PatId]>, ellipsis: Option }, + TupleStruct { path: Option>, args: Box<[PatId]>, ellipsis: Option }, Ref { pat: PatId, mutability: Mutability }, Box { inner: PatId }, ConstBlock(ExprId), diff --git a/crates/hir-def/src/nameres/collector.rs b/crates/hir-def/src/nameres/collector.rs index 4b35c1ae7192..58aca9642909 100644 --- a/crates/hir-def/src/nameres/collector.rs +++ b/crates/hir-def/src/nameres/collector.rs @@ -16,6 +16,7 @@ use hir_expand::{ name::{AsName, Name}, proc_macro::CustomProcMacroExpander, ExpandTo, HirFileId, InFile, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, + MacroFileIdExt, }; use intern::{sym, Interned}; use itertools::{izip, Itertools}; @@ -1397,7 +1398,12 @@ impl DefCollector<'_> { // Then, fetch and process the item tree. This will reuse the expansion result from above. let item_tree = self.db.file_item_tree(file_id); - let mod_dir = self.mod_dirs[&module_id].clone(); + let mod_dir = if macro_call_id.as_macro_file().is_include_macro(self.db.upcast()) { + ModDir::root() + } else { + self.mod_dirs[&module_id].clone() + }; + ModCollector { def_collector: &mut *self, macro_depth: depth, diff --git a/crates/hir-def/src/nameres/tests/macros.rs b/crates/hir-def/src/nameres/tests/macros.rs index 9f67d6fa8bd8..390c934f6dac 100644 --- a/crates/hir-def/src/nameres/tests/macros.rs +++ b/crates/hir-def/src/nameres/tests/macros.rs @@ -1355,6 +1355,70 @@ pub mod ip_address { ); } +#[test] +fn include_many_mods() { + check( + r#" +//- /lib.rs +#[rustc_builtin_macro] +macro_rules! include { () => {} } + +mod nested { + include!("out_dir/includes.rs"); + + mod different_company { + include!("out_dir/different_company/mod.rs"); + } + + mod util; +} + +//- /nested/util.rs +pub struct Helper {} +//- /out_dir/includes.rs +pub mod company_name { + pub mod network { + pub mod v1; + } +} +//- /out_dir/company_name/network/v1.rs +pub struct IpAddress {} +//- /out_dir/different_company/mod.rs +pub mod network; +//- /out_dir/different_company/network.rs +pub struct Url {} + +"#, + expect![[r#" + crate + nested: t + + crate::nested + company_name: t + different_company: t + util: t + + crate::nested::company_name + network: t + + crate::nested::company_name::network + v1: t + + crate::nested::company_name::network::v1 + IpAddress: t + + crate::nested::different_company + network: t + + crate::nested::different_company::network + Url: t + + crate::nested::util + Helper: t + "#]], + ); +} + #[test] fn macro_use_imports_all_macro_types() { let db = TestDB::with_files( diff --git a/crates/hir-expand/src/db.rs b/crates/hir-expand/src/db.rs index 5a499d2efd05..e78ab2460a7a 100644 --- a/crates/hir-expand/src/db.rs +++ b/crates/hir-expand/src/db.rs @@ -62,8 +62,6 @@ pub trait ExpandDatabase: SourceDatabase { /// file or a macro expansion. #[salsa::transparent] fn parse_or_expand(&self, file_id: HirFileId) -> SyntaxNode; - #[salsa::transparent] - fn parse_or_expand_with_err(&self, file_id: HirFileId) -> ExpandResult>; /// Implementation for the macro case. #[salsa::lru] fn parse_macro_expansion( @@ -328,18 +326,6 @@ fn parse_or_expand(db: &dyn ExpandDatabase, file_id: HirFileId) -> SyntaxNode { } } -fn parse_or_expand_with_err( - db: &dyn ExpandDatabase, - file_id: HirFileId, -) -> ExpandResult> { - match file_id.repr() { - HirFileIdRepr::FileId(file_id) => ExpandResult::ok(db.parse(file_id).to_syntax()), - HirFileIdRepr::MacroFile(macro_file) => { - db.parse_macro_expansion(macro_file).map(|(it, _)| it) - } - } -} - // FIXME: We should verify that the parsed node is one of the many macro node variants we expect // instead of having it be untyped fn parse_macro_expansion( diff --git a/crates/hir-ty/src/diagnostics/match_check.rs b/crates/hir-ty/src/diagnostics/match_check.rs index 8dcc14feb278..a0ee7c0748b1 100644 --- a/crates/hir-ty/src/diagnostics/match_check.rs +++ b/crates/hir-ty/src/diagnostics/match_check.rs @@ -206,7 +206,7 @@ impl<'a> PatCtxt<'a> { &mut self, pats: &[PatId], expected_len: usize, - ellipsis: Option, + ellipsis: Option, ) -> Vec { if pats.len() > expected_len { self.errors.push(PatternError::ExtraFields); @@ -214,7 +214,7 @@ impl<'a> PatCtxt<'a> { } pats.iter() - .enumerate_and_adjust(expected_len, ellipsis) + .enumerate_and_adjust(expected_len, ellipsis.map(|it| it as usize)) .map(|(i, &subpattern)| FieldPat { field: LocalFieldId::from_raw((i as u32).into()), pattern: self.lower_pattern(subpattern), diff --git a/crates/hir-ty/src/infer/closure.rs b/crates/hir-ty/src/infer/closure.rs index 417fca5dcd67..034ed2d691b4 100644 --- a/crates/hir-ty/src/infer/closure.rs +++ b/crates/hir-ty/src/infer/closure.rs @@ -889,7 +889,7 @@ impl InferenceContext<'_> { match &self.body[pat] { Pat::Missing | Pat::Wild => (), Pat::Tuple { args, ellipsis } => { - let (al, ar) = args.split_at(ellipsis.unwrap_or(args.len())); + let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); let field_count = match self.result[pat].kind(Interner) { TyKind::Tuple(_, s) => s.len(Interner), _ => return, @@ -964,7 +964,7 @@ impl InferenceContext<'_> { } VariantId::StructId(s) => { let vd = &*self.db.struct_data(s).variant_data; - let (al, ar) = args.split_at(ellipsis.unwrap_or(args.len())); + let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); let fields = vd.fields().iter(); let it = al.iter().zip(fields.clone()).chain(ar.iter().rev().zip(fields.rev())); diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 7857d207be7c..87233fa0113c 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -1170,7 +1170,7 @@ impl InferenceContext<'_> { Expr::Tuple { exprs, .. } => { // We don't consider multiple ellipses. This is analogous to // `hir_def::body::lower::ExprCollector::collect_tuple_pat()`. - let ellipsis = exprs.iter().position(|e| is_rest_expr(*e)); + let ellipsis = exprs.iter().position(|e| is_rest_expr(*e)).map(|it| it as u32); let exprs: Vec<_> = exprs.iter().filter(|e| !is_rest_expr(**e)).copied().collect(); self.infer_tuple_pat_like(&rhs_ty, (), ellipsis, &exprs) @@ -1184,7 +1184,7 @@ impl InferenceContext<'_> { // We don't consider multiple ellipses. This is analogous to // `hir_def::body::lower::ExprCollector::collect_tuple_pat()`. - let ellipsis = args.iter().position(|e| is_rest_expr(*e)); + let ellipsis = args.iter().position(|e| is_rest_expr(*e)).map(|it| it as u32); let args: Vec<_> = args.iter().filter(|e| !is_rest_expr(**e)).copied().collect(); self.infer_tuple_struct_pat_like(path, &rhs_ty, (), lhs, ellipsis, &args) diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index dac5a5ea6995..f3c6f13a08d0 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -68,7 +68,7 @@ impl InferenceContext<'_> { expected: &Ty, default_bm: T::BindingMode, id: T, - ellipsis: Option, + ellipsis: Option, subs: &[T], ) -> Ty { let (ty, def) = self.resolve_variant(path, true); @@ -98,7 +98,7 @@ impl InferenceContext<'_> { let visibilities = self.db.field_visibilities(def); let (pre, post) = match ellipsis { - Some(idx) => subs.split_at(idx), + Some(idx) => subs.split_at(idx as usize), None => (subs, &[][..]), }; let post_idx_offset = field_types.iter().count().saturating_sub(post.len()); @@ -219,7 +219,7 @@ impl InferenceContext<'_> { &mut self, expected: &Ty, default_bm: T::BindingMode, - ellipsis: Option, + ellipsis: Option, subs: &[T], ) -> Ty { let expected = self.resolve_ty_shallow(expected); @@ -229,7 +229,9 @@ impl InferenceContext<'_> { }; let ((pre, post), n_uncovered_patterns) = match ellipsis { - Some(idx) => (subs.split_at(idx), expectations.len().saturating_sub(subs.len())), + Some(idx) => { + (subs.split_at(idx as usize), expectations.len().saturating_sub(subs.len())) + } None => ((subs, &[][..]), 0), }; let mut expectations_iter = expectations diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs index 2e106877cbc2..06a4236e0acd 100644 --- a/crates/hir-ty/src/mir.rs +++ b/crates/hir-ty/src/mir.rs @@ -1164,6 +1164,7 @@ impl MirBody { pub enum MirSpan { ExprId(ExprId), PatId(PatId), + BindingId(BindingId), SelfParam, Unknown, } diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index 26a49412e0f2..f8083e898588 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -388,6 +388,16 @@ impl MirEvalError { Ok(s) => s.map(|it| it.syntax_node_ptr()), Err(_) => continue, }, + MirSpan::BindingId(b) => { + match source_map + .patterns_for_binding(*b) + .iter() + .find_map(|p| source_map.pat_syntax(*p).ok()) + { + Some(s) => s.map(|it| it.syntax_node_ptr()), + None => continue, + } + } MirSpan::SelfParam => match source_map.self_param_syntax() { Some(s) => s.map(|it| it.syntax_node_ptr()), None => continue, diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 11c1e8ce3cc6..057f55338056 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -1720,14 +1720,8 @@ impl<'ctx> MirLowerCtx<'ctx> { /// This function push `StorageLive` statement for the binding, and applies changes to add `StorageDead` and /// `Drop` in the appropriated places. fn push_storage_live(&mut self, b: BindingId, current: BasicBlockId) -> Result<()> { - let span = self.body.bindings[b] - .definitions - .first() - .copied() - .map(MirSpan::PatId) - .unwrap_or(MirSpan::Unknown); let l = self.binding_local(b)?; - self.push_storage_live_for_local(l, current, span) + self.push_storage_live_for_local(l, current, MirSpan::BindingId(b)) } fn push_storage_live_for_local( diff --git a/crates/hir-ty/src/mir/lower/pattern_matching.rs b/crates/hir-ty/src/mir/lower/pattern_matching.rs index 759690679434..34e0f30afb7f 100644 --- a/crates/hir-ty/src/mir/lower/pattern_matching.rs +++ b/crates/hir-ty/src/mir/lower/pattern_matching.rs @@ -23,7 +23,7 @@ macro_rules! not_supported { } pub(super) enum AdtPatternShape<'a> { - Tuple { args: &'a [PatId], ellipsis: Option }, + Tuple { args: &'a [PatId], ellipsis: Option }, Record { args: &'a [RecordFieldPat] }, Unit, } @@ -627,12 +627,12 @@ impl MirLowerCtx<'_> { current: BasicBlockId, current_else: Option, args: &[PatId], - ellipsis: Option, + ellipsis: Option, fields: impl DoubleEndedIterator + Clone, cond_place: &Place, mode: MatchingMode, ) -> Result<(BasicBlockId, Option)> { - let (al, ar) = args.split_at(ellipsis.unwrap_or(args.len())); + let (al, ar) = args.split_at(ellipsis.map_or(args.len(), |it| it as usize)); let it = al .iter() .zip(fields.clone()) diff --git a/crates/hir/src/display.rs b/crates/hir/src/display.rs index a0dbead22168..f80ccf84a2c5 100644 --- a/crates/hir/src/display.rs +++ b/crates/hir/src/display.rs @@ -134,9 +134,9 @@ impl HirDisplay for Function { .as_ref() .unwrap() } - _ => panic!("Async fn ret_type should be impl Future"), + _ => &TypeRef::Error, }, - _ => panic!("Async fn ret_type should be impl Future"), + _ => &TypeRef::Error, } }; diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index 2bd0008c701f..32057fbaff01 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -1806,6 +1806,16 @@ impl DefWithBody { Some(s) => s.map(|it| it.into()), None => continue, }, + mir::MirSpan::BindingId(b) => { + match source_map + .patterns_for_binding(b) + .iter() + .find_map(|p| source_map.pat_syntax(*p).ok()) + { + Some(s) => s.map(|it| it.into()), + None => continue, + } + } mir::MirSpan::Unknown => continue, }; acc.push( @@ -1822,8 +1832,8 @@ impl DefWithBody { let Some(&local) = mir_body.binding_locals.get(binding_id) else { continue; }; - if body[binding_id] - .definitions + if source_map + .patterns_for_binding(binding_id) .iter() .any(|&pat| source_map.pat_syntax(pat).is_err()) { @@ -1859,6 +1869,16 @@ impl DefWithBody { Ok(s) => s.map(|it| it.into()), Err(_) => continue, }, + mir::MirSpan::BindingId(b) => { + match source_map + .patterns_for_binding(*b) + .iter() + .find_map(|p| source_map.pat_syntax(*p).ok()) + { + Some(s) => s.map(|it| it.into()), + None => continue, + } + } mir::MirSpan::SelfParam => match source_map.self_param_syntax() { Some(s) => s.map(|it| it.into()), @@ -1977,7 +1997,6 @@ impl Function { return Type::new_with_resolver_inner(db, &resolver, output_eq.ty).into(); } } - never!("Async fn ret_type should be impl Future"); None } @@ -3292,8 +3311,8 @@ impl Local { source: source.map(|ast| Either::Right(ast.to_node(&root))), }] } - _ => body[self.binding_id] - .definitions + _ => source_map + .patterns_for_binding(self.binding_id) .iter() .map(|&definition| { let src = source_map.pat_syntax(definition).unwrap(); // Hmm... @@ -3321,8 +3340,8 @@ impl Local { source: source.map(|ast| Either::Right(ast.to_node(&root))), } } - _ => body[self.binding_id] - .definitions + _ => source_map + .patterns_for_binding(self.binding_id) .first() .map(|&definition| { let src = source_map.pat_syntax(definition).unwrap(); // Hmm... diff --git a/crates/ide-assists/src/assist_config.rs b/crates/ide-assists/src/assist_config.rs index f1de6aba05ba..82d8db425892 100644 --- a/crates/ide-assists/src/assist_config.rs +++ b/crates/ide-assists/src/assist_config.rs @@ -4,6 +4,7 @@ //! module, and we use to statically check that we only produce snippet //! assists if we are allowed to. +use hir::ImportPathConfig; use ide_db::{imports::insert_use::InsertUseConfig, SnippetCap}; use crate::AssistKind; @@ -20,3 +21,13 @@ pub struct AssistConfig { pub term_search_fuel: u64, pub term_search_borrowck: bool, } + +impl AssistConfig { + pub fn import_path_config(&self) -> ImportPathConfig { + ImportPathConfig { + prefer_no_std: self.prefer_no_std, + prefer_prelude: self.prefer_prelude, + prefer_absolute: self.prefer_absolute, + } + } +} diff --git a/crates/ide-assists/src/handlers/add_missing_match_arms.rs b/crates/ide-assists/src/handlers/add_missing_match_arms.rs index e1bf6b27bc7f..f4569ca848ff 100644 --- a/crates/ide-assists/src/handlers/add_missing_match_arms.rs +++ b/crates/ide-assists/src/handlers/add_missing_match_arms.rs @@ -71,11 +71,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>) .filter(|pat| !matches!(pat, Pat::WildcardPat(_))) .collect(); - let cfg = ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }; + let cfg = ctx.config.import_path_config(); let module = ctx.sema.scope(expr.syntax())?.module(); let (mut missing_pats, is_non_exhaustive, has_hidden_variants): ( diff --git a/crates/ide-assists/src/handlers/add_turbo_fish.rs b/crates/ide-assists/src/handlers/add_turbo_fish.rs index 327709b28a30..17efbcbd6c9e 100644 --- a/crates/ide-assists/src/handlers/add_turbo_fish.rs +++ b/crates/ide-assists/src/handlers/add_turbo_fish.rs @@ -124,7 +124,7 @@ pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext<'_>) -> Opti "Add `::<>`", ident.text_range(), |edit| { - edit.trigger_signature_help(); + edit.trigger_parameter_hints(); let new_arg_list = match turbofish_target { Either::Left(path_segment) => { diff --git a/crates/ide-assists/src/handlers/auto_import.rs b/crates/ide-assists/src/handlers/auto_import.rs index 80e80507ad58..db53e49d8463 100644 --- a/crates/ide-assists/src/handlers/auto_import.rs +++ b/crates/ide-assists/src/handlers/auto_import.rs @@ -1,6 +1,6 @@ use std::cmp::Reverse; -use hir::{db::HirDatabase, ImportPathConfig, Module}; +use hir::{db::HirDatabase, Module}; use ide_db::{ helpers::mod_path_to_ast, imports::{ @@ -90,11 +90,7 @@ use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel}; // # pub mod std { pub mod collections { pub struct HashMap { } } } // ``` pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { - let cfg = ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }; + let cfg = ctx.config.import_path_config(); let (import_assets, syntax_under_caret) = find_importable_node(ctx)?; let mut proposed_imports: Vec<_> = import_assets @@ -108,7 +104,6 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< NodeOrToken::Node(node) => ctx.sema.original_range(node).range, NodeOrToken::Token(token) => token.text_range(), }; - let group_label = group_label(import_assets.import_candidate()); let scope = ImportScope::find_insert_use_container( &match syntax_under_caret { NodeOrToken::Node(it) => it, @@ -121,18 +116,12 @@ pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< proposed_imports.sort_by(|a, b| a.import_path.cmp(&b.import_path)); proposed_imports.dedup_by(|a, b| a.import_path == b.import_path); - let current_node = match ctx.covering_element() { - NodeOrToken::Node(node) => Some(node), - NodeOrToken::Token(token) => token.parent(), - }; - - let current_module = - current_node.as_ref().and_then(|node| ctx.sema.scope(node)).map(|scope| scope.module()); - + let current_module = ctx.sema.scope(scope.as_syntax_node()).map(|scope| scope.module()); // prioritize more relevant imports proposed_imports .sort_by_key(|import| Reverse(relevance_score(ctx, import, current_module.as_ref()))); + let group_label = group_label(import_assets.import_candidate()); for import in proposed_imports { let import_path = import.import_path; @@ -226,7 +215,7 @@ fn group_label(import_candidate: &ImportCandidate) -> GroupLabel { /// Determine how relevant a given import is in the current context. Higher scores are more /// relevant. -fn relevance_score( +pub(crate) fn relevance_score( ctx: &AssistContext<'_>, import: &LocatedImport, current_module: Option<&Module>, diff --git a/crates/ide-assists/src/handlers/bool_to_enum.rs b/crates/ide-assists/src/handlers/bool_to_enum.rs index 65fd01fa517d..3a0754d60f85 100644 --- a/crates/ide-assists/src/handlers/bool_to_enum.rs +++ b/crates/ide-assists/src/handlers/bool_to_enum.rs @@ -1,5 +1,5 @@ use either::Either; -use hir::{ImportPathConfig, ModuleDef}; +use hir::ModuleDef; use ide_db::{ assists::{AssistId, AssistKind}, defs::Definition, @@ -337,11 +337,7 @@ fn augment_references_with_imports( ) -> Vec { let mut visited_modules = FxHashSet::default(); - let cfg = ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }; + let cfg = ctx.config.import_path_config(); references .into_iter() diff --git a/crates/ide-assists/src/handlers/convert_into_to_from.rs b/crates/ide-assists/src/handlers/convert_into_to_from.rs index 5349e86cf384..5aa94590e679 100644 --- a/crates/ide-assists/src/handlers/convert_into_to_from.rs +++ b/crates/ide-assists/src/handlers/convert_into_to_from.rs @@ -1,4 +1,3 @@ -use hir::ImportPathConfig; use ide_db::{famous_defs::FamousDefs, helpers::mod_path_to_ast, traits::resolve_target_trait}; use syntax::ast::{self, AstNode, HasGenericArgs, HasName}; @@ -44,11 +43,7 @@ pub(crate) fn convert_into_to_from(acc: &mut Assists, ctx: &AssistContext<'_>) - return None; } - let cfg = ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }; + let cfg = ctx.config.import_path_config(); let src_type_path = { let src_type_path = src_type.syntax().descendants().find_map(ast::Path::cast)?; diff --git a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs index 7432fc770958..0f0b4442d8ac 100644 --- a/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs +++ b/crates/ide-assists/src/handlers/convert_tuple_return_type_to_struct.rs @@ -1,5 +1,5 @@ use either::Either; -use hir::{ImportPathConfig, ModuleDef}; +use hir::ModuleDef; use ide_db::{ assists::{AssistId, AssistKind}, defs::Definition, @@ -183,11 +183,7 @@ fn augment_references_with_imports( ) -> Vec<(ast::NameLike, Option<(ImportScope, ast::Path)>)> { let mut visited_modules = FxHashSet::default(); - let cfg = ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }; + let cfg = ctx.config.import_path_config(); references .iter() diff --git a/crates/ide-assists/src/handlers/destructure_struct_binding.rs b/crates/ide-assists/src/handlers/destructure_struct_binding.rs index da902f7e10e8..095b8f958d09 100644 --- a/crates/ide-assists/src/handlers/destructure_struct_binding.rs +++ b/crates/ide-assists/src/handlers/destructure_struct_binding.rs @@ -1,4 +1,4 @@ -use hir::{sym, HasVisibility, ImportPathConfig}; +use hir::{sym, HasVisibility}; use ide_db::{ assists::{AssistId, AssistKind}, defs::Definition, @@ -87,11 +87,7 @@ fn collect_data(ident_pat: ast::IdentPat, ctx: &AssistContext<'_>) -> Option) -> Op ctx.sema.db, ModuleDef::from(control_flow_enum), ctx.config.insert_use.prefix_kind, - ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }, + ctx.config.import_path_config(), ); if let Some(mod_path) = mod_path { diff --git a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs index cd483c1b57ae..a62fdeb61739 100644 --- a/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs +++ b/crates/ide-assists/src/handlers/extract_struct_from_enum_variant.rs @@ -1,7 +1,7 @@ use std::iter; use either::Either; -use hir::{ImportPathConfig, Module, ModuleDef, Name, Variant}; +use hir::{Module, ModuleDef, Name, Variant}; use ide_db::{ defs::Definition, helpers::mod_path_to_ast, @@ -390,11 +390,7 @@ fn process_references( ctx.sema.db, *enum_module_def, ctx.config.insert_use.prefix_kind, - ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }, + ctx.config.import_path_config(), ); if let Some(mut mod_path) = mod_path { mod_path.pop_segment(); diff --git a/crates/ide-assists/src/handlers/generate_deref.rs b/crates/ide-assists/src/handlers/generate_deref.rs index cc33439dd59e..2ac7057fe794 100644 --- a/crates/ide-assists/src/handlers/generate_deref.rs +++ b/crates/ide-assists/src/handlers/generate_deref.rs @@ -1,6 +1,6 @@ use std::fmt::Display; -use hir::{ImportPathConfig, ModPath, ModuleDef}; +use hir::{ModPath, ModuleDef}; use ide_db::{famous_defs::FamousDefs, RootDatabase}; use syntax::{ ast::{self, HasName}, @@ -58,15 +58,8 @@ fn generate_record_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<( let module = ctx.sema.to_def(&strukt)?.module(ctx.db()); let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate())?; - let trait_path = module.find_path( - ctx.db(), - ModuleDef::Trait(trait_), - ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }, - )?; + let trait_path = + module.find_path(ctx.db(), ModuleDef::Trait(trait_), ctx.config.import_path_config())?; let field_type = field.ty()?; let field_name = field.name()?; @@ -106,15 +99,8 @@ fn generate_tuple_deref(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<() let module = ctx.sema.to_def(&strukt)?.module(ctx.db()); let trait_ = deref_type_to_generate.to_trait(&ctx.sema, module.krate())?; - let trait_path = module.find_path( - ctx.db(), - ModuleDef::Trait(trait_), - ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }, - )?; + let trait_path = + module.find_path(ctx.db(), ModuleDef::Trait(trait_), ctx.config.import_path_config())?; let field_type = field.ty()?; let target = field.syntax().text_range(); diff --git a/crates/ide-assists/src/handlers/generate_new.rs b/crates/ide-assists/src/handlers/generate_new.rs index 6056c8088807..b985b5e66c4d 100644 --- a/crates/ide-assists/src/handlers/generate_new.rs +++ b/crates/ide-assists/src/handlers/generate_new.rs @@ -1,4 +1,3 @@ -use hir::ImportPathConfig; use ide_db::{ imports::import_assets::item_for_path_search, use_trivial_constructor::use_trivial_constructor, }; @@ -62,11 +61,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option let type_path = current_module.find_path( ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?, - ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }, + ctx.config.import_path_config(), )?; let expr = use_trivial_constructor( diff --git a/crates/ide-assists/src/handlers/qualify_method_call.rs b/crates/ide-assists/src/handlers/qualify_method_call.rs index 89e24fafc556..b1e98045fcfa 100644 --- a/crates/ide-assists/src/handlers/qualify_method_call.rs +++ b/crates/ide-assists/src/handlers/qualify_method_call.rs @@ -1,7 +1,4 @@ -use hir::{ - db::HirDatabase, AsAssocItem, AssocItem, AssocItemContainer, ImportPathConfig, ItemInNs, - ModuleDef, -}; +use hir::{db::HirDatabase, AsAssocItem, AssocItem, AssocItemContainer, ItemInNs, ModuleDef}; use ide_db::assists::{AssistId, AssistKind}; use syntax::{ast, AstNode}; @@ -50,11 +47,7 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) -> let receiver_path = current_module.find_path( ctx.sema.db, item_for_path_search(ctx.sema.db, item_in_ns)?, - ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }, + ctx.config.import_path_config(), )?; let qualify_candidate = QualifyCandidate::ImplMethod(ctx.sema.db, call, resolved_call); diff --git a/crates/ide-assists/src/handlers/qualify_path.rs b/crates/ide-assists/src/handlers/qualify_path.rs index 4164a4c10245..d8e7da15d5bf 100644 --- a/crates/ide-assists/src/handlers/qualify_path.rs +++ b/crates/ide-assists/src/handlers/qualify_path.rs @@ -1,6 +1,7 @@ +use std::cmp::Reverse; use std::iter; -use hir::{AsAssocItem, ImportPathConfig}; +use hir::AsAssocItem; use ide_db::RootDatabase; use ide_db::{ helpers::mod_path_to_ast, @@ -38,11 +39,7 @@ use crate::{ // ``` pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { let (import_assets, syntax_under_caret) = find_importable_node(ctx)?; - let cfg = ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }; + let cfg = ctx.config.import_path_config(); let mut proposed_imports: Vec<_> = import_assets.search_for_relative_paths(&ctx.sema, cfg).collect(); @@ -50,12 +47,8 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option return None; } - let range = match &syntax_under_caret { - NodeOrToken::Node(node) => ctx.sema.original_range(node).range, - NodeOrToken::Token(token) => token.text_range(), - }; let candidate = import_assets.import_candidate(); - let qualify_candidate = match syntax_under_caret { + let qualify_candidate = match syntax_under_caret.clone() { NodeOrToken::Node(syntax_under_caret) => match candidate { ImportCandidate::Path(candidate) if candidate.qualifier.is_some() => { cov_mark::hit!(qualify_path_qualifier_start); @@ -89,6 +82,22 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option proposed_imports.sort_by(|a, b| a.import_path.cmp(&b.import_path)); proposed_imports.dedup_by(|a, b| a.import_path == b.import_path); + let range = match &syntax_under_caret { + NodeOrToken::Node(node) => ctx.sema.original_range(node).range, + NodeOrToken::Token(token) => token.text_range(), + }; + let current_module = ctx + .sema + .scope(&match syntax_under_caret { + NodeOrToken::Node(node) => node.clone(), + NodeOrToken::Token(t) => t.parent()?, + }) + .map(|scope| scope.module()); + // prioritize more relevant imports + proposed_imports.sort_by_key(|import| { + Reverse(super::auto_import::relevance_score(ctx, import, current_module.as_ref())) + }); + let group_label = group_label(candidate); for import in proposed_imports { acc.add_group( diff --git a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs index 5582256a1701..5ff4af19fbf1 100644 --- a/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs +++ b/crates/ide-assists/src/handlers/replace_derive_with_manual_impl.rs @@ -1,4 +1,4 @@ -use hir::{ImportPathConfig, InFile, MacroFileIdExt, ModuleDef}; +use hir::{InFile, MacroFileIdExt, ModuleDef}; use ide_db::{helpers::mod_path_to_ast, imports::import_assets::NameToImport, items_locator}; use itertools::Itertools; use syntax::{ @@ -83,15 +83,7 @@ pub(crate) fn replace_derive_with_manual_impl( }) .flat_map(|trait_| { current_module - .find_path( - ctx.sema.db, - hir::ModuleDef::Trait(trait_), - ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }, - ) + .find_path(ctx.sema.db, hir::ModuleDef::Trait(trait_), ctx.config.import_path_config()) .as_ref() .map(mod_path_to_ast) .zip(Some(trait_)) diff --git a/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs b/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs index b4e1a49aab52..d0aa835e79af 100644 --- a/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs +++ b/crates/ide-assists/src/handlers/replace_qualified_name_with_use.rs @@ -1,4 +1,4 @@ -use hir::{AsAssocItem, ImportPathConfig}; +use hir::AsAssocItem; use ide_db::{ helpers::mod_path_to_ast, imports::insert_use::{insert_use, ImportScope}, @@ -67,11 +67,7 @@ pub(crate) fn replace_qualified_name_with_use( ctx.sema.db, module, ctx.config.insert_use.prefix_kind, - ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }, + ctx.config.import_path_config(), ) }) .flatten(); diff --git a/crates/ide-assists/src/handlers/term_search.rs b/crates/ide-assists/src/handlers/term_search.rs index 7a911799757b..4913cfdea944 100644 --- a/crates/ide-assists/src/handlers/term_search.rs +++ b/crates/ide-assists/src/handlers/term_search.rs @@ -1,8 +1,5 @@ //! Term search assist -use hir::{ - term_search::{TermSearchConfig, TermSearchCtx}, - ImportPathConfig, -}; +use hir::term_search::{TermSearchConfig, TermSearchCtx}; use ide_db::{ assists::{AssistId, AssistKind, GroupLabel}, famous_defs::FamousDefs, @@ -54,16 +51,7 @@ pub(crate) fn term_search(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option< let paths = paths .into_iter() .filter_map(|path| { - path.gen_source_code( - &scope, - &mut formatter, - ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }, - ) - .ok() + path.gen_source_code(&scope, &mut formatter, ctx.config.import_path_config()).ok() }) .unique(); diff --git a/crates/ide-assists/src/handlers/toggle_async_sugar.rs b/crates/ide-assists/src/handlers/toggle_async_sugar.rs index 31d18a601385..98975a324dc3 100644 --- a/crates/ide-assists/src/handlers/toggle_async_sugar.rs +++ b/crates/ide-assists/src/handlers/toggle_async_sugar.rs @@ -1,4 +1,4 @@ -use hir::{ImportPathConfig, ModuleDef}; +use hir::ModuleDef; use ide_db::{ assists::{AssistId, AssistKind}, famous_defs::FamousDefs, @@ -139,11 +139,7 @@ pub(crate) fn desugar_async_into_impl_future( let trait_path = module.find_path( ctx.db(), ModuleDef::Trait(future_trait), - ImportPathConfig { - prefer_no_std: ctx.config.prefer_no_std, - prefer_prelude: ctx.config.prefer_prelude, - prefer_absolute: ctx.config.prefer_absolute, - }, + ctx.config.import_path_config(), )?; let trait_path = trait_path.display(ctx.db()); diff --git a/crates/ide-db/src/assists.rs b/crates/ide-db/src/assists.rs index 0ddbde49abc4..1c40685ebb13 100644 --- a/crates/ide-db/src/assists.rs +++ b/crates/ide-db/src/assists.rs @@ -36,7 +36,7 @@ pub struct Assist { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Command { /// Show the parameter hints popup. - TriggerSignatureHelp, + TriggerParameterHints, /// Rename the just inserted item. Rename, } diff --git a/crates/ide-db/src/lib.rs b/crates/ide-db/src/lib.rs index 322c4e9e5c38..4c52ba39dec4 100644 --- a/crates/ide-db/src/lib.rs +++ b/crates/ide-db/src/lib.rs @@ -168,6 +168,7 @@ impl RootDatabase { // macro expansions are usually rather small, so we can afford to keep more of them alive hir::db::ParseMacroExpansionQuery.in_db_mut(self).set_lru_capacity(4 * lru_capacity); hir::db::BorrowckQuery.in_db_mut(self).set_lru_capacity(base_db::DEFAULT_BORROWCK_LRU_CAP); + hir::db::BodyWithSourceMapQuery.in_db_mut(self).set_lru_capacity(2048); } pub fn update_lru_capacities(&mut self, lru_capacities: &FxHashMap, u16>) { @@ -192,6 +193,7 @@ impl RootDatabase { .copied() .unwrap_or(base_db::DEFAULT_BORROWCK_LRU_CAP), ); + hir::db::BodyWithSourceMapQuery.in_db_mut(self).set_lru_capacity(2048); } } diff --git a/crates/ide-db/src/source_change.rs b/crates/ide-db/src/source_change.rs index a85358098ff0..a83f8473c396 100644 --- a/crates/ide-db/src/source_change.rs +++ b/crates/ide-db/src/source_change.rs @@ -307,8 +307,8 @@ impl SourceChangeBuilder { } /// Triggers the parameter hint popup after the assist is applied - pub fn trigger_signature_help(&mut self) { - self.command = Some(Command::TriggerSignatureHelp); + pub fn trigger_parameter_hints(&mut self) { + self.command = Some(Command::TriggerParameterHints); } /// Renames the item at the cursor position after the assist is applied diff --git a/crates/ide-db/src/syntax_helpers/node_ext.rs b/crates/ide-db/src/syntax_helpers/node_ext.rs index c301e100341f..37238cc61d3b 100644 --- a/crates/ide-db/src/syntax_helpers/node_ext.rs +++ b/crates/ide-db/src/syntax_helpers/node_ext.rs @@ -36,10 +36,35 @@ pub fn walk_expr(expr: &ast::Expr, cb: &mut dyn FnMut(ast::Expr)) { }) } +pub fn is_closure_or_blk_with_modif(expr: &ast::Expr) -> bool { + match expr { + ast::Expr::BlockExpr(block_expr) => { + matches!( + block_expr.modifier(), + Some( + ast::BlockModifier::Async(_) + | ast::BlockModifier::Try(_) + | ast::BlockModifier::Const(_) + ) + ) + } + ast::Expr::ClosureExpr(_) => true, + _ => false, + } +} + /// Preorder walk all the expression's child expressions preserving events. /// If the callback returns true on an [`WalkEvent::Enter`], the subtree of the expression will be skipped. /// Note that the subtree may already be skipped due to the context analysis this function does. pub fn preorder_expr(start: &ast::Expr, cb: &mut dyn FnMut(WalkEvent) -> bool) { + preorder_expr_with_ctx_checker(start, &is_closure_or_blk_with_modif, cb); +} + +pub fn preorder_expr_with_ctx_checker( + start: &ast::Expr, + check_ctx: &dyn Fn(&ast::Expr) -> bool, + cb: &mut dyn FnMut(WalkEvent) -> bool, +) { let mut preorder = start.syntax().preorder(); while let Some(event) = preorder.next() { let node = match event { @@ -71,20 +96,7 @@ pub fn preorder_expr(start: &ast::Expr, cb: &mut dyn FnMut(WalkEvent) if ast::GenericArg::can_cast(node.kind()) { preorder.skip_subtree(); } else if let Some(expr) = ast::Expr::cast(node) { - let is_different_context = match &expr { - ast::Expr::BlockExpr(block_expr) => { - matches!( - block_expr.modifier(), - Some( - ast::BlockModifier::Async(_) - | ast::BlockModifier::Try(_) - | ast::BlockModifier::Const(_) - ) - ) - } - ast::Expr::ClosureExpr(_) => true, - _ => false, - } && expr.syntax() != start.syntax(); + let is_different_context = check_ctx(&expr) && expr.syntax() != start.syntax(); let skip = cb(WalkEvent::Enter(expr)); if skip || is_different_context { preorder.skip_subtree(); @@ -394,7 +406,7 @@ fn for_each_break_expr( } } -fn eq_label_lt(lt1: &Option, lt2: &Option) -> bool { +pub fn eq_label_lt(lt1: &Option, lt2: &Option) -> bool { lt1.as_ref().zip(lt2.as_ref()).map_or(false, |(lt, lbl)| lt.text() == lbl.text()) } diff --git a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs index 685761e8ff51..56ec45c89840 100644 --- a/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs +++ b/crates/ide-diagnostics/src/handlers/mismatched_arg_count.rs @@ -256,6 +256,75 @@ impl Foo { ); } + #[test] + fn rest_pat_in_macro_expansion() { + check_diagnostics( + r#" +// issue #17292 +#![allow(dead_code)] + +macro_rules! replace_with_2_dots { + ( $( $input:tt )* ) => { + .. + }; +} + +macro_rules! enum_str { + ( + $( + $variant:ident ( + $( $tfield:ty ),* + ) + ) + , + * + ) => { + enum Foo { + $( + $variant ( $( $tfield ),* ), + )* + } + + impl Foo { + fn variant_name_as_str(&self) -> &str { + match self { + $( + Self::$variant ( replace_with_2_dots!( $( $tfield ),* ) ) + => "", + )* + } + } + } + }; +} + +enum_str! { + TupleVariant1(i32), + TupleVariant2(), + TupleVariant3(i8,u8,i128) +} +"#, + ); + + check_diagnostics( + r#" +#![allow(dead_code)] +macro_rules! two_dots1 { + () => { .. }; +} + +macro_rules! two_dots2 { + () => { two_dots1!() }; +} + +fn test() { + let (_, _, two_dots1!()) = ((), 42); + let (_, two_dots2!(), _) = (1, true, 2, false, (), (), 3); +} +"#, + ); + } + #[test] fn varargs() { check_diagnostics( diff --git a/crates/ide/src/goto_definition.rs b/crates/ide/src/goto_definition.rs index aa485fb63d6b..d0701a45b101 100644 --- a/crates/ide/src/goto_definition.rs +++ b/crates/ide/src/goto_definition.rs @@ -1,18 +1,29 @@ use std::{iter, mem::discriminant}; use crate::{ - doc_links::token_as_doc_comment, navigation_target::ToNav, FilePosition, NavigationTarget, - RangeInfo, TryToNav, + doc_links::token_as_doc_comment, + navigation_target::{self, ToNav}, + FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult, +}; +use hir::{ + AsAssocItem, AssocItem, DescendPreference, FileRange, InFile, MacroFileIdExt, ModuleDef, + Semantics, }; -use hir::{AsAssocItem, AssocItem, DescendPreference, MacroFileIdExt, ModuleDef, Semantics}; use ide_db::{ base_db::{AnchoredPath, FileLoader}, defs::{Definition, IdentClass}, helpers::pick_best_token, - FileId, RootDatabase, + RootDatabase, SymbolKind, }; use itertools::Itertools; -use syntax::{ast, AstNode, AstToken, SyntaxKind::*, SyntaxToken, TextRange, T}; + +use span::FileId; +use syntax::{ + ast::{self, HasLoopBody}, + match_ast, AstNode, AstToken, + SyntaxKind::*, + SyntaxNode, SyntaxToken, TextRange, T, +}; // Feature: Go to Definition // @@ -68,6 +79,10 @@ pub(crate) fn goto_definition( )); } + if let Some(navs) = handle_control_flow_keywords(sema, &original_token) { + return Some(RangeInfo::new(original_token.text_range(), navs)); + } + let navs = sema .descend_into_macros(DescendPreference::None, original_token.clone()) .into_iter() @@ -190,10 +205,221 @@ fn try_filter_trait_item_definition( } } +fn handle_control_flow_keywords( + sema: &Semantics<'_, RootDatabase>, + token: &SyntaxToken, +) -> Option> { + match token.kind() { + // For `fn` / `loop` / `while` / `for` / `async`, return the keyword it self, + // so that VSCode will find the references when using `ctrl + click` + T![fn] | T![async] | T![try] | T![return] => nav_for_exit_points(sema, token), + T![loop] | T![while] | T![break] | T![continue] => nav_for_break_points(sema, token), + T![for] if token.parent().and_then(ast::ForExpr::cast).is_some() => { + nav_for_break_points(sema, token) + } + _ => None, + } +} + +pub(crate) fn find_fn_or_blocks( + sema: &Semantics<'_, RootDatabase>, + token: &SyntaxToken, +) -> Vec { + let find_ancestors = |token: SyntaxToken| { + let token_kind = token.kind(); + + for anc in sema.token_ancestors_with_macros(token) { + let node = match_ast! { + match anc { + ast::Fn(fn_) => fn_.syntax().clone(), + ast::ClosureExpr(c) => c.syntax().clone(), + ast::BlockExpr(blk) => { + match blk.modifier() { + Some(ast::BlockModifier::Async(_)) => blk.syntax().clone(), + Some(ast::BlockModifier::Try(_)) if token_kind != T![return] => blk.syntax().clone(), + _ => continue, + } + }, + _ => continue, + } + }; + + return Some(node); + } + None + }; + + sema.descend_into_macros(DescendPreference::None, token.clone()) + .into_iter() + .filter_map(find_ancestors) + .collect_vec() +} + +fn nav_for_exit_points( + sema: &Semantics<'_, RootDatabase>, + token: &SyntaxToken, +) -> Option> { + let db = sema.db; + let token_kind = token.kind(); + + let navs = find_fn_or_blocks(sema, token) + .into_iter() + .filter_map(|node| { + let file_id = sema.hir_file_for(&node); + + match_ast! { + match node { + ast::Fn(fn_) => { + let mut nav = sema.to_def(&fn_)?.try_to_nav(db)?; + // For async token, we navigate to itself, which triggers + // VSCode to find the references + let focus_token = if matches!(token_kind, T![async]) { + fn_.async_token()? + } else { + fn_.fn_token()? + }; + + let focus_frange = InFile::new(file_id, focus_token.text_range()) + .original_node_file_range_opt(db) + .map(|(frange, _)| frange); + + if let Some(FileRange { file_id, range }) = focus_frange { + let contains_frange = |nav: &NavigationTarget| { + nav.file_id == file_id && nav.full_range.contains_range(range) + }; + + if let Some(def_site) = nav.def_site.as_mut() { + if contains_frange(def_site) { + def_site.focus_range = Some(range); + } + } else if contains_frange(&nav.call_site) { + nav.call_site.focus_range = Some(range); + } + } + + Some(nav) + }, + ast::ClosureExpr(c) => { + let pipe_tok = c.param_list().and_then(|it| it.pipe_token())?.text_range(); + let closure_in_file = InFile::new(file_id, c.into()); + Some(expr_to_nav(db, closure_in_file, Some(pipe_tok))) + }, + ast::BlockExpr(blk) => { + match blk.modifier() { + Some(ast::BlockModifier::Async(_)) => { + let async_tok = blk.async_token()?.text_range(); + let blk_in_file = InFile::new(file_id, blk.into()); + Some(expr_to_nav(db, blk_in_file, Some(async_tok))) + }, + Some(ast::BlockModifier::Try(_)) if token_kind != T![return] => { + let try_tok = blk.try_token()?.text_range(); + let blk_in_file = InFile::new(file_id, blk.into()); + Some(expr_to_nav(db, blk_in_file, Some(try_tok))) + }, + _ => None, + } + }, + _ => None, + } + } + }) + .flatten() + .collect_vec(); + + Some(navs) +} + +pub(crate) fn find_loops( + sema: &Semantics<'_, RootDatabase>, + token: &SyntaxToken, +) -> Option> { + let parent = token.parent()?; + let lbl = match_ast! { + match parent { + ast::BreakExpr(break_) => break_.lifetime(), + ast::ContinueExpr(continue_) => continue_.lifetime(), + _ => None, + } + }; + let label_matches = + |it: Option| match (lbl.as_ref(), it.and_then(|it| it.lifetime())) { + (Some(lbl), Some(it)) => lbl.text() == it.text(), + (None, _) => true, + (Some(_), None) => false, + }; + + let find_ancestors = |token: SyntaxToken| { + for anc in sema.token_ancestors_with_macros(token).filter_map(ast::Expr::cast) { + let node = match &anc { + ast::Expr::LoopExpr(loop_) if label_matches(loop_.label()) => anc, + ast::Expr::WhileExpr(while_) if label_matches(while_.label()) => anc, + ast::Expr::ForExpr(for_) if label_matches(for_.label()) => anc, + ast::Expr::BlockExpr(blk) + if blk.label().is_some() && label_matches(blk.label()) => + { + anc + } + _ => continue, + }; + + return Some(node); + } + None + }; + + sema.descend_into_macros(DescendPreference::None, token.clone()) + .into_iter() + .filter_map(find_ancestors) + .collect_vec() + .into() +} + +fn nav_for_break_points( + sema: &Semantics<'_, RootDatabase>, + token: &SyntaxToken, +) -> Option> { + let db = sema.db; + + let navs = find_loops(sema, token)? + .into_iter() + .filter_map(|expr| { + let file_id = sema.hir_file_for(expr.syntax()); + let expr_in_file = InFile::new(file_id, expr.clone()); + let focus_range = match expr { + ast::Expr::LoopExpr(loop_) => loop_.loop_token()?.text_range(), + ast::Expr::WhileExpr(while_) => while_.while_token()?.text_range(), + ast::Expr::ForExpr(for_) => for_.for_token()?.text_range(), + // We guarantee that the label exists + ast::Expr::BlockExpr(blk) => blk.label().unwrap().syntax().text_range(), + _ => return None, + }; + let nav = expr_to_nav(db, expr_in_file, Some(focus_range)); + Some(nav) + }) + .flatten() + .collect_vec(); + + Some(navs) +} + fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec { def.try_to_nav(db).map(|it| it.collect()).unwrap_or_default() } +fn expr_to_nav( + db: &RootDatabase, + InFile { file_id, value }: InFile, + focus_range: Option, +) -> UpmappingResult { + let kind = SymbolKind::Label; + + let value_range = value.syntax().text_range(); + let navs = navigation_target::orig_range_with_focus_r(db, file_id, value_range, focus_range); + navs.map(|(hir::FileRangeWrapper { file_id, range }, focus_range)| { + NavigationTarget::from_syntax(file_id, "".into(), focus_range, range, kind) + }) +} + #[cfg(test)] mod tests { use ide_db::FileRange; @@ -2313,4 +2539,200 @@ pub mod prelude { "#, ); } + + #[test] + fn goto_def_on_return_kw() { + check( + r#" +macro_rules! N { + ($i:ident, $x:expr, $blk:expr) => { + for $i in 0..$x { + $blk + } + }; +} + +fn main() { + fn f() { + // ^^ + N!(i, 5, { + println!("{}", i); + return$0; + }); + + for i in 1..5 { + return; + } + (|| { + return; + })(); + } +} +"#, + ) + } + + #[test] + fn goto_def_on_return_kw_in_closure() { + check( + r#" +macro_rules! N { + ($i:ident, $x:expr, $blk:expr) => { + for $i in 0..$x { + $blk + } + }; +} + +fn main() { + fn f() { + N!(i, 5, { + println!("{}", i); + return; + }); + + for i in 1..5 { + return; + } + (|| { + // ^ + return$0; + })(); + } +} +"#, + ) + } + + #[test] + fn goto_def_on_break_kw() { + check( + r#" +fn main() { + for i in 1..5 { + // ^^^ + break$0; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_continue_kw() { + check( + r#" +fn main() { + for i in 1..5 { + // ^^^ + continue$0; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_break_kw_for_block() { + check( + r#" +fn main() { + 'a:{ + // ^^^ + break$0 'a; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_break_with_label() { + check( + r#" +fn foo() { + 'outer: loop { + // ^^^^ + 'inner: loop { + 'innermost: loop { + } + break$0 'outer; + } + } +} +"#, + ); + } + + #[test] + fn goto_def_on_return_in_try() { + check( + r#" +fn main() { + fn f() { + // ^^ + try { + return$0; + } + + return; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_break_in_try() { + check( + r#" +fn main() { + for i in 1..100 { + // ^^^ + let x: Result<(), ()> = try { + break$0; + }; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_return_in_async_block() { + check( + r#" +fn main() { + async { + // ^^^^^ + return$0; + } +} +"#, + ) + } + + #[test] + fn goto_def_on_for_kw() { + check( + r#" +fn main() { + for$0 i in 1..5 {} + // ^^^ +} +"#, + ) + } + + #[test] + fn goto_def_on_fn_kw() { + check( + r#" +fn main() { + fn$0 foo() {} + // ^^ +} +"#, + ) + } } diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index 98651900050e..8fcd38b4e343 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -1,24 +1,25 @@ use std::iter; -use hir::{DescendPreference, FilePosition, FileRange, Semantics}; +use hir::{db, DescendPreference, FilePosition, FileRange, HirFileId, InFile, Semantics}; use ide_db::{ defs::{Definition, IdentClass}, helpers::pick_best_token, search::{FileReference, ReferenceCategory, SearchScope}, syntax_helpers::node_ext::{ - for_each_break_and_continue_expr, for_each_tail_expr, full_path_of_name_ref, walk_expr, + eq_label_lt, for_each_tail_expr, full_path_of_name_ref, is_closure_or_blk_with_modif, + preorder_expr_with_ctx_checker, }, - FxHashSet, RootDatabase, + FxHashMap, FxHashSet, RootDatabase, }; use span::EditionedFileId; use syntax::{ ast::{self, HasLoopBody}, match_ast, AstNode, SyntaxKind::{self, IDENT, INT_NUMBER}, - SyntaxToken, TextRange, T, + SyntaxToken, TextRange, WalkEvent, T, }; -use crate::{navigation_target::ToNav, NavigationTarget, TryToNav}; +use crate::{goto_definition, navigation_target::ToNav, NavigationTarget, TryToNav}; #[derive(PartialEq, Eq, Hash)] pub struct HighlightedRange { @@ -72,15 +73,19 @@ pub(crate) fn highlight_related( // most if not all of these should be re-implemented with information seeded from hir match token.kind() { T![?] if config.exit_points && token.parent().and_then(ast::TryExpr::cast).is_some() => { - highlight_exit_points(sema, token) + highlight_exit_points(sema, token).remove(&file_id) + } + T![fn] | T![return] | T![->] if config.exit_points => { + highlight_exit_points(sema, token).remove(&file_id) + } + T![await] | T![async] if config.yield_points => { + highlight_yield_points(sema, token).remove(&file_id) } - T![fn] | T![return] | T![->] if config.exit_points => highlight_exit_points(sema, token), - T![await] | T![async] if config.yield_points => highlight_yield_points(token), T![for] if config.break_points && token.parent().and_then(ast::ForExpr::cast).is_some() => { - highlight_break_points(token) + highlight_break_points(sema, token).remove(&file_id) } T![break] | T![loop] | T![while] | T![continue] if config.break_points => { - highlight_break_points(token) + highlight_break_points(sema, token).remove(&file_id) } T![|] if config.closure_captures => highlight_closure_captures(sema, token, file_id), T![move] if config.closure_captures => highlight_closure_captures(sema, token, file_id), @@ -276,50 +281,66 @@ fn highlight_references( } } -fn highlight_exit_points( +// If `file_id` is None, +pub(crate) fn highlight_exit_points( sema: &Semantics<'_, RootDatabase>, token: SyntaxToken, -) -> Option> { +) -> FxHashMap> { fn hl( sema: &Semantics<'_, RootDatabase>, - def_ranges: [Option; 2], - body: Option, - ) -> Option> { - let mut highlights = Vec::new(); - highlights.extend( - def_ranges - .into_iter() - .flatten() - .map(|range| HighlightedRange { category: ReferenceCategory::empty(), range }), - ); - let body = body?; - walk_expr(&body, &mut |expr| match expr { - ast::Expr::ReturnExpr(expr) => { - if let Some(token) = expr.return_token() { - highlights.push(HighlightedRange { - category: ReferenceCategory::empty(), - range: token.text_range(), - }); - } + def_token: Option, + body: ast::Expr, + ) -> Option>> { + let mut highlights: FxHashMap> = FxHashMap::default(); + + let mut push_to_highlights = |file_id, range| { + if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) { + let hrange = HighlightedRange { category: ReferenceCategory::empty(), range }; + highlights.entry(file_id).or_default().insert(hrange); } - ast::Expr::TryExpr(try_) => { - if let Some(token) = try_.question_mark_token() { - highlights.push(HighlightedRange { - category: ReferenceCategory::empty(), - range: token.text_range(), - }); + }; + + if let Some(tok) = def_token { + let file_id = sema.hir_file_for(&tok.parent()?); + let range = Some(tok.text_range()); + push_to_highlights(file_id, range); + } + + WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| { + let file_id = sema.hir_file_for(expr.syntax()); + + let range = match &expr { + ast::Expr::TryExpr(try_) => { + try_.question_mark_token().map(|token| token.text_range()) } - } - ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) => { - if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) { - highlights.push(HighlightedRange { - category: ReferenceCategory::empty(), - range: expr.syntax().text_range(), - }); + ast::Expr::MethodCallExpr(_) | ast::Expr::CallExpr(_) | ast::Expr::MacroExpr(_) + if sema.type_of_expr(&expr).map_or(false, |ty| ty.original.is_never()) => + { + Some(expr.syntax().text_range()) } - } - _ => (), + _ => None, + }; + + push_to_highlights(file_id, range); }); + + // We should handle `return` separately, because when it is used in a `try` block, + // it will exit the outside function instead of the block itself. + WalkExpandedExprCtx::new(sema) + .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure) + .walk(&body, &mut |_, expr| { + let file_id = sema.hir_file_for(expr.syntax()); + + let range = match &expr { + ast::Expr::ReturnExpr(expr) => { + expr.return_token().map(|token| token.text_range()) + } + _ => None, + }; + + push_to_highlights(file_id, range); + }); + let tail = match body { ast::Expr::BlockExpr(b) => b.tail_expr(), e => Some(e), @@ -327,171 +348,188 @@ fn highlight_exit_points( if let Some(tail) = tail { for_each_tail_expr(&tail, &mut |tail| { + let file_id = sema.hir_file_for(tail.syntax()); let range = match tail { ast::Expr::BreakExpr(b) => b .break_token() .map_or_else(|| tail.syntax().text_range(), |tok| tok.text_range()), _ => tail.syntax().text_range(), }; - highlights.push(HighlightedRange { category: ReferenceCategory::empty(), range }) + push_to_highlights(file_id, Some(range)); }); } Some(highlights) } - for anc in token.parent_ancestors() { - return match_ast! { - match anc { - ast::Fn(fn_) => hl(sema, [fn_.fn_token().map(|it| it.text_range()), None], fn_.body().map(ast::Expr::BlockExpr)), - ast::ClosureExpr(closure) => hl( - sema, - closure.param_list().map_or([None; 2], |p| [p.l_paren_token().map(|it| it.text_range()), p.r_paren_token().map(|it| it.text_range())]), - closure.body() - ), - ast::BlockExpr(block_expr) => if matches!(block_expr.modifier(), Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Try(_)| ast::BlockModifier::Const(_))) { - hl( - sema, - [block_expr.modifier().and_then(|modifier| match modifier { - ast::BlockModifier::Async(t) | ast::BlockModifier::Try(t) | ast::BlockModifier::Const(t) => Some(t.text_range()), - _ => None, - }), None], - Some(block_expr.into()) - ) - } else { - continue; + + let mut res = FxHashMap::default(); + for def in goto_definition::find_fn_or_blocks(sema, &token) { + let new_map = match_ast! { + match def { + ast::Fn(fn_) => fn_.body().and_then(|body| hl(sema, fn_.fn_token(), body.into())), + ast::ClosureExpr(closure) => { + let pipe_tok = closure.param_list().and_then(|p| p.pipe_token()); + closure.body().and_then(|body| hl(sema, pipe_tok, body)) + }, + ast::BlockExpr(blk) => match blk.modifier() { + Some(ast::BlockModifier::Async(t)) => hl(sema, Some(t), blk.into()), + Some(ast::BlockModifier::Try(t)) if token.kind() != T![return] => { + hl(sema, Some(t), blk.into()) + }, + _ => continue, }, _ => continue, } }; + merge_map(&mut res, new_map); } - None + + res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect() } -fn highlight_break_points(token: SyntaxToken) -> Option> { - fn hl( +pub(crate) fn highlight_break_points( + sema: &Semantics<'_, RootDatabase>, + token: SyntaxToken, +) -> FxHashMap> { + pub(crate) fn hl( + sema: &Semantics<'_, RootDatabase>, cursor_token_kind: SyntaxKind, - token: Option, + loop_token: Option, label: Option, - body: Option, - ) -> Option> { - let mut highlights = Vec::new(); - let range = cover_range( - token.map(|tok| tok.text_range()), + expr: ast::Expr, + ) -> Option>> { + let mut highlights: FxHashMap> = FxHashMap::default(); + + let mut push_to_highlights = |file_id, range| { + if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) { + let hrange = HighlightedRange { category: ReferenceCategory::empty(), range }; + highlights.entry(file_id).or_default().insert(hrange); + } + }; + + let label_lt = label.as_ref().and_then(|it| it.lifetime()); + + if let Some(range) = cover_range( + loop_token.as_ref().map(|tok| tok.text_range()), label.as_ref().map(|it| it.syntax().text_range()), - ); - highlights.extend( - range.map(|range| HighlightedRange { category: ReferenceCategory::empty(), range }), - ); - for_each_break_and_continue_expr(label, body, &mut |expr| { - let range: Option = match (cursor_token_kind, expr) { - (T![for] | T![while] | T![loop] | T![break], ast::Expr::BreakExpr(break_)) => { - cover_range( - break_.break_token().map(|it| it.text_range()), - break_.lifetime().map(|it| it.syntax().text_range()), - ) + ) { + let file_id = loop_token + .and_then(|tok| Some(sema.hir_file_for(&tok.parent()?))) + .unwrap_or_else(|| sema.hir_file_for(label.unwrap().syntax())); + push_to_highlights(file_id, Some(range)); + } + + WalkExpandedExprCtx::new(sema) + .with_check_ctx(&WalkExpandedExprCtx::is_async_const_block_or_closure) + .walk(&expr, &mut |depth, expr| { + let file_id = sema.hir_file_for(expr.syntax()); + + // Only highlight the `break`s for `break` and `continue`s for `continue` + let (token, token_lt) = match expr { + ast::Expr::BreakExpr(b) if cursor_token_kind != T![continue] => { + (b.break_token(), b.lifetime()) + } + ast::Expr::ContinueExpr(c) if cursor_token_kind != T![break] => { + (c.continue_token(), c.lifetime()) + } + _ => return, + }; + + if !(depth == 1 && token_lt.is_none() || eq_label_lt(&label_lt, &token_lt)) { + return; } - ( - T![for] | T![while] | T![loop] | T![continue], - ast::Expr::ContinueExpr(continue_), - ) => cover_range( - continue_.continue_token().map(|it| it.text_range()), - continue_.lifetime().map(|it| it.syntax().text_range()), - ), - _ => None, - }; - highlights.extend( - range.map(|range| HighlightedRange { category: ReferenceCategory::empty(), range }), - ); - }); + + let text_range = cover_range( + token.map(|it| it.text_range()), + token_lt.map(|it| it.syntax().text_range()), + ); + + push_to_highlights(file_id, text_range); + }); + Some(highlights) } - let parent = token.parent()?; - let lbl = match_ast! { - match parent { - ast::BreakExpr(b) => b.lifetime(), - ast::ContinueExpr(c) => c.lifetime(), - ast::LoopExpr(l) => l.label().and_then(|it| it.lifetime()), - ast::ForExpr(f) => f.label().and_then(|it| it.lifetime()), - ast::WhileExpr(w) => w.label().and_then(|it| it.lifetime()), - ast::BlockExpr(b) => Some(b.label().and_then(|it| it.lifetime())?), - _ => return None, - } - }; - let lbl = lbl.as_ref(); - let label_matches = |def_lbl: Option| match lbl { - Some(lbl) => { - Some(lbl.text()) == def_lbl.and_then(|it| it.lifetime()).as_ref().map(|it| it.text()) - } - None => true, + + let Some(loops) = goto_definition::find_loops(sema, &token) else { + return FxHashMap::default(); }; + + let mut res = FxHashMap::default(); let token_kind = token.kind(); - for anc in token.parent_ancestors().flat_map(ast::Expr::cast) { - return match anc { - ast::Expr::LoopExpr(l) if label_matches(l.label()) => hl( - token_kind, - l.loop_token(), - l.label(), - l.loop_body().and_then(|it| it.stmt_list()), - ), - ast::Expr::ForExpr(f) if label_matches(f.label()) => hl( - token_kind, - f.for_token(), - f.label(), - f.loop_body().and_then(|it| it.stmt_list()), - ), - ast::Expr::WhileExpr(w) if label_matches(w.label()) => hl( - token_kind, - w.while_token(), - w.label(), - w.loop_body().and_then(|it| it.stmt_list()), - ), - ast::Expr::BlockExpr(e) if e.label().is_some() && label_matches(e.label()) => { - hl(token_kind, None, e.label(), e.stmt_list()) - } + for expr in loops { + let new_map = match &expr { + ast::Expr::LoopExpr(l) => hl(sema, token_kind, l.loop_token(), l.label(), expr), + ast::Expr::ForExpr(f) => hl(sema, token_kind, f.for_token(), f.label(), expr), + ast::Expr::WhileExpr(w) => hl(sema, token_kind, w.while_token(), w.label(), expr), + ast::Expr::BlockExpr(e) => hl(sema, token_kind, None, e.label(), expr), _ => continue, }; + merge_map(&mut res, new_map); } - None + + res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect() } -fn highlight_yield_points(token: SyntaxToken) -> Option> { +pub(crate) fn highlight_yield_points( + sema: &Semantics<'_, RootDatabase>, + token: SyntaxToken, +) -> FxHashMap> { fn hl( + sema: &Semantics<'_, RootDatabase>, async_token: Option, body: Option, - ) -> Option> { - let mut highlights = vec![HighlightedRange { - category: ReferenceCategory::empty(), - range: async_token?.text_range(), - }]; - if let Some(body) = body { - walk_expr(&body, &mut |expr| { - if let ast::Expr::AwaitExpr(expr) = expr { - if let Some(token) = expr.await_token() { - highlights.push(HighlightedRange { - category: ReferenceCategory::empty(), - range: token.text_range(), - }); - } - } - }); - } + ) -> Option>> { + let mut highlights: FxHashMap> = FxHashMap::default(); + + let mut push_to_highlights = |file_id, range| { + if let Some(FileRange { file_id, range }) = original_frange(sema.db, file_id, range) { + let hrange = HighlightedRange { category: ReferenceCategory::empty(), range }; + highlights.entry(file_id).or_default().insert(hrange); + } + }; + + let async_token = async_token?; + let async_tok_file_id = sema.hir_file_for(&async_token.parent()?); + push_to_highlights(async_tok_file_id, Some(async_token.text_range())); + + let Some(body) = body else { + return Some(highlights); + }; + + WalkExpandedExprCtx::new(sema).walk(&body, &mut |_, expr| { + let file_id = sema.hir_file_for(expr.syntax()); + + let text_range = match expr { + ast::Expr::AwaitExpr(expr) => expr.await_token(), + ast::Expr::ReturnExpr(expr) => expr.return_token(), + _ => None, + } + .map(|it| it.text_range()); + + push_to_highlights(file_id, text_range); + }); + Some(highlights) } - for anc in token.parent_ancestors() { - return match_ast! { + + let mut res = FxHashMap::default(); + for anc in goto_definition::find_fn_or_blocks(sema, &token) { + let new_map = match_ast! { match anc { - ast::Fn(fn_) => hl(fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)), + ast::Fn(fn_) => hl(sema, fn_.async_token(), fn_.body().map(ast::Expr::BlockExpr)), ast::BlockExpr(block_expr) => { if block_expr.async_token().is_none() { continue; } - hl(block_expr.async_token(), Some(block_expr.into())) + hl(sema, block_expr.async_token(), Some(block_expr.into())) }, - ast::ClosureExpr(closure) => hl(closure.async_token(), closure.body()), + ast::ClosureExpr(closure) => hl(sema, closure.async_token(), closure.body()), _ => continue, } }; + merge_map(&mut res, new_map); } - None + + res.into_iter().map(|(file_id, ranges)| (file_id, ranges.into_iter().collect())).collect() } fn cover_range(r0: Option, r1: Option) -> Option { @@ -511,6 +549,115 @@ fn find_defs(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> FxHashSe .collect() } +fn original_frange( + db: &dyn db::ExpandDatabase, + file_id: HirFileId, + text_range: Option, +) -> Option { + InFile::new(file_id, text_range?).original_node_file_range_opt(db).map(|(frange, _)| frange) +} + +fn merge_map( + res: &mut FxHashMap>, + new: Option>>, +) { + let Some(new) = new else { + return; + }; + new.into_iter().for_each(|(file_id, ranges)| { + res.entry(file_id).or_default().extend(ranges); + }); +} + +/// Preorder walk all the expression's child expressions. +/// For macro calls, the callback will be called on the expanded expressions after +/// visiting the macro call itself. +struct WalkExpandedExprCtx<'a> { + sema: &'a Semantics<'a, RootDatabase>, + depth: usize, + check_ctx: &'static dyn Fn(&ast::Expr) -> bool, +} + +impl<'a> WalkExpandedExprCtx<'a> { + fn new(sema: &'a Semantics<'a, RootDatabase>) -> Self { + Self { sema, depth: 0, check_ctx: &is_closure_or_blk_with_modif } + } + + fn with_check_ctx(&self, check_ctx: &'static dyn Fn(&ast::Expr) -> bool) -> Self { + Self { check_ctx, ..*self } + } + + fn walk(&mut self, expr: &ast::Expr, cb: &mut dyn FnMut(usize, ast::Expr)) { + preorder_expr_with_ctx_checker(expr, self.check_ctx, &mut |ev: WalkEvent| { + match ev { + syntax::WalkEvent::Enter(expr) => { + cb(self.depth, expr.clone()); + + if Self::should_change_depth(&expr) { + self.depth += 1; + } + + if let ast::Expr::MacroExpr(expr) = expr { + if let Some(expanded) = + expr.macro_call().and_then(|call| self.sema.expand(&call)) + { + match_ast! { + match expanded { + ast::MacroStmts(it) => { + self.handle_expanded(it, cb); + }, + ast::Expr(it) => { + self.walk(&it, cb); + }, + _ => {} + } + } + } + } + } + syntax::WalkEvent::Leave(expr) if Self::should_change_depth(&expr) => { + self.depth -= 1; + } + _ => {} + } + false + }) + } + + fn handle_expanded(&mut self, expanded: ast::MacroStmts, cb: &mut dyn FnMut(usize, ast::Expr)) { + if let Some(expr) = expanded.expr() { + self.walk(&expr, cb); + } + + for stmt in expanded.statements() { + if let ast::Stmt::ExprStmt(stmt) = stmt { + if let Some(expr) = stmt.expr() { + self.walk(&expr, cb); + } + } + } + } + + fn should_change_depth(expr: &ast::Expr) -> bool { + match expr { + ast::Expr::LoopExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::ForExpr(_) => true, + ast::Expr::BlockExpr(blk) if blk.label().is_some() => true, + _ => false, + } + } + + fn is_async_const_block_or_closure(expr: &ast::Expr) -> bool { + match expr { + ast::Expr::BlockExpr(b) => matches!( + b.modifier(), + Some(ast::BlockModifier::Async(_) | ast::BlockModifier::Const(_)) + ), + ast::Expr::ClosureExpr(_) => true, + _ => false, + } + } +} + #[cfg(test)] mod tests { use itertools::Itertools; @@ -897,6 +1044,7 @@ impl Never { } macro_rules! never { () => { never() } + // ^^^^^^^ } fn never() -> ! { loop {} } fn foo() ->$0 u32 { @@ -1723,4 +1871,140 @@ fn test() { "#, ); } + + #[test] + fn return_in_macros() { + check( + r#" +macro_rules! N { + ($i:ident, $x:expr, $blk:expr) => { + for $i in 0..$x { + $blk + } + }; +} + +fn main() { + fn f() { + // ^^ + N!(i, 5, { + println!("{}", i); + return$0; + // ^^^^^^ + }); + + for i in 1..5 { + return; + // ^^^^^^ + } + (|| { + return; + })(); + } +} +"#, + ) + } + + #[test] + fn return_in_closure() { + check( + r#" +macro_rules! N { + ($i:ident, $x:expr, $blk:expr) => { + for $i in 0..$x { + $blk + } + }; +} + +fn main() { + fn f() { + N!(i, 5, { + println!("{}", i); + return; + }); + + for i in 1..5 { + return; + } + (|| { + // ^ + return$0; + // ^^^^^^ + })(); + } +} +"#, + ) + } + + #[test] + fn return_in_try() { + check( + r#" +fn main() { + fn f() { + // ^^ + try { + return$0; + // ^^^^^^ + } + + return; + // ^^^^^^ + } +} +"#, + ) + } + + #[test] + fn break_in_try() { + check( + r#" +fn main() { + for i in 1..100 { + // ^^^ + let x: Result<(), ()> = try { + break$0; + // ^^^^^ + }; + } +} +"#, + ) + } + + #[test] + fn no_highlight_on_return_in_macro_call() { + check( + r#" +//- minicore:include +//- /lib.rs +macro_rules! M { + ($blk:expr) => { + $blk + }; +} + +fn main() { + fn f() { + // ^^ + M!({ return$0; }); + // ^^^^^^ + // ^^^^^^^^^^^^^^^ + + include!("a.rs") + // ^^^^^^^^^^^^^^^^ + } +} + +//- /a.rs +{ + return; +} +"#, + ) + } } diff --git a/crates/ide/src/inlay_hints/implicit_drop.rs b/crates/ide/src/inlay_hints/implicit_drop.rs index 86075bd9bb72..7f901db28d36 100644 --- a/crates/ide/src/inlay_hints/implicit_drop.rs +++ b/crates/ide/src/inlay_hints/implicit_drop.rs @@ -32,9 +32,8 @@ pub(super) fn hints( let def = sema.to_def(def)?; let def: DefWithBody = def.into(); - let source_map = sema.db.body_with_source_map(def.into()).1; + let (hir, source_map) = sema.db.body_with_source_map(def.into()); - let hir = sema.db.body(def.into()); let mir = sema.db.mir_body(def.into()).ok()?; let local_to_binding = mir.local_to_binding_map(); @@ -74,22 +73,33 @@ pub(super) fn hints( Ok(s) => s.value.text_range(), Err(_) => continue, }, + MirSpan::BindingId(b) => { + match source_map + .patterns_for_binding(b) + .iter() + .find_map(|p| source_map.pat_syntax(*p).ok()) + { + Some(s) => s.value.text_range(), + None => continue, + } + } MirSpan::SelfParam => match source_map.self_param_syntax() { Some(s) => s.value.text_range(), None => continue, }, MirSpan::Unknown => continue, }; + let binding_source = source_map + .patterns_for_binding(*binding) + .first() + .and_then(|d| source_map.pat_syntax(*d).ok()) + .and_then(|d| { + Some(FileRange { + file_id: d.file_id.file_id()?.into(), + range: d.value.text_range(), + }) + }); let binding = &hir.bindings[*binding]; - let binding_source = - binding.definitions.first().and_then(|d| source_map.pat_syntax(*d).ok()).and_then( - |d| { - Some(FileRange { - file_id: d.file_id.file_id()?.into(), - range: d.value.text_range(), - }) - }, - ); let name = binding.name.display_no_db().to_smolstr(); if name.starts_with(", @@ -710,7 +710,7 @@ impl IntoIterator for UpmappingResult { } impl UpmappingResult { - fn map(self, f: impl Fn(T) -> U) -> UpmappingResult { + pub(crate) fn map(self, f: impl Fn(T) -> U) -> UpmappingResult { UpmappingResult { call_site: f(self.call_site), def_site: self.def_site.map(f) } } } @@ -732,13 +732,13 @@ fn orig_range_with_focus( ) } -fn orig_range_with_focus_r( +pub(crate) fn orig_range_with_focus_r( db: &RootDatabase, hir_file: HirFileId, value: TextRange, - name: Option, + focus_range: Option, ) -> UpmappingResult<(FileRange, Option)> { - let Some(name) = name else { return orig_range_r(db, hir_file, value) }; + let Some(name) = focus_range else { return orig_range_r(db, hir_file, value) }; let call_kind = || db.lookup_intern_macro_call(hir_file.macro_file().unwrap().macro_call_id).kind; diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 6cb0225fe2af..46c2d47ee827 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs @@ -24,7 +24,7 @@ use syntax::{ SyntaxNode, TextRange, TextSize, T, }; -use crate::{FilePosition, NavigationTarget, TryToNav}; +use crate::{highlight_related, FilePosition, HighlightedRange, NavigationTarget, TryToNav}; #[derive(Debug, Clone)] pub struct ReferenceSearchResult { @@ -103,6 +103,11 @@ pub(crate) fn find_all_refs( } }; + // Find references for control-flow keywords. + if let Some(res) = handle_control_flow_keywords(sema, position) { + return Some(vec![res]); + } + match name_for_constructor_search(&syntax, position) { Some(name) => { let def = match NameClass::classify(sema, &name)? { @@ -296,6 +301,37 @@ fn is_lit_name_ref(name_ref: &ast::NameRef) -> bool { }).unwrap_or(false) } +fn handle_control_flow_keywords( + sema: &Semantics<'_, RootDatabase>, + FilePosition { file_id, offset }: FilePosition, +) -> Option { + let file = sema.parse_guess_edition(file_id); + let token = file.syntax().token_at_offset(offset).find(|t| t.kind().is_keyword())?; + + let references = match token.kind() { + T![fn] | T![return] | T![try] => highlight_related::highlight_exit_points(sema, token), + T![async] => highlight_related::highlight_yield_points(sema, token), + T![loop] | T![while] | T![break] | T![continue] => { + highlight_related::highlight_break_points(sema, token) + } + T![for] if token.parent().and_then(ast::ForExpr::cast).is_some() => { + highlight_related::highlight_break_points(sema, token) + } + _ => return None, + } + .into_iter() + .map(|(file_id, ranges)| { + let ranges = ranges + .into_iter() + .map(|HighlightedRange { range, category }| (range, category)) + .collect(); + (file_id.into(), ranges) + }) + .collect(); + + Some(ReferenceSearchResult { declaration: None, references }) +} + #[cfg(test)] mod tests { use expect_test::{expect, Expect}; @@ -1200,7 +1236,7 @@ impl Foo { let refs = analysis.find_all_refs(pos, search_scope).unwrap().unwrap(); let mut actual = String::new(); - for refs in refs { + for mut refs in refs { actual += "\n\n"; if let Some(decl) = refs.declaration { @@ -1211,7 +1247,8 @@ impl Foo { actual += "\n\n"; } - for (file_id, references) in &refs.references { + for (file_id, references) in &mut refs.references { + references.sort_by_key(|(range, _)| range.start()); for (range, category) in references { format_to!(actual, "{:?} {:?}", file_id, range); for (name, _flag) in category.iter_names() { @@ -2187,4 +2224,264 @@ fn test() { "#]], ); } + + #[test] + fn goto_ref_fn_kw() { + check( + r#" +macro_rules! N { + ($i:ident, $x:expr, $blk:expr) => { + for $i in 0..$x { + $blk + } + }; +} + +fn main() { + $0fn f() { + N!(i, 5, { + println!("{}", i); + return; + }); + + for i in 1..5 { + return; + } + + (|| { + return; + })(); + } +} +"#, + expect![[r#" + FileId(0) 136..138 + FileId(0) 207..213 + FileId(0) 264..270 + "#]], + ) + } + + #[test] + fn goto_ref_exit_points() { + check( + r#" +fn$0 foo() -> u32 { + if true { + return 0; + } + + 0?; + 0xDEAD_BEEF +} +"#, + expect![[r#" + FileId(0) 0..2 + FileId(0) 40..46 + FileId(0) 62..63 + FileId(0) 69..80 + "#]], + ); + } + + #[test] + fn test_ref_yield_points() { + check( + r#" +pub async$0 fn foo() { + let x = foo() + .await + .await; + || { 0.await }; + (async { 0.await }).await +} +"#, + expect![[r#" + FileId(0) 4..9 + FileId(0) 48..53 + FileId(0) 63..68 + FileId(0) 114..119 + "#]], + ); + } + + #[test] + fn goto_ref_for_kw() { + check( + r#" +fn main() { + $0for i in 1..5 { + break; + continue; + } +} +"#, + expect![[r#" + FileId(0) 16..19 + FileId(0) 40..45 + FileId(0) 55..63 + "#]], + ) + } + + #[test] + fn goto_ref_on_break_kw() { + check( + r#" +fn main() { + for i in 1..5 { + $0break; + continue; + } +} +"#, + expect![[r#" + FileId(0) 16..19 + FileId(0) 40..45 + "#]], + ) + } + + #[test] + fn goto_ref_on_break_kw_for_block() { + check( + r#" +fn main() { + 'a:{ + $0break 'a; + } +} +"#, + expect![[r#" + FileId(0) 16..19 + FileId(0) 29..37 + "#]], + ) + } + + #[test] + fn goto_ref_on_break_with_label() { + check( + r#" +fn foo() { + 'outer: loop { + break; + 'inner: loop { + 'innermost: loop { + } + $0break 'outer; + break; + } + break; + } +} +"#, + expect![[r#" + FileId(0) 15..27 + FileId(0) 39..44 + FileId(0) 127..139 + FileId(0) 178..183 + "#]], + ); + } + + #[test] + fn goto_ref_on_return_in_try() { + check( + r#" +fn main() { + fn f() { + try { + $0return; + } + + return; + } + return; +} +"#, + expect![[r#" + FileId(0) 16..18 + FileId(0) 51..57 + FileId(0) 78..84 + "#]], + ) + } + + #[test] + fn goto_ref_on_break_in_try() { + check( + r#" +fn main() { + for i in 1..100 { + let x: Result<(), ()> = try { + $0break; + }; + } +} +"#, + expect![[r#" + FileId(0) 16..19 + FileId(0) 84..89 + "#]], + ) + } + + #[test] + fn goto_ref_on_return_in_async_block() { + check( + r#" +fn main() { + $0async { + return; + } +} +"#, + expect![[r#" + FileId(0) 16..21 + FileId(0) 32..38 + "#]], + ) + } + + #[test] + fn goto_ref_on_return_in_macro_call() { + check( + r#" +//- minicore:include +//- /lib.rs +macro_rules! M { + ($blk:expr) => { + fn f() { + $blk + } + + $blk + }; +} + +fn main() { + M!({ + return$0; + }); + + f(); + include!("a.rs") +} + +//- /a.rs +{ + return; +} +"#, + expect![[r#" + FileId(0) 46..48 + FileId(0) 106..108 + FileId(0) 122..149 + FileId(0) 135..141 + FileId(0) 165..181 + FileId(1) 6..12 + "#]], + ) + } } diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 9191ced27aba..380105d2c214 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -663,8 +663,10 @@ impl flags::AnalysisStats { bar.println(msg()); } bar.set_message(msg); - let (body, sm) = db.body_with_source_map(body_id.into()); + let body = db.body(body_id.into()); let inference_result = db.infer(body_id.into()); + // This query is LRU'd, so actually calling it will skew the timing results. + let sm = || db.body_with_source_map(body_id.into()).1; // region:expressions let (previous_exprs, previous_unknown, previous_partially_unknown) = @@ -675,7 +677,8 @@ impl flags::AnalysisStats { let unknown_or_partial = if ty.is_unknown() { num_exprs_unknown += 1; if verbosity.is_spammy() { - if let Some((path, start, end)) = expr_syntax_range(db, vfs, &sm, expr_id) { + if let Some((path, start, end)) = expr_syntax_range(db, vfs, &sm(), expr_id) + { bar.println(format!( "{} {}:{}-{}:{}: Unknown type", path, @@ -699,7 +702,7 @@ impl flags::AnalysisStats { }; if self.only.is_some() && verbosity.is_spammy() { // in super-verbose mode for just one function, we print every single expression - if let Some((_, start, end)) = expr_syntax_range(db, vfs, &sm, expr_id) { + if let Some((_, start, end)) = expr_syntax_range(db, vfs, &sm(), expr_id) { bar.println(format!( "{}:{}-{}:{}: {}", start.line + 1, @@ -715,14 +718,15 @@ impl flags::AnalysisStats { if unknown_or_partial && self.output == Some(OutputFormat::Csv) { println!( r#"{},type,"{}""#, - location_csv_expr(db, vfs, &sm, expr_id), + location_csv_expr(db, vfs, &sm(), expr_id), ty.display(db) ); } if let Some(mismatch) = inference_result.type_mismatch_for_expr(expr_id) { num_expr_type_mismatches += 1; if verbosity.is_verbose() { - if let Some((path, start, end)) = expr_syntax_range(db, vfs, &sm, expr_id) { + if let Some((path, start, end)) = expr_syntax_range(db, vfs, &sm(), expr_id) + { bar.println(format!( "{} {}:{}-{}:{}: Expected {}, got {}", path, @@ -745,7 +749,7 @@ impl flags::AnalysisStats { if self.output == Some(OutputFormat::Csv) { println!( r#"{},mismatch,"{}","{}""#, - location_csv_expr(db, vfs, &sm, expr_id), + location_csv_expr(db, vfs, &sm(), expr_id), mismatch.expected.display(db), mismatch.actual.display(db) ); @@ -772,7 +776,7 @@ impl flags::AnalysisStats { let unknown_or_partial = if ty.is_unknown() { num_pats_unknown += 1; if verbosity.is_spammy() { - if let Some((path, start, end)) = pat_syntax_range(db, vfs, &sm, pat_id) { + if let Some((path, start, end)) = pat_syntax_range(db, vfs, &sm(), pat_id) { bar.println(format!( "{} {}:{}-{}:{}: Unknown type", path, @@ -796,7 +800,7 @@ impl flags::AnalysisStats { }; if self.only.is_some() && verbosity.is_spammy() { // in super-verbose mode for just one function, we print every single pattern - if let Some((_, start, end)) = pat_syntax_range(db, vfs, &sm, pat_id) { + if let Some((_, start, end)) = pat_syntax_range(db, vfs, &sm(), pat_id) { bar.println(format!( "{}:{}-{}:{}: {}", start.line + 1, @@ -812,14 +816,14 @@ impl flags::AnalysisStats { if unknown_or_partial && self.output == Some(OutputFormat::Csv) { println!( r#"{},type,"{}""#, - location_csv_pat(db, vfs, &sm, pat_id), + location_csv_pat(db, vfs, &sm(), pat_id), ty.display(db) ); } if let Some(mismatch) = inference_result.type_mismatch_for_pat(pat_id) { num_pat_type_mismatches += 1; if verbosity.is_verbose() { - if let Some((path, start, end)) = pat_syntax_range(db, vfs, &sm, pat_id) { + if let Some((path, start, end)) = pat_syntax_range(db, vfs, &sm(), pat_id) { bar.println(format!( "{} {}:{}-{}:{}: Expected {}, got {}", path, @@ -842,7 +846,7 @@ impl flags::AnalysisStats { if self.output == Some(OutputFormat::Csv) { println!( r#"{},mismatch,"{}","{}""#, - location_csv_pat(db, vfs, &sm, pat_id), + location_csv_pat(db, vfs, &sm(), pat_id), mismatch.expected.display(db), mismatch.actual.display(db) ); @@ -957,7 +961,7 @@ impl flags::AnalysisStats { bar.println(msg()); } bar.set_message(msg); - db.body_with_source_map(body_id.into()); + db.body(body_id.into()); bar.inc(1); } diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index f3ee7a98ac64..e71ad9da9572 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -857,7 +857,7 @@ impl Config { tracing::info!("updating config from JSON: {:#}", json); if !(json.is_null() || json.as_object().map_or(false, |it| it.is_empty())) { let mut json_errors = vec![]; - let detached_files = get_field::>( + let detached_files = get_field_json::>( &mut json, &mut json_errors, "detachedFiles", @@ -2042,8 +2042,8 @@ impl Config { debug_single: get("rust-analyzer.debugSingle"), show_reference: get("rust-analyzer.showReferences"), goto_location: get("rust-analyzer.gotoLocation"), - trigger_parameter_hints: get("editor.action.triggerParameterHints"), - rename: get("editor.action.rename"), + trigger_parameter_hints: get("rust-analyzer.triggerParameterHints"), + rename: get("rust-analyzer.rename"), } } @@ -2660,7 +2660,7 @@ macro_rules! _config_data { fn from_json(json: &mut serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>) -> Self { Self {$( - $field: get_field( + $field: get_field_json( json, error_sink, stringify!($field), @@ -2778,7 +2778,7 @@ impl GlobalLocalConfigInput { } } -fn get_field( +fn get_field_json( json: &mut serde_json::Value, error_sink: &mut Vec<(String, serde_json::Error)>, field: &'static str, diff --git a/crates/rust-analyzer/src/lsp/to_proto.rs b/crates/rust-analyzer/src/lsp/to_proto.rs index cb9b141002eb..eb6bc2a9ce9b 100644 --- a/crates/rust-analyzer/src/lsp/to_proto.rs +++ b/crates/rust-analyzer/src/lsp/to_proto.rs @@ -1338,7 +1338,7 @@ pub(crate) fn code_action( let commands = snap.config.client_commands(); res.command = match assist.command { - Some(assists::Command::TriggerSignatureHelp) if commands.trigger_parameter_hints => { + Some(assists::Command::TriggerParameterHints) if commands.trigger_parameter_hints => { Some(command::trigger_parameter_hints()) } Some(assists::Command::Rename) if commands.rename => Some(command::rename()), diff --git a/editors/code/src/bootstrap.ts b/editors/code/src/bootstrap.ts index f2884ad0b055..527edf19eb45 100644 --- a/editors/code/src/bootstrap.ts +++ b/editors/code/src/bootstrap.ts @@ -42,6 +42,7 @@ async function getServer( enableProposedApi: boolean | undefined; } = context.extension.packageJSON; + // check if the server path is configured explicitly const explicitPath = process.env["__RA_LSP_SERVER_DEBUG"] ?? config.serverPath; if (explicitPath) { if (explicitPath.startsWith("~/")) { @@ -51,12 +52,29 @@ async function getServer( } if (packageJson.releaseTag === null) return "rust-analyzer"; + if (vscode.workspace.workspaceFolders?.length === 1) { + // otherwise check if there is a toolchain override for the current vscode workspace + // and if the toolchain of this override has a rust-analyzer component + // if so, use the rust-analyzer component + const toolchainTomlExists = await fileExists( + vscode.Uri.joinPath(vscode.workspace.workspaceFolders[0]!.uri, "rust-toolchain.toml"), + ); + if (toolchainTomlExists) { + const res = spawnSync("rustup", ["which", "rust-analyzer"], { + encoding: "utf8", + env: { ...process.env }, + cwd: vscode.workspace.workspaceFolders[0]!.uri.fsPath, + }); + if (!res.error && res.status === 0) { + return res.stdout.trim(); + } + } + } + + // finally, use the bundled one const ext = process.platform === "win32" ? ".exe" : ""; const bundled = vscode.Uri.joinPath(context.extensionUri, "server", `rust-analyzer${ext}`); - const bundledExists = await vscode.workspace.fs.stat(bundled).then( - () => true, - () => false, - ); + const bundledExists = await fileExists(bundled); if (bundledExists) { let server = bundled; if (await isNixOs()) { @@ -84,6 +102,13 @@ async function getServer( return undefined; } +async function fileExists(uri: vscode.Uri) { + return await vscode.workspace.fs.stat(uri).then( + () => true, + () => false, + ); +} + export function isValidExecutable(path: string, extraEnv: Env): boolean { log.debug("Checking availability of a binary at", path); diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 1599f4b13aea..916e266cfd4d 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -347,8 +347,8 @@ class ExperimentalFeatures implements lc.StaticFeature { "rust-analyzer.debugSingle", "rust-analyzer.showReferences", "rust-analyzer.gotoLocation", - "editor.action.triggerParameterHints", - "editor.action.rename", + "rust-analyzer.triggerParameterHints", + "rust-analyzer.rename", ], }, ...capabilities.experimental, diff --git a/editors/code/src/ctx.ts b/editors/code/src/ctx.ts index caa99d76194b..675bfda65abe 100644 --- a/editors/code/src/ctx.ts +++ b/editors/code/src/ctx.ts @@ -187,19 +187,7 @@ export class Ctx implements RustAnalyzerExtensionApi { } if (!this._client) { - this._serverPath = await bootstrap(this.extCtx, this.config, this.state).catch( - (err) => { - let message = "bootstrap error. "; - - message += - 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). '; - message += - 'To enable verbose logs use { "rust-analyzer.trace.extension": true }'; - - log.error("Bootstrap error", err); - throw new Error(message); - }, - ); + this._serverPath = await this.bootstrap(); text(spawn(this._serverPath, ["--version"]).stdout.setEncoding("utf-8")).then( (data) => { const prefix = `rust-analyzer `; @@ -291,6 +279,19 @@ export class Ctx implements RustAnalyzerExtensionApi { return this._client; } + private async bootstrap(): Promise { + return bootstrap(this.extCtx, this.config, this.state).catch((err) => { + let message = "bootstrap error. "; + + message += + 'See the logs in "OUTPUT > Rust Analyzer Client" (should open automatically). '; + message += 'To enable verbose logs use { "rust-analyzer.trace.extension": true }'; + + log.error("Bootstrap error", err); + throw new Error(message); + }); + } + async start() { log.info("Starting language client"); const client = await this.getOrCreateClient(); @@ -501,7 +502,7 @@ export class Ctx implements RustAnalyzerExtensionApi { const toggleCheckOnSave = this.config.checkOnSave ? "Disable" : "Enable"; statusBar.tooltip.appendMarkdown( - `[Extension Info](command:analyzer.serverVersion "Show version and server binary info"): Version ${this.version}, Server Version ${this._serverVersion}` + + `[Extension Info](command:rust-analyzer.serverVersion "Show version and server binary info"): Version ${this.version}, Server Version ${this._serverVersion}` + "\n\n---\n\n" + '[$(terminal) Open Logs](command:rust-analyzer.openLogs "Open the server logs")' + "\n\n" +