Skip to content

Commit e7485a0

Browse files
committed
Diagnose unresolved method calls
1 parent 78b2dd8 commit e7485a0

File tree

8 files changed

+320
-48
lines changed

8 files changed

+320
-48
lines changed

crates/hir-ty/src/infer.rs

+55-8
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,45 @@ pub(crate) type InferResult<T> = Result<InferOk<T>, TypeError>;
164164

165165
#[derive(Debug, PartialEq, Eq, Clone)]
166166
pub enum InferenceDiagnostic {
167-
NoSuchField { expr: ExprId },
168-
PrivateField { expr: ExprId, field: FieldId },
169-
PrivateAssocItem { id: ExprOrPatId, item: AssocItemId },
170-
UnresolvedField { expr: ExprId, receiver: Ty, name: Name, method_with_same_name_exists: bool },
167+
NoSuchField {
168+
expr: ExprId,
169+
},
170+
PrivateField {
171+
expr: ExprId,
172+
field: FieldId,
173+
},
174+
PrivateAssocItem {
175+
id: ExprOrPatId,
176+
item: AssocItemId,
177+
},
178+
UnresolvedField {
179+
expr: ExprId,
180+
receiver: Ty,
181+
name: Name,
182+
method_with_same_name_exists: bool,
183+
},
184+
UnresolvedMethodCall {
185+
expr: ExprId,
186+
receiver: Ty,
187+
name: Name,
188+
/// Contains the type the field resolves to
189+
field_with_same_name: Option<Ty>,
190+
},
171191
// FIXME: Make this proper
172-
BreakOutsideOfLoop { expr: ExprId, is_break: bool, bad_value_break: bool },
173-
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
174-
ExpectedFunction { call_expr: ExprId, found: Ty },
192+
BreakOutsideOfLoop {
193+
expr: ExprId,
194+
is_break: bool,
195+
bad_value_break: bool,
196+
},
197+
MismatchedArgCount {
198+
call_expr: ExprId,
199+
expected: usize,
200+
found: usize,
201+
},
202+
ExpectedFunction {
203+
call_expr: ExprId,
204+
found: Ty,
205+
},
175206
}
176207

177208
/// A mismatch between an expected and an inferred type.
@@ -509,12 +540,28 @@ impl<'a> InferenceContext<'a> {
509540
}
510541
result.diagnostics.retain_mut(|diagnostic| {
511542
if let InferenceDiagnostic::ExpectedFunction { found: ty, .. }
512-
| InferenceDiagnostic::UnresolvedField { receiver: ty, .. } = diagnostic
543+
| InferenceDiagnostic::UnresolvedField { receiver: ty, .. }
544+
| InferenceDiagnostic::UnresolvedMethodCall { receiver: ty, .. } = diagnostic
513545
{
514546
*ty = table.resolve_completely(ty.clone());
547+
// FIXME: Remove this when we are on par with rustc in terms of inference
515548
if ty.is_unknown() {
516549
return false;
517550
}
551+
552+
if let InferenceDiagnostic::UnresolvedMethodCall { field_with_same_name, .. } =
553+
diagnostic
554+
{
555+
let clear = if let Some(ty) = field_with_same_name {
556+
*ty = table.resolve_completely(ty.clone());
557+
ty.is_unknown()
558+
} else {
559+
false
560+
};
561+
if clear {
562+
*field_with_same_name = None;
563+
}
564+
}
518565
}
519566
true
520567
});

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

+83-38
Original file line numberDiff line numberDiff line change
@@ -1212,12 +1212,14 @@ impl<'a> InferenceContext<'a> {
12121212
}
12131213
}
12141214

1215-
fn infer_field_access(&mut self, tgt_expr: ExprId, expr: ExprId, name: &Name) -> Ty {
1216-
let receiver_ty = self.infer_expr_inner(expr, &Expectation::none());
1217-
1215+
fn lookup_field(
1216+
&mut self,
1217+
receiver_ty: &Ty,
1218+
name: &Name,
1219+
) -> Option<(Ty, Option<FieldId>, Vec<Adjustment>, bool)> {
12181220
let mut autoderef = Autoderef::new(&mut self.table, receiver_ty.clone());
12191221
let mut private_field = None;
1220-
let ty = autoderef.by_ref().find_map(|(derefed_ty, _)| {
1222+
let res = autoderef.by_ref().find_map(|(derefed_ty, _)| {
12211223
let (field_id, parameters) = match derefed_ty.kind(Interner) {
12221224
TyKind::Tuple(_, substs) => {
12231225
return name.as_tuple_index().and_then(|idx| {
@@ -1226,6 +1228,7 @@ impl<'a> InferenceContext<'a> {
12261228
.get(idx)
12271229
.map(|a| a.assert_ty_ref(Interner))
12281230
.cloned()
1231+
.map(|ty| (None, ty))
12291232
});
12301233
}
12311234
TyKind::Adt(AdtId(hir_def::AdtId::StructId(s)), parameters) => {
@@ -1244,58 +1247,81 @@ impl<'a> InferenceContext<'a> {
12441247
.is_visible_from(self.db.upcast(), self.resolver.module());
12451248
if !is_visible {
12461249
if private_field.is_none() {
1247-
private_field = Some(field_id);
1250+
private_field = Some((field_id, parameters));
12481251
}
12491252
return None;
12501253
}
1251-
// can't have `write_field_resolution` here because `self.table` is borrowed :(
1252-
self.result.field_resolutions.insert(tgt_expr, field_id);
12531254
let ty = self.db.field_types(field_id.parent)[field_id.local_id]
12541255
.clone()
12551256
.substitute(Interner, &parameters);
1256-
Some(ty)
1257+
Some((Some(field_id), ty))
12571258
});
1258-
let ty = match ty {
1259-
Some(ty) => {
1259+
1260+
Some(match res {
1261+
Some((field_id, ty)) => {
1262+
let adjustments = auto_deref_adjust_steps(&autoderef);
1263+
let ty = self.insert_type_vars(ty);
1264+
let ty = self.normalize_associated_types_in(ty);
1265+
1266+
(ty, field_id, adjustments, true)
1267+
}
1268+
None => {
1269+
let (field_id, subst) = private_field?;
12601270
let adjustments = auto_deref_adjust_steps(&autoderef);
1261-
self.write_expr_adj(expr, adjustments);
1271+
let ty = self.db.field_types(field_id.parent)[field_id.local_id]
1272+
.clone()
1273+
.substitute(Interner, &subst);
12621274
let ty = self.insert_type_vars(ty);
12631275
let ty = self.normalize_associated_types_in(ty);
1276+
1277+
(ty, Some(field_id), adjustments, false)
1278+
}
1279+
})
1280+
}
1281+
1282+
fn infer_field_access(&mut self, tgt_expr: ExprId, receiver: ExprId, name: &Name) -> Ty {
1283+
let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none());
1284+
match self.lookup_field(&receiver_ty, name) {
1285+
Some((ty, field_id, adjustments, is_public)) => {
1286+
self.write_expr_adj(receiver, adjustments);
1287+
if let Some(field_id) = field_id {
1288+
self.result.field_resolutions.insert(tgt_expr, field_id);
1289+
}
1290+
if !is_public {
1291+
if let Some(field) = field_id {
1292+
// FIXME: Merge this diagnostic into UnresolvedField?
1293+
self.result
1294+
.diagnostics
1295+
.push(InferenceDiagnostic::PrivateField { expr: tgt_expr, field });
1296+
}
1297+
}
12641298
ty
12651299
}
1266-
_ => {
1267-
// Write down the first private field resolution if we found no field
1268-
// This aids IDE features for private fields like goto def
1269-
if let Some(field) = private_field {
1270-
self.result.field_resolutions.insert(tgt_expr, field);
1271-
// FIXME: Merge this diagnostic into UnresolvedField
1272-
self.result
1273-
.diagnostics
1274-
.push(InferenceDiagnostic::PrivateField { expr: tgt_expr, field });
1275-
} else {
1276-
// no field found, try looking for a method of the same name
1300+
None => {
1301+
// no field found,
1302+
let method_with_same_name_exists = {
12771303
let canonicalized_receiver = self.canonicalize(receiver_ty.clone());
12781304
let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast());
12791305

1280-
let resolved = method_resolution::lookup_method(
1306+
method_resolution::lookup_method(
12811307
self.db,
12821308
&canonicalized_receiver.value,
12831309
self.trait_env.clone(),
12841310
&traits_in_scope,
12851311
VisibleFromModule::Filter(self.resolver.module()),
12861312
name,
1287-
);
1288-
self.result.diagnostics.push(InferenceDiagnostic::UnresolvedField {
1289-
expr: tgt_expr,
1290-
receiver: receiver_ty,
1291-
name: name.clone(),
1292-
method_with_same_name_exists: resolved.is_some(),
1293-
});
1294-
}
1313+
)
1314+
.is_some()
1315+
};
1316+
self.result.diagnostics.push(InferenceDiagnostic::UnresolvedField {
1317+
expr: tgt_expr,
1318+
receiver: receiver_ty,
1319+
name: name.clone(),
1320+
method_with_same_name_exists,
1321+
});
12951322
self.err_ty()
12961323
}
1297-
};
1298-
ty
1324+
}
12991325
}
13001326

13011327
fn infer_method_call(
@@ -1335,11 +1361,30 @@ impl<'a> InferenceContext<'a> {
13351361
}
13361362
(ty, self.db.value_ty(func.into()), substs)
13371363
}
1338-
None => (
1339-
receiver_ty,
1340-
Binders::empty(Interner, self.err_ty()),
1341-
Substitution::empty(Interner),
1342-
),
1364+
None => {
1365+
let field_with_same_name_exists = match self.lookup_field(&receiver_ty, method_name)
1366+
{
1367+
Some((ty, field_id, adjustments, _public)) => {
1368+
self.write_expr_adj(receiver, adjustments);
1369+
if let Some(field_id) = field_id {
1370+
self.result.field_resolutions.insert(tgt_expr, field_id);
1371+
}
1372+
Some(ty)
1373+
}
1374+
None => None,
1375+
};
1376+
self.result.diagnostics.push(InferenceDiagnostic::UnresolvedMethodCall {
1377+
expr: tgt_expr,
1378+
receiver: receiver_ty.clone(),
1379+
name: method_name.clone(),
1380+
field_with_same_name: field_with_same_name_exists,
1381+
});
1382+
(
1383+
receiver_ty,
1384+
Binders::empty(Interner, self.err_ty()),
1385+
Substitution::empty(Interner),
1386+
)
1387+
}
13431388
};
13441389
let method_ty = method_ty.substitute(Interner, &substs);
13451390
self.register_obligations_for_call(&method_ty);

crates/hir/src/diagnostics.rs

+9
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ diagnostics![
5151
UnresolvedField,
5252
UnresolvedImport,
5353
UnresolvedMacroCall,
54+
UnresolvedMethodCall,
5455
UnresolvedModule,
5556
UnresolvedProcMacro,
5657
];
@@ -146,6 +147,14 @@ pub struct UnresolvedField {
146147
pub method_with_same_name_exists: bool,
147148
}
148149

150+
#[derive(Debug)]
151+
pub struct UnresolvedMethodCall {
152+
pub expr: InFile<AstPtr<ast::Expr>>,
153+
pub receiver: Type,
154+
pub name: Name,
155+
pub field_with_same_name: Option<Type>,
156+
}
157+
149158
#[derive(Debug)]
150159
pub struct PrivateField {
151160
pub expr: InFile<AstPtr<ast::Expr>>,

crates/hir/src/lib.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pub use crate::{
8989
MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField,
9090
ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
9191
UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall,
92-
UnresolvedModule, UnresolvedProcMacro,
92+
UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro,
9393
},
9494
has_source::HasSource,
9595
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
@@ -1441,6 +1441,26 @@ impl DefWithBody {
14411441
.into(),
14421442
)
14431443
}
1444+
hir_ty::InferenceDiagnostic::UnresolvedMethodCall {
1445+
expr,
1446+
receiver,
1447+
name,
1448+
field_with_same_name,
1449+
} => {
1450+
let expr = expr_syntax(*expr);
1451+
1452+
acc.push(
1453+
UnresolvedMethodCall {
1454+
expr,
1455+
name: name.clone(),
1456+
receiver: Type::new(db, DefWithBodyId::from(self), receiver.clone()),
1457+
field_with_same_name: field_with_same_name
1458+
.clone()
1459+
.map(|ty| Type::new(db, DefWithBodyId::from(self), ty)),
1460+
}
1461+
.into(),
1462+
)
1463+
}
14441464
}
14451465
}
14461466
for (pat_or_expr, mismatch) in infer.type_mismatches() {

crates/ide-db/src/source_change.rs

+8
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ impl From<NoHashHashMap<FileId, TextEdit>> for SourceChange {
8383
}
8484
}
8585

86+
impl FromIterator<(FileId, TextEdit)> for SourceChange {
87+
fn from_iter<T: IntoIterator<Item = (FileId, TextEdit)>>(iter: T) -> Self {
88+
let mut this = SourceChange::default();
89+
this.extend(iter);
90+
this
91+
}
92+
}
93+
8694
pub struct SourceChangeBuilder {
8795
pub edit: TextEditBuilder,
8896
pub file_id: FileId,

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

+12-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,18 @@ fn fixes(
5555

5656
#[cfg(test)]
5757
mod tests {
58-
use crate::tests::{check_diagnostics, check_fix};
58+
use crate::{
59+
tests::{check_diagnostics_with_config, check_fix},
60+
DiagnosticsConfig,
61+
};
62+
63+
#[track_caller]
64+
pub(crate) fn check_diagnostics(ra_fixture: &str) {
65+
let mut config = DiagnosticsConfig::test_sample();
66+
config.disabled.insert("inactive-code".to_string());
67+
config.disabled.insert("unresolved-method".to_string());
68+
check_diagnostics_with_config(config, ra_fixture)
69+
}
5970

6071
#[test]
6172
fn replace_filter_map_next_with_find_map2() {

0 commit comments

Comments
 (0)