Implement basic support for Associated Methods and Constants

This is done in `infer_path_expr`. When `Resolver::resolve_path` returns
`PartiallyResolved`, we use the returned `Resolution` together with the given
`segment_index` to check if we can find something matching the segment at
segment_index in the impls for that particular type.
This commit is contained in:
Ville Penttinen 2019-02-21 12:04:14 +02:00
parent c84561bb62
commit 816971ebc9
13 changed files with 430 additions and 50 deletions

View file

@ -88,7 +88,7 @@ impl ImplBlock {
if let Some(TypeRef::Path(path)) = self.target_trait_ref(db) {
let resolver = self.resolver(db);
if let Some(Resolution::Def(ModuleDef::Trait(tr))) =
resolver.resolve_path(db, &path).take_types()
resolver.resolve_path(db, &path).into_per_ns().take_types()
{
return Some(tr);
}

View file

@ -119,6 +119,10 @@ impl<T> PerNs<T> {
self.types.is_some() && self.values.is_some()
}
pub fn is_values(&self) -> bool {
self.values.is_some() && self.types.is_none()
}
pub fn take(self, namespace: Namespace) -> Option<T> {
match namespace {
Namespace::Types => self.types,
@ -297,7 +301,14 @@ where
);
(res, if res.is_none() { ReachedFixedPoint::No } else { ReachedFixedPoint::Yes })
} else {
self.result.resolve_path_fp(self.db, ResolveMode::Import, original_module, &import.path)
let res = self.result.resolve_path_fp(
self.db,
ResolveMode::Import,
original_module,
&import.path,
);
(res.module, res.reached_fixedpoint)
};
if reached_fixedpoint != ReachedFixedPoint::Yes {
@ -435,6 +446,27 @@ where
}
}
#[derive(Debug, Clone)]
pub struct ResolvePathResult {
pub(crate) module: PerNs<ModuleDef>,
pub(crate) segment_index: Option<usize>,
reached_fixedpoint: ReachedFixedPoint,
}
impl ResolvePathResult {
fn empty(reached_fixedpoint: ReachedFixedPoint) -> ResolvePathResult {
ResolvePathResult::with(PerNs::none(), reached_fixedpoint, None)
}
fn with(
module: PerNs<ModuleDef>,
reached_fixedpoint: ReachedFixedPoint,
segment_index: Option<usize>,
) -> ResolvePathResult {
ResolvePathResult { module, reached_fixedpoint, segment_index }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ResolveMode {
Import,
@ -468,8 +500,9 @@ impl ItemMap {
db: &impl PersistentHirDatabase,
original_module: Module,
path: &Path,
) -> PerNs<ModuleDef> {
self.resolve_path_fp(db, ResolveMode::Other, original_module, path).0
) -> (PerNs<ModuleDef>, Option<usize>) {
let res = self.resolve_path_fp(db, ResolveMode::Other, original_module, path);
(res.module, res.segment_index)
}
fn resolve_in_prelude(
@ -534,7 +567,7 @@ impl ItemMap {
mode: ResolveMode,
original_module: Module,
path: &Path,
) -> (PerNs<ModuleDef>, ReachedFixedPoint) {
) -> ResolvePathResult {
let mut segments = path.segments.iter().enumerate();
let mut curr_per_ns: PerNs<ModuleDef> = match path.kind {
PathKind::Crate => PerNs::types(original_module.crate_root(db).into()),
@ -549,7 +582,7 @@ impl ItemMap {
{
let segment = match segments.next() {
Some((_, segment)) => segment,
None => return (PerNs::none(), ReachedFixedPoint::Yes),
None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
};
log::debug!("resolving {:?} in crate root (+ extern prelude)", segment);
self.resolve_name_in_crate_root_or_extern_prelude(
@ -561,7 +594,7 @@ impl ItemMap {
PathKind::Plain => {
let segment = match segments.next() {
Some((_, segment)) => segment,
None => return (PerNs::none(), ReachedFixedPoint::Yes),
None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
};
log::debug!("resolving {:?} in module", segment);
self.resolve_name_in_module(db, original_module, &segment.name)
@ -571,20 +604,20 @@ impl ItemMap {
PerNs::types(p.into())
} else {
log::debug!("super path in root module");
return (PerNs::none(), ReachedFixedPoint::Yes);
return ResolvePathResult::empty(ReachedFixedPoint::Yes);
}
}
PathKind::Abs => {
// 2018-style absolute path -- only extern prelude
let segment = match segments.next() {
Some((_, segment)) => segment,
None => return (PerNs::none(), ReachedFixedPoint::Yes),
None => return ResolvePathResult::empty(ReachedFixedPoint::Yes),
};
if let Some(def) = self.extern_prelude.get(&segment.name) {
log::debug!("absolute path {:?} resolved to crate {:?}", path, def);
PerNs::types(*def)
} else {
return (PerNs::none(), ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude
return ResolvePathResult::empty(ReachedFixedPoint::No); // extern crate declarations can add to the extern prelude
}
}
};
@ -598,7 +631,7 @@ impl ItemMap {
// (don't break here because `curr_per_ns` might contain
// something in the value namespace, and it would be wrong
// to return that)
return (PerNs::none(), ReachedFixedPoint::No);
return ResolvePathResult::empty(ReachedFixedPoint::No);
}
};
// resolve segment in curr
@ -612,15 +645,15 @@ impl ItemMap {
};
log::debug!("resolving {:?} in other crate", path);
let item_map = db.item_map(module.krate);
let def = item_map.resolve_path(db, *module, &path);
return (def, ReachedFixedPoint::Yes);
let (def, s) = item_map.resolve_path(db, *module, &path);
return ResolvePathResult::with(def, ReachedFixedPoint::Yes, s);
}
match self[module.module_id].items.get(&segment.name) {
Some(res) if !res.def.is_none() => res.def,
_ => {
log::debug!("path segment {:?} not found", segment.name);
return (PerNs::none(), ReachedFixedPoint::No);
return ResolvePathResult::empty(ReachedFixedPoint::No);
}
}
}
@ -629,9 +662,22 @@ impl ItemMap {
tested_by!(item_map_enum_importing);
match e.variant(db, &segment.name) {
Some(variant) => PerNs::both(variant.into(), variant.into()),
None => PerNs::none(),
None => {
return ResolvePathResult::with(
PerNs::types((*e).into()),
ReachedFixedPoint::Yes,
Some(i),
);
}
}
}
ModuleDef::Struct(s) => {
return ResolvePathResult::with(
PerNs::types((*s).into()),
ReachedFixedPoint::Yes,
Some(i),
);
}
_ => {
// could be an inherent method call in UFCS form
// (`Struct::method`), or some other kind of associated
@ -641,11 +687,11 @@ impl ItemMap {
segment.name,
curr,
);
return (PerNs::none(), ReachedFixedPoint::Yes);
return ResolvePathResult::empty(ReachedFixedPoint::Yes);
}
};
}
(curr_per_ns, ReachedFixedPoint::Yes)
ResolvePathResult::with(curr_per_ns, ReachedFixedPoint::Yes, None)
}
}

View file

@ -32,6 +32,32 @@ pub(crate) struct ExprScope {
scope_id: ScopeId,
}
#[derive(Debug, Clone)]
pub enum PathResult {
/// Path was fully resolved
FullyResolved(PerNs<Resolution>),
/// Path was partially resolved, first element contains the resolution
/// second contains the index in the Path.segments which we were unable to resolve
PartiallyResolved(PerNs<Resolution>, usize),
}
impl PathResult {
pub fn segment_index(&self) -> Option<usize> {
match self {
PathResult::FullyResolved(_) => None,
PathResult::PartiallyResolved(_, ref i) => Some(*i),
}
}
/// Consumes `PathResult` and returns the contained `PerNs<Resolution>`
pub fn into_per_ns(self) -> PerNs<Resolution> {
match self {
PathResult::FullyResolved(def) => def,
PathResult::PartiallyResolved(def, _) => def,
}
}
}
#[derive(Debug, Clone)]
pub(crate) enum Scope {
/// All the items and imported names of a module
@ -67,18 +93,26 @@ impl Resolver {
resolution
}
pub fn resolve_path(&self, db: &impl HirDatabase, path: &Path) -> PerNs<Resolution> {
pub fn resolve_path(&self, db: &impl HirDatabase, path: &Path) -> PathResult {
use self::PathResult::*;
if let Some(name) = path.as_ident() {
self.resolve_name(db, name)
FullyResolved(self.resolve_name(db, name))
} else if path.is_self() {
self.resolve_name(db, &Name::self_param())
FullyResolved(self.resolve_name(db, &Name::self_param()))
} else {
let (item_map, module) = match self.module() {
Some(m) => m,
_ => return PerNs::none(),
_ => return FullyResolved(PerNs::none()),
};
let module_res = item_map.resolve_path(db, module, path);
module_res.map(Resolution::Def)
let (module_res, segment_index) = item_map.resolve_path(db, module, path);
let def = module_res.map(Resolution::Def);
if let Some(index) = segment_index {
PartiallyResolved(def, index)
} else {
FullyResolved(def)
}
}
}

View file

@ -32,17 +32,20 @@ use rustc_hash::FxHashMap;
use test_utils::tested_by;
use ra_syntax::ast::NameOwner;
use crate::{
Function, Struct, StructField, Enum, EnumVariant, Path, Name,
Const,
FnSignature, ModuleDef, AdtDef,
HirDatabase,
type_ref::{TypeRef, Mutability},
name::KnownName,
name::{KnownName, AsName},
expr::{Body, Expr, BindingAnnotation, Literal, ExprId, Pat, PatId, UnaryOp, BinaryOp, Statement, FieldPat, self},
generics::GenericParams,
path::GenericArg,
adt::VariantDef,
resolve::{Resolver, Resolution}, nameres::Namespace
resolve::{Resolver, Resolution, PathResult}, nameres::Namespace
};
/// The ID of a type variable.
@ -370,7 +373,7 @@ impl Ty {
}
// Resolve the path (in type namespace)
let resolution = resolver.resolve_path(db, path).take_types();
let resolution = resolver.resolve_path(db, path).into_per_ns().take_types();
let def = match resolution {
Some(Resolution::Def(def)) => def,
@ -678,6 +681,19 @@ fn type_for_fn(db: &impl HirDatabase, def: Function) -> Ty {
Ty::FnDef { def: def.into(), sig, name, substs }
}
fn type_for_const(db: &impl HirDatabase, resolver: &Resolver, def: Const) -> Ty {
let node = def.source(db).1;
let tr = node
.type_ref()
.map(TypeRef::from_ast)
.as_ref()
.map(|tr| Ty::from_hir(db, resolver, tr))
.unwrap_or_else(|| Ty::Unknown);
tr
}
/// Compute the type of a tuple struct constructor.
fn type_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> Ty {
let var_data = def.variant_data(db);
@ -1172,15 +1188,56 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
}
fn infer_path_expr(&mut self, resolver: &Resolver, path: &Path) -> Option<Ty> {
let resolved = resolver.resolve_path(self.db, &path).take_values()?;
let resolved = resolver.resolve_path(self.db, &path);
let (resolved, segment_index) = match resolved {
PathResult::FullyResolved(def) => (def.take_values()?, None),
PathResult::PartiallyResolved(def, index) => (def.take_types()?, Some(index)),
};
match resolved {
Resolution::Def(def) => {
let typable: Option<TypableDef> = def.into();
let typable = typable?;
let substs = Ty::substs_from_path(self.db, &self.resolver, path, typable);
let ty = self.db.type_for_def(typable, Namespace::Values).apply_substs(substs);
let ty = self.insert_type_vars(ty);
Some(ty)
if let Some(segment_index) = segment_index {
let ty = self.db.type_for_def(typable, Namespace::Types);
// TODO: What to do if segment_index is not the last segment
// in the path
let segment = &path.segments[segment_index];
// Attempt to find an impl_item for the type which has a name matching
// the current segment
let ty = ty.iterate_impl_items(self.db, |item| match item {
crate::ImplItem::Method(func) => {
let sig = func.signature(self.db);
if segment.name == *sig.name() {
return Some(type_for_fn(self.db, func));
}
None
}
crate::ImplItem::Const(c) => {
let node = c.source(self.db).1;
if let Some(name) = node.name().map(|n| n.as_name()) {
if segment.name == name {
return Some(type_for_const(self.db, resolver, c));
}
}
None
}
// TODO: Resolve associated types
crate::ImplItem::Type(_) => None,
});
ty
} else {
let substs = Ty::substs_from_path(self.db, &self.resolver, path, typable);
let ty = self.db.type_for_def(typable, Namespace::Values).apply_substs(substs);
let ty = self.insert_type_vars(ty);
Some(ty)
}
}
Resolution::LocalBinding(pat) => {
let ty = self.type_of_pat.get(pat)?;
@ -1204,23 +1261,24 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
None => return (Ty::Unknown, None),
};
let resolver = &self.resolver;
let typable: Option<TypableDef> = match resolver.resolve_path(self.db, &path).take_types() {
Some(Resolution::Def(def)) => def.into(),
Some(Resolution::LocalBinding(..)) => {
// this cannot happen
log::error!("path resolved to local binding in type ns");
return (Ty::Unknown, None);
}
Some(Resolution::GenericParam(..)) => {
// generic params can't be used in struct literals
return (Ty::Unknown, None);
}
Some(Resolution::SelfType(..)) => {
// TODO this is allowed in an impl for a struct, handle this
return (Ty::Unknown, None);
}
None => return (Ty::Unknown, None),
};
let typable: Option<TypableDef> =
match resolver.resolve_path(self.db, &path).into_per_ns().take_types() {
Some(Resolution::Def(def)) => def.into(),
Some(Resolution::LocalBinding(..)) => {
// this cannot happen
log::error!("path resolved to local binding in type ns");
return (Ty::Unknown, None);
}
Some(Resolution::GenericParam(..)) => {
// generic params can't be used in struct literals
return (Ty::Unknown, None);
}
Some(Resolution::SelfType(..)) => {
// TODO this is allowed in an impl for a struct, handle this
return (Ty::Unknown, None);
}
None => return (Ty::Unknown, None),
};
let def = match typable {
None => return (Ty::Unknown, None),
Some(it) => it,

View file

@ -0,0 +1,14 @@
---
created: "2019-02-20T11:04:56.553382800Z"
creator: insta@0.6.3
source: crates/ra_hir/src/ty/tests.rs
expression: "&result"
---
[227; 305) '{ ...:ID; }': ()
[237; 238) 'x': u32
[241; 252) 'Struct::FOO': u32
[262; 263) 'y': u32
[266; 275) 'Enum::BAR': u32
[285; 286) 'z': u32
[289; 302) 'TraitTest::ID': u32

View file

@ -0,0 +1,20 @@
---
created: "2019-02-20T11:04:56.553382800Z"
creator: insta@0.6.3
source: crates/ra_hir/src/ty/tests.rs
expression: "&result"
---
[48; 68) '{ ... }': A
[58; 62) 'A::B': A
[89; 109) '{ ... }': A
[99; 103) 'A::C': A
[122; 179) '{ ... c; }': ()
[132; 133) 'a': A
[136; 140) 'A::b': fn b() -> A
[136; 142) 'A::b()': A
[148; 149) 'a': A
[159; 160) 'c': A
[163; 167) 'A::c': fn c() -> A
[163; 169) 'A::c()': A
[175; 176) 'c': A

View file

@ -0,0 +1,16 @@
---
created: "2019-02-21T10:25:18.568887300Z"
creator: insta@0.6.3
source: crates/ra_hir/src/ty/tests.rs
expression: "&result"
---
[64; 67) 'val': T
[82; 109) '{ ... }': Gen<T>
[92; 103) 'Gen { val }': Gen<T>
[98; 101) 'val': T
[123; 155) '{ ...32); }': ()
[133; 134) 'a': Gen<[unknown]>
[137; 146) 'Gen::make': fn make<[unknown]>(T) -> Gen<T>
[137; 152) 'Gen::make(0u32)': Gen<[unknown]>
[147; 151) '0u32': u32

View file

@ -0,0 +1,16 @@
---
created: "2019-02-20T11:04:56.553382800Z"
creator: insta@0.6.3
source: crates/ra_hir/src/ty/tests.rs
expression: "&result"
---
[50; 76) '{ ... }': A
[60; 70) 'A { x: 0 }': A
[67; 68) '0': u32
[89; 123) '{ ...a.x; }': ()
[99; 100) 'a': A
[103; 109) 'A::new': fn new() -> A
[103; 111) 'A::new()': A
[117; 118) 'a': A
[117; 120) 'a.x': u32

View file

@ -0,0 +1,23 @@
---
created: "2019-02-21T08:55:53.926725400Z"
creator: insta@0.6.3
source: crates/ra_hir/src/ty/tests.rs
expression: "&result"
---
[56; 64) '{ A {} }': A
[58; 62) 'A {}': A
[126; 132) '{ 99 }': u32
[128; 130) '99': u32
[202; 210) '{ C {} }': C
[204; 208) 'C {}': C
[241; 325) '{ ...g(); }': ()
[251; 252) 'x': A
[255; 266) 'a::A::thing': fn thing() -> A
[255; 268) 'a::A::thing()': A
[278; 279) 'y': u32
[282; 293) 'b::B::thing': fn thing() -> u32
[282; 295) 'b::B::thing()': u32
[305; 306) 'z': C
[309; 320) 'c::C::thing': fn thing() -> C
[309; 322) 'c::C::thing()': C

View file

@ -586,6 +586,139 @@ fn test() -> i128 {
);
}
#[test]
fn infer_associated_const() {
check_inference(
"infer_associated_const",
r#"
struct Struct;
impl Struct {
const FOO: u32 = 1;
}
enum Enum;
impl Enum {
const BAR: u32 = 2;
}
trait Trait {
const ID: u32;
}
struct TraitTest;
impl Trait for TraitTest {
const ID: u32 = 5;
}
fn test() {
let x = Struct::FOO;
let y = Enum::BAR;
let z = TraitTest::ID;
}
"#,
);
}
#[test]
fn infer_associated_method_struct() {
check_inference(
"infer_associated_method_struct",
r#"
struct A { x: u32 };
impl A {
fn new() -> A {
A { x: 0 }
}
}
fn test() {
let a = A::new();
a.x;
}
"#,
);
}
#[test]
fn infer_associated_method_enum() {
check_inference(
"infer_associated_method_enum",
r#"
enum A { B, C };
impl A {
pub fn b() -> A {
A::B
}
pub fn c() -> A {
A::C
}
}
fn test() {
let a = A::b();
a;
let c = A::c();
c;
}
"#,
);
}
#[test]
fn infer_associated_method_with_modules() {
check_inference(
"infer_associated_method_with_modules",
r#"
mod a {
struct A;
impl A { pub fn thing() -> A { A {} }}
}
mod b {
struct B;
impl B { pub fn thing() -> u32 { 99 }}
mod c {
struct C;
impl C { pub fn thing() -> C { C {} }}
}
}
use b::c;
fn test() {
let x = a::A::thing();
let y = b::B::thing();
let z = c::C::thing();
}
"#,
);
}
#[test]
fn infer_associated_method_generics() {
check_inference(
"infer_associated_method_generics",
r#"
struct Gen<T> {
val: T
}
impl<T> Gen<T> {
pub fn make(val: T) -> Gen<T> {
Gen { val }
}
}
fn test() {
let a = Gen::make(0u32);
}
"#,
);
}
#[test]
fn no_panic_on_field_of_enum() {
check_inference(

View file

@ -10,7 +10,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) {
Some(path) => path.clone(),
_ => return,
};
let def = match ctx.resolver.resolve_path(ctx.db, &path).take_types() {
let def = match ctx.resolver.resolve_path(ctx.db, &path).into_per_ns().take_types() {
Some(Resolution::Def(def)) => def,
_ => return,
};

View file

@ -79,7 +79,7 @@ pub(crate) fn reference_definition(
if let Some(path) =
name_ref.syntax().ancestors().find_map(ast::Path::cast).and_then(hir::Path::from_ast)
{
let resolved = resolver.resolve_path(db, &path);
let resolved = resolver.resolve_path(db, &path).into_per_ns();
match resolved.clone().take_types().or_else(|| resolved.take_values()) {
Some(Resolution::Def(def)) => return Exact(NavigationTarget::from_def(db, def)),
Some(Resolution::LocalBinding(pat)) => {

View file

@ -223,4 +223,24 @@ mod tests {
assert_eq!("usize", &type_name);
}
#[test]
fn test_hover_infer_associated_method_result() {
let (analysis, position) = single_file_with_position(
"
struct Thing { x: u32 };
impl Thing {
fn new() -> Thing {
Thing { x: 0 }
}
}
fn main() {
let foo_<|>test = Thing::new();
}
",
);
let hover = analysis.hover(position).unwrap().unwrap();
assert_eq!(hover.info, "Thing");
}
}