Repository: rust-lang/rust-analyzer (rust)

Issue:
Derived `PartialEq` not recognized for const-generic type when other `PartialEq` is present (type-mismatch)
I *think* I discovered a pretty severe bug related to const-generics as they are available on stable.

**rust-analyzer version**:  1.65.0-nightly (7480389 2022-08-25)
**rustc version**: 1.63.0 (4b91a6ea7 2022-08-08)
**relevant settings**: n/a

```rust
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
struct MyStruct<const X: usize>(u32);

impl<const X: usize> PartialEq<u16> for MyStruct<X> {
    fn eq(&self, v: &u16) -> bool {
        self.0 == *v as u32
    }
}

fn compare<const X: usize>(a: MyStruct<X>, b: MyStruct<X>) {
    let eq = a == b; // expected u16, found MyStruct<X> rust-analyzer[type-mismatch](https://rust-analyzer.github.io/manual.html#type-mismatch)
}
```

The error goes away if
- the const-generic `X` is removed or
- the `PartialEq<u16>` impl is removed


Pull request description:
Support const generics for builtin derive macro
Fixes #13121

We have been treating every generic parameter as type parameter during builtin derive macro expansion. This patch adds support for const generics in such expansions.

Patch:
diff --git a/crates/hir-expand/src/builtin_derive_macro.rs b/crates/hir-expand/src/builtin_derive_macro.rs
index 79989bc2e3..8966047c9b 100644
--- a/crates/hir-expand/src/builtin_derive_macro.rs
+++ b/crates/hir-expand/src/builtin_derive_macro.rs
@@ -60,7 +60,8 @@ pub fn find_builtin_derive(ident: &name::Name) -> Option<BuiltinDeriveExpander>
 
 struct BasicAdtInfo {
     name: tt::Ident,
-    type_or_const_params: usize,
+    /// `Some(ty)` if it's a const param of type `ty`, `None` if it's a type param.
+    param_types: Vec<Option<tt::Subtree>>,
 }
 
 fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, ExpandError> {
@@ -92,50 +93,22 @@ fn parse_adt(tt: &tt::Subtree) -> Result<BasicAdtInfo, ExpandError> {
     let name_token_id =
         token_map.token_by_range(name.syntax().text_range()).unwrap_or_else(TokenId::unspecified);
     let name_token = tt::Ident { id: name_token_id, text: name.text().into() };
-    let type_or_const_params =
-        params.map_or(0, |type_param_list| type_param_list.type_or_const_params().count());
-    Ok(BasicAdtInfo { name: name_token, type_or_const_params })
-}
-
-fn make_type_args(n: usize, bound: Vec<tt::TokenTree>) -> Vec<tt::TokenTree> {
-    let mut result = Vec::<tt::TokenTree>::with_capacity(n * 2);
-    result.push(
-        tt::Leaf::Punct(tt::Punct {
-            char: '<',
-            spacing: tt::Spacing::Alone,
-            id: tt::TokenId::unspecified(),
-        })
-        .into(),
-    );
-    for i in 0..n {
-        if i > 0 {
-            result.push(
-                tt::Leaf::Punct(tt::Punct {
-                    char: ',',
-                    spacing: tt::Spacing::Alone,
-                    id: tt::TokenId::unspecified(),
-                })
-                .into(),
-            );
-        }
-        result.push(
-            tt::Leaf::Ident(tt::Ident {
-                id: tt::TokenId::unspecified(),
-                text: format!("T{}", i).into(),
-            })
-            .into(),
-        );
-        result.extend(bound.iter().cloned());
-    }
-    result.push(
-        tt::Leaf::Punct(tt::Punct {
-            char: '>',
-            spacing: tt::Spacing::Alone,
-            id: tt::TokenId::unspecified(),
+    let param_types = params
+        .into_iter()
+        .flat_map(|param_list| param_list.type_or_const_params())
+        .map(|param| {
+            if let ast::TypeOrConstParam::Const(param) = param {
+                let ty = param
+                    .ty()
+                    .map(|ty| mbe::syntax_node_to_token_tree(ty.syntax()).0)
+                    .unwrap_or_default();
+                Some(ty)
+            } else {
+                None
+            }
         })
-        .into(),
-    );
-    result
+        .collect();
+    Ok(BasicAdtInfo { name: name_token, param_types })
 }
 
 fn expand_simple_derive(tt: &tt::Subtree, trait_path: tt::Subtree) -> ExpandResult<tt::Subtree> {
@@ -143,14 +116,27 @@ fn expand_simple_derive(tt: &tt::Subtree, trait_path: tt::Subtree) -> ExpandResu
         Ok(info) => info,
         Err(e) => return ExpandResult::only_err(e),
     };
+    let (params, args): (Vec<_>, Vec<_>) = info
+        .param_types
+        .into_iter()
+        .enumerate()
+        .map(|(idx, param_ty)| {
+            let ident = tt::Leaf::Ident(tt::Ident {
+                id: tt::TokenId::unspecified(),
+                text: format!("T{idx}").into(),
+            });
+            let ident_ = ident.clone();
+            if let Some(ty) = param_ty {
+                (quote! { const #ident : #ty , }, quote! { #ident_ , })
+            } else {
+                let bound = trait_path.clone();
+                (quote! { #ident : #bound , }, quote! { #ident_ , })
+            }
+        })
+        .unzip();
     let name = info.name;
-    let trait_path_clone = trait_path.token_trees.clone();
-    let bound = (quote! { : ##trait_path_clone }).token_trees;
-    let type_params = make_type_args(info.type_or_const_params, bound);
-    let type_args = make_type_args(info.type_or_const_params, Vec::new());
-    let trait_path = trait_path.token_trees;
     let expanded = quote! {
-        impl ##type_params ##trait_path for #name ##type_args {}
+        impl < ##params > #trait_path for #name < ##args > {}
     };
     ExpandResult::ok(expanded)
 }
diff --git a/crates/ide/src/static_index.rs b/crates/ide/src/static_index.rs
index 27ad1a948d..9e5eb90950 100644
--- a/crates/ide/src/static_index.rs
+++ b/crates/ide/src/static_index.rs
@@ -210,7 +210,9 @@ fn get_definition(sema: &Semantics<'_, RootDatabase>, token: SyntaxToken) -> Opt
         let def = IdentClass::classify_token(sema, &token).map(IdentClass::definitions_no_ops);
         if let Some(&[x]) = def.as_deref() {
             return Some(x);
-        }
+        } else {
+            continue;
+        };
     }
     None
 }
diff --git a/crates/rust-analyzer/src/cli/scip.rs b/crates/rust-analyzer/src/cli/scip.rs
index 8b77ccde0e..2c29b3ee3a 100644
--- a/crates/rust-analyzer/src/cli/scip.rs
+++ b/crates/rust-analyzer/src/cli/scip.rs
@@ -8,8 +8,8 @@ use std::{
 use crate::line_index::{LineEndings, LineIndex, OffsetEncoding};
 use hir::Name;
 use ide::{
-    LineCol, MonikerDescriptorKind, StaticIndex, StaticIndexedFile, TextRange, TokenId,
-    TokenStaticData,
+    LineCol, MonikerDescriptorKind, MonikerResult, StaticIndex, StaticIndexedFile, TextRange,
+    TokenId,
 };
 use ide_db::LineIndexDatabase;
 use project_model::{CargoConfig, ProjectManifest, ProjectWorkspace};
@@ -75,7 +75,7 @@ impl flags::Scip {
         let mut symbols_emitted: HashSet<TokenId> = HashSet::default();
         let mut tokens_to_symbol: HashMap<TokenId, String> = HashMap::new();
 
-        for StaticIndexedFile { file_id, tokens, .. } in si.files {
+        for file in si.files {
             let mut local_count = 0;
             let mut new_local_symbol = || {
                 let new_symbol = scip::types::Symbol::new_local(local_count);
@@ -84,6 +84,7 @@ impl flags::Scip {
                 new_symbol
             };
 
+            let StaticIndexedFile { file_id, tokens, .. } = file;
             let relative_path = match get_relative_filepath(&vfs, &rootpath, file_id) {
                 Some(relative_path) => relative_path,
                 None => continue,
@@ -106,20 +107,28 @@ impl flags::Scip {
 
                 let mut occurrence = scip_types::Occurrence::default();
                 occurrence.range = text_range_to_scip_range(&line_index, range);
-                occurrence.symbol = tokens_to_symbol
-                    .entry(id)
-                    .or_insert_with(|| {
-                        let symbol = token_to_symbol(&token).unwrap_or_else(&mut new_local_symbol);
-                        scip::symbol::format_symbol(symbol)
-                    })
-                    .clone();
+                occurrence.symbol = match tokens_to_symbol.get(&id) {
+                    Some(symbol) => symbol.clone(),
+                    None => {
+                        let symbol = match &token.moniker {
+                            Some(moniker) => moniker_to_symbol(&moniker),
+                            None => new_local_symbol(),
+                        };
+
+                        let symbol = scip::symbol::format_symbol(symbol);
+                        tokens_to_symbol.insert(id, symbol.clone());
+                        symbol
+                    }
+                };
 
                 if let Some(def) = token.definition {
                     if def.range == range {
                         occurrence.symbol_roles |= scip_types::SymbolRole::Definition as i32;
                     }
 
-                    if symbols_emitted.insert(id) {
+                    if !symbols_emitted.contains(&id) {
+                        symbols_emitted.insert(id);
+
                         let mut symbol_info = scip_types::SymbolInformation::default();
                         symbol_info.symbol = occurrence.symbol.clone();
                         if let Some(hover) = &token.hover {
@@ -198,11 +207,9 @@ fn new_descriptor(name: Name, suffix: scip_types::descriptor::Suffix) -> scip_ty
 ///
 /// Only returns a Symbol when it's a non-local symbol.
 ///     So if the visibility isn't outside of a document, then it will return None
-fn token_to_symbol(token: &TokenStaticData) -> Option<scip_types::Symbol> {
+fn moniker_to_symbol(moniker: &MonikerResult) -> scip_types::Symbol {
     use scip_types::descriptor::Suffix::*;
 
-    let moniker = token.moniker.as_ref()?;
-
     let package_name = moniker.package_information.name.clone();
     let version = moniker.package_information.version.clone();
     let descriptors = moniker
@@ -226,7 +233,7 @@ fn token_to_symbol(token: &TokenStaticData) -> Option<scip_types::Symbol> {
         })
         .collect();
 
-    Some(scip_types::Symbol {
+    scip_types::Symbol {
         scheme: "rust-analyzer".into(),
         package: Some(scip_types::Package {
             manager: "cargo".to_string(),
@@ -237,15 +244,19 @@ fn token_to_symbol(token: &TokenStaticData) -> Option<scip_types::Symbol> {
         .into(),
         descriptors,
         ..Default::default()
-    })
+    }
 }
 
 #[cfg(test)]
 mod test {
     use super::*;
-    use ide::{AnalysisHost, FilePosition, StaticIndex, TextSize};
-    use ide_db::base_db::fixture::ChangeFixture;
+    use hir::Semantics;
+    use ide::{AnalysisHost, FilePosition};
+    use ide_db::defs::IdentClass;
+    use ide_db::{base_db::fixture::ChangeFixture, helpers::pick_best_token};
     use scip::symbol::format_symbol;
+    use syntax::SyntaxKind::*;
+    use syntax::{AstNode, T};
 
     fn position(ra_fixture: &str) -> (AnalysisHost, FilePosition) {
         let mut host = AnalysisHost::default();
@@ -262,33 +273,53 @@ mod test {
     fn check_symbol(ra_fixture: &str, expected: &str) {
         let (host, position) = position(ra_fixture);
 
-        let analysis = host.analysis();
-        let si = StaticIndex::compute(&analysis);
-
         let FilePosition { file_id, offset } = position;
 
-        let mut found_symbol = None;
-        for file in &si.files {
-            if file.file_id != file_id {
-                continue;
-            }
-            for &(range, id) in &file.tokens {
-                if range.contains(offset - TextSize::from(1)) {
-                    let token = si.tokens.get(id).unwrap();
-                    found_symbol = token_to_symbol(token);
-                    break;
-                }
-            }
-        }
+        let db = host.raw_database();
+        let sema = &Semantics::new(db);
+        let file = sema.parse(file_id).syntax().clone();
+        let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
+            IDENT
+            | INT_NUMBER
+            | LIFETIME_IDENT
+            | T![self]
+            | T![super]
+            | T![crate]
+            | T![Self]
+            | COMMENT => 2,
+            kind if kind.is_trivia() => 0,
+            _ => 1,
+        })
+        .expect("OK OK");
+
+        let navs = sema
+            .descend_into_macros(original_token.clone())
+            .into_iter()
+            .filter_map(|token| {
+                IdentClass::classify_token(sema, &token).map(IdentClass::definitions).map(|it| {
+                    it.into_iter().flat_map(|def| {
+                        let module = def.module(db).unwrap();
+                        let current_crate = module.krate();
+
+                        match MonikerResult::from_def(sema.db, def, current_crate) {
+                            Some(moniker_result) => Some(moniker_to_symbol(&moniker_result)),
+                            None => None,
+                        }
+                    })
+                })
+            })
+            .flatten()
+            .collect::<Vec<_>>();
 
         if expected == "" {
-            assert!(found_symbol.is_none(), "must have no symbols {:?}", found_symbol);
+            assert_eq!(0, navs.len(), "must have no symbols {:?}", navs);
             return;
         }
 
-        assert!(found_symbol.is_some(), "must have one symbol {:?}", found_symbol);
-        let res = found_symbol.unwrap();
-        let formatted = format_symbol(res);
+        assert_eq!(1, navs.len(), "must have one symbol {:?}", navs);
+
+        let res = navs.get(0).unwrap();
+        let formatted = format_symbol(res.clone());
         assert_eq!(formatted, expected);
     }
 


Test patch:
diff --git a/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs b/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
index 6819e9114a..fafcde25ae 100644
--- a/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
+++ b/crates/hir-def/src/macro_expansion_tests/builtin_derive_macro.rs
@@ -12,11 +12,11 @@ fn test_copy_expand_simple() {
 #[derive(Copy)]
 struct Foo;
 "#,
-        expect![[r##"
+        expect![[r#"
 #[derive(Copy)]
 struct Foo;
 
-impl < > core::marker::Copy for Foo< > {}"##]],
+impl < > core::marker::Copy for Foo< > {}"#]],
     );
 }
 
@@ -33,7 +33,7 @@ macro Copy {}
 #[derive(Copy)]
 struct Foo;
 "#,
-        expect![[r##"
+        expect![[r#"
 #[rustc_builtin_macro]
 macro derive {}
 #[rustc_builtin_macro]
@@ -41,7 +41,7 @@ macro Copy {}
 #[derive(Copy)]
 struct Foo;
 
-impl < > crate ::marker::Copy for Foo< > {}"##]],
+impl < > crate ::marker::Copy for Foo< > {}"#]],
     );
 }
 
@@ -53,11 +53,11 @@ fn test_copy_expand_with_type_params() {
 #[derive(Copy)]
 struct Foo<A, B>;
 "#,
-        expect![[r##"
+        expect![[r#"
 #[derive(Copy)]
 struct Foo<A, B>;
 
-impl <T0: core::marker::Copy, T1: core::marker::Copy> core::marker::Copy for Foo<T0, T1> {}"##]],
+impl <T0: core::marker::Copy, T1: core::marker::Copy, > core::marker::Copy for Foo<T0, T1, > {}"#]],
     );
 }
 
@@ -70,11 +70,11 @@ fn test_copy_expand_with_lifetimes() {
 #[derive(Copy)]
 struct Foo<A, B, 'a, 'b>;
 "#,
-        expect![[r##"
+        expect![[r#"
 #[derive(Copy)]
 struct Foo<A, B, 'a, 'b>;
 
-impl <T0: core::marker::Copy, T1: core::marker::Copy> core::marker::Copy for Foo<T0, T1> {}"##]],
+impl <T0: core::marker::Copy, T1: core::marker::Copy, > core::marker::Copy for Foo<T0, T1, > {}"#]],
     );
 }
 
@@ -86,10 +86,26 @@ fn test_clone_expand() {
 #[derive(Clone)]
 struct Foo<A, B>;
 "#,
-        expect![[r##"
+        expect![[r#"
 #[derive(Clone)]
 struct Foo<A, B>;
 
-impl <T0: core::clone::Clone, T1: core::clone::Clone> core::clone::Clone for Foo<T0, T1> {}"##]],
+impl <T0: core::clone::Clone, T1: core::clone::Clone, > core::clone::Clone for Foo<T0, T1, > {}"#]],
+    );
+}
+
+#[test]
+fn test_clone_expand_with_const_generics() {
+    check(
+        r#"
+//- minicore: derive, clone
+#[derive(Clone)]
+struct Foo<const X: usize, T>(u32);
+"#,
+        expect![[r#"
+#[derive(Clone)]
+struct Foo<const X: usize, T>(u32);
+
+impl <const T0: usize, T1: core::clone::Clone, > core::clone::Clone for Foo<T0, T1, > {}"#]],
     );
 }