mirror of
				https://github.com/rust-lang/rust-analyzer.git
				synced 2025-10-31 20:09:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			419 lines
		
	
	
	
		
			8.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			419 lines
		
	
	
	
		
			8.9 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use hir::{AsAssocItem, Impl, Semantics};
 | |
| use ide_db::{
 | |
|     RootDatabase,
 | |
|     defs::{Definition, NameClass, NameRefClass},
 | |
|     helpers::pick_best_token,
 | |
| };
 | |
| use syntax::{AstNode, SyntaxKind::*, T, ast};
 | |
| 
 | |
| use crate::{FilePosition, NavigationTarget, RangeInfo, TryToNav};
 | |
| 
 | |
| // Feature: Go to Implementation
 | |
| //
 | |
| // Navigates to the impl items of types.
 | |
| //
 | |
| // | Editor  | Shortcut |
 | |
| // |---------|----------|
 | |
| // | VS Code | <kbd>Ctrl+F12</kbd>
 | |
| //
 | |
| // 
 | |
| pub(crate) fn goto_implementation(
 | |
|     db: &RootDatabase,
 | |
|     FilePosition { file_id, offset }: FilePosition,
 | |
| ) -> Option<RangeInfo<Vec<NavigationTarget>>> {
 | |
|     let sema = Semantics::new(db);
 | |
|     let source_file = sema.parse_guess_edition(file_id);
 | |
|     let syntax = source_file.syntax().clone();
 | |
| 
 | |
|     let original_token = pick_best_token(syntax.token_at_offset(offset), |kind| match kind {
 | |
|         IDENT | T![self] | INT_NUMBER => 1,
 | |
|         _ => 0,
 | |
|     })?;
 | |
|     let range = original_token.text_range();
 | |
|     let navs = sema
 | |
|         .descend_into_macros_exact(original_token)
 | |
|         .iter()
 | |
|         .filter_map(|token| {
 | |
|             token
 | |
|                 .parent()
 | |
|                 .and_then(ast::NameLike::cast)
 | |
|                 .and_then(|node| match &node {
 | |
|                     ast::NameLike::Name(name) => {
 | |
|                         NameClass::classify(&sema, name).and_then(|class| match class {
 | |
|                             NameClass::Definition(it) | NameClass::ConstReference(it) => Some(it),
 | |
|                             NameClass::PatFieldShorthand { .. } => None,
 | |
|                         })
 | |
|                     }
 | |
|                     ast::NameLike::NameRef(name_ref) => NameRefClass::classify(&sema, name_ref)
 | |
|                         .and_then(|class| match class {
 | |
|                             NameRefClass::Definition(def, _) => Some(def),
 | |
|                             NameRefClass::FieldShorthand { .. }
 | |
|                             | NameRefClass::ExternCrateShorthand { .. } => None,
 | |
|                         }),
 | |
|                     ast::NameLike::Lifetime(_) => None,
 | |
|                 })
 | |
|                 .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::TypeAlias(alias) => impls_for_ty(&sema, alias.ty(sema.db)),
 | |
|                         Definition::BuiltinType(builtin) => {
 | |
|                             impls_for_ty(&sema, builtin.ty(sema.db))
 | |
|                         }
 | |
|                         Definition::Function(f) => {
 | |
|                             let assoc = f.as_assoc_item(sema.db)?;
 | |
|                             let name = assoc.name(sema.db)?;
 | |
|                             let trait_ = assoc.container_or_implemented_trait(sema.db)?;
 | |
|                             impls_for_trait_item(&sema, trait_, name)
 | |
|                         }
 | |
|                         Definition::Const(c) => {
 | |
|                             let assoc = c.as_assoc_item(sema.db)?;
 | |
|                             let name = assoc.name(sema.db)?;
 | |
|                             let trait_ = assoc.container_or_implemented_trait(sema.db)?;
 | |
|                             impls_for_trait_item(&sema, trait_, name)
 | |
|                         }
 | |
|                         _ => return None,
 | |
|                     };
 | |
|                     Some(navs)
 | |
|                 })
 | |
|         })
 | |
|         .flatten()
 | |
|         .collect();
 | |
| 
 | |
|     Some(RangeInfo { range, info: navs })
 | |
| }
 | |
| 
 | |
| fn impls_for_ty(sema: &Semantics<'_, RootDatabase>, ty: hir::Type<'_>) -> Vec<NavigationTarget> {
 | |
|     Impl::all_for_type(sema.db, ty)
 | |
|         .into_iter()
 | |
|         .filter_map(|imp| imp.try_to_nav(sema.db))
 | |
|         .flatten()
 | |
|         .collect()
 | |
| }
 | |
| 
 | |
| fn impls_for_trait(
 | |
|     sema: &Semantics<'_, RootDatabase>,
 | |
|     trait_: hir::Trait,
 | |
| ) -> Vec<NavigationTarget> {
 | |
|     Impl::all_for_trait(sema.db, trait_)
 | |
|         .into_iter()
 | |
|         .filter_map(|imp| imp.try_to_nav(sema.db))
 | |
|         .flatten()
 | |
|         .collect()
 | |
| }
 | |
| 
 | |
| fn impls_for_trait_item(
 | |
|     sema: &Semantics<'_, RootDatabase>,
 | |
|     trait_: hir::Trait,
 | |
|     fun_name: hir::Name,
 | |
| ) -> Vec<NavigationTarget> {
 | |
|     Impl::all_for_trait(sema.db, trait_)
 | |
|         .into_iter()
 | |
|         .filter_map(|imp| {
 | |
|             let item = imp.items(sema.db).iter().find_map(|itm| {
 | |
|                 let itm_name = itm.name(sema.db)?;
 | |
|                 (itm_name == fun_name).then_some(*itm)
 | |
|             })?;
 | |
|             item.try_to_nav(sema.db)
 | |
|         })
 | |
|         .flatten()
 | |
|         .collect()
 | |
| }
 | |
| 
 | |
| #[cfg(test)]
 | |
| mod tests {
 | |
|     use ide_db::FileRange;
 | |
|     use itertools::Itertools;
 | |
| 
 | |
|     use crate::fixture;
 | |
| 
 | |
|     fn check(#[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 cmp = |frange: &FileRange| (frange.file_id, frange.range.start());
 | |
| 
 | |
|         let actual = navs
 | |
|             .into_iter()
 | |
|             .map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
 | |
|             .sorted_by_key(cmp)
 | |
|             .collect::<Vec<_>>();
 | |
|         let expected =
 | |
|             expected.into_iter().map(|(range, _)| range).sorted_by_key(cmp).collect::<Vec<_>>();
 | |
|         assert_eq!(expected, actual);
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_works() {
 | |
|         check(
 | |
|             r#"
 | |
| struct Foo$0;
 | |
| impl Foo {}
 | |
|    //^^^
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_works_multiple_blocks() {
 | |
|         check(
 | |
|             r#"
 | |
| struct Foo$0;
 | |
| impl Foo {}
 | |
|    //^^^
 | |
| impl Foo {}
 | |
|    //^^^
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_works_multiple_mods() {
 | |
|         check(
 | |
|             r#"
 | |
| struct Foo$0;
 | |
| mod a {
 | |
|     impl super::Foo {}
 | |
|        //^^^^^^^^^^
 | |
| }
 | |
| mod b {
 | |
|     impl super::Foo {}
 | |
|        //^^^^^^^^^^
 | |
| }
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_works_multiple_files() {
 | |
|         check(
 | |
|             r#"
 | |
| //- /lib.rs
 | |
| struct Foo$0;
 | |
| mod a;
 | |
| mod b;
 | |
| //- /a.rs
 | |
| impl crate::Foo {}
 | |
|    //^^^^^^^^^^
 | |
| //- /b.rs
 | |
| impl crate::Foo {}
 | |
|    //^^^^^^^^^^
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_for_trait() {
 | |
|         check(
 | |
|             r#"
 | |
| trait T$0 {}
 | |
| struct Foo;
 | |
| impl T for Foo {}
 | |
|          //^^^
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_for_trait_multiple_files() {
 | |
|         check(
 | |
|             r#"
 | |
| //- /lib.rs
 | |
| trait T$0 {};
 | |
| struct Foo;
 | |
| mod a;
 | |
| mod b;
 | |
| //- /a.rs
 | |
| impl crate::T for crate::Foo {}
 | |
|                 //^^^^^^^^^^
 | |
| //- /b.rs
 | |
| impl crate::T for crate::Foo {}
 | |
|                 //^^^^^^^^^^
 | |
|             "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     // FIXME(next-solver): it would be nice to be able to also point to `&Foo`
 | |
|     #[test]
 | |
|     fn goto_implementation_all_impls() {
 | |
|         check(
 | |
|             r#"
 | |
| //- /lib.rs
 | |
| trait T {}
 | |
| struct Foo$0;
 | |
| impl Foo {}
 | |
|    //^^^
 | |
| impl T for Foo {}
 | |
|          //^^^
 | |
| impl T for &Foo {}
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_to_builtin_derive() {
 | |
|         check(
 | |
|             r#"
 | |
| //- minicore: copy, derive
 | |
|   #[derive(Copy)]
 | |
|          //^^^^
 | |
| struct Foo$0;
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_type_alias() {
 | |
|         check(
 | |
|             r#"
 | |
| struct Foo;
 | |
| 
 | |
| type Bar$0 = Foo;
 | |
| 
 | |
| impl Foo {}
 | |
|    //^^^
 | |
| impl Bar {}
 | |
|    //^^^
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_adt_generic() {
 | |
|         check(
 | |
|             r#"
 | |
| struct Foo$0<T>;
 | |
| 
 | |
| impl<T> Foo<T> {}
 | |
|       //^^^^^^
 | |
| impl Foo<str> {}
 | |
|    //^^^^^^^^
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_builtin() {
 | |
|         check(
 | |
|             r#"
 | |
| //- /lib.rs crate:main deps:core
 | |
| fn foo(_: bool$0) {{}}
 | |
| //- /libcore.rs crate:core
 | |
| #![rustc_coherence_is_core]
 | |
| #[lang = "bool"]
 | |
| impl bool {}
 | |
|    //^^^^
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_trait_functions() {
 | |
|         check(
 | |
|             r#"
 | |
| trait Tr {
 | |
|     fn f$0();
 | |
| }
 | |
| 
 | |
| struct S;
 | |
| 
 | |
| impl Tr for S {
 | |
|     fn f() {
 | |
|      //^
 | |
|         println!("Hello, world!");
 | |
|     }
 | |
| }
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_implementation_trait_assoc_const() {
 | |
|         check(
 | |
|             r#"
 | |
| trait Tr {
 | |
|     const C$0: usize;
 | |
| }
 | |
| 
 | |
| struct S;
 | |
| 
 | |
| impl Tr for S {
 | |
|     const C: usize = 4;
 | |
|         //^
 | |
| }
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_adt_implementation_inside_block() {
 | |
|         check(
 | |
|             r#"
 | |
| //- minicore: copy, derive
 | |
| trait Bar {}
 | |
| 
 | |
| fn test() {
 | |
|     #[derive(Copy)]
 | |
|   //^^^^^^^^^^^^^^^
 | |
|     struct Foo$0;
 | |
| 
 | |
|     impl Foo {}
 | |
|        //^^^
 | |
| 
 | |
|     trait Baz {}
 | |
| 
 | |
|     impl Bar for Foo {}
 | |
|                //^^^
 | |
| 
 | |
|     impl Baz for Foo {}
 | |
|                //^^^
 | |
| }
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     #[test]
 | |
|     fn goto_trait_implementation_inside_block() {
 | |
|         check(
 | |
|             r#"
 | |
| struct Bar;
 | |
| 
 | |
| fn test() {
 | |
|     trait Foo$0 {}
 | |
| 
 | |
|     struct Baz;
 | |
| 
 | |
|     impl Foo for Bar {}
 | |
|                //^^^
 | |
| 
 | |
|     impl Foo for Baz {}
 | |
|                //^^^
 | |
| }
 | |
| "#,
 | |
|         );
 | |
|         check(
 | |
|             r#"
 | |
| struct Bar;
 | |
| 
 | |
| fn test() {
 | |
|     trait Foo {
 | |
|         fn foo$0() {}
 | |
|     }
 | |
| 
 | |
|     struct Baz;
 | |
| 
 | |
|     impl Foo for Bar {
 | |
|         fn foo() {}
 | |
|          //^^^
 | |
|     }
 | |
| 
 | |
|     impl Foo for Baz {
 | |
|         fn foo() {}
 | |
|          //^^^
 | |
|     }
 | |
| }
 | |
| "#,
 | |
|         );
 | |
|     }
 | |
| }
 | 
