]> git.lizzy.rs Git - rust.git/commitdiff
Handle associated type shorthand (`T::Item`)
authorFlorian Diebold <flodiebold@gmail.com>
Sun, 22 Sep 2019 18:01:12 +0000 (20:01 +0200)
committerFlorian Diebold <flodiebold@gmail.com>
Sun, 22 Sep 2019 18:02:32 +0000 (20:02 +0200)
This is only allowed for generic parameters (including `Self` in traits), and
special care needs to be taken to not run into cycles while resolving it,
because we use the where clauses of the generic parameter to find candidates for
the trait containing the associated type, but the where clauses may themselves
contain instances of short-hand associated types.

In some cases this is even fine, e.g. we might have `T: Trait<U::Item>, U:
Iterator`. If there is a cycle, we'll currently panic, which isn't great, but
better than overflowing the stack...

crates/ra_assists/src/raw_string.rs
crates/ra_hir/src/db.rs
crates/ra_hir/src/generics.rs
crates/ra_hir/src/resolve.rs
crates/ra_hir/src/ty.rs
crates/ra_hir/src/ty/lower.rs
crates/ra_hir/src/ty/tests.rs

index e0026706012a9dc0e73d0686b6b73ec98117802f..965a64c9870c1836327baa741f1cba7768b66e9e 100644 (file)
-use hir::db::HirDatabase;\r
-use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit};\r
-\r
-use crate::{Assist, AssistCtx, AssistId};\r
-\r
-pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {\r
-    let literal = ctx.node_at_offset::<Literal>()?;\r
-    if literal.token().kind() != ra_syntax::SyntaxKind::STRING {\r
-        return None;\r
-    }\r
-    ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| {\r
-        edit.target(literal.syntax().text_range());\r
-        edit.insert(literal.syntax().text_range().start(), "r");\r
-    });\r
-    ctx.build()\r
-}\r
-\r
-fn find_usual_string_range(s: &str) -> Option<TextRange> {\r
-    Some(TextRange::from_to(\r
-        TextUnit::from(s.find('"')? as u32),\r
-        TextUnit::from(s.rfind('"')? as u32),\r
-    ))\r
-}\r
-\r
-pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {\r
-    let literal = ctx.node_at_offset::<Literal>()?;\r
-    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {\r
-        return None;\r
-    }\r
-    let token = literal.token();\r
-    let text = token.text().as_str();\r
-    let usual_string_range = find_usual_string_range(text)?;\r
-    ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| {\r
-        edit.target(literal.syntax().text_range());\r
-        // parse inside string to escape `"`\r
-        let start_of_inside = usual_string_range.start().to_usize() + 1;\r
-        let end_of_inside = usual_string_range.end().to_usize();\r
-        let inside_str = &text[start_of_inside..end_of_inside];\r
-        let escaped = inside_str.escape_default().to_string();\r
-        edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped));\r
-    });\r
-    ctx.build()\r
-}\r
-\r
-pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {\r
-    let literal = ctx.node_at_offset::<Literal>()?;\r
-    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {\r
-        return None;\r
-    }\r
-    ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| {\r
-        edit.target(literal.syntax().text_range());\r
-        edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#");\r
-        edit.insert(literal.syntax().text_range().end(), "#");\r
-    });\r
-    ctx.build()\r
-}\r
-\r
-pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {\r
-    let literal = ctx.node_at_offset::<Literal>()?;\r
-    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {\r
-        return None;\r
-    }\r
-    let token = literal.token();\r
-    let text = token.text().as_str();\r
-    if text.starts_with("r\"") {\r
-        // no hash to remove\r
-        return None;\r
-    }\r
-    ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| {\r
-        edit.target(literal.syntax().text_range());\r
-        let result = &text[2..text.len() - 1];\r
-        let result = if result.starts_with("\"") {\r
-            // no more hash, escape\r
-            let internal_str = &result[1..result.len() - 1];\r
-            format!("\"{}\"", internal_str.escape_default().to_string())\r
-        } else {\r
-            result.to_owned()\r
-        };\r
-        edit.replace(literal.syntax().text_range(), format!("r{}", result));\r
-    });\r
-    ctx.build()\r
-}\r
-\r
-#[cfg(test)]\r
-mod test {\r
-    use super::*;\r
-    use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};\r
-\r
-    #[test]\r
-    fn make_raw_string_target() {\r
-        check_assist_target(\r
-            make_raw_string,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>"random string";\r
-            }\r
-            "#,\r
-            r#""random string""#,\r
-        );\r
-    }\r
-\r
-    #[test]\r
-    fn make_raw_string_works() {\r
-        check_assist(\r
-            make_raw_string,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>"random string";\r
-            }\r
-            "#,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>r"random string";\r
-            }\r
-            "#,\r
-        )\r
-    }\r
-\r
-    #[test]\r
-    fn make_raw_string_with_escaped_works() {\r
-        check_assist(\r
-            make_raw_string,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>"random\nstring";\r
-            }\r
-            "#,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>r"random\nstring";\r
-            }\r
-            "#,\r
-        )\r
-    }\r
-\r
-    #[test]\r
-    fn make_raw_string_not_works() {\r
-        check_assist_not_applicable(\r
-            make_raw_string,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>r"random string";\r
-            }\r
-            "#,\r
-        );\r
-    }\r
-\r
-    #[test]\r
-    fn add_hash_target() {\r
-        check_assist_target(\r
-            add_hash,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>r"random string";\r
-            }\r
-            "#,\r
-            r#"r"random string""#,\r
-        );\r
-    }\r
-\r
-    #[test]\r
-    fn add_hash_works() {\r
-        check_assist(\r
-            add_hash,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>r"random string";\r
-            }\r
-            "#,\r
-            r##"\r
-            fn f() {\r
-                let s = <|>r#"random string"#;\r
-            }\r
-            "##,\r
-        )\r
-    }\r
-\r
-    #[test]\r
-    fn add_more_hash_works() {\r
-        check_assist(\r
-            add_hash,\r
-            r##"\r
-            fn f() {\r
-                let s = <|>r#"random"string"#;\r
-            }\r
-            "##,\r
-            r###"\r
-            fn f() {\r
-                let s = <|>r##"random"string"##;\r
-            }\r
-            "###,\r
-        )\r
-    }\r
-\r
-    #[test]\r
-    fn add_hash_not_works() {\r
-        check_assist_not_applicable(\r
-            add_hash,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>"random string";\r
-            }\r
-            "#,\r
-        );\r
-    }\r
-\r
-    #[test]\r
-    fn remove_hash_target() {\r
-        check_assist_target(\r
-            remove_hash,\r
-            r##"\r
-            fn f() {\r
-                let s = <|>r#"random string"#;\r
-            }\r
-            "##,\r
-            r##"r#"random string"#"##,\r
-        );\r
-    }\r
-\r
-    #[test]\r
-    fn remove_hash_works() {\r
-        check_assist(\r
-            remove_hash,\r
-            r##"\r
-            fn f() {\r
-                let s = <|>r#"random string"#;\r
-            }\r
-            "##,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>r"random string";\r
-            }\r
-            "#,\r
-        )\r
-    }\r
-\r
-    #[test]\r
-    fn remove_hash_with_quote_works() {\r
-        check_assist(\r
-            remove_hash,\r
-            r##"\r
-            fn f() {\r
-                let s = <|>r#"random"str"ing"#;\r
-            }\r
-            "##,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>r"random\"str\"ing";\r
-            }\r
-            "#,\r
-        )\r
-    }\r
-\r
-    #[test]\r
-    fn remove_more_hash_works() {\r
-        check_assist(\r
-            remove_hash,\r
-            r###"\r
-            fn f() {\r
-                let s = <|>r##"random string"##;\r
-            }\r
-            "###,\r
-            r##"\r
-            fn f() {\r
-                let s = <|>r#"random string"#;\r
-            }\r
-            "##,\r
-        )\r
-    }\r
-\r
-    #[test]\r
-    fn remove_hash_not_works() {\r
-        check_assist_not_applicable(\r
-            remove_hash,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>"random string";\r
-            }\r
-            "#,\r
-        );\r
-    }\r
-\r
-    #[test]\r
-    fn remove_hash_no_hash_not_works() {\r
-        check_assist_not_applicable(\r
-            remove_hash,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>r"random string";\r
-            }\r
-            "#,\r
-        );\r
-    }\r
-\r
-    #[test]\r
-    fn make_usual_string_target() {\r
-        check_assist_target(\r
-            make_usual_string,\r
-            r##"\r
-            fn f() {\r
-                let s = <|>r#"random string"#;\r
-            }\r
-            "##,\r
-            r##"r#"random string"#"##,\r
-        );\r
-    }\r
-\r
-    #[test]\r
-    fn make_usual_string_works() {\r
-        check_assist(\r
-            make_usual_string,\r
-            r##"\r
-            fn f() {\r
-                let s = <|>r#"random string"#;\r
-            }\r
-            "##,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>"random string";\r
-            }\r
-            "#,\r
-        )\r
-    }\r
-\r
-    #[test]\r
-    fn make_usual_string_with_quote_works() {\r
-        check_assist(\r
-            make_usual_string,\r
-            r##"\r
-            fn f() {\r
-                let s = <|>r#"random"str"ing"#;\r
-            }\r
-            "##,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>"random\"str\"ing";\r
-            }\r
-            "#,\r
-        )\r
-    }\r
-\r
-    #[test]\r
-    fn make_usual_string_more_hash_works() {\r
-        check_assist(\r
-            make_usual_string,\r
-            r###"\r
-            fn f() {\r
-                let s = <|>r##"random string"##;\r
-            }\r
-            "###,\r
-            r##"\r
-            fn f() {\r
-                let s = <|>"random string";\r
-            }\r
-            "##,\r
-        )\r
-    }\r
-\r
-    #[test]\r
-    fn make_usual_string_not_works() {\r
-        check_assist_not_applicable(\r
-            make_usual_string,\r
-            r#"\r
-            fn f() {\r
-                let s = <|>"random string";\r
-            }\r
-            "#,\r
-        );\r
-    }\r
-}\r
+use hir::db::HirDatabase;
+use ra_syntax::{ast::AstNode, ast::Literal, TextRange, TextUnit};
+
+use crate::{Assist, AssistCtx, AssistId};
+
+pub(crate) fn make_raw_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+    let literal = ctx.node_at_offset::<Literal>()?;
+    if literal.token().kind() != ra_syntax::SyntaxKind::STRING {
+        return None;
+    }
+    ctx.add_action(AssistId("make_raw_string"), "make raw string", |edit| {
+        edit.target(literal.syntax().text_range());
+        edit.insert(literal.syntax().text_range().start(), "r");
+    });
+    ctx.build()
+}
+
+fn find_usual_string_range(s: &str) -> Option<TextRange> {
+    Some(TextRange::from_to(
+        TextUnit::from(s.find('"')? as u32),
+        TextUnit::from(s.rfind('"')? as u32),
+    ))
+}
+
+pub(crate) fn make_usual_string(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+    let literal = ctx.node_at_offset::<Literal>()?;
+    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
+        return None;
+    }
+    let token = literal.token();
+    let text = token.text().as_str();
+    let usual_string_range = find_usual_string_range(text)?;
+    ctx.add_action(AssistId("make_usual_string"), "make usual string", |edit| {
+        edit.target(literal.syntax().text_range());
+        // parse inside string to escape `"`
+        let start_of_inside = usual_string_range.start().to_usize() + 1;
+        let end_of_inside = usual_string_range.end().to_usize();
+        let inside_str = &text[start_of_inside..end_of_inside];
+        let escaped = inside_str.escape_default().to_string();
+        edit.replace(literal.syntax().text_range(), format!("\"{}\"", escaped));
+    });
+    ctx.build()
+}
+
+pub(crate) fn add_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+    let literal = ctx.node_at_offset::<Literal>()?;
+    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
+        return None;
+    }
+    ctx.add_action(AssistId("add_hash"), "add hash to raw string", |edit| {
+        edit.target(literal.syntax().text_range());
+        edit.insert(literal.syntax().text_range().start() + TextUnit::of_char('r'), "#");
+        edit.insert(literal.syntax().text_range().end(), "#");
+    });
+    ctx.build()
+}
+
+pub(crate) fn remove_hash(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
+    let literal = ctx.node_at_offset::<Literal>()?;
+    if literal.token().kind() != ra_syntax::SyntaxKind::RAW_STRING {
+        return None;
+    }
+    let token = literal.token();
+    let text = token.text().as_str();
+    if text.starts_with("r\"") {
+        // no hash to remove
+        return None;
+    }
+    ctx.add_action(AssistId("remove_hash"), "remove hash from raw string", |edit| {
+        edit.target(literal.syntax().text_range());
+        let result = &text[2..text.len() - 1];
+        let result = if result.starts_with("\"") {
+            // no more hash, escape
+            let internal_str = &result[1..result.len() - 1];
+            format!("\"{}\"", internal_str.escape_default().to_string())
+        } else {
+            result.to_owned()
+        };
+        edit.replace(literal.syntax().text_range(), format!("r{}", result));
+    });
+    ctx.build()
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+    use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};
+
+    #[test]
+    fn make_raw_string_target() {
+        check_assist_target(
+            make_raw_string,
+            r#"
+            fn f() {
+                let s = <|>"random string";
+            }
+            "#,
+            r#""random string""#,
+        );
+    }
+
+    #[test]
+    fn make_raw_string_works() {
+        check_assist(
+            make_raw_string,
+            r#"
+            fn f() {
+                let s = <|>"random string";
+            }
+            "#,
+            r#"
+            fn f() {
+                let s = <|>r"random string";
+            }
+            "#,
+        )
+    }
+
+    #[test]
+    fn make_raw_string_with_escaped_works() {
+        check_assist(
+            make_raw_string,
+            r#"
+            fn f() {
+                let s = <|>"random\nstring";
+            }
+            "#,
+            r#"
+            fn f() {
+                let s = <|>r"random\nstring";
+            }
+            "#,
+        )
+    }
+
+    #[test]
+    fn make_raw_string_not_works() {
+        check_assist_not_applicable(
+            make_raw_string,
+            r#"
+            fn f() {
+                let s = <|>r"random string";
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn add_hash_target() {
+        check_assist_target(
+            add_hash,
+            r#"
+            fn f() {
+                let s = <|>r"random string";
+            }
+            "#,
+            r#"r"random string""#,
+        );
+    }
+
+    #[test]
+    fn add_hash_works() {
+        check_assist(
+            add_hash,
+            r#"
+            fn f() {
+                let s = <|>r"random string";
+            }
+            "#,
+            r##"
+            fn f() {
+                let s = <|>r#"random string"#;
+            }
+            "##,
+        )
+    }
+
+    #[test]
+    fn add_more_hash_works() {
+        check_assist(
+            add_hash,
+            r##"
+            fn f() {
+                let s = <|>r#"random"string"#;
+            }
+            "##,
+            r###"
+            fn f() {
+                let s = <|>r##"random"string"##;
+            }
+            "###,
+        )
+    }
+
+    #[test]
+    fn add_hash_not_works() {
+        check_assist_not_applicable(
+            add_hash,
+            r#"
+            fn f() {
+                let s = <|>"random string";
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn remove_hash_target() {
+        check_assist_target(
+            remove_hash,
+            r##"
+            fn f() {
+                let s = <|>r#"random string"#;
+            }
+            "##,
+            r##"r#"random string"#"##,
+        );
+    }
+
+    #[test]
+    fn remove_hash_works() {
+        check_assist(
+            remove_hash,
+            r##"
+            fn f() {
+                let s = <|>r#"random string"#;
+            }
+            "##,
+            r#"
+            fn f() {
+                let s = <|>r"random string";
+            }
+            "#,
+        )
+    }
+
+    #[test]
+    fn remove_hash_with_quote_works() {
+        check_assist(
+            remove_hash,
+            r##"
+            fn f() {
+                let s = <|>r#"random"str"ing"#;
+            }
+            "##,
+            r#"
+            fn f() {
+                let s = <|>r"random\"str\"ing";
+            }
+            "#,
+        )
+    }
+
+    #[test]
+    fn remove_more_hash_works() {
+        check_assist(
+            remove_hash,
+            r###"
+            fn f() {
+                let s = <|>r##"random string"##;
+            }
+            "###,
+            r##"
+            fn f() {
+                let s = <|>r#"random string"#;
+            }
+            "##,
+        )
+    }
+
+    #[test]
+    fn remove_hash_not_works() {
+        check_assist_not_applicable(
+            remove_hash,
+            r#"
+            fn f() {
+                let s = <|>"random string";
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn remove_hash_no_hash_not_works() {
+        check_assist_not_applicable(
+            remove_hash,
+            r#"
+            fn f() {
+                let s = <|>r"random string";
+            }
+            "#,
+        );
+    }
+
+    #[test]
+    fn make_usual_string_target() {
+        check_assist_target(
+            make_usual_string,
+            r##"
+            fn f() {
+                let s = <|>r#"random string"#;
+            }
+            "##,
+            r##"r#"random string"#"##,
+        );
+    }
+
+    #[test]
+    fn make_usual_string_works() {
+        check_assist(
+            make_usual_string,
+            r##"
+            fn f() {
+                let s = <|>r#"random string"#;
+            }
+            "##,
+            r#"
+            fn f() {
+                let s = <|>"random string";
+            }
+            "#,
+        )
+    }
+
+    #[test]
+    fn make_usual_string_with_quote_works() {
+        check_assist(
+            make_usual_string,
+            r##"
+            fn f() {
+                let s = <|>r#"random"str"ing"#;
+            }
+            "##,
+            r#"
+            fn f() {
+                let s = <|>"random\"str\"ing";
+            }
+            "#,
+        )
+    }
+
+    #[test]
+    fn make_usual_string_more_hash_works() {
+        check_assist(
+            make_usual_string,
+            r###"
+            fn f() {
+                let s = <|>r##"random string"##;
+            }
+            "###,
+            r##"
+            fn f() {
+                let s = <|>"random string";
+            }
+            "##,
+        )
+    }
+
+    #[test]
+    fn make_usual_string_not_works() {
+        check_assist_not_applicable(
+            make_usual_string,
+            r#"
+            fn f() {
+                let s = <|>"random string";
+            }
+            "#,
+        );
+    }
+}
index f7f124904e927d4228b1974d1c3c40ad4b270056..05259dcbb7e1104d10b4887b7fb36a8d2fccd81e 100644 (file)
@@ -164,6 +164,13 @@ pub trait HirDatabase: DefDatabase + AstDatabase {
     #[salsa::invoke(crate::ty::callable_item_sig)]
     fn callable_item_signature(&self, def: CallableDef) -> FnSig;
 
+    #[salsa::invoke(crate::ty::generic_predicates_for_param_query)]
+    fn generic_predicates_for_param(
+        &self,
+        def: GenericDef,
+        param_idx: u32,
+    ) -> Arc<[GenericPredicate]>;
+
     #[salsa::invoke(crate::ty::generic_predicates_query)]
     fn generic_predicates(&self, def: GenericDef) -> Arc<[GenericPredicate]>;
 
index 77fb76bfcc1c55815a09ebcc98444a665615c651..ccb7774920091259b7eac5d5cc0b652c58cd5291 100644 (file)
@@ -26,8 +26,9 @@ pub struct GenericParam {
 }
 
 /// Data about the generic parameters of a function, struct, impl, etc.
-#[derive(Clone, PartialEq, Eq, Debug, Default)]
+#[derive(Clone, PartialEq, Eq, Debug)]
 pub struct GenericParams {
+    pub(crate) def: GenericDef,
     pub(crate) parent_params: Option<Arc<GenericParams>>,
     pub(crate) params: Vec<GenericParam>,
     pub(crate) where_predicates: Vec<WherePredicate>,
@@ -69,7 +70,6 @@ pub(crate) fn generic_params_query(
         db: &(impl DefDatabase + AstDatabase),
         def: GenericDef,
     ) -> Arc<GenericParams> {
-        let mut generics = GenericParams::default();
         let parent = match def {
             GenericDef::Function(it) => it.container(db).map(GenericDef::from),
             GenericDef::TypeAlias(it) => it.container(db).map(GenericDef::from),
@@ -77,7 +77,12 @@ pub(crate) fn generic_params_query(
             GenericDef::Adt(_) | GenericDef::Trait(_) => None,
             GenericDef::ImplBlock(_) => None,
         };
-        generics.parent_params = parent.map(|p| db.generic_params(p));
+        let mut generics = GenericParams {
+            def,
+            params: Vec::new(),
+            parent_params: parent.map(|p| db.generic_params(p)),
+            where_predicates: Vec::new(),
+        };
         let start = generics.parent_params.as_ref().map(|p| p.params.len()).unwrap_or(0) as u32;
         // FIXME: add `: Sized` bound for everything except for `Self` in traits
         match def {
index 254d1a9640e2c765bdd6f8881b3adf8836df134d..39f8e1d8a519659e3c681d7d21c5566cf76b27e6 100644 (file)
@@ -344,6 +344,13 @@ pub(crate) fn where_predicates_in_scope<'a>(
             })
             .flat_map(|params| params.where_predicates.iter())
     }
+
+    pub(crate) fn generic_def(&self) -> Option<crate::generics::GenericDef> {
+        self.scopes.iter().find_map(|scope| match scope {
+            Scope::GenericParams(params) => Some(params.def),
+            _ => None,
+        })
+    }
 }
 
 impl Resolver {
index a223e120a722bea40c36720db723478ef9d04d0c..36bfb10cef61f3045382195a437c40368b41dfc1 100644 (file)
@@ -23,8 +23,8 @@
 pub(crate) use infer::{infer_query, InferTy, InferenceResult};
 pub use lower::CallableDef;
 pub(crate) use lower::{
-    callable_item_sig, generic_defaults_query, generic_predicates_query, type_for_def,
-    type_for_field, TypableDef,
+    callable_item_sig, generic_defaults_query, generic_predicates_for_param_query,
+    generic_predicates_query, type_for_def, type_for_field, TypableDef,
 };
 pub(crate) use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment};
 
index a83842b0f3ae0e56ac4bae068f54d769dd42ee12..8d71abc95d2103fd50aeb51f79980c258147410f 100644 (file)
@@ -86,6 +86,35 @@ pub(crate) fn from_hir(db: &impl HirDatabase, resolver: &Resolver, type_ref: &Ty
         }
     }
 
+    /// This is only for `generic_predicates_for_param`, where we can't just
+    /// lower the self types of the predicates since that could lead to cycles.
+    /// So we just check here if the `type_ref` resolves to a generic param, and which.
+    fn from_hir_only_param(
+        db: &impl HirDatabase,
+        resolver: &Resolver,
+        type_ref: &TypeRef,
+    ) -> Option<u32> {
+        let path = match type_ref {
+            TypeRef::Path(path) => path,
+            _ => return None,
+        };
+        if let crate::PathKind::Type(_) = &path.kind {
+            return None;
+        }
+        if path.segments.len() > 1 {
+            return None;
+        }
+        let resolution = match resolver.resolve_path_in_type_ns(db, path) {
+            Some((it, None)) => it,
+            _ => return None,
+        };
+        if let TypeNs::GenericParam(idx) = resolution {
+            Some(idx)
+        } else {
+            None
+        }
+    }
+
     pub(crate) fn from_type_relative_path(
         db: &impl HirDatabase,
         resolver: &Resolver,
@@ -189,11 +218,37 @@ pub(crate) fn from_hir_path(db: &impl HirDatabase, resolver: &Resolver, path: &P
     }
 
     fn select_associated_type(
-        _db: &impl HirDatabase,
-        _resolver: &Resolver,
-        _self_ty: Ty,
-        _segment: &PathSegment,
+        db: &impl HirDatabase,
+        resolver: &Resolver,
+        self_ty: Ty,
+        segment: &PathSegment,
     ) -> Ty {
+        let param_idx = match self_ty {
+            Ty::Param { idx, .. } => idx,
+            _ => return Ty::Unknown, // Error: Ambiguous associated type
+        };
+        let def = match resolver.generic_def() {
+            Some(def) => def,
+            None => return Ty::Unknown, // this can't actually happen
+        };
+        let predicates = db.generic_predicates_for_param(def, param_idx);
+        let traits_from_env = predicates.iter().filter_map(|pred| match pred {
+            GenericPredicate::Implemented(tr) if tr.self_ty() == &self_ty => Some(tr.trait_),
+            _ => None,
+        });
+        let traits = traits_from_env.flat_map(|t| t.all_super_traits(db));
+        for t in traits {
+            if let Some(associated_ty) = t.associated_type_by_name(db, &segment.name) {
+                let generics = t.generic_params(db);
+                let mut substs = Vec::new();
+                substs.push(self_ty.clone());
+                substs.extend(
+                    iter::repeat(Ty::Unknown).take(generics.count_params_including_parent() - 1),
+                );
+                // FIXME handle type parameters on the segment
+                return Ty::Projection(ProjectionTy { associated_ty, parameters: substs.into() });
+            }
+        }
         Ty::Unknown
     }
 
@@ -269,9 +324,10 @@ pub(super) fn substs_from_path_segment(
     add_self_param: bool,
 ) -> Substs {
     let mut substs = Vec::new();
-    let def_generics = def_generic.map(|def| def.generic_params(db)).unwrap_or_default();
+    let def_generics = def_generic.map(|def| def.generic_params(db));
 
-    let parent_param_count = def_generics.count_parent_params();
+    let (parent_param_count, param_count) =
+        def_generics.map_or((0, 0), |g| (g.count_parent_params(), g.params.len()));
     substs.extend(iter::repeat(Ty::Unknown).take(parent_param_count));
     if add_self_param {
         // FIXME this add_self_param argument is kind of a hack: Traits have the
@@ -283,7 +339,7 @@ pub(super) fn substs_from_path_segment(
     if let Some(generic_args) = &segment.args_and_bindings {
         // if args are provided, it should be all of them, but we can't rely on that
         let self_param_correction = if add_self_param { 1 } else { 0 };
-        let param_count = def_generics.params.len() - self_param_correction;
+        let param_count = param_count - self_param_correction;
         for arg in generic_args.args.iter().take(param_count) {
             match arg {
                 GenericArg::Type(type_ref) => {
@@ -295,10 +351,10 @@ pub(super) fn substs_from_path_segment(
     }
     // add placeholders for args that were not provided
     let supplied_params = substs.len();
-    for _ in supplied_params..def_generics.count_params_including_parent() {
+    for _ in supplied_params..parent_param_count + param_count {
         substs.push(Ty::Unknown);
     }
-    assert_eq!(substs.len(), def_generics.count_params_including_parent());
+    assert_eq!(substs.len(), parent_param_count + param_count);
 
     // handle defaults
     if let Some(def_generic) = def_generic {
@@ -491,6 +547,29 @@ pub(crate) fn type_for_field(db: &impl HirDatabase, field: StructField) -> Ty {
     Ty::from_hir(db, &resolver, type_ref)
 }
 
+/// This query exists only to be used when resolving short-hand associated types
+/// like `T::Item`.
+///
+/// See the analogous query in rustc and its comment:
+/// https://github.com/rust-lang/rust/blob/9150f844e2624eb013ec78ca08c1d416e6644026/src/librustc_typeck/astconv.rs#L46
+/// This is a query mostly to handle cycles somewhat gracefully; e.g. the
+/// following bounds are disallowed: `T: Foo<U::Item>, U: Foo<T::Item>`, but
+/// these are fine: `T: Foo<U::Item>, U: Foo<()>`.
+pub(crate) fn generic_predicates_for_param_query(
+    db: &impl HirDatabase,
+    def: GenericDef,
+    param_idx: u32,
+) -> Arc<[GenericPredicate]> {
+    let resolver = def.resolver(db);
+    let predicates = resolver
+        .where_predicates_in_scope()
+        // we have to filter out all other predicates *first*, before attempting to lower them
+        .filter(|pred| Ty::from_hir_only_param(db, &resolver, &pred.type_ref) == Some(param_idx))
+        .flat_map(|pred| GenericPredicate::from_where_predicate(db, &resolver, pred))
+        .collect::<Vec<_>>();
+    predicates.into()
+}
+
 pub(crate) fn trait_env(
     db: &impl HirDatabase,
     resolver: &Resolver,
index 3b0a994603447724b17c55778246df3c481eafc6..3ac1fbdd50df364144b79e12714e04b5c5ac0a6c 100644 (file)
@@ -2740,17 +2740,17 @@ fn test() {
     [202; 203) 't': T
     [221; 223) '{}': ()
     [234; 300) '{     ...(S); }': ()
-    [244; 245) 'x': {unknown}
-    [248; 252) 'foo1': fn foo1<S>(T) -> {unknown}
-    [248; 255) 'foo1(S)': {unknown}
+    [244; 245) 'x': u32
+    [248; 252) 'foo1': fn foo1<S>(T) -> <T as Iterable>::Item
+    [248; 255) 'foo1(S)': u32
     [253; 254) 'S': S
     [265; 266) 'y': u32
     [269; 273) 'foo2': fn foo2<S>(T) -> <T as Iterable>::Item
     [269; 276) 'foo2(S)': u32
     [274; 275) 'S': S
-    [286; 287) 'z': {unknown}
-    [290; 294) 'foo3': fn foo3<S>(T) -> {unknown}
-    [290; 297) 'foo3(S)': {unknown}
+    [286; 287) 'z': u32
+    [290; 294) 'foo3': fn foo3<S>(T) -> <T as Iterable>::Item
+    [290; 297) 'foo3(S)': u32
     [295; 296) 'S': S
     "###
     );
@@ -4080,7 +4080,7 @@ fn test<F: FnOnce(u32) -> u64>(f: F) {
 }
 
 #[test]
-fn unselected_projection_in_trait_env() {
+fn unselected_projection_in_trait_env_1() {
     let t = type_at(
         r#"
 //- /main.rs
@@ -4102,7 +4102,33 @@ fn test<T: Trait>() where T::Item: Trait2 {
 }
 
 #[test]
-fn unselected_projection_in_trait_env_cycle() {
+fn unselected_projection_in_trait_env_2() {
+    let t = type_at(
+        r#"
+//- /main.rs
+trait Trait<T> {
+    type Item;
+}
+
+trait Trait2 {
+    fn foo(&self) -> u32;
+}
+
+fn test<T, U>() where T::Item: Trait2, T: Trait<U::Item>, U: Trait<()> {
+    let x: T::Item = no_matter;
+    x.foo()<|>;
+}
+"#,
+    );
+    assert_eq!(t, "u32");
+}
+
+#[test]
+// FIXME this is currently a Salsa panic; it would be nicer if it just returned
+// in Unknown, and we should be able to do that once Salsa allows us to handle
+// the cycle. But at least it doesn't overflow for now.
+#[should_panic]
+fn unselected_projection_in_trait_env_cycle_1() {
     let t = type_at(
         r#"
 //- /main.rs
@@ -4121,6 +4147,28 @@ fn test<T: Trait>() where T: Trait2<T::Item> {
     assert_eq!(t, "{unknown}");
 }
 
+#[test]
+// FIXME this is currently a Salsa panic; it would be nicer if it just returned
+// in Unknown, and we should be able to do that once Salsa allows us to handle
+// the cycle. But at least it doesn't overflow for now.
+#[should_panic]
+fn unselected_projection_in_trait_env_cycle_2() {
+    let t = type_at(
+        r#"
+//- /main.rs
+trait Trait<T> {
+    type Item;
+}
+
+fn test<T, U>() where T: Trait<U::Item>, U: Trait<T::Item> {
+    let x: T::Item = no_matter<|>;
+}
+"#,
+    );
+    // this is a legitimate cycle
+    assert_eq!(t, "{unknown}");
+}
+
 fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String {
     let file = db.parse(pos.file_id).ok().unwrap();
     let expr = algo::find_node_at_offset::<ast::Expr>(file.syntax(), pos.offset).unwrap();