1076: Const body inference r=flodiebold a=Lapz

This is the second part of #887. I've added type inference on const bodies and introduced the DefWithBody containing Function, Const and Static. I want to add tests but im unsure on how I would go about testing that completions work.


Co-authored-by: Lenard Pratt <l3np27@gmail.com>
This commit is contained in:
bors[bot] 2019-04-02 19:01:54 +00:00
commit fdbebccd71
10 changed files with 231 additions and 49 deletions

View file

@ -429,6 +429,45 @@ impl Docs for EnumVariant {
}
}
/// The defs which have a body.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DefWithBody {
Function(Function),
Const(Const),
Static(Static),
}
impl_froms!(DefWithBody: Function, Const, Static);
impl DefWithBody {
pub fn infer(&self, db: &impl HirDatabase) -> Arc<InferenceResult> {
db.infer(*self)
}
pub fn body_source_map(&self, db: &impl HirDatabase) -> Arc<BodySourceMap> {
db.body_with_source_map(*self).1
}
pub fn body(&self, db: &impl HirDatabase) -> Arc<Body> {
db.body_hir(*self)
}
/// Builds a resolver for code inside this item.
pub fn resolver(&self, db: &impl HirDatabase) -> Resolver {
match *self {
DefWithBody::Const(ref c) => c.resolver(db),
DefWithBody::Function(ref f) => f.resolver(db),
DefWithBody::Static(ref s) => s.resolver(db),
}
}
pub fn scopes(&self, db: &impl HirDatabase) -> ScopesWithSourceMap {
let scopes = db.expr_scopes(*self);
let source_map = db.body_with_source_map(*self).1;
ScopesWithSourceMap { scopes, source_map }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Function {
pub(crate) id: FunctionId,
@ -479,11 +518,11 @@ impl Function {
}
pub fn body_source_map(&self, db: &impl HirDatabase) -> Arc<BodySourceMap> {
db.body_with_source_map(*self).1
db.body_with_source_map((*self).into()).1
}
pub fn body(&self, db: &impl HirDatabase) -> Arc<Body> {
db.body_hir(*self)
db.body_hir((*self).into())
}
pub fn ty(&self, db: &impl HirDatabase) -> Ty {
@ -491,8 +530,8 @@ impl Function {
}
pub fn scopes(&self, db: &impl HirDatabase) -> ScopesWithSourceMap {
let scopes = db.expr_scopes(*self);
let source_map = db.body_with_source_map(*self).1;
let scopes = db.expr_scopes((*self).into());
let source_map = db.body_with_source_map((*self).into()).1;
ScopesWithSourceMap { scopes, source_map }
}
@ -501,7 +540,7 @@ impl Function {
}
pub fn infer(&self, db: &impl HirDatabase) -> Arc<InferenceResult> {
db.infer(*self)
db.infer((*self).into())
}
pub fn generic_params(&self, db: &impl DefDatabase) -> Arc<GenericParams> {
@ -557,6 +596,14 @@ impl Const {
db.const_signature(*self)
}
pub fn infer(&self, db: &impl HirDatabase) -> Arc<InferenceResult> {
db.infer((*self).into())
}
pub fn body_source_map(&self, db: &impl HirDatabase) -> Arc<BodySourceMap> {
db.body_with_source_map((*self).into()).1
}
/// The containing impl block, if this is a method.
pub fn impl_block(&self, db: &impl DefDatabase) -> Option<ImplBlock> {
let module_impls = db.impls_in_module(self.module(db));
@ -621,6 +668,14 @@ impl Static {
// take the outer scope...
self.module(db).resolver(db)
}
pub fn infer(&self, db: &impl HirDatabase) -> Arc<InferenceResult> {
db.infer((*self).into())
}
pub fn body_source_map(&self, db: &impl HirDatabase) -> Arc<BodySourceMap> {
db.body_with_source_map((*self).into()).1
}
}
impl Docs for Static {

View file

@ -8,6 +8,7 @@ use crate::{
Function, FnSignature, ExprScopes, TypeAlias,
Struct, Enum, StructField,
Const, ConstSignature, Static,
DefWithBody,
nameres::{Namespace, ImportSourceMap, RawItems, CrateDefMap},
ty::{InferenceResult, Ty, method_resolution::CrateImplBlocks, TypableDef, CallableDef, FnSig},
adt::{StructData, EnumData},
@ -83,10 +84,10 @@ pub trait DefDatabase: SourceDatabase + AsRef<HirInterner> {
#[salsa::query_group(HirDatabaseStorage)]
pub trait HirDatabase: DefDatabase {
#[salsa::invoke(ExprScopes::expr_scopes_query)]
fn expr_scopes(&self, func: Function) -> Arc<ExprScopes>;
fn expr_scopes(&self, def: DefWithBody) -> Arc<ExprScopes>;
#[salsa::invoke(crate::ty::infer)]
fn infer(&self, func: Function) -> Arc<InferenceResult>;
fn infer(&self, def: DefWithBody) -> Arc<InferenceResult>;
#[salsa::invoke(crate::ty::type_for_def)]
fn type_for_def(&self, def: TypableDef, ns: Namespace) -> Ty;
@ -100,11 +101,11 @@ pub trait HirDatabase: DefDatabase {
#[salsa::invoke(crate::expr::body_with_source_map_query)]
fn body_with_source_map(
&self,
func: Function,
def: DefWithBody,
) -> (Arc<crate::expr::Body>, Arc<crate::expr::BodySourceMap>);
#[salsa::invoke(crate::expr::body_hir_query)]
fn body_hir(&self, func: Function) -> Arc<crate::expr::Body>;
fn body_hir(&self, def: DefWithBody) -> Arc<crate::expr::Body>;
#[salsa::invoke(crate::ty::method_resolution::CrateImplBlocks::impls_in_crate_query)]
fn impls_in_crate(&self, krate: Crate) -> Arc<CrateImplBlocks>;

View file

@ -10,7 +10,7 @@ use ra_syntax::{
};
use crate::{
Path, Name, HirDatabase, Function, Resolver,
Path, Name, HirDatabase, Resolver,DefWithBody,
name::AsName,
type_ref::{Mutability, TypeRef},
};
@ -27,9 +27,8 @@ impl_arena_id!(ExprId);
/// The body of an item (function, const etc.).
#[derive(Debug, Eq, PartialEq)]
pub struct Body {
// FIXME: this should be more general, consts & statics also have bodies
/// The Function of the item this body belongs to
owner: Function,
/// The def of the item this body belongs to
owner: DefWithBody,
exprs: Arena<ExprId, Expr>,
pats: Arena<PatId, Pat>,
/// The patterns for the function's parameters. While the parameter types are
@ -66,7 +65,7 @@ impl Body {
self.body_expr
}
pub fn owner(&self) -> Function {
pub fn owner(&self) -> DefWithBody {
self.owner
}
@ -463,8 +462,8 @@ impl Pat {
// Queries
struct ExprCollector {
owner: Function,
pub(crate) struct ExprCollector {
owner: DefWithBody,
exprs: Arena<ExprId, Expr>,
pats: Arena<PatId, Pat>,
source_map: BodySourceMap,
@ -473,7 +472,7 @@ struct ExprCollector {
}
impl ExprCollector {
fn new(owner: Function) -> Self {
fn new(owner: DefWithBody) -> Self {
ExprCollector {
owner,
exprs: Arena::default(),
@ -866,6 +865,16 @@ impl ExprCollector {
}
}
fn collect_const_body(&mut self, node: &ast::ConstDef) {
let body = self.collect_expr_opt(node.body());
self.body_expr = Some(body);
}
fn collect_static_body(&mut self, node: &ast::StaticDef) {
let body = self.collect_expr_opt(node.body());
self.body_expr = Some(body);
}
fn collect_fn_body(&mut self, node: &ast::FnDef) {
if let Some(param_list) = node.param_list() {
if let Some(self_param) = param_list.self_param() {
@ -910,24 +919,20 @@ impl ExprCollector {
pub(crate) fn body_with_source_map_query(
db: &impl HirDatabase,
func: Function,
def: DefWithBody,
) -> (Arc<Body>, Arc<BodySourceMap>) {
let mut collector = ExprCollector::new(func);
let mut collector = ExprCollector::new(def);
// FIXME: consts, etc.
collector.collect_fn_body(&func.source(db).1);
match def {
DefWithBody::Const(ref c) => collector.collect_const_body(&c.source(db).1),
DefWithBody::Function(ref f) => collector.collect_fn_body(&f.source(db).1),
DefWithBody::Static(ref s) => collector.collect_static_body(&s.source(db).1),
}
let (body, source_map) = collector.finish();
(Arc::new(body), Arc::new(source_map))
}
pub(crate) fn body_hir_query(db: &impl HirDatabase, func: Function) -> Arc<Body> {
db.body_with_source_map(func).0
}
#[cfg(test)]
fn collect_fn_body_syntax(function: Function, node: &ast::FnDef) -> (Body, BodySourceMap) {
let mut collector = ExprCollector::new(function);
collector.collect_fn_body(node);
collector.finish()
pub(crate) fn body_hir_query(db: &impl HirDatabase, def: DefWithBody) -> Arc<Body> {
db.body_with_source_map(def).0
}

View file

@ -10,7 +10,7 @@ use ra_syntax::{
use ra_arena::{Arena, RawId, impl_arena_id};
use crate::{
Name, AsName, Function,
Name, AsName,DefWithBody,
expr::{PatId, ExprId, Pat, Expr, Body, Statement, BodySourceMap},
HirDatabase,
};
@ -40,8 +40,8 @@ pub struct ScopeData {
impl ExprScopes {
// FIXME: This should take something more general than Function
pub(crate) fn expr_scopes_query(db: &impl HirDatabase, function: Function) -> Arc<ExprScopes> {
let body = db.body_hir(function);
pub(crate) fn expr_scopes_query(db: &impl HirDatabase, def: DefWithBody) -> Arc<ExprScopes> {
let body = db.body_hir(def);
let res = ExprScopes::new(body);
Arc::new(res)
}
@ -297,8 +297,9 @@ mod tests {
use ra_syntax::{SourceFile, algo::find_node_at_offset};
use test_utils::{extract_offset, assert_eq_text};
use ra_arena::ArenaId;
use crate::Function;
use crate::expr;
use crate::expr::{ExprCollector};
use super::*;
@ -316,7 +317,7 @@ mod tests {
let marker: &ast::PathExpr = find_node_at_offset(file.syntax(), off).unwrap();
let fn_def: &ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap();
let irrelevant_function = Function { id: crate::ids::FunctionId::from_raw(0.into()) };
let (body, source_map) = expr::collect_fn_body_syntax(irrelevant_function, fn_def);
let (body, source_map) = collect_fn_body_syntax(irrelevant_function, fn_def);
let scopes = ExprScopes::new(Arc::new(body));
let scopes =
ScopesWithSourceMap { scopes: Arc::new(scopes), source_map: Arc::new(source_map) };
@ -405,6 +406,12 @@ mod tests {
);
}
fn collect_fn_body_syntax(function: Function, node: &ast::FnDef) -> (Body, BodySourceMap) {
let mut collector = ExprCollector::new(DefWithBody::Function(function));
collector.collect_fn_body(node);
collector.finish()
}
fn do_check_local_name(code: &str, expected_offset: u32) {
let (off, code) = extract_offset(code);
let file = SourceFile::parse(&code);
@ -415,7 +422,7 @@ mod tests {
let name_ref: &ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap();
let irrelevant_function = Function { id: crate::ids::FunctionId::from_raw(0.into()) };
let (body, source_map) = expr::collect_fn_body_syntax(irrelevant_function, fn_def);
let (body, source_map) = collect_fn_body_syntax(irrelevant_function, fn_def);
let scopes = ExprScopes::new(Arc::new(body));
let scopes =
ScopesWithSourceMap { scopes: Arc::new(scopes), source_map: Arc::new(source_map) };

View file

@ -67,6 +67,7 @@ pub use self::{
pub use self::code_model_api::{
Crate, CrateDependency,
DefWithBody,
Module, ModuleDef, ModuleSource,
Struct, Enum, EnumVariant,
Function, FnSignature,

View file

@ -13,7 +13,7 @@ use ra_syntax::{
};
use crate::{
HirDatabase, Function, Struct, Enum,
HirDatabase, Function, Struct, Enum,Const,Static,
AsName, Module, HirFileId, Crate, Trait, Resolver,
ids::LocationCtx,
expr, AstId
@ -87,6 +87,27 @@ fn module_from_source(
)
}
pub fn const_from_source(
db: &impl HirDatabase,
file_id: FileId,
const_def: &ast::ConstDef,
) -> Option<Const> {
let module = module_from_child_node(db, file_id, const_def.syntax())?;
let res = const_from_module(db, module, const_def);
Some(res)
}
pub fn const_from_module(
db: &impl HirDatabase,
module: Module,
const_def: &ast::ConstDef,
) -> Const {
let (file_id, _) = module.definition_source(db);
let file_id = file_id.into();
let ctx = LocationCtx::new(db, module, file_id);
Const { id: ctx.to_def(const_def) }
}
pub fn function_from_position(db: &impl HirDatabase, position: FilePosition) -> Option<Function> {
let file = db.parse(position.file_id);
let fn_def = find_node_at_offset::<ast::FnDef>(file.syntax(), position.offset)?;
@ -134,6 +155,27 @@ pub fn struct_from_module(
Struct { id: ctx.to_def(struct_def) }
}
pub fn static_from_source(
db: &impl HirDatabase,
file_id: FileId,
static_def: &ast::StaticDef,
) -> Option<Static> {
let module = module_from_child_node(db, file_id, static_def.syntax())?;
let res = static_from_module(db, module, static_def);
Some(res)
}
pub fn static_from_module(
db: &impl HirDatabase,
module: Module,
static_def: &ast::StaticDef,
) -> Static {
let (file_id, _) = module.definition_source(db);
let file_id = file_id.into();
let ctx = LocationCtx::new(db, module, file_id);
Static { id: ctx.to_def(static_def) }
}
pub fn enum_from_module(db: &impl HirDatabase, module: Module, enum_def: &ast::EnumDef) -> Enum {
let (file_id, _) = module.definition_source(db);
let file_id = file_id.into();

View file

@ -27,8 +27,9 @@ use test_utils::tested_by;
use crate::{
Function, StructField, Path, Name,
FnSignature, AdtDef,
FnSignature, AdtDef,ConstSignature,
HirDatabase,
DefWithBody,
ImplItem,
type_ref::{TypeRef, Mutability},
expr::{Body, Expr, BindingAnnotation, Literal, ExprId, Pat, PatId, UnaryOp, BinaryOp, Statement, FieldPat, self},
@ -43,14 +44,17 @@ use crate::{
use super::{Ty, TypableDef, Substs, primitive, op, FnSig, ApplicationTy, TypeCtor};
/// The entry point of type inference.
pub fn infer(db: &impl HirDatabase, func: Function) -> Arc<InferenceResult> {
pub fn infer(db: &impl HirDatabase, def: DefWithBody) -> Arc<InferenceResult> {
db.check_canceled();
let body = func.body(db);
let resolver = func.resolver(db);
let body = def.body(db);
let resolver = def.resolver(db);
let mut ctx = InferenceContext::new(db, body, resolver);
let signature = func.signature(db);
ctx.collect_fn_signature(&signature);
match def {
DefWithBody::Const(ref c) => ctx.collect_const_signature(&c.signature(db)),
DefWithBody::Function(ref f) => ctx.collect_fn_signature(&f.signature(db)),
DefWithBody::Static(ref s) => ctx.collect_const_signature(&s.signature(db)),
}
ctx.infer_body();
@ -1142,6 +1146,10 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
ty
}
fn collect_const_signature(&mut self, signature: &ConstSignature) {
self.return_ty = self.make_ty(signature.type_ref());
}
fn collect_fn_signature(&mut self, signature: &FnSignature) {
let body = Arc::clone(&self.body); // avoid borrow checker problem
for (type_ref, pat) in signature.params().iter().zip(body.params()) {

View file

@ -11,6 +11,8 @@ use crate::{
source_binder,
mock::MockDatabase,
ty::display::HirDisplay,
ty::InferenceResult,
expr::BodySourceMap
};
// These tests compare the inference results for all expressions in a file
@ -1267,6 +1269,9 @@ fn test() {
}
"#),
@r###"
[52; 53) '1': u32
[103; 104) '2': u32
[211; 212) '5': u32
[227; 305) '{ ...:ID; }': ()
[237; 238) 'x': u32
[241; 252) 'Struct::FOO': u32
@ -1855,6 +1860,9 @@ fn test() {
}
"#),
@r###"
[49; 50) '0': u32
[80; 83) '101': u32
[126; 128) '99': u32
[95; 213) '{ ...NST; }': ()
[138; 139) 'x': {unknown}
[142; 153) 'LOCAL_CONST': {unknown}
@ -1881,6 +1889,10 @@ fn test() {
}
"#),
@r###"
[29; 32) '101': u32
[70; 73) '101': u32
[118; 120) '99': u32
[161; 163) '99': u32
[85; 280) '{ ...MUT; }': ()
[173; 174) 'x': {unknown}
[177; 189) 'LOCAL_STATIC': {unknown}
@ -2212,6 +2224,24 @@ fn test<T: Iterable<Item=u32>>() {
);
}
#[test]
fn infer_const_body() {
assert_snapshot_matches!(
infer(r#"
const A: u32 = 1 + 1;
static B: u64 = { let x = 1; x };
"#),
@r###"
[16; 17) '1': u32
[16; 21) '1 + 1': u32
[20; 21) '1': u32
[39; 55) '{ let ...1; x }': u64
[45; 46) 'x': u64
[49; 50) '1': u64
[52; 53) 'x': u64"###
);
}
fn type_at_pos(db: &MockDatabase, pos: FilePosition) -> String {
let func = source_binder::function_from_position(db, pos).unwrap();
let body_source_map = func.body_source_map(db);
@ -2228,11 +2258,11 @@ fn infer(content: &str) -> String {
let source_file = db.parse(file_id);
let mut acc = String::new();
acc.push_str("\n");
for fn_def in source_file.syntax().descendants().filter_map(ast::FnDef::cast) {
let func = source_binder::function_from_source(&db, file_id, fn_def).unwrap();
let inference_result = func.infer(&db);
let body_source_map = func.body_source_map(&db);
let mut infer_def = |inference_result: Arc<InferenceResult>,
body_source_map: Arc<BodySourceMap>| {
let mut types = Vec::new();
for (pat, ty) in inference_result.type_of_pat.iter() {
let syntax_ptr = match body_source_map.pat_syntax(pat) {
Some(sp) => sp,
@ -2240,6 +2270,7 @@ fn infer(content: &str) -> String {
};
types.push((syntax_ptr, ty));
}
for (expr, ty) in inference_result.type_of_expr.iter() {
let syntax_ptr = match body_source_map.expr_syntax(expr) {
Some(sp) => sp,
@ -2258,7 +2289,29 @@ fn infer(content: &str) -> String {
};
write!(acc, "{} '{}': {}\n", range, ellipsize(text, 15), ty.display(&db)).unwrap();
}
};
for const_def in source_file.syntax().descendants().filter_map(ast::ConstDef::cast) {
let konst = source_binder::const_from_source(&db, file_id, const_def).unwrap();
let inference_result = konst.infer(&db);
let body_source_map = konst.body_source_map(&db);
infer_def(inference_result, body_source_map)
}
for static_def in source_file.syntax().descendants().filter_map(ast::StaticDef::cast) {
let static_ = source_binder::static_from_source(&db, file_id, static_def).unwrap();
let inference_result = static_.infer(&db);
let body_source_map = static_.body_source_map(&db);
infer_def(inference_result, body_source_map)
}
for fn_def in source_file.syntax().descendants().filter_map(ast::FnDef::cast) {
let func = source_binder::function_from_source(&db, file_id, fn_def).unwrap();
let inference_result = func.infer(&db);
let body_source_map = func.body_source_map(&db);
infer_def(inference_result, body_source_map)
}
acc.truncate(acc.trim_end().len());
acc
}

View file

@ -513,7 +513,11 @@ impl ast::TypeParamsOwner for ConstDef {}
impl ast::AttrsOwner for ConstDef {}
impl ast::DocCommentsOwner for ConstDef {}
impl ast::TypeAscriptionOwner for ConstDef {}
impl ConstDef {}
impl ConstDef {
pub fn body(&self) -> Option<&Expr> {
super::child_opt(self)
}
}
// ContinueExpr
#[derive(Debug, PartialEq, Eq, Hash)]
@ -3364,7 +3368,11 @@ impl ast::TypeParamsOwner for StaticDef {}
impl ast::AttrsOwner for StaticDef {}
impl ast::DocCommentsOwner for StaticDef {}
impl ast::TypeAscriptionOwner for StaticDef {}
impl StaticDef {}
impl StaticDef {
pub fn body(&self) -> Option<&Expr> {
super::child_opt(self)
}
}
// Stmt
#[derive(Debug, PartialEq, Eq, Hash)]

View file

@ -315,6 +315,7 @@ Grammar(
"DocCommentsOwner",
"TypeAscriptionOwner",
],
options: [ ["body","Expr"]],
),
"StaticDef": (
traits: [
@ -325,6 +326,7 @@ Grammar(
"DocCommentsOwner",
"TypeAscriptionOwner",
],
options: [ ["body","Expr"]],
),
"TypeAliasDef": (
traits: [