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

Issue:
Type mismatch on &str
```rust
fn _foo(s: String) {
    match s.as_str() {
        "foo" => {}
        _ => {}
    }
}
```

This reports `Expected str, got &str`. Probably a known issue.

Pull request description:
Fix string literal inference in match
Fixes #5020 

r? @flodiebold 

Patch:
diff --git a/crates/ra_hir_ty/src/infer/pat.rs b/crates/ra_hir_ty/src/infer/pat.rs
index 4006f595d1..23de2bd6b0 100644
--- a/crates/ra_hir_ty/src/infer/pat.rs
+++ b/crates/ra_hir_ty/src/infer/pat.rs
@@ -4,7 +4,7 @@ use std::iter::repeat;
 use std::sync::Arc;
 
 use hir_def::{
-    expr::{BindingAnnotation, Pat, PatId, RecordFieldPat},
+    expr::{BindingAnnotation, Expr, Literal, Pat, PatId, RecordFieldPat},
     path::Path,
     type_ref::Mutability,
     FieldId,
@@ -90,18 +90,7 @@ impl<'a> InferenceContext<'a> {
     ) -> Ty {
         let body = Arc::clone(&self.body); // avoid borrow checker problem
 
-        let is_non_ref_pat = match &body[pat] {
-            Pat::Tuple { .. }
-            | Pat::Or(..)
-            | Pat::TupleStruct { .. }
-            | Pat::Record { .. }
-            | Pat::Range { .. }
-            | Pat::Slice { .. } => true,
-            // FIXME: Path/Lit might actually evaluate to ref, but inference is unimplemented.
-            Pat::Path(..) | Pat::Lit(..) => true,
-            Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Missing => false,
-        };
-        if is_non_ref_pat {
+        if is_non_ref_pat(&body, pat) {
             while let Some((inner, mutability)) = expected.as_reference() {
                 expected = inner;
                 default_bm = match default_bm {
@@ -227,3 +216,21 @@ impl<'a> InferenceContext<'a> {
         ty
     }
 }
+
+fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool {
+    match &body[pat] {
+        Pat::Tuple { .. }
+        | Pat::TupleStruct { .. }
+        | Pat::Record { .. }
+        | Pat::Range { .. }
+        | Pat::Slice { .. } => true,
+        Pat::Or(pats) => pats.iter().all(|p| is_non_ref_pat(body, *p)),
+        // FIXME: Path/Lit might actually evaluate to ref, but inference is unimplemented.
+        Pat::Path(..) => true,
+        Pat::Lit(expr) => match body[*expr] {
+            Expr::Literal(Literal::String(..)) => false,
+            _ => true,
+        },
+        Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Missing => false,
+    }
+}
diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs
index b7325bfc3c..e99c2b0a33 100644
--- a/crates/ra_ide/src/mock_analysis.rs
+++ b/crates/ra_ide/src/mock_analysis.rs
@@ -79,6 +79,9 @@ pub struct MockAnalysis {
 }
 
 impl MockAnalysis {
+    pub fn new() -> MockAnalysis {
+        MockAnalysis::default()
+    }
     /// Creates `MockAnalysis` using a fixture data in the following format:
     ///
     /// ```not_rust
@@ -90,7 +93,7 @@ impl MockAnalysis {
     /// struct Baz;
     /// ```
     pub fn with_files(fixture: &str) -> MockAnalysis {
-        let mut res = MockAnalysis::default();
+        let mut res = MockAnalysis::new();
         for entry in Fixture::parse(fixture) {
             res.add_file_fixture(entry);
         }
@@ -101,14 +104,11 @@ impl MockAnalysis {
     /// whose position is also returned.
     pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) {
         let mut position = None;
-        let mut res = MockAnalysis::default();
-        for mut entry in Fixture::parse(fixture) {
+        let mut res = MockAnalysis::new();
+        for entry in Fixture::parse(fixture) {
             if entry.text.contains(CURSOR_MARKER) {
                 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed");
-                let (offset, text) = extract_offset(&entry.text);
-                entry.text = text;
-                let file_id = res.add_file_fixture(entry);
-                position = Some(FilePosition { file_id, offset });
+                position = Some(res.add_file_fixture_with_position(entry));
             } else {
                 res.add_file_fixture(entry);
             }
@@ -123,6 +123,19 @@ impl MockAnalysis {
         file_id
     }
 
+    fn add_file_fixture_with_position(&mut self, mut fixture: Fixture) -> FilePosition {
+        let (offset, text) = extract_offset(&fixture.text);
+        fixture.text = text;
+        let file_id = self.next_id();
+        self.files.push(MockFileData::from(fixture));
+        FilePosition { file_id, offset }
+    }
+
+    pub fn add_file(&mut self, path: &str, text: &str) -> FileId {
+        let file_id = self.next_id();
+        self.files.push(MockFileData::new(path.to_string(), text.to_string()));
+        file_id
+    }
     fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange {
         let (range, text) = extract_range(text);
         let file_id = self.next_id();
@@ -210,7 +223,7 @@ pub fn single_file(ra_fixture: &str) -> (Analysis, FileId) {
 
 /// Creates analysis for a single file, returns range marked with a pair of <|>.
 pub fn single_file_with_range(ra_fixture: &str) -> (Analysis, FileRange) {
-    let mut mock = MockAnalysis::default();
+    let mut mock = MockAnalysis::new();
     let pos = mock.add_file_with_range("/main.rs", ra_fixture);
     (mock.analysis(), pos)
 }


Test patch:
diff --git a/crates/ra_hir_ty/src/tests/patterns.rs b/crates/ra_hir_ty/src/tests/patterns.rs
index 8fa2961377..e5ef241cae 100644
--- a/crates/ra_hir_ty/src/tests/patterns.rs
+++ b/crates/ra_hir_ty/src/tests/patterns.rs
@@ -270,6 +270,63 @@ fn test() {
     );
 }
 
+#[test]
+fn infer_pattern_match_string_literal() {
+    assert_snapshot!(
+        infer_with_mismatches(r#"
+fn test() {
+    let s: &str = "hello";
+    match s {
+        "hello" => {}
+        _ => {}
+    }
+}
+"#, true),
+    @r###"
+    10..98 '{     ...   } }': ()
+    20..21 's': &str
+    30..37 '"hello"': &str
+    43..96 'match ...     }': ()
+    49..50 's': &str
+    61..68 '"hello"': &str
+    61..68 '"hello"': &str
+    72..74 '{}': ()
+    83..84 '_': &str
+    88..90 '{}': ()
+    "###
+    );
+}
+
+#[test]
+fn infer_pattern_match_or() {
+    assert_snapshot!(
+        infer_with_mismatches(r#"
+fn test() {
+    let s: &str = "hello";
+    match s {
+        "hello" | "world" => {}
+        _ => {}
+    }
+}
+"#, true),
+    @r###"
+    10..108 '{     ...   } }': ()
+    20..21 's': &str
+    30..37 '"hello"': &str
+    43..106 'match ...     }': ()
+    49..50 's': &str
+    61..68 '"hello"': &str
+    61..68 '"hello"': &str
+    61..78 '"hello...world"': &str
+    71..78 '"world"': &str
+    71..78 '"world"': &str
+    82..84 '{}': ()
+    93..94 '_': &str
+    98..100 '{}': ()
+    "###
+    );
+}
+
 #[test]
 fn infer_pattern_match_arr() {
     assert_snapshot!(
diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs
index b7fad97197..c8943816f3 100644
--- a/crates/ra_ide/src/syntax_highlighting/tests.rs
+++ b/crates/ra_ide/src/syntax_highlighting/tests.rs
@@ -2,7 +2,10 @@ use std::fs;
 
 use test_utils::{assert_eq_text, project_dir, read_text};
 
-use crate::{mock_analysis::single_file, FileRange, TextRange};
+use crate::{
+    mock_analysis::{single_file, MockAnalysis},
+    FileRange, TextRange,
+};
 
 #[test]
 fn test_highlighting() {
@@ -124,10 +127,12 @@ fn accidentally_quadratic() {
     let file = project_dir().join("crates/ra_syntax/test_data/accidentally_quadratic");
     let src = fs::read_to_string(file).unwrap();
 
-    let (analysis, file_id) = single_file(&src);
+    let mut mock = MockAnalysis::new();
+    let file_id = mock.add_file("/main.rs", &src);
+    let host = mock.analysis_host();
 
     // let t = std::time::Instant::now();
-    let _ = analysis.highlight(file_id).unwrap();
+    let _ = host.analysis().highlight(file_id).unwrap();
     // eprintln!("elapsed: {:?}", t.elapsed());
 }