Skip to content

Commit fc2b395

Browse files
committed
Show pattern mismatch diagnostics
1 parent 9b441b9 commit fc2b395

File tree

8 files changed

+117
-71
lines changed

8 files changed

+117
-71
lines changed

crates/hir-expand/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,15 @@ impl<T> InFile<Option<T>> {
771771
}
772772
}
773773

774+
impl<L, R> InFile<Either<L, R>> {
775+
pub fn transpose(self) -> Either<InFile<L>, InFile<R>> {
776+
match self.value {
777+
Either::Left(l) => Either::Left(InFile::new(self.file_id, l)),
778+
Either::Right(r) => Either::Right(InFile::new(self.file_id, r)),
779+
}
780+
}
781+
}
782+
774783
impl<'a> InFile<&'a SyntaxNode> {
775784
pub fn ancestors_with_macros(
776785
self,

crates/hir-ty/src/infer.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -389,18 +389,15 @@ impl InferenceResult {
389389
pub fn type_mismatch_for_pat(&self, pat: PatId) -> Option<&TypeMismatch> {
390390
self.type_mismatches.get(&pat.into())
391391
}
392+
pub fn type_mismatches(&self) -> impl Iterator<Item = (ExprOrPatId, &TypeMismatch)> {
393+
self.type_mismatches.iter().map(|(expr_or_pat, mismatch)| (*expr_or_pat, mismatch))
394+
}
392395
pub fn expr_type_mismatches(&self) -> impl Iterator<Item = (ExprId, &TypeMismatch)> {
393396
self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat {
394397
ExprOrPatId::ExprId(expr) => Some((expr, mismatch)),
395398
_ => None,
396399
})
397400
}
398-
pub fn pat_type_mismatches(&self) -> impl Iterator<Item = (PatId, &TypeMismatch)> {
399-
self.type_mismatches.iter().filter_map(|(expr_or_pat, mismatch)| match *expr_or_pat {
400-
ExprOrPatId::PatId(pat) => Some((pat, mismatch)),
401-
_ => None,
402-
})
403-
}
404401
}
405402

406403
impl Index<ExprId> for InferenceResult {

crates/hir-ty/src/infer/pat.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,7 @@ impl<'a> InferenceContext<'a> {
196196
Pat::Ref { pat, mutability } => {
197197
let mutability = lower_to_chalk_mutability(*mutability);
198198
let expectation = match expected.as_reference() {
199-
Some((inner_ty, _lifetime, exp_mut)) => {
200-
if mutability != exp_mut {
201-
// FIXME: emit type error?
202-
}
203-
inner_ty.clone()
204-
}
199+
Some((inner_ty, _lifetime, exp_mut)) => inner_ty.clone(),
205200
_ => self.result.standard_types.unknown.clone(),
206201
};
207202
let subty = self.infer_pat(*pat, &expectation, default_bm);

crates/hir-ty/src/tests.rs

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -191,30 +191,11 @@ fn check_impl(ra_fixture: &str, allow_none: bool, only_types: bool, display_sour
191191
}
192192
}
193193

194-
for (pat, mismatch) in inference_result.pat_type_mismatches() {
195-
let node = match pat_node(&body_source_map, pat, &db) {
196-
Some(value) => value,
197-
None => continue,
198-
};
199-
let range = node.as_ref().original_file_range(&db);
200-
let actual = format!(
201-
"expected {}, got {}",
202-
mismatch.expected.display_test(&db),
203-
mismatch.actual.display_test(&db)
204-
);
205-
match mismatches.remove(&range) {
206-
Some(annotation) => assert_eq!(actual, annotation),
207-
None => format_to!(unexpected_type_mismatches, "{:?}: {}\n", range.range, actual),
208-
}
209-
}
210-
for (expr, mismatch) in inference_result.expr_type_mismatches() {
211-
let node = match body_source_map.expr_syntax(expr) {
212-
Ok(sp) => {
213-
let root = db.parse_or_expand(sp.file_id).unwrap();
214-
sp.map(|ptr| ptr.to_node(&root).syntax().clone())
215-
}
216-
Err(SyntheticSyntax) => continue,
217-
};
194+
for (expr_or_pat, mismatch) in inference_result.type_mismatches() {
195+
let Some(node) = (match expr_or_pat {
196+
hir_def::expr::ExprOrPatId::ExprId(expr) => expr_node(&body_source_map, expr, &db),
197+
hir_def::expr::ExprOrPatId::PatId(pat) => pat_node(&body_source_map, pat, &db),
198+
}) else { continue; };
218199
let range = node.as_ref().original_file_range(&db);
219200
let actual = format!(
220201
"expected {}, got {}",

crates/hir-ty/src/tests/patterns.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1092,3 +1092,19 @@ fn my_fn(foo: ...) {}
10921092
"#,
10931093
);
10941094
}
1095+
1096+
#[test]
1097+
fn ref_pat_mutability() {
1098+
check(
1099+
r#"
1100+
fn foo() {
1101+
let &() = &();
1102+
let &mut () = &mut ();
1103+
let &mut () = &();
1104+
//^^^^^^^ expected &(), got &mut ()
1105+
let &() = &mut ();
1106+
//^^^ expected &mut (), got &()
1107+
}
1108+
"#,
1109+
);
1110+
}

crates/hir/src/diagnostics.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,7 @@ pub struct MissingMatchArms {
178178

179179
#[derive(Debug)]
180180
pub struct TypeMismatch {
181-
// FIXME: add mismatches in patterns as well
182-
pub expr: InFile<AstPtr<ast::Expr>>,
181+
pub expr_or_pat: Either<InFile<AstPtr<ast::Expr>>, InFile<AstPtr<ast::Pat>>>,
183182
pub expected: Type,
184183
pub actual: Type,
185184
}

crates/hir/src/lib.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1413,14 +1413,22 @@ impl DefWithBody {
14131413
}
14141414
}
14151415
}
1416-
for (expr, mismatch) in infer.expr_type_mismatches() {
1417-
let expr = match source_map.expr_syntax(expr) {
1418-
Ok(expr) => expr,
1419-
Err(SyntheticSyntax) => continue,
1416+
for (pat_or_expr, mismatch) in infer.type_mismatches() {
1417+
let expr_or_pat = match pat_or_expr {
1418+
ExprOrPatId::ExprId(expr) => source_map.expr_syntax(expr).map(Either::Left),
1419+
ExprOrPatId::PatId(pat) => source_map.pat_syntax(pat).map(Either::Right),
14201420
};
1421+
let expr_or_pat = match expr_or_pat {
1422+
Ok(Either::Left(expr)) => Either::Left(expr),
1423+
Ok(Either::Right(InFile { file_id, value: Either::Left(pat) })) => {
1424+
Either::Right(InFile { file_id, value: pat })
1425+
}
1426+
Ok(Either::Right(_)) | Err(SyntheticSyntax) => continue,
1427+
};
1428+
14211429
acc.push(
14221430
TypeMismatch {
1423-
expr,
1431+
expr_or_pat,
14241432
expected: Type::new(db, DefWithBodyId::from(self), mismatch.expected.clone()),
14251433
actual: Type::new(db, DefWithBodyId::from(self), mismatch.actual.clone()),
14261434
}

crates/ide-diagnostics/src/handlers/type_mismatch.rs

Lines changed: 69 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
1-
use hir::{db::AstDatabase, HirDisplay, Type};
1+
use either::Either;
2+
use hir::{db::AstDatabase, HirDisplay, InFile, Type};
23
use ide_db::{famous_defs::FamousDefs, source_change::SourceChange};
34
use syntax::{
45
ast::{self, BlockExpr, ExprStmt},
5-
AstNode,
6+
AstNode, AstPtr,
67
};
78
use text_edit::TextEdit;
89

910
use crate::{adjusted_display_range, fix, Assist, Diagnostic, DiagnosticsContext};
1011

1112
// Diagnostic: type-mismatch
1213
//
13-
// This diagnostic is triggered when the type of an expression does not match
14+
// This diagnostic is triggered when the type of an expression or pattern does not match
1415
// the expected type.
1516
pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic {
16-
let display_range = adjusted_display_range::<ast::BlockExpr>(
17-
ctx,
18-
d.expr.clone().map(|it| it.into()),
19-
&|block| {
20-
let r_curly_range = block.stmt_list()?.r_curly_token()?.text_range();
21-
cov_mark::hit!(type_mismatch_on_block);
22-
Some(r_curly_range)
23-
},
24-
);
25-
17+
let display_range = match &d.expr_or_pat {
18+
Either::Left(expr) => adjusted_display_range::<ast::BlockExpr>(
19+
ctx,
20+
expr.clone().map(|it| it.into()),
21+
&|block| {
22+
let r_curly_range = block.stmt_list()?.r_curly_token()?.text_range();
23+
cov_mark::hit!(type_mismatch_on_block);
24+
Some(r_curly_range)
25+
},
26+
),
27+
Either::Right(pat) => {
28+
ctx.sema.diagnostics_display_range(pat.clone().map(|it| it.into())).range
29+
}
30+
};
2631
let mut diag = Diagnostic::new(
2732
"type-mismatch",
2833
format!(
@@ -42,24 +47,38 @@ pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch)
4247
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option<Vec<Assist>> {
4348
let mut fixes = Vec::new();
4449

45-
add_reference(ctx, d, &mut fixes);
46-
add_missing_ok_or_some(ctx, d, &mut fixes);
47-
remove_semicolon(ctx, d, &mut fixes);
48-
str_ref_to_owned(ctx, d, &mut fixes);
50+
match &d.expr_or_pat {
51+
Either::Left(expr_ptr) => {
52+
add_reference(ctx, d, expr_ptr, &mut fixes);
53+
add_missing_ok_or_some(ctx, d, expr_ptr, &mut fixes);
54+
remove_semicolon(ctx, d, expr_ptr, &mut fixes);
55+
str_ref_to_owned(ctx, d, expr_ptr, &mut fixes);
56+
}
57+
Either::Right(_pat_ptr) => (),
58+
}
4959

5060
if fixes.is_empty() {
5161
None
5262
} else {
5363
Some(fixes)
5464
}
5565
}
66+
fn add_reference_pat(
67+
ctx: &DiagnosticsContext<'_>,
68+
d: &hir::TypeMismatch,
69+
expr_ptr: &InFile<AstPtr<ast::Pat>>,
70+
acc: &mut Vec<Assist>,
71+
) -> Option<()> {
72+
None
73+
}
5674

5775
fn add_reference(
5876
ctx: &DiagnosticsContext<'_>,
5977
d: &hir::TypeMismatch,
78+
expr_ptr: &InFile<AstPtr<ast::Expr>>,
6079
acc: &mut Vec<Assist>,
6180
) -> Option<()> {
62-
let range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range;
81+
let range = ctx.sema.diagnostics_display_range(expr_ptr.clone().map(|it| it.into())).range;
6382

6483
let (_, mutability) = d.expected.as_reference()?;
6584
let actual_with_ref = Type::reference(&d.actual, mutability);
@@ -71,18 +90,19 @@ fn add_reference(
7190

7291
let edit = TextEdit::insert(range.start(), ampersands);
7392
let source_change =
74-
SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
93+
SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit);
7594
acc.push(fix("add_reference_here", "Add reference here", source_change, range));
7695
Some(())
7796
}
7897

7998
fn add_missing_ok_or_some(
8099
ctx: &DiagnosticsContext<'_>,
81100
d: &hir::TypeMismatch,
101+
expr_ptr: &InFile<AstPtr<ast::Expr>>,
82102
acc: &mut Vec<Assist>,
83103
) -> Option<()> {
84-
let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
85-
let expr = d.expr.value.to_node(&root);
104+
let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?;
105+
let expr = expr_ptr.value.to_node(&root);
86106
let expr_range = expr.syntax().text_range();
87107
let scope = ctx.sema.scope(expr.syntax())?;
88108

@@ -109,7 +129,7 @@ fn add_missing_ok_or_some(
109129
builder.insert(expr.syntax().text_range().start(), format!("{variant_name}("));
110130
builder.insert(expr.syntax().text_range().end(), ")".to_string());
111131
let source_change =
112-
SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), builder.finish());
132+
SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), builder.finish());
113133
let name = format!("Wrap in {variant_name}");
114134
acc.push(fix("wrap_in_constructor", &name, source_change, expr_range));
115135
Some(())
@@ -118,10 +138,11 @@ fn add_missing_ok_or_some(
118138
fn remove_semicolon(
119139
ctx: &DiagnosticsContext<'_>,
120140
d: &hir::TypeMismatch,
141+
expr_ptr: &InFile<AstPtr<ast::Expr>>,
121142
acc: &mut Vec<Assist>,
122143
) -> Option<()> {
123-
let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
124-
let expr = d.expr.value.to_node(&root);
144+
let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?;
145+
let expr = expr_ptr.value.to_node(&root);
125146
if !d.actual.is_unit() {
126147
return None;
127148
}
@@ -136,7 +157,7 @@ fn remove_semicolon(
136157

137158
let edit = TextEdit::delete(semicolon_range);
138159
let source_change =
139-
SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
160+
SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit);
140161

141162
acc.push(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon_range));
142163
Some(())
@@ -145,24 +166,26 @@ fn remove_semicolon(
145166
fn str_ref_to_owned(
146167
ctx: &DiagnosticsContext<'_>,
147168
d: &hir::TypeMismatch,
169+
expr_ptr: &InFile<AstPtr<ast::Expr>>,
148170
acc: &mut Vec<Assist>,
149171
) -> Option<()> {
150172
let expected = d.expected.display(ctx.sema.db);
151173
let actual = d.actual.display(ctx.sema.db);
152174

175+
// FIXME do this properly
153176
if expected.to_string() != "String" || actual.to_string() != "&str" {
154177
return None;
155178
}
156179

157-
let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
158-
let expr = d.expr.value.to_node(&root);
180+
let root = ctx.sema.db.parse_or_expand(expr_ptr.file_id)?;
181+
let expr = expr_ptr.value.to_node(&root);
159182
let expr_range = expr.syntax().text_range();
160183

161184
let to_owned = format!(".to_owned()");
162185

163186
let edit = TextEdit::insert(expr.syntax().text_range().end(), to_owned);
164187
let source_change =
165-
SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
188+
SourceChange::from_text_edit(expr_ptr.file_id.original_file(ctx.sema.db), edit);
166189
acc.push(fix("str_ref_to_owned", "Add .to_owned() here", source_change, expr_range));
167190

168191
Some(())
@@ -592,6 +615,24 @@ fn f() -> i32 {
592615
let _ = x + y;
593616
}
594617
//^ error: expected i32, found ()
618+
"#,
619+
);
620+
}
621+
622+
#[test]
623+
fn type_mismatch_pat_smoke_test() {
624+
check_diagnostics(
625+
r#"
626+
fn f() {
627+
let &() = &mut ();
628+
//^^^ error: expected &mut (), found &()
629+
match &() {
630+
&9 => ()
631+
//^^ error: expected &(), found &i32
632+
//^ error: expected (), found i32
633+
//^ error: expected (), found i32
634+
}
635+
}
595636
"#,
596637
);
597638
}

0 commit comments

Comments
 (0)