From 7bb279b365e54ee0051e09ead5aa157ff6be917b Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Sun, 6 Jan 2019 19:51:42 +0100 Subject: Implement autoderef for field accesses --- crates/ra_analysis/src/completion/complete_dot.rs | 57 +++++++++++------ crates/ra_hir/src/db.rs | 2 +- crates/ra_hir/src/ty.rs | 72 +++++++++++++++------- crates/ra_hir/src/ty/autoderef.rs | 21 +++++++ crates/ra_hir/src/ty/tests.rs | 33 +++++++++- .../ra_hir/src/ty/tests/data/field_autoderef.txt | 43 +++++++++++++ 6 files changed, 185 insertions(+), 43 deletions(-) create mode 100644 crates/ra_hir/src/ty/autoderef.rs create mode 100644 crates/ra_hir/src/ty/tests/data/field_autoderef.txt (limited to 'crates') diff --git a/crates/ra_analysis/src/completion/complete_dot.rs b/crates/ra_analysis/src/completion/complete_dot.rs index 54ce1b638..5d4e60dc5 100644 --- a/crates/ra_analysis/src/completion/complete_dot.rs +++ b/crates/ra_analysis/src/completion/complete_dot.rs @@ -23,31 +23,35 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) -> Ca } fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) -> Cancelable<()> { - // TODO: autoderef etc. - match receiver { - Ty::Adt { def_id, .. } => { - match def_id.resolve(ctx.db)? { - Def::Struct(s) => { - let variant_data = s.variant_data(ctx.db)?; - for field in variant_data.fields() { - CompletionItem::new(CompletionKind::Reference, field.name().to_string()) + for receiver in receiver.autoderef(ctx.db) { + match receiver { + Ty::Adt { def_id, .. } => { + match def_id.resolve(ctx.db)? { + Def::Struct(s) => { + let variant_data = s.variant_data(ctx.db)?; + for field in variant_data.fields() { + CompletionItem::new( + CompletionKind::Reference, + field.name().to_string(), + ) .kind(CompletionItemKind::Field) .add_to(acc); + } } + // TODO unions + _ => {} } - // TODO unions - _ => {} } - } - Ty::Tuple(fields) => { - for (i, _ty) in fields.iter().enumerate() { - CompletionItem::new(CompletionKind::Reference, i.to_string()) - .kind(CompletionItemKind::Field) - .add_to(acc); + Ty::Tuple(fields) => { + for (i, _ty) in fields.iter().enumerate() { + CompletionItem::new(CompletionKind::Reference, i.to_string()) + .kind(CompletionItemKind::Field) + .add_to(acc); + } } - } - _ => {} - }; + _ => {} + }; + } Ok(()) } @@ -87,6 +91,21 @@ mod tests { ); } + #[test] + fn test_struct_field_completion_autoderef() { + check_ref_completion( + r" + struct A { the_field: u32 } + impl A { + fn foo(&self) { + self.<|> + } + } + ", + r#"the_field"#, + ); + } + #[test] fn test_no_struct_field_completion_for_method_call() { check_ref_completion( diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 033f9d25f..04249400b 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -56,7 +56,7 @@ pub trait HirDatabase: SyntaxDatabase use fn crate::ty::type_for_def; } - fn type_for_field(def_id: DefId, field: Name) -> Cancelable { + fn type_for_field(def_id: DefId, field: Name) -> Cancelable> { type TypeForFieldQuery; use fn crate::ty::type_for_field; } diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index d57990cd2..bba8527b7 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -13,6 +13,7 @@ //! the union-find implementation from the `ena` crate, which is extracted from //! rustc. +mod autoderef; mod primitive; #[cfg(test)] mod tests; @@ -36,6 +37,14 @@ use crate::{ expr::{Body, Expr, ExprId, PatId, UnaryOp, BinaryOp, Statement}, }; +fn transpose(x: Cancelable>) -> Option> { + match x { + Ok(Some(t)) => Some(Ok(t)), + Ok(None) => None, + Err(e) => Some(Err(e)), + } +} + /// The ID of a type variable. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct TypeVarId(u32); @@ -357,6 +366,14 @@ impl Ty { }); self } + + fn builtin_deref(&self) -> Option { + match self { + Ty::Ref(t, _) => Some(Ty::clone(t)), + Ty::RawPtr(t, _) => Some(Ty::clone(t)), + _ => None, + } + } } impl fmt::Display for Ty { @@ -443,7 +460,11 @@ pub(super) fn type_for_def(db: &impl HirDatabase, def_id: DefId) -> Cancelable Cancelable { +pub(super) fn type_for_field( + db: &impl HirDatabase, + def_id: DefId, + field: Name, +) -> Cancelable> { let def = def_id.resolve(db)?; let variant_data = match def { Def::Struct(s) => { @@ -459,12 +480,13 @@ pub(super) fn type_for_field(db: &impl HirDatabase, def_id: DefId, field: Name) }; let module = def_id.module(db)?; let impl_block = def_id.impl_block(db)?; - let type_ref = if let Some(tr) = variant_data.get_field_type_ref(&field) { - tr - } else { - return Ok(Ty::Unknown); - }; - Ty::from_hir(db, &module, impl_block.as_ref(), &type_ref) + let type_ref = ctry!(variant_data.get_field_type_ref(&field)); + Ok(Some(Ty::from_hir( + db, + &module, + impl_block.as_ref(), + &type_ref, + )?)) } /// The result of type inference: A mapping from expressions and patterns to types. @@ -802,7 +824,9 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let (ty, def_id) = self.resolve_variant(path.as_ref())?; for field in fields { let field_ty = if let Some(def_id) = def_id { - self.db.type_for_field(def_id, field.name.clone())? + self.db + .type_for_field(def_id, field.name.clone())? + .unwrap_or(Ty::Unknown) } else { Ty::Unknown }; @@ -815,15 +839,20 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { } Expr::Field { expr, name } => { let receiver_ty = self.infer_expr(*expr, &Expectation::none())?; - let ty = match receiver_ty { - Ty::Tuple(fields) => { - let i = name.to_string().parse::().ok(); - i.and_then(|i| fields.get(i).cloned()) - .unwrap_or(Ty::Unknown) - } - Ty::Adt { def_id, .. } => self.db.type_for_field(def_id, name.clone())?, - _ => Ty::Unknown, - }; + let ty = receiver_ty + .autoderef(self.db) + .find_map(|derefed_ty| match derefed_ty { + // this is more complicated than necessary because type_for_field is cancelable + Ty::Tuple(fields) => { + let i = name.to_string().parse::().ok(); + i.and_then(|i| fields.get(i).cloned()).map(Ok) + } + Ty::Adt { def_id, .. } => { + transpose(self.db.type_for_field(def_id, name.clone())) + } + _ => None, + }) + .unwrap_or(Ok(Ty::Unknown))?; self.insert_type_vars(ty) } Expr::Try { expr } => { @@ -848,12 +877,11 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let inner_ty = self.infer_expr(*expr, &Expectation::none())?; match op { Some(UnaryOp::Deref) => { - match inner_ty { - // builtin deref: - Ty::Ref(ref_inner, _) => (*ref_inner).clone(), - Ty::RawPtr(ptr_inner, _) => (*ptr_inner).clone(), + if let Some(derefed_ty) = inner_ty.builtin_deref() { + derefed_ty + } else { // TODO Deref::deref - _ => Ty::Unknown, + Ty::Unknown } } _ => Ty::Unknown, diff --git a/crates/ra_hir/src/ty/autoderef.rs b/crates/ra_hir/src/ty/autoderef.rs new file mode 100644 index 000000000..24a386558 --- /dev/null +++ b/crates/ra_hir/src/ty/autoderef.rs @@ -0,0 +1,21 @@ +//! In certain situations, rust automatically inserts derefs as necessary: For +//! example, field accesses `foo.bar` still work when `foo` is actually a +//! reference to a type with the field `bar`. This is an approximation of the +//! logic in rustc (which lives in librustc_typeck/check/autoderef.rs). + +use ra_syntax::algo::generate; + +use crate::HirDatabase; +use super::Ty; + +impl Ty { + /// Iterates over the possible derefs of `ty`. + pub fn autoderef<'a>(self, db: &'a impl HirDatabase) -> impl Iterator + 'a { + generate(Some(self), move |ty| ty.autoderef_step(db)) + } + + fn autoderef_step(&self, _db: &impl HirDatabase) -> Option { + // TODO Deref::deref + self.builtin_deref() + } +} diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index 83aedaa00..e6c7e225b 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs @@ -95,7 +95,7 @@ fn test() { } #[test] -fn infer_refs_and_ptrs() { +fn infer_refs() { check_inference( r#" fn test(a: &u32, b: &mut u32, c: *const u32, d: *mut u32) { @@ -180,6 +180,37 @@ fn test() { ); } +#[test] +fn infer_field_autoderef() { + check_inference( + r#" +struct A { + b: B, +} +struct B; + +fn test1(a: A) { + let a1 = a; + a1.b; + let a2 = &a; + a2.b; + let a3 = &mut a; + a3.b; + let a4 = &&&&&&&a; + a4.b; + let a5 = &mut &&mut &&mut a; + a5.b; +} + +fn test2(a1: *const A, a2: *mut A) { + a1.b; + a2.b; +} +"#, + "field_autoderef.txt", + ); +} + fn infer(content: &str) -> String { let (db, _, file_id) = MockDatabase::with_single_file(content); let source_file = db.source_file(file_id); diff --git a/crates/ra_hir/src/ty/tests/data/field_autoderef.txt b/crates/ra_hir/src/ty/tests/data/field_autoderef.txt new file mode 100644 index 000000000..e1db1db40 --- /dev/null +++ b/crates/ra_hir/src/ty/tests/data/field_autoderef.txt @@ -0,0 +1,43 @@ +[44; 45) 'a': A +[50; 213) '{ ...5.b; }': () +[60; 62) 'a1': A +[65; 66) 'a': A +[72; 74) 'a1': A +[72; 76) 'a1.b': B +[86; 88) 'a2': &A +[91; 93) '&a': &A +[92; 93) 'a': A +[99; 101) 'a2': &A +[99; 103) 'a2.b': B +[113; 115) 'a3': &mut A +[118; 124) '&mut a': &mut A +[123; 124) 'a': A +[130; 132) 'a3': &mut A +[130; 134) 'a3.b': B +[144; 146) 'a4': &&&&&&&A +[149; 157) '&&&&&&&a': &&&&&&&A +[150; 157) '&&&&&&a': &&&&&&A +[151; 157) '&&&&&a': &&&&&A +[152; 157) '&&&&a': &&&&A +[153; 157) '&&&a': &&&A +[154; 157) '&&a': &&A +[155; 157) '&a': &A +[156; 157) 'a': A +[163; 165) 'a4': &&&&&&&A +[163; 167) 'a4.b': B +[177; 179) 'a5': &mut &&mut &&mut A +[182; 200) '&mut &...&mut a': &mut &&mut &&mut A +[187; 200) '&&mut &&mut a': &&mut &&mut A +[188; 200) '&mut &&mut a': &mut &&mut A +[193; 200) '&&mut a': &&mut A +[194; 200) '&mut a': &mut A +[199; 200) 'a': A +[206; 208) 'a5': &mut &&mut &&mut A +[206; 210) 'a5.b': B +[224; 226) 'a1': *const A +[238; 240) 'a2': *mut A +[250; 273) '{ ...2.b; }': () +[256; 258) 'a1': *const A +[256; 260) 'a1.b': B +[266; 268) 'a2': *mut A +[266; 270) 'a2.b': B -- cgit v1.2.3