Repository: rust-analyzer/rust-analyzer
Pull request #11011 (2021-12-14 11:56:16)

minor: Simplify
bors r+


Patch:
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs
index 880596b37c..79163ad4d0 100644
--- a/crates/hir/src/source_analyzer.rs
+++ b/crates/hir/src/source_analyzer.rs
@@ -291,15 +291,12 @@ impl SourceAnalyzer {
             }
         }
 
-        if let Some(pat) = parent()
-            .and_then(ast::RecordPat::cast)
-            .map(ast::Pat::from)
-            .or_else(|| parent().and_then(ast::TupleStructPat::cast).map(ast::Pat::from))
-        {
+        let record_pat = parent().and_then(ast::RecordPat::cast).map(ast::Pat::from);
+        let tuple_struct_pat = || parent().and_then(ast::TupleStructPat::cast).map(ast::Pat::from);
+        if let Some(pat) = record_pat.or_else(tuple_struct_pat) {
             let pat_id = self.pat_id(&pat)?;
-            if let Some(VariantId::EnumVariantId(variant)) =
-                self.infer.as_ref()?.variant_resolution_for_pat(pat_id)
-            {
+            let variant_res_for_pat = self.infer.as_ref()?.variant_resolution_for_pat(pat_id);
+            if let Some(VariantId::EnumVariantId(variant)) = variant_res_for_pat {
                 return Some(PathResolution::Def(ModuleDef::Variant(variant.into())));
             }
         }
@@ -335,6 +332,9 @@ impl SourceAnalyzer {
                 }
             }
         } else if is_path_of_attr {
+            // Case where we are resolving the final path segment of a path in an attribute
+            // in this case we have to check for inert/builtin attributes and tools and prioritize
+            // resolution of attributes over other namesapces
             let name_ref = path.as_single_name_ref();
             let builtin =
                 name_ref.as_ref().map(ast::NameRef::text).as_deref().and_then(BuiltinAttr::by_name);
diff --git a/crates/ide/src/doc_links.rs b/crates/ide/src/doc_links.rs
index 4985a71a85..df25d80178 100644
--- a/crates/ide/src/doc_links.rs
+++ b/crates/ide/src/doc_links.rs
@@ -1,8 +1,5 @@
 //! Extracts, resolves and rewrites links and intra-doc links in markdown documentation.
 
-#[cfg(test)]
-mod tests;
-
 mod intra_doc_links;
 
 use either::Either;
@@ -531,3 +528,399 @@ fn get_assoc_item_fragment(db: &dyn HirDatabase, assoc_item: hir::AssocItem) ->
         AssocItem::TypeAlias(ty) => format!("associatedtype.{}", ty.name(db)),
     })
 }
+
+#[cfg(test)]
+mod tests {
+    use expect_test::{expect, Expect};
+    use ide_db::base_db::FileRange;
+    use itertools::Itertools;
+
+    use crate::{fixture, TryToNav};
+
+    use super::*;
+
+    #[test]
+    fn external_docs_doc_url_crate() {
+        check_external_docs(
+            r#"
+//- /main.rs crate:main deps:foo
+use foo$0::Foo;
+//- /lib.rs crate:foo
+pub struct Foo;
+"#,
+            expect![[r#"https://docs.rs/foo/*/foo/index.html"#]],
+        );
+    }
+
+    #[test]
+    fn external_docs_doc_url_std_crate() {
+        check_external_docs(
+            r#"
+//- /main.rs crate:std
+use self$0;
+"#,
+            expect![[r#"https://doc.rust-lang.org/nightly/std/index.html"#]],
+        );
+    }
+
+    #[test]
+    fn external_docs_doc_url_struct() {
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub struct Fo$0o;
+"#,
+            expect![[r#"https://docs.rs/foo/*/foo/struct.Foo.html"#]],
+        );
+    }
+
+    #[test]
+    fn external_docs_doc_url_struct_field() {
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub struct Foo {
+    field$0: ()
+}
+"#,
+            expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#structfield.field"##]],
+        );
+    }
+
+    #[test]
+    fn external_docs_doc_url_fn() {
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub fn fo$0o() {}
+"#,
+            expect![[r#"https://docs.rs/foo/*/foo/fn.foo.html"#]],
+        );
+    }
+
+    #[test]
+    fn external_docs_doc_url_impl_assoc() {
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub struct Foo;
+impl Foo {
+    pub fn method$0() {}
+}
+"#,
+            expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#method.method"##]],
+        );
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub struct Foo;
+impl Foo {
+    const CONST$0: () = ();
+}
+"#,
+            expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedconstant.CONST"##]],
+        );
+    }
+
+    #[test]
+    fn external_docs_doc_url_impl_trait_assoc() {
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub struct Foo;
+pub trait Trait {
+    fn method() {}
+}
+impl Trait for Foo {
+    pub fn method$0() {}
+}
+"#,
+            expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#method.method"##]],
+        );
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub struct Foo;
+pub trait Trait {
+    const CONST: () = ();
+}
+impl Trait for Foo {
+    const CONST$0: () = ();
+}
+"#,
+            expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedconstant.CONST"##]],
+        );
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub struct Foo;
+pub trait Trait {
+    type Type;
+}
+impl Trait for Foo {
+    type Type$0 = ();
+}
+"#,
+            expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedtype.Type"##]],
+        );
+    }
+
+    #[test]
+    fn external_docs_doc_url_trait_assoc() {
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub trait Foo {
+    fn method$0();
+}
+"#,
+            expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#tymethod.method"##]],
+        );
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub trait Foo {
+    const CONST$0: ();
+}
+"#,
+            expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#associatedconstant.CONST"##]],
+        );
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub trait Foo {
+    type Type$0;
+}
+"#,
+            expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#associatedtype.Type"##]],
+        );
+    }
+
+    #[test]
+    fn external_docs_trait() {
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+trait Trait$0 {}
+"#,
+            expect![[r#"https://docs.rs/foo/*/foo/trait.Trait.html"#]],
+        )
+    }
+
+    #[test]
+    fn external_docs_module() {
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub mod foo {
+    pub mod ba$0r {}
+}
+"#,
+            expect![[r#"https://docs.rs/foo/*/foo/foo/bar/index.html"#]],
+        )
+    }
+
+    #[test]
+    fn external_docs_reexport_order() {
+        check_external_docs(
+            r#"
+//- /main.rs crate:foo
+pub mod wrapper {
+    pub use module::Item;
+
+    pub mod module {
+        pub struct Item;
+    }
+}
+
+fn foo() {
+    let bar: wrapper::It$0em;
+}
+        "#,
+            expect![[r#"https://docs.rs/foo/*/foo/wrapper/module/struct.Item.html"#]],
+        )
+    }
+
+    #[test]
+    fn test_trait_items() {
+        check_doc_links(
+            r#"
+/// [`Trait`]
+/// [`Trait::Type`]
+/// [`Trait::CONST`]
+/// [`Trait::func`]
+trait Trait$0 {
+   // ^^^^^ Trait
+    type Type;
+      // ^^^^ Trait::Type
+    const CONST: usize;
+       // ^^^^^ Trait::CONST
+    fn func();
+    // ^^^^ Trait::func
+}
+        "#,
+        )
+    }
+
+    #[test]
+    fn rewrite_html_root_url() {
+        check_rewrite(
+            r#"
+//- /main.rs crate:foo
+#![doc(arbitrary_attribute = "test", html_root_url = "https:/example.com", arbitrary_attribute2)]
+
+pub mod foo {
+    pub struct Foo;
+}
+/// [Foo](foo::Foo)
+pub struct B$0ar
+"#,
+            expect![[r#"[Foo](https://example.com/foo/foo/struct.Foo.html)"#]],
+        );
+    }
+
+    #[test]
+    fn rewrite_on_field() {
+        check_rewrite(
+            r#"
+//- /main.rs crate:foo
+pub struct Foo {
+    /// [Foo](struct.Foo.html)
+    fie$0ld: ()
+}
+"#,
+            expect![[r#"[Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+        );
+    }
+
+    #[test]
+    fn rewrite_struct() {
+        check_rewrite(
+            r#"
+//- /main.rs crate:foo
+/// [Foo]
+pub struct $0Foo;
+"#,
+            expect![[r#"[Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+        );
+        check_rewrite(
+            r#"
+//- /main.rs crate:foo
+/// [`Foo`]
+pub struct $0Foo;
+"#,
+            expect![[r#"[`Foo`](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+        );
+        check_rewrite(
+            r#"
+//- /main.rs crate:foo
+/// [Foo](struct.Foo.html)
+pub struct $0Foo;
+"#,
+            expect![[r#"[Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+        );
+        check_rewrite(
+            r#"
+//- /main.rs crate:foo
+/// [struct Foo](struct.Foo.html)
+pub struct $0Foo;
+"#,
+            expect![[r#"[struct Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+        );
+        check_rewrite(
+            r#"
+//- /main.rs crate:foo
+/// [my Foo][foo]
+///
+/// [foo]: Foo
+pub struct $0Foo;
+"#,
+            expect![[r#"[my Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
+        );
+    }
+
+    fn check_external_docs(ra_fixture: &str, expect: Expect) {
+        let (analysis, position) = fixture::position(ra_fixture);
+        let url = analysis.external_docs(position).unwrap().expect("could not find url for symbol");
+
+        expect.assert_eq(&url)
+    }
+
+    fn check_rewrite(ra_fixture: &str, expect: Expect) {
+        let (analysis, position) = fixture::position(ra_fixture);
+        let sema = &Semantics::new(&*analysis.db);
+        let (cursor_def, docs) = def_under_cursor(sema, &position);
+        let res = rewrite_links(sema.db, docs.as_str(), cursor_def);
+        expect.assert_eq(&res)
+    }
+
+    fn check_doc_links(ra_fixture: &str) {
+        let key_fn = |&(FileRange { file_id, range }, _): &_| (file_id, range.start());
+
+        let (analysis, position, mut expected) = fixture::annotations(ra_fixture);
+        expected.sort_by_key(key_fn);
+        let sema = &Semantics::new(&*analysis.db);
+        let (cursor_def, docs) = def_under_cursor(sema, &position);
+        let defs = extract_definitions_from_docs(&docs);
+        let actual: Vec<_> = defs
+            .into_iter()
+            .map(|(_, link, ns)| {
+                let def = resolve_doc_path_for_def(sema.db, cursor_def, &link, ns)
+                    .unwrap_or_else(|| panic!("Failed to resolve {}", link));
+                let nav_target = def.try_to_nav(sema.db).unwrap();
+                let range = FileRange {
+                    file_id: nav_target.file_id,
+                    range: nav_target.focus_or_full_range(),
+                };
+                (range, link)
+            })
+            .sorted_by_key(key_fn)
+            .collect();
+        assert_eq!(expected, actual);
+    }
+
+    fn def_under_cursor(
+        sema: &Semantics<RootDatabase>,
+        position: &FilePosition,
+    ) -> (Definition, hir::Documentation) {
+        let (docs, def) = sema
+            .parse(position.file_id)
+            .syntax()
+            .token_at_offset(position.offset)
+            .left_biased()
+            .unwrap()
+            .ancestors()
+            .find_map(|it| node_to_def(sema, &it))
+            .expect("no def found")
+            .unwrap();
+        let docs = docs.expect("no docs found for cursor def");
+        (def, docs)
+    }
+
+    fn node_to_def(
+        sema: &Semantics<RootDatabase>,
+        node: &SyntaxNode,
+    ) -> Option<Option<(Option<hir::Documentation>, Definition)>> {
+        Some(match_ast! {
+            match node {
+                ast::SourceFile(it)  => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Module(def))),
+                ast::Module(it)      => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Module(def))),
+                ast::Fn(it)          => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Function(def))),
+                ast::Struct(it)      => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Struct(def)))),
+                ast::Union(it)       => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Union(def)))),
+                ast::Enum(it)        => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Enum(def)))),
+                ast::Variant(it)     => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Variant(def))),
+                ast::Trait(it)       => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Trait(def))),
+                ast::Static(it)      => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Static(def))),
+                ast::Const(it)       => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Const(def))),
+                ast::TypeAlias(it)   => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::TypeAlias(def))),
+                ast::Impl(it)        => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::SelfType(def))),
+                ast::RecordField(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Field(def))),
+                ast::TupleField(it)  => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Field(def))),
+                ast::Macro(it)       => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Macro(def))),
+                // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
+                _ => return None,
+            }
+        })
+    }
+}
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 6b196681ca..2c540dc80d 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
@@ -102,10 +102,10 @@ fn shorten_paths(node: &SyntaxNode, path: &ast::Path) {
             match child {
                 // Don't modify `use` items, as this can break the `use` item when injecting a new
                 // import into the use tree.
-                ast::Use(_it) => continue,
+                ast::Use(_) => continue,
                 // Don't descend into submodules, they don't have the same `use` items in scope.
                 // FIXME: This isn't true due to `super::*` imports?
-                ast::Module(_it) => continue,
+                ast::Module(_) => continue,
                 ast::Path(p) => if maybe_replace_path(p.clone(), path.clone()).is_none() {
                     shorten_paths(p.syntax(), path);
                 },
diff --git a/crates/ide_completion/src/context.rs b/crates/ide_completion/src/context.rs
index d459e51161..c6af285e86 100644
--- a/crates/ide_completion/src/context.rs
+++ b/crates/ide_completion/src/context.rs
@@ -517,7 +517,7 @@ impl<'a> CompletionContext<'a> {
 
                         (ty, name)
                     },
-                    ast::ArgList(_it) => {
+                    ast::ArgList(_) => {
                         cov_mark::hit!(expected_type_fn_param);
                         ActiveParameter::at_token(
                             &self.sema,
@@ -608,9 +608,9 @@ impl<'a> CompletionContext<'a> {
                             .map(|c| (Some(c.return_type()), None))
                             .unwrap_or((None, None))
                     },
-                    ast::ParamList(__) => (None, None),
-                    ast::Stmt(__) => (None, None),
-                    ast::Item(__) => (None, None),
+                    ast::ParamList(_) => (None, None),
+                    ast::Stmt(_) => (None, None),
+                    ast::Item(_) => (None, None),
                     _ => {
                         match node.parent() {
                             Some(n) => {
@@ -702,10 +702,10 @@ impl<'a> CompletionContext<'a> {
 
         Some(match_ast! {
             match parent {
-                ast::LifetimeParam(_it) => LifetimeContext::LifetimeParam(sema.find_node_at_offset_with_macros(original_file, offset)),
-                ast::BreakExpr(_it) => LifetimeContext::LabelRef,
-                ast::ContinueExpr(_it) => LifetimeContext::LabelRef,
-                ast::Label(_it) => LifetimeContext::LabelDef,
+                ast::LifetimeParam(_) => LifetimeContext::LifetimeParam(sema.find_node_at_offset_with_macros(original_file, offset)),
+                ast::BreakExpr(_) => LifetimeContext::LabelRef,
+                ast::ContinueExpr(_) => LifetimeContext::LabelRef,
+                ast::Label(_) => LifetimeContext::LabelDef,
                 _ => LifetimeContext::Lifetime,
             }
         })
@@ -753,7 +753,7 @@ impl<'a> CompletionContext<'a> {
         path_ctx.kind  = path.syntax().ancestors().find_map(|it| {
             match_ast! {
                 match it {
-                    ast::PathType(_it) => Some(PathKind::Type),
+                    ast::PathType(_) => Some(PathKind::Type),
                     ast::PathExpr(it) => {
                         path_ctx.has_call_parens = it.syntax().parent().map_or(false, |it| ast::CallExpr::can_cast(it.kind()));
                         Some(PathKind::Expr)
@@ -772,9 +772,9 @@ impl<'a> CompletionContext<'a> {
                         Some(PathKind::Pat)
                     },
                     ast::MacroCall(it) => it.excl_token().and(Some(PathKind::Mac)),
-                    ast::Meta(_it) => Some(PathKind::Attr),
+                    ast::Meta(_) => Some(PathKind::Attr),
                     ast::Visibility(it) => Some(PathKind::Vis { has_in_token: it.in_token().is_some() }),
-                    ast::UseTree(_it) => Some(PathKind::Use),
+                    ast::UseTree(_) => Some(PathKind::Use),
                     _ => None,
                 }
             }
@@ -851,9 +851,9 @@ fn pattern_context_for(pat: ast::Pat) -> PatternContext {
                         });
                         return (PatternRefutability::Irrefutable, param.ty().is_some())
                     },
-                    ast::MatchArm(__) => PatternRefutability::Refutable,
-                    ast::Condition(__) => PatternRefutability::Refutable,
-                    ast::ForExpr(__) => PatternRefutability::Irrefutable,
+                    ast::MatchArm(_) => PatternRefutability::Refutable,
+                    ast::Condition(_) => PatternRefutability::Refutable,
+                    ast::ForExpr(_) => PatternRefutability::Irrefutable,
                     _ => PatternRefutability::Irrefutable,
                 }
             };
diff --git a/crates/ide_completion/src/patterns.rs b/crates/ide_completion/src/patterns.rs
index b50f76b911..41e2423f46 100644
--- a/crates/ide_completion/src/patterns.rs
+++ b/crates/ide_completion/src/patterns.rs
@@ -100,7 +100,7 @@ pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<Immedi
         let res = match_ast! {
             match prev_sibling {
                 // vis followed by random ident will always error the parser
-                ast::Visibility(_it) => ImmediatePrevSibling::Visibility,
+                ast::Visibility(_) => ImmediatePrevSibling::Visibility,
                 _ => return None,
             }
         };
@@ -112,7 +112,7 @@ pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<Immedi
                 let node = it.expr().filter(|_| it.semicolon_token().is_none())?.syntax().clone();
                 match_ast! {
                     match node {
-                        ast::IfExpr(_it) => ImmediatePrevSibling::IfExpr,
+                        ast::IfExpr(_) => ImmediatePrevSibling::IfExpr,
                         _ => return None,
                     }
                 }
@@ -128,7 +128,7 @@ pub(crate) fn determine_prev_sibling(name_like: &ast::NameLike) -> Option<Immedi
                 } else {
                     return None
             },
-            ast::Attr(_it) => ImmediatePrevSibling::Attribute,
+            ast::Attr(_) => ImmediatePrevSibling::Attribute,
             _ => return None,
         }
     };
@@ -200,31 +200,31 @@ pub(crate) fn determine_location(
 
     let res = match_ast! {
         match parent {
-            ast::IdentPat(_it) => ImmediateLocation::IdentPat,
-            ast::Rename(_it) => ImmediateLocation::Rename,
-            ast::StmtList(_it) => ImmediateLocation::StmtList,
-            ast::SourceFile(_it) => ImmediateLocation::ItemList,
-            ast::ItemList(_it) => ImmediateLocation::ItemList,
-            ast::RefExpr(_it) => ImmediateLocation::RefExpr,
-            ast::Variant(_it) => ImmediateLocation::Variant,
+            ast::IdentPat(_) => ImmediateLocation::IdentPat,
+            ast::Rename(_) => ImmediateLocation::Rename,
+            ast::StmtList(_) => ImmediateLocation::StmtList,
+            ast::SourceFile(_) => ImmediateLocation::ItemList,
+            ast::ItemList(_) => ImmediateLocation::ItemList,
+            ast::RefExpr(_) => ImmediateLocation::RefExpr,
+            ast::Variant(_) => ImmediateLocation::Variant,
             ast::RecordField(it) => if it.ty().map_or(false, |it| it.syntax().text_range().contains(offset)) {
                 return None;
             } else {
                 ImmediateLocation::RecordField
             },
-            ast::RecordExprFieldList(_it) => sema
+            ast::RecordExprFieldList(_) => sema
                 .find_node_at_offset_with_macros(original_file, offset)
                 .map(ImmediateLocation::RecordExprUpdate)?,
-            ast::TupleField(_it) => ImmediateLocation::TupleField,
-            ast::TupleFieldList(_it) => ImmediateLocation::TupleField,
-            ast::TypeBound(_it) => ImmediateLocation::TypeBound,
-            ast::TypeBoundList(_it) => ImmediateLocation::TypeBound,
+            ast::TupleField(_) => ImmediateLocation::TupleField,
+            ast::TupleFieldList(_) => ImmediateLocation::TupleField,
+            ast::TypeBound(_) => ImmediateLocation::TypeBound,
+            ast::TypeBoundList(_) => ImmediateLocation::TypeBound,
             ast::AssocItemList(it) => match it.syntax().parent().map(|it| it.kind()) {
                 Some(IMPL) => ImmediateLocation::Impl,
                 Some(TRAIT) => ImmediateLocation::Trait,
                 _ => return None,
             },
-            ast::GenericArgList(_it) => sema
+            ast::GenericArgList(_) => sema
                 .find_node_at_offset_with_macros(original_file, offset)
                 .map(ImmediateLocation::GenericArgList)?,
             ast::Module(it) => {
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs
index ef2f0f940f..30ebf7828a 100644
--- a/crates/ide_db/src/defs.rs
+++ b/crates/ide_db/src/defs.rs
@@ -447,7 +447,7 @@ impl NameRefClass {
             }
         }
 
-        if let Some(path) = name_ref.syntax().ancestors().find_map(ast::Path::cast) {
+        if let Some(path) = ast::PathSegment::cast(parent.clone()).map(|it| it.parent_path()) {
             if path.qualifier().is_none() {
                 if let Some(macro_call) = path.syntax().parent().and_then(ast::MacroCall::cast) {
                     // Only use this to resolve single-segment macro calls like `foo!()`. Multi-segment
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index c23a9ee257..0d0f7b78d8 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -657,7 +657,7 @@ impl ReferenceCategory {
 
         let mode = r.syntax().ancestors().find_map(|node| {
         match_ast! {
-            match (node) {
+            match node {
                 ast::BinExpr(expr) => {
                     if matches!(expr.op_kind()?, ast::BinaryOp::Assignment { .. }) {
                         // If the variable or field ends on the LHS's end then it's a Write (covers fields and locals).
diff --git a/crates/syntax/src/lib.rs b/crates/syntax/src/lib.rs
index 24a099cb3c..ec281625a6 100644
--- a/crates/syntax/src/lib.rs
+++ b/crates/syntax/src/lib.rs
@@ -242,7 +242,7 @@ macro_rules! match_ast {
     (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) };
 
     (match ($node:expr) {
-        $( ast::$ast:ident($it:ident) => $res:expr, )*
+        $( ast::$ast:ident($it:pat) => $res:expr, )*
         _ => $catch_all:expr $(,)?
     }) => {{
         $( if let Some($it) = ast::$ast::cast($node.clone()) { $res } else )*
diff --git a/crates/syntax/src/validation.rs b/crates/syntax/src/validation.rs
index a0a0bab5b3..8dc47e0bd3 100644
--- a/crates/syntax/src/validation.rs
+++ b/crates/syntax/src/validation.rs
@@ -275,7 +275,7 @@ fn validate_path_keywords(segment: ast::PathSegment, errors: &mut Vec<SyntaxErro
                             return Some(tree_path);
                         }
                     },
-                    ast::UseTreeList(_it) => continue,
+                    ast::UseTreeList(_) => continue,
                     ast::Path(parent) => path = parent,
                     _ => return None,
                 }


Test patch:
diff --git a/crates/ide/src/doc_links/tests.rs b/crates/ide/src/doc_links/tests.rs
deleted file mode 100644
index 27a70b18aa..0000000000
--- a/crates/ide/src/doc_links/tests.rs
+++ /dev/null
@@ -1,411 +0,0 @@
-use expect_test::{expect, Expect};
-use hir::{HasAttrs, Semantics};
-use ide_db::{
-    base_db::{FilePosition, FileRange},
-    defs::Definition,
-    RootDatabase,
-};
-use itertools::Itertools;
-use syntax::{ast, match_ast, AstNode, SyntaxNode};
-
-use crate::{
-    doc_links::{extract_definitions_from_docs, resolve_doc_path_for_def, rewrite_links},
-    fixture, TryToNav,
-};
-
-fn check_external_docs(ra_fixture: &str, expect: Expect) {
-    let (analysis, position) = fixture::position(ra_fixture);
-    let url = analysis.external_docs(position).unwrap().expect("could not find url for symbol");
-
-    expect.assert_eq(&url)
-}
-
-fn check_rewrite(ra_fixture: &str, expect: Expect) {
-    let (analysis, position) = fixture::position(ra_fixture);
-    let sema = &Semantics::new(&*analysis.db);
-    let (cursor_def, docs) = def_under_cursor(sema, &position);
-    let res = rewrite_links(sema.db, docs.as_str(), cursor_def);
-    expect.assert_eq(&res)
-}
-
-fn check_doc_links(ra_fixture: &str) {
-    let key_fn = |&(FileRange { file_id, range }, _): &_| (file_id, range.start());
-
-    let (analysis, position, mut expected) = fixture::annotations(ra_fixture);
-    expected.sort_by_key(key_fn);
-    let sema = &Semantics::new(&*analysis.db);
-    let (cursor_def, docs) = def_under_cursor(sema, &position);
-    let defs = extract_definitions_from_docs(&docs);
-    let actual: Vec<_> = defs
-        .into_iter()
-        .map(|(_, link, ns)| {
-            let def = resolve_doc_path_for_def(sema.db, cursor_def, &link, ns)
-                .unwrap_or_else(|| panic!("Failed to resolve {}", link));
-            let nav_target = def.try_to_nav(sema.db).unwrap();
-            let range =
-                FileRange { file_id: nav_target.file_id, range: nav_target.focus_or_full_range() };
-            (range, link)
-        })
-        .sorted_by_key(key_fn)
-        .collect();
-    assert_eq!(expected, actual);
-}
-
-fn def_under_cursor(
-    sema: &Semantics<RootDatabase>,
-    position: &FilePosition,
-) -> (Definition, hir::Documentation) {
-    let (docs, def) = sema
-        .parse(position.file_id)
-        .syntax()
-        .token_at_offset(position.offset)
-        .left_biased()
-        .unwrap()
-        .ancestors()
-        .find_map(|it| node_to_def(sema, &it))
-        .expect("no def found")
-        .unwrap();
-    let docs = docs.expect("no docs found for cursor def");
-    (def, docs)
-}
-
-fn node_to_def(
-    sema: &Semantics<RootDatabase>,
-    node: &SyntaxNode,
-) -> Option<Option<(Option<hir::Documentation>, Definition)>> {
-    Some(match_ast! {
-        match node {
-            ast::SourceFile(it)  => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Module(def))),
-            ast::Module(it)      => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Module(def))),
-            ast::Fn(it)          => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Function(def))),
-            ast::Struct(it)      => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Struct(def)))),
-            ast::Union(it)       => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Union(def)))),
-            ast::Enum(it)        => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Adt(hir::Adt::Enum(def)))),
-            ast::Variant(it)     => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Variant(def))),
-            ast::Trait(it)       => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Trait(def))),
-            ast::Static(it)      => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Static(def))),
-            ast::Const(it)       => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Const(def))),
-            ast::TypeAlias(it)   => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::TypeAlias(def))),
-            ast::Impl(it)        => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::SelfType(def))),
-            ast::RecordField(it) => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Field(def))),
-            ast::TupleField(it)  => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Field(def))),
-            ast::Macro(it)       => sema.to_def(&it).map(|def| (def.docs(sema.db), Definition::Macro(def))),
-            // ast::Use(it) => sema.to_def(&it).map(|def| (Box::new(it) as _, def.attrs(sema.db))),
-            _ => return None,
-        }
-    })
-}
-
-#[test]
-fn external_docs_doc_url_crate() {
-    check_external_docs(
-        r#"
-//- /main.rs crate:main deps:foo
-use foo$0::Foo;
-//- /lib.rs crate:foo
-pub struct Foo;
-"#,
-        expect![[r#"https://docs.rs/foo/*/foo/index.html"#]],
-    );
-}
-
-#[test]
-fn external_docs_doc_url_std_crate() {
-    check_external_docs(
-        r#"
-//- /main.rs crate:std
-use self$0;
-"#,
-        expect![[r#"https://doc.rust-lang.org/nightly/std/index.html"#]],
-    );
-}
-
-#[test]
-fn external_docs_doc_url_struct() {
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub struct Fo$0o;
-"#,
-        expect![[r#"https://docs.rs/foo/*/foo/struct.Foo.html"#]],
-    );
-}
-
-#[test]
-fn external_docs_doc_url_struct_field() {
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub struct Foo {
-    field$0: ()
-}
-"#,
-        expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#structfield.field"##]],
-    );
-}
-
-#[test]
-fn external_docs_doc_url_fn() {
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub fn fo$0o() {}
-"#,
-        expect![[r#"https://docs.rs/foo/*/foo/fn.foo.html"#]],
-    );
-}
-
-#[test]
-fn external_docs_doc_url_impl_assoc() {
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub struct Foo;
-impl Foo {
-    pub fn method$0() {}
-}
-"#,
-        expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#method.method"##]],
-    );
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub struct Foo;
-impl Foo {
-    const CONST$0: () = ();
-}
-"#,
-        expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedconstant.CONST"##]],
-    );
-}
-
-#[test]
-fn external_docs_doc_url_impl_trait_assoc() {
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub struct Foo;
-pub trait Trait {
-    fn method() {}
-}
-impl Trait for Foo {
-    pub fn method$0() {}
-}
-"#,
-        expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#method.method"##]],
-    );
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub struct Foo;
-pub trait Trait {
-    const CONST: () = ();
-}
-impl Trait for Foo {
-    const CONST$0: () = ();
-}
-"#,
-        expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedconstant.CONST"##]],
-    );
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub struct Foo;
-pub trait Trait {
-    type Type;
-}
-impl Trait for Foo {
-    type Type$0 = ();
-}
-"#,
-        expect![[r##"https://docs.rs/foo/*/foo/struct.Foo.html#associatedtype.Type"##]],
-    );
-}
-
-#[test]
-fn external_docs_doc_url_trait_assoc() {
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub trait Foo {
-    fn method$0();
-}
-"#,
-        expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#tymethod.method"##]],
-    );
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub trait Foo {
-    const CONST$0: ();
-}
-"#,
-        expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#associatedconstant.CONST"##]],
-    );
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub trait Foo {
-    type Type$0;
-}
-"#,
-        expect![[r##"https://docs.rs/foo/*/foo/trait.Foo.html#associatedtype.Type"##]],
-    );
-}
-
-#[test]
-fn external_docs_trait() {
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-trait Trait$0 {}
-"#,
-        expect![[r#"https://docs.rs/foo/*/foo/trait.Trait.html"#]],
-    )
-}
-
-#[test]
-fn external_docs_module() {
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub mod foo {
-    pub mod ba$0r {}
-}
-"#,
-        expect![[r#"https://docs.rs/foo/*/foo/foo/bar/index.html"#]],
-    )
-}
-
-#[test]
-fn external_docs_reexport_order() {
-    check_external_docs(
-        r#"
-//- /main.rs crate:foo
-pub mod wrapper {
-    pub use module::Item;
-
-    pub mod module {
-        pub struct Item;
-    }
-}
-
-fn foo() {
-    let bar: wrapper::It$0em;
-}
-        "#,
-        expect![[r#"https://docs.rs/foo/*/foo/wrapper/module/struct.Item.html"#]],
-    )
-}
-
-#[test]
-fn test_trait_items() {
-    check_doc_links(
-        r#"
-/// [`Trait`]
-/// [`Trait::Type`]
-/// [`Trait::CONST`]
-/// [`Trait::func`]
-trait Trait$0 {
-   // ^^^^^ Trait
-    type Type;
-      // ^^^^ Trait::Type
-    const CONST: usize;
-       // ^^^^^ Trait::CONST
-    fn func();
-    // ^^^^ Trait::func
-}
-        "#,
-    )
-}
-
-#[test]
-fn rewrite_html_root_url() {
-    check_rewrite(
-        r#"
-//- /main.rs crate:foo
-#![doc(arbitrary_attribute = "test", html_root_url = "https:/example.com", arbitrary_attribute2)]
-
-pub mod foo {
-    pub struct Foo;
-}
-/// [Foo](foo::Foo)
-pub struct B$0ar
-"#,
-        expect![[r#"[Foo](https://example.com/foo/foo/struct.Foo.html)"#]],
-    );
-}
-
-#[test]
-fn rewrite_on_field() {
-    check_rewrite(
-        r#"
-//- /main.rs crate:foo
-pub struct Foo {
-    /// [Foo](struct.Foo.html)
-    fie$0ld: ()
-}
-"#,
-        expect![[r#"[Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
-    );
-}
-
-#[test]
-fn rewrite_struct() {
-    check_rewrite(
-        r#"
-//- /main.rs crate:foo
-/// [Foo]
-pub struct $0Foo;
-"#,
-        expect![[r#"[Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
-    );
-    check_rewrite(
-        r#"
-//- /main.rs crate:foo
-/// [`Foo`]
-pub struct $0Foo;
-"#,
-        expect![[r#"[`Foo`](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
-    );
-    check_rewrite(
-        r#"
-//- /main.rs crate:foo
-/// [Foo](struct.Foo.html)
-pub struct $0Foo;
-"#,
-        expect![[r#"[Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
-    );
-    check_rewrite(
-        r#"
-//- /main.rs crate:foo
-/// [struct Foo](struct.Foo.html)
-pub struct $0Foo;
-"#,
-        expect![[r#"[struct Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
-    );
-    check_rewrite(
-        r#"
-//- /main.rs crate:foo
-/// [my Foo][foo]
-///
-/// [foo]: Foo
-pub struct $0Foo;
-"#,
-        expect![[r#"[my Foo](https://docs.rs/foo/*/foo/struct.Foo.html)"#]],
-    );
-    // FIXME #11008
-    check_rewrite(
-        r#"
-//- /main.rs crate:foo
-/// [`foo`]
-///
-/// [`foo`]: Foo
-pub struct $0Foo;
-"#,
-        expect![[r#"
-            [`foo`]
-
-            []: https://docs.rs/foo/*/foo/struct.Foo.html"#]],
-    );
-}