From 410da90cb4c4ba4e54fc4318bb97c844b372e49f Mon Sep 17 00:00:00 2001 From: Chayim Refael Friedman Date: Sun, 6 Jul 2025 16:10:11 +0300 Subject: [PATCH] Provide an option to not show derives near the ADT for "Goto Implementations" or "Implementations" codelens I don't do it by default, for three reasons: (1) it's more expensive, (2) I actually quite like seeing the derives, and they may expand to no impl/more than one impl, (3) if #19130 will ever be merged this will become even more useful. Even a config might be too much, but it was fun and easy to code so I did that. --- crates/hir/src/semantics.rs | 16 ++++++ crates/ide/src/annotations.rs | 30 +++++++++-- crates/ide/src/goto_implementation.rs | 53 +++++++++++++++++-- crates/ide/src/lib.rs | 16 ++++-- .../rust-analyzer/src/cli/analysis_stats.rs | 8 ++- crates/rust-analyzer/src/config.rs | 20 +++++-- crates/rust-analyzer/src/handlers/request.rs | 29 +++++++--- docs/book/src/configuration_generated.md | 7 +++ editors/code/package.json | 10 ++++ 9 files changed, 164 insertions(+), 25 deletions(-) diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 247bb6939831..ba1c7881ebb2 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -2083,6 +2083,22 @@ impl<'db> SemanticsImpl<'db> { parent = parent_; } } + + pub fn impl_generated_from_derive(&self, impl_: Impl) -> Option { + let source = hir_def::src::HasSource::ast_ptr(&impl_.id.loc(self.db), self.db); + let mut file_id = source.file_id; + let adt_ast_id = loop { + let macro_call = file_id.macro_file()?; + match macro_call.loc(self.db).kind { + hir_expand::MacroCallKind::Derive { ast_id, .. } => break ast_id, + hir_expand::MacroCallKind::FnLike { ast_id, .. } => file_id = ast_id.file_id, + hir_expand::MacroCallKind::Attr { ast_id, .. } => file_id = ast_id.file_id, + } + }; + let adt_source = adt_ast_id.to_in_file_node(self.db); + self.cache(adt_source.value.syntax().ancestors().last().unwrap(), adt_source.file_id); + ToDef::to_def(self, adt_source.as_ref()) + } } // FIXME This can't be the best way to do this diff --git a/crates/ide/src/annotations.rs b/crates/ide/src/annotations.rs index 05196ac98c03..2f82ca92fd12 100644 --- a/crates/ide/src/annotations.rs +++ b/crates/ide/src/annotations.rs @@ -9,7 +9,7 @@ use syntax::{AstNode, TextRange, ast::HasName}; use crate::{ NavigationTarget, RunnableKind, annotations::fn_references::find_all_methods, - goto_implementation::goto_implementation, + goto_implementation::{GotoImplementationConfig, goto_implementation}, navigation_target, references::find_all_refs, runnables::{Runnable, runnables}, @@ -44,6 +44,11 @@ pub struct AnnotationConfig { pub annotate_method_references: bool, pub annotate_enum_variant_references: bool, pub location: AnnotationLocation, + pub filter_adjacent_derive_implementations: bool, +} + +pub struct ResolveAnnotationConfig { + pub filter_adjacent_derive_implementations: bool, } pub enum AnnotationLocation { @@ -196,10 +201,19 @@ pub(crate) fn annotations( .collect() } -pub(crate) fn resolve_annotation(db: &RootDatabase, mut annotation: Annotation) -> Annotation { +pub(crate) fn resolve_annotation( + db: &RootDatabase, + config: &ResolveAnnotationConfig, + mut annotation: Annotation, +) -> Annotation { match annotation.kind { AnnotationKind::HasImpls { pos, ref mut data } => { - *data = goto_implementation(db, pos).map(|range| range.info); + let goto_implementation_config = GotoImplementationConfig { + filter_adjacent_derive_implementations: config + .filter_adjacent_derive_implementations, + }; + *data = + goto_implementation(db, &goto_implementation_config, pos).map(|range| range.info); } AnnotationKind::HasReferences { pos, ref mut data } => { *data = find_all_refs(&Semantics::new(db), pos, None).map(|result| { @@ -229,7 +243,7 @@ fn should_skip_runnable(kind: &RunnableKind, binary_target: bool) -> bool { mod tests { use expect_test::{Expect, expect}; - use crate::{Annotation, AnnotationConfig, fixture}; + use crate::{Annotation, AnnotationConfig, ResolveAnnotationConfig, fixture}; use super::AnnotationLocation; @@ -241,8 +255,12 @@ mod tests { annotate_method_references: true, annotate_enum_variant_references: true, location: AnnotationLocation::AboveName, + filter_adjacent_derive_implementations: false, }; + const DEFAULT_RESOLVE_CONFIG: ResolveAnnotationConfig = + ResolveAnnotationConfig { filter_adjacent_derive_implementations: false }; + fn check_with_config( #[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect, @@ -254,7 +272,9 @@ mod tests { .annotations(config, file_id) .unwrap() .into_iter() - .map(|annotation| analysis.resolve_annotation(annotation).unwrap()) + .map(|annotation| { + analysis.resolve_annotation(&DEFAULT_RESOLVE_CONFIG, annotation).unwrap() + }) .collect(); expect.assert_debug_eq(&annotations); diff --git a/crates/ide/src/goto_implementation.rs b/crates/ide/src/goto_implementation.rs index 02d96a647328..76a0a7b1f2b5 100644 --- a/crates/ide/src/goto_implementation.rs +++ b/crates/ide/src/goto_implementation.rs @@ -8,6 +8,10 @@ use syntax::{AstNode, SyntaxKind::*, T, ast}; use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav}; +pub struct GotoImplementationConfig { + pub filter_adjacent_derive_implementations: bool, +} + // Feature: Go to Implementation // // Navigates to the impl items of types. @@ -19,6 +23,7 @@ use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav}; // ![Go to Implementation](https://user-images.githubusercontent.com/48062697/113065566-02f85480-91b1-11eb-9288-aaad8abd8841.gif) pub(crate) fn goto_implementation( db: &RootDatabase, + config: &GotoImplementationConfig, FilePosition { file_id, offset }: FilePosition, ) -> Option>> { let sema = Semantics::new(db); @@ -55,7 +60,19 @@ pub(crate) fn goto_implementation( .and_then(|def| { let navs = match def { Definition::Trait(trait_) => impls_for_trait(&sema, trait_), - Definition::Adt(adt) => impls_for_ty(&sema, adt.ty(sema.db)), + Definition::Adt(adt) => { + let mut impls = Impl::all_for_type(db, adt.ty(sema.db)); + if config.filter_adjacent_derive_implementations { + impls.retain(|impl_| { + sema.impl_generated_from_derive(*impl_) != Some(adt) + }); + } + impls + .into_iter() + .filter_map(|imp| imp.try_to_nav(sema.db)) + .flatten() + .collect() + } Definition::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)), Definition::BuiltinType(builtin) => { impls_for_ty(&sema, builtin.ty(sema.db)) @@ -125,12 +142,24 @@ mod tests { use ide_db::FileRange; use itertools::Itertools; - use crate::fixture; + use crate::{GotoImplementationConfig, fixture}; + const TEST_CONFIG: &GotoImplementationConfig = + &GotoImplementationConfig { filter_adjacent_derive_implementations: false }; + + #[track_caller] fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str) { + check_with_config(TEST_CONFIG, ra_fixture); + } + + #[track_caller] + fn check_with_config( + config: &GotoImplementationConfig, + #[rust_analyzer::rust_fixture] ra_fixture: &str, + ) { let (analysis, position, expected) = fixture::annotations(ra_fixture); - let navs = analysis.goto_implementation(position).unwrap().unwrap().info; + let navs = analysis.goto_implementation(config, position).unwrap().unwrap().info; let cmp = |frange: &FileRange| (frange.file_id, frange.range.start()); @@ -416,4 +445,22 @@ fn test() { "#, ); } + + #[test] + fn filter_adjacent_derives() { + check_with_config( + &GotoImplementationConfig { filter_adjacent_derive_implementations: true }, + r#" +//- minicore: clone, copy, derive + +#[derive(Clone, Copy)] +struct Foo$0; + +trait Bar {} + +impl Bar for Foo {} + // ^^^ + "#, + ); + } } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index b3b8deb61fc0..6420d539777c 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -78,11 +78,14 @@ use view_memory_layout::{RecursiveMemoryLayout, view_memory_layout}; use crate::navigation_target::ToNav; pub use crate::{ - annotations::{Annotation, AnnotationConfig, AnnotationKind, AnnotationLocation}, + annotations::{ + Annotation, AnnotationConfig, AnnotationKind, AnnotationLocation, ResolveAnnotationConfig, + }, call_hierarchy::{CallHierarchyConfig, CallItem}, expand_macro::ExpandedMacro, file_structure::{StructureNode, StructureNodeKind}, folding_ranges::{Fold, FoldKind}, + goto_implementation::GotoImplementationConfig, highlight_related::{HighlightRelatedConfig, HighlightedRange}, hover::{ HoverAction, HoverConfig, HoverDocFormat, HoverGotoTypeData, HoverResult, @@ -501,9 +504,10 @@ impl Analysis { /// Returns the impls from the symbol at `position`. pub fn goto_implementation( &self, + config: &GotoImplementationConfig, position: FilePosition, ) -> Cancellable>>> { - self.with_db(|db| goto_implementation::goto_implementation(db, position)) + self.with_db(|db| goto_implementation::goto_implementation(db, config, position)) } /// Returns the type definitions for the symbol at `position`. @@ -823,8 +827,12 @@ impl Analysis { self.with_db(|db| annotations::annotations(db, config, file_id)) } - pub fn resolve_annotation(&self, annotation: Annotation) -> Cancellable { - self.with_db(|db| annotations::resolve_annotation(db, annotation)) + pub fn resolve_annotation( + &self, + config: &ResolveAnnotationConfig, + annotation: Annotation, + ) -> Cancellable { + self.with_db(|db| annotations::resolve_annotation(db, config, annotation)) } pub fn move_item( diff --git a/crates/rust-analyzer/src/cli/analysis_stats.rs b/crates/rust-analyzer/src/cli/analysis_stats.rs index 0ee01982fea2..497ccf009c5b 100644 --- a/crates/rust-analyzer/src/cli/analysis_stats.rs +++ b/crates/rust-analyzer/src/cli/analysis_stats.rs @@ -21,7 +21,7 @@ use hir_def::{ use hir_ty::{Interner, Substitution, TyExt, TypeFlags}; use ide::{ Analysis, AnalysisHost, AnnotationConfig, DiagnosticsConfig, Edition, InlayFieldsToResolve, - InlayHintsConfig, LineCol, RootDatabase, + InlayHintsConfig, LineCol, ResolveAnnotationConfig, RootDatabase, }; use ide_db::{ EditionedFileId, LineIndexDatabase, SnippetCap, @@ -1192,13 +1192,17 @@ impl flags::AnalysisStats { annotate_method_references: false, annotate_enum_variant_references: false, location: ide::AnnotationLocation::AboveName, + filter_adjacent_derive_implementations: false, }, analysis.editioned_file_id_to_vfs(file_id), ) .unwrap() .into_iter() .for_each(|annotation| { - _ = analysis.resolve_annotation(annotation); + _ = analysis.resolve_annotation( + &ResolveAnnotationConfig { filter_adjacent_derive_implementations: false }, + annotation, + ); }); } let ide_time = sw.elapsed(); diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index 51d4c29aa74e..a0997d02985a 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -9,10 +9,10 @@ use cfg::{CfgAtom, CfgDiff}; use hir::Symbol; use ide::{ AssistConfig, CallHierarchyConfig, CallableSnippets, CompletionConfig, - CompletionFieldsToResolve, DiagnosticsConfig, GenericParameterHints, HighlightConfig, - HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, InlayHintsConfig, - JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, Snippet, SnippetScope, - SourceRootId, + CompletionFieldsToResolve, DiagnosticsConfig, GenericParameterHints, GotoImplementationConfig, + HighlightConfig, HighlightRelatedConfig, HoverConfig, HoverDocFormat, InlayFieldsToResolve, + InlayHintsConfig, JoinLinesConfig, MemoryLayoutHoverConfig, MemoryLayoutHoverRenderKind, + Snippet, SnippetScope, SourceRootId, }; use ide_db::{ SnippetCap, @@ -93,6 +93,8 @@ config_data! { files_exclude | files_excludeDirs: Vec = vec![], + /// If this is `true`, when "Goto Implementations" and in "Implementations" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type. + gotoImplementations_filterAdjacentDerives: bool = false, /// Enables highlighting of related return values while the cursor is on any `match`, `if`, or match arm arrow (`=>`). highlightRelated_branchExitPoints_enable: bool = true, @@ -1249,6 +1251,7 @@ pub struct LensConfig { // annotations pub location: AnnotationLocation, + pub filter_adjacent_derive_implementations: bool, } #[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] @@ -2289,6 +2292,15 @@ impl Config { refs_trait: *self.lens_enable() && *self.lens_references_trait_enable(), enum_variant_refs: *self.lens_enable() && *self.lens_references_enumVariant_enable(), location: *self.lens_location(), + filter_adjacent_derive_implementations: *self + .gotoImplementations_filterAdjacentDerives(), + } + } + + pub fn goto_implementation(&self) -> GotoImplementationConfig { + GotoImplementationConfig { + filter_adjacent_derive_implementations: *self + .gotoImplementations_filterAdjacentDerives(), } } diff --git a/crates/rust-analyzer/src/handlers/request.rs b/crates/rust-analyzer/src/handlers/request.rs index a76a65220d3b..abc34cf2fdf5 100644 --- a/crates/rust-analyzer/src/handlers/request.rs +++ b/crates/rust-analyzer/src/handlers/request.rs @@ -9,7 +9,8 @@ use base64::{Engine, prelude::BASE64_STANDARD}; use ide::{ AnnotationConfig, AssistKind, AssistResolveStrategy, Cancellable, CompletionFieldsToResolve, FilePosition, FileRange, HoverAction, HoverGotoTypeData, InlayFieldsToResolve, Query, - RangeInfo, ReferenceCategory, Runnable, RunnableKind, SingleResolve, SourceChange, TextEdit, + RangeInfo, ReferenceCategory, ResolveAnnotationConfig, Runnable, RunnableKind, SingleResolve, + SourceChange, TextEdit, }; use ide_db::{FxHashMap, SymbolKind}; use itertools::Itertools; @@ -838,10 +839,11 @@ pub(crate) fn handle_goto_implementation( let _p = tracing::info_span!("handle_goto_implementation").entered(); let position = try_default!(from_proto::file_position(&snap, params.text_document_position_params)?); - let nav_info = match snap.analysis.goto_implementation(position)? { - None => return Ok(None), - Some(it) => it, - }; + let nav_info = + match snap.analysis.goto_implementation(&snap.config.goto_implementation(), position)? { + None => return Ok(None), + Some(it) => it, + }; let src = FileRange { file_id: position.file_id, range: nav_info.range }; let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; Ok(Some(res)) @@ -1624,6 +1626,8 @@ pub(crate) fn handle_code_lens( annotate_method_references: lens_config.method_refs, annotate_enum_variant_references: lens_config.enum_variant_refs, location: lens_config.location.into(), + filter_adjacent_derive_implementations: lens_config + .filter_adjacent_derive_implementations, }, file_id, )?; @@ -1647,7 +1651,14 @@ pub(crate) fn handle_code_lens_resolve( let Some(annotation) = from_proto::annotation(&snap, code_lens.range, resolve)? else { return Ok(code_lens); }; - let annotation = snap.analysis.resolve_annotation(annotation)?; + let lens_config = snap.config.lens(); + let annotation = snap.analysis.resolve_annotation( + &ResolveAnnotationConfig { + filter_adjacent_derive_implementations: lens_config + .filter_adjacent_derive_implementations, + }, + annotation, + )?; let mut acc = Vec::new(); to_proto::code_lens(&mut acc, &snap, annotation)?; @@ -2123,7 +2134,11 @@ fn show_impl_command_link( position: &FilePosition, ) -> Option { if snap.config.hover_actions().implementations && snap.config.client_commands().show_reference { - if let Some(nav_data) = snap.analysis.goto_implementation(*position).unwrap_or(None) { + if let Some(nav_data) = snap + .analysis + .goto_implementation(&snap.config.goto_implementation(), *position) + .unwrap_or(None) + { let uri = to_proto::url(snap, position.file_id); let line_index = snap.file_line_index(position.file_id).ok()?; let position = to_proto::position(&line_index, position.offset); diff --git a/docs/book/src/configuration_generated.md b/docs/book/src/configuration_generated.md index ebac26e1d60a..df5b21d96bf4 100644 --- a/docs/book/src/configuration_generated.md +++ b/docs/book/src/configuration_generated.md @@ -612,6 +612,13 @@ Default: `"client"` Controls file watching implementation. +## rust-analyzer.gotoImplementations.filterAdjacentDerives {#gotoImplementations.filterAdjacentDerives} + +Default: `false` + +If this is `true`, when "Goto Implementations" and in "Implementations" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type. + + ## rust-analyzer.highlightRelated.branchExitPoints.enable {#highlightRelated.branchExitPoints.enable} Default: `true` diff --git a/editors/code/package.json b/editors/code/package.json index 3cb4c21ee1fb..a6ac6565a685 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -1529,6 +1529,16 @@ } } }, + { + "title": "gotoImplementations", + "properties": { + "rust-analyzer.gotoImplementations.filterAdjacentDerives": { + "markdownDescription": "If this is `true`, when \"Goto Implementations\" and in \"Implementations\" lens, are triggered on a `struct` or `enum` or `union`, we filter out trait implementations that originate from `derive`s above the type.", + "default": false, + "type": "boolean" + } + } + }, { "title": "highlightRelated", "properties": {