Skip to content

Commit ab3313b

Browse files
committed
Add new type-mismatch diagnostic
1 parent 2d30dd6 commit ab3313b

File tree

7 files changed

+151
-81
lines changed

7 files changed

+151
-81
lines changed

crates/hir/src/diagnostics.rs

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ macro_rules! diagnostics {
2828
}
2929

3030
diagnostics![
31-
AddReferenceHere,
3231
BreakOutsideOfLoop,
3332
InactiveCode,
3433
IncorrectCase,
@@ -38,11 +37,10 @@ diagnostics![
3837
MismatchedArgCount,
3938
MissingFields,
4039
MissingMatchArms,
41-
MissingOkOrSomeInTailExpr,
4240
MissingUnsafe,
4341
NoSuchField,
44-
RemoveThisSemicolon,
4542
ReplaceFilterMapNextWithFindMap,
43+
TypeMismatch,
4644
UnimplementedBuiltinMacro,
4745
UnresolvedExternCrate,
4846
UnresolvedImport,
@@ -147,29 +145,17 @@ pub struct MismatchedArgCount {
147145
pub found: usize,
148146
}
149147

150-
#[derive(Debug)]
151-
pub struct RemoveThisSemicolon {
152-
pub expr: InFile<AstPtr<ast::Expr>>,
153-
}
154-
155-
#[derive(Debug)]
156-
pub struct MissingOkOrSomeInTailExpr {
157-
pub expr: InFile<AstPtr<ast::Expr>>,
158-
// `Some` or `Ok` depending on whether the return type is Result or Option
159-
pub required: String,
160-
pub expected: Type,
161-
}
162-
163148
#[derive(Debug)]
164149
pub struct MissingMatchArms {
165150
pub file: HirFileId,
166151
pub match_expr: AstPtr<ast::Expr>,
167152
}
168153

169154
#[derive(Debug)]
170-
pub struct AddReferenceHere {
155+
pub struct TypeMismatch {
171156
pub expr: InFile<AstPtr<ast::Expr>>,
172-
pub mutability: Mutability,
157+
pub expected: Type,
158+
pub actual: Type,
173159
}
174160

175161
pub use hir_ty::diagnostics::IncorrectCase;

crates/hir/src/lib.rs

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,11 @@ use crate::db::{DefDatabase, HirDatabase};
8585
pub use crate::{
8686
attrs::{HasAttrs, Namespace},
8787
diagnostics::{
88-
AddReferenceHere, AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase,
89-
InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields,
90-
MissingMatchArms, MissingOkOrSomeInTailExpr, MissingUnsafe, NoSuchField,
91-
RemoveThisSemicolon, ReplaceFilterMapNextWithFindMap, UnimplementedBuiltinMacro,
92-
UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule,
93-
UnresolvedProcMacro,
88+
AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget,
89+
MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms,
90+
MissingUnsafe, NoSuchField, ReplaceFilterMapNextWithFindMap, TypeMismatch,
91+
UnimplementedBuiltinMacro, UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall,
92+
UnresolvedModule, UnresolvedProcMacro,
9493
},
9594
has_source::HasSource,
9695
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo},
@@ -1163,6 +1162,28 @@ impl DefWithBody {
11631162
}
11641163
}
11651164
}
1165+
for (expr, mismatch) in infer.expr_type_mismatches() {
1166+
let expr =
1167+
source_map.expr_syntax(expr).expect("break outside of loop in synthetic syntax");
1168+
acc.push(
1169+
TypeMismatch {
1170+
expr,
1171+
expected: Type::new(
1172+
db,
1173+
krate,
1174+
DefWithBodyId::from(self),
1175+
mismatch.expected.clone(),
1176+
),
1177+
actual: Type::new(
1178+
db,
1179+
krate,
1180+
DefWithBodyId::from(self),
1181+
mismatch.actual.clone(),
1182+
),
1183+
}
1184+
.into(),
1185+
);
1186+
}
11661187

11671188
for expr in hir_ty::diagnostics::missing_unsafe(db, self.into()) {
11681189
match source_map.expr_syntax(expr) {
@@ -1259,25 +1280,6 @@ impl DefWithBody {
12591280
Err(SyntheticSyntax) => (),
12601281
}
12611282
}
1262-
BodyValidationDiagnostic::RemoveThisSemicolon { expr } => {
1263-
match source_map.expr_syntax(expr) {
1264-
Ok(expr) => acc.push(RemoveThisSemicolon { expr }.into()),
1265-
Err(SyntheticSyntax) => (),
1266-
}
1267-
}
1268-
BodyValidationDiagnostic::MissingOkOrSomeInTailExpr { expr, required } => {
1269-
match source_map.expr_syntax(expr) {
1270-
Ok(expr) => acc.push(
1271-
MissingOkOrSomeInTailExpr {
1272-
expr,
1273-
required,
1274-
expected: self.body_type(db),
1275-
}
1276-
.into(),
1277-
),
1278-
Err(SyntheticSyntax) => (),
1279-
}
1280-
}
12811283
BodyValidationDiagnostic::MissingMatchArms { match_expr } => {
12821284
match source_map.expr_syntax(match_expr) {
12831285
Ok(source_ptr) => {
@@ -1299,12 +1301,7 @@ impl DefWithBody {
12991301
Err(SyntheticSyntax) => (),
13001302
}
13011303
}
1302-
BodyValidationDiagnostic::AddReferenceHere { arg_expr, mutability } => {
1303-
match source_map.expr_syntax(arg_expr) {
1304-
Ok(expr) => acc.push(AddReferenceHere { expr, mutability }.into()),
1305-
Err(SyntheticSyntax) => (),
1306-
}
1307-
}
1304+
_ => {} // TODO fixme
13081305
}
13091306
}
13101307

@@ -2618,6 +2615,14 @@ impl Type {
26182615
Type { krate, env: environment, ty }
26192616
}
26202617

2618+
pub fn reference(inner: &Type, m: Mutability) -> Type {
2619+
inner.derived(TyKind::Ref(
2620+
if m.is_mut() { hir_ty::Mutability::Mut } else { hir_ty::Mutability::Not },
2621+
hir_ty::static_lifetime(),
2622+
inner.ty.clone(),
2623+
).intern(Interner))
2624+
}
2625+
26212626
fn new(db: &dyn HirDatabase, krate: CrateId, lexical_env: impl HasResolver, ty: Ty) -> Type {
26222627
let resolver = lexical_env.resolver(db.upcast());
26232628
let environment = resolver
@@ -2659,6 +2664,12 @@ impl Type {
26592664
matches!(self.ty.kind(Interner), TyKind::Ref(..))
26602665
}
26612666

2667+
pub fn as_reference(&self) -> Option<(Type, Mutability)> {
2668+
let (ty, _lt, m) = self.ty.as_reference()?;
2669+
let m = Mutability::from_mutable(matches!(m, hir_ty::Mutability::Mut));
2670+
Some((self.derived(ty.clone()), m))
2671+
}
2672+
26622673
pub fn is_slice(&self) -> bool {
26632674
matches!(self.ty.kind(Interner), TyKind::Slice(..))
26642675
}

crates/hir_def/src/type_ref.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,22 @@ impl Mutability {
3838
Mutability::Mut => "mut ",
3939
}
4040
}
41+
42+
/// Returns `true` if the mutability is [`Mut`].
43+
///
44+
/// [`Mut`]: Mutability::Mut
45+
#[must_use]
46+
pub fn is_mut(&self) -> bool {
47+
matches!(self, Self::Mut)
48+
}
49+
50+
/// Returns `true` if the mutability is [`Shared`].
51+
///
52+
/// [`Shared`]: Mutability::Shared
53+
#[must_use]
54+
pub fn is_shared(&self) -> bool {
55+
matches!(self, Self::Shared)
56+
}
4157
}
4258

4359
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]

crates/ide_diagnostics/src/handlers/field_shorthand.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,13 +108,13 @@ mod tests {
108108
check_diagnostics(
109109
r#"
110110
struct A { a: &'static str }
111-
fn main() { A { a: "hello" } }
111+
fn main() { A { a: "hello" }; }
112112
"#,
113113
);
114114
check_diagnostics(
115115
r#"
116116
struct A(usize);
117-
fn main() { A { 0: 0 } }
117+
fn main() { A { 0: 0 }; }
118118
"#,
119119
);
120120

@@ -123,14 +123,14 @@ fn main() { A { 0: 0 } }
123123
struct A { a: &'static str }
124124
fn main() {
125125
let a = "haha";
126-
A { a$0: a }
126+
A { a$0: a };
127127
}
128128
"#,
129129
r#"
130130
struct A { a: &'static str }
131131
fn main() {
132132
let a = "haha";
133-
A { a }
133+
A { a };
134134
}
135135
"#,
136136
);
@@ -141,15 +141,15 @@ struct A { a: &'static str, b: &'static str }
141141
fn main() {
142142
let a = "haha";
143143
let b = "bb";
144-
A { a$0: a, b }
144+
A { a$0: a, b };
145145
}
146146
"#,
147147
r#"
148148
struct A { a: &'static str, b: &'static str }
149149
fn main() {
150150
let a = "haha";
151151
let b = "bb";
152-
A { a, b }
152+
A { a, b };
153153
}
154154
"#,
155155
);

crates/ide_diagnostics/src/handlers/missing_match_arms.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ fn main() {
278278
match (true, false) {
279279
(true, false, true) => (),
280280
(true) => (),
281+
// ^^^^ error: expected (bool, bool), found bool
281282
}
282283
match (true, false) { (true,) => {} }
283284
match (0) { () => () }

crates/ide_diagnostics/src/handlers/add_reference_here.rs renamed to crates/ide_diagnostics/src/handlers/type_mismatch.rs

Lines changed: 77 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,62 @@
1-
use hir::db::AstDatabase;
1+
use hir::{db::AstDatabase, HirDisplay, Type};
22
use ide_db::source_change::SourceChange;
3-
use syntax::AstNode;
3+
use syntax::{AstNode, TextRange};
44
use text_edit::TextEdit;
55

66
use crate::{fix, Assist, Diagnostic, DiagnosticsContext};
77

8-
// Diagnostic: add-reference-here
8+
// Diagnostic: type-mismatch
99
//
10-
// This diagnostic is triggered when there's a missing referencing of expression.
11-
pub(crate) fn add_reference_here(
12-
ctx: &DiagnosticsContext<'_>,
13-
d: &hir::AddReferenceHere,
14-
) -> Diagnostic {
15-
Diagnostic::new(
16-
"add-reference-here",
17-
"add reference here",
10+
// This diagnostic is triggered when the type of an expression does not match
11+
// the expected type.
12+
pub(crate) fn type_mismatch(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Diagnostic {
13+
let mut diag = Diagnostic::new(
14+
"type-mismatch",
15+
format!(
16+
"expected {}, found {}",
17+
d.expected.display(ctx.sema.db),
18+
d.actual.display(ctx.sema.db)
19+
),
1820
ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range,
1921
)
20-
.with_fixes(fixes(ctx, d))
22+
.with_fixes(fixes(ctx, d));
23+
if diag.fixes.is_none() {
24+
diag.experimental = true;
25+
}
26+
diag
2127
}
2228

23-
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::AddReferenceHere) -> Option<Vec<Assist>> {
29+
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch) -> Option<Vec<Assist>> {
30+
let mut fixes = Vec::new();
31+
32+
add_reference(ctx, d, &mut fixes);
33+
34+
if fixes.is_empty() {
35+
None
36+
} else {
37+
Some(fixes)
38+
}
39+
}
40+
41+
fn add_reference(ctx: &DiagnosticsContext<'_>, d: &hir::TypeMismatch, acc: &mut Vec<Assist>) -> Option<()> {
2442
let root = ctx.sema.db.parse_or_expand(d.expr.file_id)?;
25-
let arg_expr = d.expr.value.to_node(&root);
43+
let expr_node = d.expr.value.to_node(&root);
44+
45+
let range = ctx.sema.diagnostics_display_range(d.expr.clone().map(|it| it.into())).range;
2646

27-
let arg_with_ref = format!("&{}{}", d.mutability.as_keyword_for_ref(), arg_expr.syntax());
47+
let (_, mutability) = d.expected.as_reference()?;
48+
let actual_with_ref = Type::reference(&d.actual, mutability);
49+
if !actual_with_ref.could_coerce_to(ctx.sema.db, &d.expected) {
50+
return None;
51+
}
52+
53+
let ampersands = format!("&{}", mutability.as_keyword_for_ref());
2854

29-
let arg_range = arg_expr.syntax().text_range();
30-
let edit = TextEdit::replace(arg_range, arg_with_ref);
55+
let edit = TextEdit::insert(expr_node.syntax().text_range().start(), ampersands);
3156
let source_change =
3257
SourceChange::from_text_edit(d.expr.file_id.original_file(ctx.sema.db), edit);
33-
34-
Some(vec![fix("add_reference_here", "Add reference here", source_change, arg_range)])
58+
acc.push(fix("add_reference_here", "Add reference here", source_change, range));
59+
Some(())
3560
}
3661

3762
#[cfg(test)]
@@ -44,7 +69,7 @@ mod tests {
4469
r#"
4570
fn main() {
4671
test(123);
47-
//^^^ 💡 error: add reference here
72+
//^^^ 💡 error: expected &i32, found i32
4873
}
4974
fn test(arg: &i32) {}
5075
"#,
@@ -91,6 +116,7 @@ fn test(arg: &mut i32) {}
91116
fn test_add_reference_to_array() {
92117
check_fix(
93118
r#"
119+
//- minicore: coerce_unsized
94120
fn main() {
95121
test($0[1, 2, 3]);
96122
}
@@ -105,6 +131,37 @@ fn test(arg: &[i32]) {}
105131
);
106132
}
107133

134+
#[test]
135+
fn test_add_reference_with_autoderef() {
136+
check_fix(
137+
r#"
138+
//- minicore: coerce_unsized, deref
139+
struct Foo;
140+
struct Bar;
141+
impl core::ops::Deref for Foo {
142+
type Target = Bar;
143+
}
144+
145+
fn main() {
146+
test($0Foo);
147+
}
148+
fn test(arg: &Bar) {}
149+
"#,
150+
r#"
151+
struct Foo;
152+
struct Bar;
153+
impl core::ops::Deref for Foo {
154+
type Target = Bar;
155+
}
156+
157+
fn main() {
158+
test(&Foo);
159+
}
160+
fn test(arg: &Bar) {}
161+
"#,
162+
);
163+
}
164+
108165
#[test]
109166
fn test_add_reference_to_method_call() {
110167
check_fix(

0 commit comments

Comments
 (0)