Skip to content

Commit 78b2dd8

Browse files
committed
Diagnose unresolved field accesses
1 parent 3c7a0aa commit 78b2dd8

File tree

7 files changed

+273
-94
lines changed

7 files changed

+273
-94
lines changed

crates/hir-ty/src/infer.rs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use hir_def::{
3131
AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, FunctionId, HasModule,
3232
ItemContainerId, Lookup, TraitId, TypeAliasId, VariantId,
3333
};
34-
use hir_expand::name::name;
34+
use hir_expand::name::{name, Name};
3535
use la_arena::ArenaMap;
3636
use rustc_hash::FxHashMap;
3737
use stdx::always;
@@ -167,6 +167,7 @@ pub enum InferenceDiagnostic {
167167
NoSuchField { expr: ExprId },
168168
PrivateField { expr: ExprId, field: FieldId },
169169
PrivateAssocItem { id: ExprOrPatId, item: AssocItemId },
170+
UnresolvedField { expr: ExprId, receiver: Ty, name: Name, method_with_same_name_exists: bool },
170171
// FIXME: Make this proper
171172
BreakOutsideOfLoop { expr: ExprId, is_break: bool, bad_value_break: bool },
172173
MismatchedArgCount { call_expr: ExprId, expected: usize, found: usize },
@@ -506,14 +507,17 @@ impl<'a> InferenceContext<'a> {
506507
mismatch.expected = table.resolve_completely(mismatch.expected.clone());
507508
mismatch.actual = table.resolve_completely(mismatch.actual.clone());
508509
}
509-
for diagnostic in &mut result.diagnostics {
510-
match diagnostic {
511-
InferenceDiagnostic::ExpectedFunction { found, .. } => {
512-
*found = table.resolve_completely(found.clone())
510+
result.diagnostics.retain_mut(|diagnostic| {
511+
if let InferenceDiagnostic::ExpectedFunction { found: ty, .. }
512+
| InferenceDiagnostic::UnresolvedField { receiver: ty, .. } = diagnostic
513+
{
514+
*ty = table.resolve_completely(ty.clone());
515+
if ty.is_unknown() {
516+
return false;
513517
}
514-
_ => (),
515518
}
516-
}
519+
true
520+
});
517521
for (_, subst) in result.method_resolutions.values_mut() {
518522
*subst = table.resolve_completely(subst.clone());
519523
}

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

Lines changed: 87 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -552,71 +552,7 @@ impl<'a> InferenceContext<'a> {
552552
}
553553
ty
554554
}
555-
Expr::Field { expr, name } => {
556-
let receiver_ty = self.infer_expr_inner(*expr, &Expectation::none());
557-
558-
let mut autoderef = Autoderef::new(&mut self.table, receiver_ty);
559-
let mut private_field = None;
560-
let ty = autoderef.by_ref().find_map(|(derefed_ty, _)| {
561-
let (field_id, parameters) = match derefed_ty.kind(Interner) {
562-
TyKind::Tuple(_, substs) => {
563-
return name.as_tuple_index().and_then(|idx| {
564-
substs
565-
.as_slice(Interner)
566-
.get(idx)
567-
.map(|a| a.assert_ty_ref(Interner))
568-
.cloned()
569-
});
570-
}
571-
TyKind::Adt(AdtId(hir_def::AdtId::StructId(s)), parameters) => {
572-
let local_id = self.db.struct_data(*s).variant_data.field(name)?;
573-
let field = FieldId { parent: (*s).into(), local_id };
574-
(field, parameters.clone())
575-
}
576-
TyKind::Adt(AdtId(hir_def::AdtId::UnionId(u)), parameters) => {
577-
let local_id = self.db.union_data(*u).variant_data.field(name)?;
578-
let field = FieldId { parent: (*u).into(), local_id };
579-
(field, parameters.clone())
580-
}
581-
_ => return None,
582-
};
583-
let is_visible = self.db.field_visibilities(field_id.parent)[field_id.local_id]
584-
.is_visible_from(self.db.upcast(), self.resolver.module());
585-
if !is_visible {
586-
if private_field.is_none() {
587-
private_field = Some(field_id);
588-
}
589-
return None;
590-
}
591-
// can't have `write_field_resolution` here because `self.table` is borrowed :(
592-
self.result.field_resolutions.insert(tgt_expr, field_id);
593-
let ty = self.db.field_types(field_id.parent)[field_id.local_id]
594-
.clone()
595-
.substitute(Interner, &parameters);
596-
Some(ty)
597-
});
598-
let ty = match ty {
599-
Some(ty) => {
600-
let adjustments = auto_deref_adjust_steps(&autoderef);
601-
self.write_expr_adj(*expr, adjustments);
602-
let ty = self.insert_type_vars(ty);
603-
let ty = self.normalize_associated_types_in(ty);
604-
ty
605-
}
606-
_ => {
607-
// Write down the first private field resolution if we found no field
608-
// This aids IDE features for private fields like goto def
609-
if let Some(field) = private_field {
610-
self.result.field_resolutions.insert(tgt_expr, field);
611-
self.result
612-
.diagnostics
613-
.push(InferenceDiagnostic::PrivateField { expr: tgt_expr, field });
614-
}
615-
self.err_ty()
616-
}
617-
};
618-
ty
619-
}
555+
Expr::Field { expr, name } => self.infer_field_access(tgt_expr, *expr, name),
620556
Expr::Await { expr } => {
621557
let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
622558
self.resolve_associated_type(inner_ty, self.resolve_future_future_output())
@@ -1276,6 +1212,92 @@ impl<'a> InferenceContext<'a> {
12761212
}
12771213
}
12781214

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+
1218+
let mut autoderef = Autoderef::new(&mut self.table, receiver_ty.clone());
1219+
let mut private_field = None;
1220+
let ty = autoderef.by_ref().find_map(|(derefed_ty, _)| {
1221+
let (field_id, parameters) = match derefed_ty.kind(Interner) {
1222+
TyKind::Tuple(_, substs) => {
1223+
return name.as_tuple_index().and_then(|idx| {
1224+
substs
1225+
.as_slice(Interner)
1226+
.get(idx)
1227+
.map(|a| a.assert_ty_ref(Interner))
1228+
.cloned()
1229+
});
1230+
}
1231+
TyKind::Adt(AdtId(hir_def::AdtId::StructId(s)), parameters) => {
1232+
let local_id = self.db.struct_data(*s).variant_data.field(name)?;
1233+
let field = FieldId { parent: (*s).into(), local_id };
1234+
(field, parameters.clone())
1235+
}
1236+
TyKind::Adt(AdtId(hir_def::AdtId::UnionId(u)), parameters) => {
1237+
let local_id = self.db.union_data(*u).variant_data.field(name)?;
1238+
let field = FieldId { parent: (*u).into(), local_id };
1239+
(field, parameters.clone())
1240+
}
1241+
_ => return None,
1242+
};
1243+
let is_visible = self.db.field_visibilities(field_id.parent)[field_id.local_id]
1244+
.is_visible_from(self.db.upcast(), self.resolver.module());
1245+
if !is_visible {
1246+
if private_field.is_none() {
1247+
private_field = Some(field_id);
1248+
}
1249+
return None;
1250+
}
1251+
// can't have `write_field_resolution` here because `self.table` is borrowed :(
1252+
self.result.field_resolutions.insert(tgt_expr, field_id);
1253+
let ty = self.db.field_types(field_id.parent)[field_id.local_id]
1254+
.clone()
1255+
.substitute(Interner, &parameters);
1256+
Some(ty)
1257+
});
1258+
let ty = match ty {
1259+
Some(ty) => {
1260+
let adjustments = auto_deref_adjust_steps(&autoderef);
1261+
self.write_expr_adj(expr, adjustments);
1262+
let ty = self.insert_type_vars(ty);
1263+
let ty = self.normalize_associated_types_in(ty);
1264+
ty
1265+
}
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
1277+
let canonicalized_receiver = self.canonicalize(receiver_ty.clone());
1278+
let traits_in_scope = self.resolver.traits_in_scope(self.db.upcast());
1279+
1280+
let resolved = method_resolution::lookup_method(
1281+
self.db,
1282+
&canonicalized_receiver.value,
1283+
self.trait_env.clone(),
1284+
&traits_in_scope,
1285+
VisibleFromModule::Filter(self.resolver.module()),
1286+
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+
}
1295+
self.err_ty()
1296+
}
1297+
};
1298+
ty
1299+
}
1300+
12791301
fn infer_method_call(
12801302
&mut self,
12811303
tgt_expr: ExprId,

crates/hir/src/diagnostics.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ diagnostics![
4848
TypeMismatch,
4949
UnimplementedBuiltinMacro,
5050
UnresolvedExternCrate,
51+
UnresolvedField,
5152
UnresolvedImport,
5253
UnresolvedMacroCall,
5354
UnresolvedModule,
@@ -137,6 +138,14 @@ pub struct ExpectedFunction {
137138
pub found: Type,
138139
}
139140

141+
#[derive(Debug)]
142+
pub struct UnresolvedField {
143+
pub expr: InFile<AstPtr<ast::Expr>>,
144+
pub receiver: Type,
145+
pub name: Name,
146+
pub method_with_same_name_exists: bool,
147+
}
148+
140149
#[derive(Debug)]
141150
pub struct PrivateField {
142151
pub expr: InFile<AstPtr<ast::Expr>>,

crates/hir/src/lib.rs

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,8 @@ pub use crate::{
8888
InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields,
8989
MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField,
9090
ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
91-
UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule,
92-
UnresolvedProcMacro,
91+
UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall,
92+
UnresolvedModule, UnresolvedProcMacro,
9393
},
9494
has_source::HasSource,
9595
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
@@ -1375,6 +1375,7 @@ impl DefWithBody {
13751375

13761376
let infer = db.infer(self.into());
13771377
let source_map = Lazy::new(|| db.body_with_source_map(self.into()).1);
1378+
let expr_syntax = |expr| source_map.expr_syntax(expr).expect("unexpected synthetic");
13781379
for d in &infer.diagnostics {
13791380
match d {
13801381
&hir_ty::InferenceDiagnostic::NoSuchField { expr } => {
@@ -1386,30 +1387,23 @@ impl DefWithBody {
13861387
is_break,
13871388
bad_value_break,
13881389
} => {
1389-
let expr = source_map
1390-
.expr_syntax(expr)
1391-
.expect("break outside of loop in synthetic syntax");
1390+
let expr = expr_syntax(expr);
13921391
acc.push(BreakOutsideOfLoop { expr, is_break, bad_value_break }.into())
13931392
}
13941393
&hir_ty::InferenceDiagnostic::MismatchedArgCount { call_expr, expected, found } => {
1395-
match source_map.expr_syntax(call_expr) {
1396-
Ok(source_ptr) => acc.push(
1397-
MismatchedArgCount { call_expr: source_ptr, expected, found }.into(),
1398-
),
1399-
Err(SyntheticSyntax) => (),
1400-
}
1394+
acc.push(
1395+
MismatchedArgCount { call_expr: expr_syntax(call_expr), expected, found }
1396+
.into(),
1397+
)
14011398
}
14021399
&hir_ty::InferenceDiagnostic::PrivateField { expr, field } => {
1403-
let expr = source_map.expr_syntax(expr).expect("unexpected synthetic");
1400+
let expr = expr_syntax(expr);
14041401
let field = field.into();
14051402
acc.push(PrivateField { expr, field }.into())
14061403
}
14071404
&hir_ty::InferenceDiagnostic::PrivateAssocItem { id, item } => {
14081405
let expr_or_pat = match id {
1409-
ExprOrPatId::ExprId(expr) => source_map
1410-
.expr_syntax(expr)
1411-
.expect("unexpected synthetic")
1412-
.map(Either::Left),
1406+
ExprOrPatId::ExprId(expr) => expr_syntax(expr).map(Either::Left),
14131407
ExprOrPatId::PatId(pat) => source_map
14141408
.pat_syntax(pat)
14151409
.expect("unexpected synthetic")
@@ -1419,8 +1413,7 @@ impl DefWithBody {
14191413
acc.push(PrivateAssocItem { expr_or_pat, item }.into())
14201414
}
14211415
hir_ty::InferenceDiagnostic::ExpectedFunction { call_expr, found } => {
1422-
let call_expr =
1423-
source_map.expr_syntax(*call_expr).expect("unexpected synthetic");
1416+
let call_expr = expr_syntax(*call_expr);
14241417

14251418
acc.push(
14261419
ExpectedFunction {
@@ -1430,6 +1423,24 @@ impl DefWithBody {
14301423
.into(),
14311424
)
14321425
}
1426+
hir_ty::InferenceDiagnostic::UnresolvedField {
1427+
expr,
1428+
receiver,
1429+
name,
1430+
method_with_same_name_exists,
1431+
} => {
1432+
let expr = expr_syntax(*expr);
1433+
1434+
acc.push(
1435+
UnresolvedField {
1436+
expr,
1437+
name: name.clone(),
1438+
receiver: Type::new(db, DefWithBodyId::from(self), receiver.clone()),
1439+
method_with_same_name_exists: *method_with_same_name_exists,
1440+
}
1441+
.into(),
1442+
)
1443+
}
14331444
}
14341445
}
14351446
for (pat_or_expr, mismatch) in infer.type_mismatches() {

crates/ide-completion/src/completions/flyimport.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@ use ide_db::imports::{
55
insert_use::ImportScope,
66
};
77
use itertools::Itertools;
8-
use syntax::{
9-
ast::{self},
10-
AstNode, SyntaxNode, T,
11-
};
8+
use syntax::{ast, AstNode, SyntaxNode, T};
129

1310
use crate::{
1411
context::{

0 commit comments

Comments
 (0)