From a6f33b4ca5e70a056c60b24cb8cb3283d8209624 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Sat, 5 Jan 2019 13:42:47 +0100 Subject: Add test for invalidation of inferred types when typing inside function This currently fails, but should work once we have hir::Expr. --- crates/ra_hir/src/source_binder.rs | 14 ++++++++++++ crates/ra_hir/src/ty/tests.rs | 44 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) (limited to 'crates/ra_hir') diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 85bd84469..551f44d4e 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -87,6 +87,20 @@ fn module_from_source( Ok(Some(Module::new(db, source_root_id, module_id)?)) } +pub fn function_from_position( + db: &impl HirDatabase, + position: FilePosition, +) -> Cancelable> { + let file = db.source_file(position.file_id); + let fn_def = if let Some(f) = find_node_at_offset::(file.syntax(), position.offset) + { + f + } else { + return Ok(None); + }; + function_from_source(db, position.file_id, fn_def) +} + pub fn function_from_source( db: &impl HirDatabase, file_id: FileId, diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index fb53fcf0b..515c66e85 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs @@ -1,7 +1,10 @@ +use std::sync::Arc; use std::fmt::Write; use std::path::{PathBuf, Path}; use std::fs; +use salsa::Database; + use ra_db::{SyntaxDatabase}; use ra_syntax::ast::{self, AstNode}; use test_utils::{project_dir, assert_eq_text, read_text}; @@ -217,3 +220,44 @@ fn ellipsize(mut text: String, max_len: usize) -> String { fn test_data_dir() -> PathBuf { project_dir().join("crates/ra_hir/src/ty/tests/data") } + +#[test] +#[should_panic] // TODO this should work once hir::Expr is used +fn typing_whitespace_inside_a_function_should_not_invalidate_types() { + let (mut db, pos) = MockDatabase::with_position( + " + //- /lib.rs + fn foo() -> i32 { + <|>1 + 1 + } + ", + ); + let func = source_binder::function_from_position(&db, pos) + .unwrap() + .unwrap(); + { + let events = db.log_executed(|| { + func.infer(&db).unwrap(); + }); + assert!(format!("{:?}", events).contains("infer")) + } + + let new_text = " + fn foo() -> i32 { + 1 + + + 1 + } + " + .to_string(); + + db.query_mut(ra_db::FileTextQuery) + .set(pos.file_id, Arc::new(new_text)); + + { + let events = db.log_executed(|| { + func.infer(&db).unwrap(); + }); + assert!(!format!("{:?}", events).contains("infer"), "{:#?}", events) + } +} -- cgit v1.2.3 From 136aba1cf32646278c4034541ee415f656f8bb5e Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Sat, 5 Jan 2019 16:32:07 +0100 Subject: Add HIR Expr machinery --- crates/ra_hir/src/db.rs | 10 + crates/ra_hir/src/expr.rs | 507 ++++++++++++++++++++++++++++++++++++++++++++++ crates/ra_hir/src/lib.rs | 1 + crates/ra_hir/src/mock.rs | 2 + crates/ra_hir/src/path.rs | 8 + 5 files changed, 528 insertions(+) create mode 100644 crates/ra_hir/src/expr.rs (limited to 'crates/ra_hir') diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 58296fc6f..188b96872 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -93,6 +93,16 @@ pub trait HirDatabase: SyntaxDatabase type ImplsInModuleQuery; use fn crate::impl_block::impls_in_module; } + + fn body_hir(def_id: DefId) -> Cancelable> { + type BodyHirQuery; + use fn crate::expr::body_hir; + } + + fn body_syntax_mapping(def_id: DefId) -> Cancelable> { + type BodySyntaxMappingQuery; + use fn crate::expr::body_syntax_mapping; + } } } diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs new file mode 100644 index 000000000..5cdcca082 --- /dev/null +++ b/crates/ra_hir/src/expr.rs @@ -0,0 +1,507 @@ +use std::sync::Arc; + +use rustc_hash::FxHashMap; + +use ra_arena::{Arena, RawId, impl_arena_id}; +use ra_db::{LocalSyntaxPtr, Cancelable}; +use ra_syntax::ast::{self, AstNode, LoopBodyOwner, ArgListOwner}; + +use crate::{Path, type_ref::{Mutability, TypeRef}, Name, HirDatabase, DefId, Def, name::AsName}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ExprId(RawId); +impl_arena_id!(ExprId); + +/// The body of an item (function, const etc.). +#[derive(Debug, Eq, PartialEq)] +pub struct Body { + exprs: Arena, + pats: Arena, + /// The patterns for the function's arguments. While the argument types are + /// part of the function signature, the patterns are not (they don't change + /// the external type of the function). + /// + /// If this `ExprTable` is for the body of a constant, this will just be + /// empty. + args: Vec, + /// The `ExprId` of the actual body expression. + body_expr: ExprId, +} + +/// An item body together with the mapping from syntax nodes to HIR expression +/// IDs. This is needed to go from e.g. a position in a file to the HIR +/// expression containing it; but for type inference etc., we want to operate on +/// a structure that is agnostic to the actual positions of expressions in the +/// file, so that we don't recompute the type inference whenever some whitespace +/// is typed. +#[derive(Debug, Eq, PartialEq)] +pub struct BodySyntaxMapping { + body: Arc, + expr_syntax_mapping: FxHashMap, + expr_syntax_mapping_back: FxHashMap, + pat_syntax_mapping: FxHashMap, + pat_syntax_mapping_back: FxHashMap, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Expr { + /// This is produced if syntax tree does not have a required expression piece. + Missing, + Path(Path), + If { + condition: ExprId, + then_branch: ExprId, + else_branch: Option, + }, + Block { + statements: Vec, + tail: Option, + }, + Loop { + body: ExprId, + }, + While { + condition: ExprId, + body: ExprId, + }, + For { + iterable: ExprId, + pat: PatId, + body: ExprId, + }, + Call { + callee: ExprId, + args: Vec, + }, + MethodCall { + receiver: ExprId, + method_name: Name, + args: Vec, + }, + Match { + expr: ExprId, + arms: Vec, + }, + Continue, + Break { + expr: Option, + }, + Return { + expr: Option, + }, + StructLit { + path: Option, + fields: Vec, + spread: Option, + }, + Field { + expr: ExprId, + name: Name, + }, + Try { + expr: ExprId, + }, + Cast { + expr: ExprId, + type_ref: TypeRef, + }, + Ref { + expr: ExprId, + mutability: Mutability, + }, + UnaryOp { + expr: ExprId, + op: Option, + }, +} + +pub type UnaryOp = ast::PrefixOp; + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct MatchArm { + pats: Vec, + // guard: Option, // TODO + expr: ExprId, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct StructLitField { + name: Name, + expr: ExprId, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Statement { + Let { + pat: PatId, + type_ref: Option, + initializer: Option, + }, + Expr(ExprId), +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PatId(RawId); +impl_arena_id!(PatId); + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct Pat; + +// Queries + +pub(crate) fn body_hir(db: &impl HirDatabase, def_id: DefId) -> Cancelable> { + Ok(Arc::clone(&body_syntax_mapping(db, def_id)?.body)) +} + +struct ExprCollector { + exprs: Arena, + pats: Arena, + expr_syntax_mapping: FxHashMap, + expr_syntax_mapping_back: FxHashMap, + pat_syntax_mapping: FxHashMap, + pat_syntax_mapping_back: FxHashMap, +} + +impl ExprCollector { + fn alloc_expr(&mut self, expr: Expr, syntax_ptr: LocalSyntaxPtr) -> ExprId { + let id = self.exprs.alloc(expr); + self.expr_syntax_mapping.insert(syntax_ptr, id); + self.expr_syntax_mapping_back.insert(id, syntax_ptr); + id + } + + fn alloc_pat(&mut self, pat: Pat, syntax_ptr: LocalSyntaxPtr) -> PatId { + let id = self.pats.alloc(pat); + self.pat_syntax_mapping.insert(syntax_ptr, id); + self.pat_syntax_mapping_back.insert(id, syntax_ptr); + id + } + + fn collect_expr(&mut self, expr: ast::Expr) -> ExprId { + let syntax_ptr = LocalSyntaxPtr::new(expr.syntax()); + match expr { + ast::Expr::IfExpr(e) => { + let condition = if let Some(condition) = e.condition() { + if condition.pat().is_none() { + self.collect_expr_opt(condition.expr()) + } else { + // TODO handle if let + return self.alloc_expr(Expr::Missing, syntax_ptr); + } + } else { + self.exprs.alloc(Expr::Missing) + }; + let then_branch = self.collect_block_opt(e.then_branch()); + let else_branch = e.else_branch().map(|e| self.collect_block(e)); + self.alloc_expr( + Expr::If { + condition, + then_branch, + else_branch, + }, + syntax_ptr, + ) + } + ast::Expr::BlockExpr(e) => self.collect_block_opt(e.block()), + ast::Expr::LoopExpr(e) => { + let body = self.collect_block_opt(e.loop_body()); + self.alloc_expr(Expr::Loop { body }, syntax_ptr) + } + ast::Expr::WhileExpr(e) => { + let condition = if let Some(condition) = e.condition() { + if condition.pat().is_none() { + self.collect_expr_opt(condition.expr()) + } else { + // TODO handle while let + return self.alloc_expr(Expr::Missing, syntax_ptr); + } + } else { + self.exprs.alloc(Expr::Missing) + }; + let body = self.collect_block_opt(e.loop_body()); + self.alloc_expr(Expr::While { condition, body }, syntax_ptr) + } + ast::Expr::ForExpr(e) => { + let iterable = self.collect_expr_opt(e.iterable()); + let pat = self.collect_pat_opt(e.pat()); + let body = self.collect_block_opt(e.loop_body()); + self.alloc_expr( + Expr::For { + iterable, + pat, + body, + }, + syntax_ptr, + ) + } + ast::Expr::CallExpr(e) => { + let callee = self.collect_expr_opt(e.expr()); + let args = if let Some(arg_list) = e.arg_list() { + arg_list.args().map(|e| self.collect_expr(e)).collect() + } else { + Vec::new() + }; + self.alloc_expr(Expr::Call { callee, args }, syntax_ptr) + } + ast::Expr::MethodCallExpr(e) => { + let receiver = self.collect_expr_opt(e.expr()); + let args = if let Some(arg_list) = e.arg_list() { + arg_list.args().map(|e| self.collect_expr(e)).collect() + } else { + Vec::new() + }; + let method_name = e + .name_ref() + .map(|nr| nr.as_name()) + .unwrap_or_else(Name::missing); + self.alloc_expr( + Expr::MethodCall { + receiver, + method_name, + args, + }, + syntax_ptr, + ) + } + ast::Expr::MatchExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + let arms = if let Some(match_arm_list) = e.match_arm_list() { + match_arm_list + .arms() + .map(|arm| MatchArm { + pats: arm.pats().map(|p| self.collect_pat(p)).collect(), + expr: self.collect_expr_opt(arm.expr()), + }) + .collect() + } else { + Vec::new() + }; + self.alloc_expr(Expr::Match { expr, arms }, syntax_ptr) + } + ast::Expr::PathExpr(e) => { + let path = e + .path() + .and_then(Path::from_ast) + .map(Expr::Path) + .unwrap_or(Expr::Missing); + self.alloc_expr(path, syntax_ptr) + } + ast::Expr::ContinueExpr(_e) => { + // TODO: labels + self.alloc_expr(Expr::Continue, syntax_ptr) + } + ast::Expr::BreakExpr(e) => { + let expr = e.expr().map(|e| self.collect_expr(e)); + self.alloc_expr(Expr::Break { expr }, syntax_ptr) + } + ast::Expr::ParenExpr(e) => { + let inner = self.collect_expr_opt(e.expr()); + // make the paren expr point to the inner expression as well + self.expr_syntax_mapping.insert(syntax_ptr, inner); + inner + } + ast::Expr::ReturnExpr(e) => { + let expr = e.expr().map(|e| self.collect_expr(e)); + self.alloc_expr(Expr::Return { expr }, syntax_ptr) + } + ast::Expr::StructLit(e) => { + let path = e.path().and_then(Path::from_ast); + let fields = if let Some(nfl) = e.named_field_list() { + nfl.fields() + .map(|field| StructLitField { + name: field + .name_ref() + .map(|nr| nr.as_name()) + .unwrap_or_else(Name::missing), + expr: if let Some(e) = field.expr() { + self.collect_expr(e) + } else if let Some(nr) = field.name_ref() { + // field shorthand + let id = self.exprs.alloc(Expr::Path(Path::from_name_ref(nr))); + self.expr_syntax_mapping + .insert(LocalSyntaxPtr::new(nr.syntax()), id); + self.expr_syntax_mapping_back + .insert(id, LocalSyntaxPtr::new(nr.syntax())); + id + } else { + self.exprs.alloc(Expr::Missing) + }, + }) + .collect() + } else { + Vec::new() + }; + let spread = e.spread().map(|s| self.collect_expr(s)); + self.alloc_expr( + Expr::StructLit { + path, + fields, + spread, + }, + syntax_ptr, + ) + } + ast::Expr::FieldExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + let name = e + .name_ref() + .map(|nr| nr.as_name()) + .unwrap_or_else(Name::missing); + self.alloc_expr(Expr::Field { expr, name }, syntax_ptr) + } + ast::Expr::TryExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + self.alloc_expr(Expr::Try { expr }, syntax_ptr) + } + ast::Expr::CastExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + let type_ref = TypeRef::from_ast_opt(e.type_ref()); + self.alloc_expr(Expr::Cast { expr, type_ref }, syntax_ptr) + } + ast::Expr::RefExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + let mutability = Mutability::from_mutable(e.is_mut()); + self.alloc_expr(Expr::Ref { expr, mutability }, syntax_ptr) + } + ast::Expr::PrefixExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + let op = e.op(); + self.alloc_expr(Expr::UnaryOp { expr, op }, syntax_ptr) + } + + // We should never get to these because they're handled in MatchExpr resp. StructLit: + ast::Expr::MatchArmList(_) | ast::Expr::MatchArm(_) | ast::Expr::MatchGuard(_) => { + panic!("collect_expr called on {:?}", expr) + } + ast::Expr::NamedFieldList(_) | ast::Expr::NamedField(_) => { + panic!("collect_expr called on {:?}", expr) + } + + // TODO implement HIR for these: + ast::Expr::Label(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), + ast::Expr::LambdaExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), + ast::Expr::IndexExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), + ast::Expr::TupleExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), + ast::Expr::ArrayExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), + ast::Expr::RangeExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), + ast::Expr::BinExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), + ast::Expr::Literal(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), + } + } + + fn collect_expr_opt(&mut self, expr: Option) -> ExprId { + if let Some(expr) = expr { + self.collect_expr(expr) + } else { + self.exprs.alloc(Expr::Missing) + } + } + + fn collect_block(&mut self, block: ast::Block) -> ExprId { + let statements = block + .statements() + .map(|s| match s { + ast::Stmt::LetStmt(stmt) => { + let pat = self.collect_pat_opt(stmt.pat()); + let type_ref = stmt.type_ref().map(TypeRef::from_ast); + let initializer = stmt.initializer().map(|e| self.collect_expr(e)); + Statement::Let { + pat, + type_ref, + initializer, + } + } + ast::Stmt::ExprStmt(stmt) => Statement::Expr(self.collect_expr_opt(stmt.expr())), + }) + .collect(); + let tail = block.expr().map(|e| self.collect_expr(e)); + self.alloc_expr( + Expr::Block { statements, tail }, + LocalSyntaxPtr::new(block.syntax()), + ) + } + + fn collect_block_opt(&mut self, block: Option) -> ExprId { + if let Some(block) = block { + self.collect_block(block) + } else { + self.exprs.alloc(Expr::Missing) + } + } + + fn collect_pat(&mut self, pat: ast::Pat) -> PatId { + let syntax_ptr = LocalSyntaxPtr::new(pat.syntax()); + // TODO + self.alloc_pat(Pat, syntax_ptr) + } + + fn collect_pat_opt(&mut self, pat: Option) -> PatId { + if let Some(pat) = pat { + self.collect_pat(pat) + } else { + // TODO + self.pats.alloc(Pat) + } + } + + fn into_body_syntax_mapping(self, args: Vec, body_expr: ExprId) -> BodySyntaxMapping { + let body = Body { + exprs: self.exprs, + pats: self.pats, + args, + body_expr, + }; + BodySyntaxMapping { + body: Arc::new(body), + expr_syntax_mapping: self.expr_syntax_mapping, + expr_syntax_mapping_back: self.expr_syntax_mapping_back, + pat_syntax_mapping: self.pat_syntax_mapping, + pat_syntax_mapping_back: self.pat_syntax_mapping_back, + } + } +} + +pub(crate) fn body_syntax_mapping( + db: &impl HirDatabase, + def_id: DefId, +) -> Cancelable> { + let def = def_id.resolve(db)?; + let mut collector = ExprCollector { + exprs: Arena::default(), + pats: Arena::default(), + expr_syntax_mapping: FxHashMap::default(), + expr_syntax_mapping_back: FxHashMap::default(), + pat_syntax_mapping: FxHashMap::default(), + pat_syntax_mapping_back: FxHashMap::default(), + }; + + let (body, args) = match def { + Def::Function(f) => { + let node = f.syntax(db); + let node = node.borrowed(); + + let args = if let Some(param_list) = node.param_list() { + let mut args = Vec::new(); + // TODO self param + for param in param_list.params() { + let pat = if let Some(pat) = param.pat() { + pat + } else { + continue; + }; + args.push(collector.collect_pat(pat)); + } + args + } else { + Vec::new() + }; + + let body = collector.collect_block_opt(node.body()); + (body, args) + } + // TODO: consts, etc. + _ => panic!("Trying to get body for item type without body"), + }; + + Ok(Arc::new(collector.into_body_syntax_mapping(args, body))) +} diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index 2abcec441..fea9e141b 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -32,6 +32,7 @@ mod adt; mod type_ref; mod ty; mod impl_block; +mod expr; use crate::{ db::HirDatabase, diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs index a9db932ff..661a5a26b 100644 --- a/crates/ra_hir/src/mock.rs +++ b/crates/ra_hir/src/mock.rs @@ -208,6 +208,8 @@ salsa::database_storage! { fn struct_data() for db::StructDataQuery; fn enum_data() for db::EnumDataQuery; fn impls_in_module() for db::ImplsInModuleQuery; + fn body_hir() for db::BodyHirQuery; + fn body_syntax_mapping() for db::BodySyntaxMappingQuery; } } } diff --git a/crates/ra_hir/src/path.rs b/crates/ra_hir/src/path.rs index 9fdfa0d13..2e42caffe 100644 --- a/crates/ra_hir/src/path.rs +++ b/crates/ra_hir/src/path.rs @@ -65,6 +65,14 @@ impl Path { } } + /// Converts an `ast::NameRef` into a single-identifier `Path`. + pub fn from_name_ref(name_ref: ast::NameRef) -> Path { + Path { + kind: PathKind::Plain, + segments: vec![name_ref.as_name()], + } + } + /// `true` is this path is a single identifier, like `foo` pub fn is_ident(&self) -> bool { self.kind == PathKind::Plain && self.segments.len() == 1 -- cgit v1.2.3 From 8e3e5ab2c81f238ea4e731f55eac79b74d9d84c3 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Sat, 5 Jan 2019 22:37:59 +0100 Subject: Make FnScopes use hir::Expr This was a bit complicated. I've added a wrapper type for now that does the LocalSyntaxPtr <-> ExprId translation; we might want to get rid of that or give it a nicer interface. --- crates/ra_hir/src/db.rs | 2 +- crates/ra_hir/src/expr.rs | 363 ++++++++++++++++++++++++++------ crates/ra_hir/src/function.rs | 21 +- crates/ra_hir/src/function/scope.rs | 368 ++++++++++++++++----------------- crates/ra_hir/src/lib.rs | 2 +- crates/ra_hir/src/name.rs | 10 +- crates/ra_hir/src/query_definitions.rs | 11 +- crates/ra_hir/src/ty.rs | 19 +- 8 files changed, 514 insertions(+), 282 deletions(-) (limited to 'crates/ra_hir') diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 188b96872..aaf367e08 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -31,7 +31,7 @@ pub trait HirDatabase: SyntaxDatabase use fn crate::macros::expand_macro_invocation; } - fn fn_scopes(def_id: DefId) -> Arc { + fn fn_scopes(def_id: DefId) -> Cancelable> { type FnScopesQuery; use fn query_definitions::fn_scopes; } diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs index 5cdcca082..5cf0f5e3f 100644 --- a/crates/ra_hir/src/expr.rs +++ b/crates/ra_hir/src/expr.rs @@ -4,7 +4,7 @@ use rustc_hash::FxHashMap; use ra_arena::{Arena, RawId, impl_arena_id}; use ra_db::{LocalSyntaxPtr, Cancelable}; -use ra_syntax::ast::{self, AstNode, LoopBodyOwner, ArgListOwner}; +use ra_syntax::ast::{self, AstNode, LoopBodyOwner, ArgListOwner, NameOwner}; use crate::{Path, type_ref::{Mutability, TypeRef}, Name, HirDatabase, DefId, Def, name::AsName}; @@ -21,7 +21,7 @@ pub struct Body { /// part of the function signature, the patterns are not (they don't change /// the external type of the function). /// - /// If this `ExprTable` is for the body of a constant, this will just be + /// If this `Body` is for the body of a constant, this will just be /// empty. args: Vec, /// The `ExprId` of the actual body expression. @@ -43,6 +43,43 @@ pub struct BodySyntaxMapping { pat_syntax_mapping_back: FxHashMap, } +impl Body { + pub fn expr(&self, expr: ExprId) -> &Expr { + &self.exprs[expr] + } + + pub fn pat(&self, pat: PatId) -> &Pat { + &self.pats[pat] + } + + pub fn args(&self) -> &[PatId] { + &self.args + } + + pub fn body_expr(&self) -> ExprId { + self.body_expr + } +} + +impl BodySyntaxMapping { + pub fn expr_syntax(&self, expr: ExprId) -> Option { + self.expr_syntax_mapping_back.get(&expr).cloned() + } + pub fn syntax_expr(&self, ptr: LocalSyntaxPtr) -> Option { + self.expr_syntax_mapping.get(&ptr).cloned() + } + pub fn pat_syntax(&self, pat: PatId) -> Option { + self.pat_syntax_mapping_back.get(&pat).cloned() + } + pub fn syntax_pat(&self, ptr: LocalSyntaxPtr) -> Option { + self.pat_syntax_mapping.get(&ptr).cloned() + } + + pub fn body(&self) -> &Arc { + &self.body + } +} + #[derive(Debug, Clone, Eq, PartialEq)] pub enum Expr { /// This is produced if syntax tree does not have a required expression piece. @@ -113,21 +150,26 @@ pub enum Expr { expr: ExprId, op: Option, }, + Lambda { + args: Vec, + arg_types: Vec>, + body: ExprId, + }, } pub type UnaryOp = ast::PrefixOp; #[derive(Debug, Clone, Eq, PartialEq)] pub struct MatchArm { - pats: Vec, + pub pats: Vec, // guard: Option, // TODO - expr: ExprId, + pub expr: ExprId, } #[derive(Debug, Clone, Eq, PartialEq)] pub struct StructLitField { - name: Name, - expr: ExprId, + pub name: Name, + pub expr: ExprId, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -140,12 +182,118 @@ pub enum Statement { Expr(ExprId), } +impl Expr { + pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) { + match self { + Expr::Missing => {} + Expr::Path(_) => {} + Expr::If { + condition, + then_branch, + else_branch, + } => { + f(*condition); + f(*then_branch); + if let Some(else_branch) = else_branch { + f(*else_branch); + } + } + Expr::Block { statements, tail } => { + for stmt in statements { + match stmt { + Statement::Let { initializer, .. } => { + if let Some(expr) = initializer { + f(*expr); + } + } + Statement::Expr(e) => f(*e), + } + } + if let Some(expr) = tail { + f(*expr); + } + } + Expr::Loop { body } => f(*body), + Expr::While { condition, body } => { + f(*condition); + f(*body); + } + Expr::For { iterable, body, .. } => { + f(*iterable); + f(*body); + } + Expr::Call { callee, args } => { + f(*callee); + for arg in args { + f(*arg); + } + } + Expr::MethodCall { receiver, args, .. } => { + f(*receiver); + for arg in args { + f(*arg); + } + } + Expr::Match { expr, arms } => { + f(*expr); + for arm in arms { + f(arm.expr); + } + } + Expr::Continue => {} + Expr::Break { expr } | Expr::Return { expr } => { + if let Some(expr) = expr { + f(*expr); + } + } + Expr::StructLit { fields, spread, .. } => { + for field in fields { + f(field.expr); + } + if let Some(expr) = spread { + f(*expr); + } + } + Expr::Lambda { body, .. } => { + f(*body); + } + Expr::Field { expr, .. } + | Expr::Try { expr } + | Expr::Cast { expr, .. } + | Expr::Ref { expr, .. } + | Expr::UnaryOp { expr, .. } => { + f(*expr); + } + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PatId(RawId); impl_arena_id!(PatId); #[derive(Debug, Clone, Eq, PartialEq)] -pub struct Pat; +pub enum Pat { + Missing, + Bind { + name: Name, + }, + TupleStruct { + path: Option, + args: Vec, + }, +} + +impl Pat { + pub fn walk_child_pats(&self, f: impl FnMut(PatId)) { + match self { + Pat::Missing | Pat::Bind { .. } => {} + Pat::TupleStruct { args, .. } => { + args.iter().map(|pat| *pat).for_each(f); + } + } + } +} // Queries @@ -163,6 +311,17 @@ struct ExprCollector { } impl ExprCollector { + fn new() -> Self { + ExprCollector { + exprs: Arena::default(), + pats: Arena::default(), + expr_syntax_mapping: FxHashMap::default(), + expr_syntax_mapping_back: FxHashMap::default(), + pat_syntax_mapping: FxHashMap::default(), + pat_syntax_mapping_back: FxHashMap::default(), + } + } + fn alloc_expr(&mut self, expr: Expr, syntax_ptr: LocalSyntaxPtr) -> ExprId { let id = self.exprs.alloc(expr); self.expr_syntax_mapping.insert(syntax_ptr, id); @@ -177,30 +336,63 @@ impl ExprCollector { id } + fn empty_block(&mut self) -> ExprId { + let block = Expr::Block { + statements: Vec::new(), + tail: None, + }; + self.exprs.alloc(block) + } + fn collect_expr(&mut self, expr: ast::Expr) -> ExprId { let syntax_ptr = LocalSyntaxPtr::new(expr.syntax()); match expr { ast::Expr::IfExpr(e) => { - let condition = if let Some(condition) = e.condition() { - if condition.pat().is_none() { + if let Some(pat) = e.condition().and_then(|c| c.pat()) { + // if let -- desugar to match + let pat = self.collect_pat(pat); + let match_expr = + self.collect_expr_opt(e.condition().expect("checked above").expr()); + let then_branch = self.collect_block_opt(e.then_branch()); + let else_branch = e + .else_branch() + .map(|e| self.collect_block(e)) + .unwrap_or_else(|| self.empty_block()); + let placeholder_pat = self.pats.alloc(Pat::Missing); + let arms = vec![ + MatchArm { + pats: vec![pat], + expr: then_branch, + }, + MatchArm { + pats: vec![placeholder_pat], + expr: else_branch, + }, + ]; + self.alloc_expr( + Expr::Match { + expr: match_expr, + arms, + }, + syntax_ptr, + ) + } else { + let condition = if let Some(condition) = e.condition() { self.collect_expr_opt(condition.expr()) } else { - // TODO handle if let - return self.alloc_expr(Expr::Missing, syntax_ptr); - } - } else { - self.exprs.alloc(Expr::Missing) - }; - let then_branch = self.collect_block_opt(e.then_branch()); - let else_branch = e.else_branch().map(|e| self.collect_block(e)); - self.alloc_expr( - Expr::If { - condition, - then_branch, - else_branch, - }, - syntax_ptr, - ) + self.exprs.alloc(Expr::Missing) + }; + let then_branch = self.collect_block_opt(e.then_branch()); + let else_branch = e.else_branch().map(|e| self.collect_block(e)); + self.alloc_expr( + Expr::If { + condition, + then_branch, + else_branch, + }, + syntax_ptr, + ) + } } ast::Expr::BlockExpr(e) => self.collect_block_opt(e.block()), ast::Expr::LoopExpr(e) => { @@ -368,18 +560,30 @@ impl ExprCollector { let op = e.op(); self.alloc_expr(Expr::UnaryOp { expr, op }, syntax_ptr) } - - // We should never get to these because they're handled in MatchExpr resp. StructLit: - ast::Expr::MatchArmList(_) | ast::Expr::MatchArm(_) | ast::Expr::MatchGuard(_) => { - panic!("collect_expr called on {:?}", expr) - } - ast::Expr::NamedFieldList(_) | ast::Expr::NamedField(_) => { - panic!("collect_expr called on {:?}", expr) + ast::Expr::LambdaExpr(e) => { + let mut args = Vec::new(); + let mut arg_types = Vec::new(); + if let Some(pl) = e.param_list() { + for param in pl.params() { + let pat = self.collect_pat_opt(param.pat()); + let type_ref = param.type_ref().map(TypeRef::from_ast); + args.push(pat); + arg_types.push(type_ref); + } + } + let body = self.collect_expr_opt(e.body()); + self.alloc_expr( + Expr::Lambda { + args, + arg_types, + body, + }, + syntax_ptr, + ) } // TODO implement HIR for these: ast::Expr::Label(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), - ast::Expr::LambdaExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), ast::Expr::IndexExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), ast::Expr::TupleExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), ast::Expr::ArrayExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), @@ -431,16 +635,31 @@ impl ExprCollector { fn collect_pat(&mut self, pat: ast::Pat) -> PatId { let syntax_ptr = LocalSyntaxPtr::new(pat.syntax()); - // TODO - self.alloc_pat(Pat, syntax_ptr) + match pat { + ast::Pat::BindPat(bp) => { + let name = bp + .name() + .map(|nr| nr.as_name()) + .unwrap_or_else(Name::missing); + self.alloc_pat(Pat::Bind { name }, syntax_ptr) + } + ast::Pat::TupleStructPat(p) => { + let path = p.path().and_then(Path::from_ast); + let args = p.args().map(|p| self.collect_pat(p)).collect(); + self.alloc_pat(Pat::TupleStruct { path, args }, syntax_ptr) + } + _ => { + // TODO + self.alloc_pat(Pat::Missing, syntax_ptr) + } + } } fn collect_pat_opt(&mut self, pat: Option) -> PatId { if let Some(pat) = pat { self.collect_pat(pat) } else { - // TODO - self.pats.alloc(Pat) + self.pats.alloc(Pat::Missing) } } @@ -461,47 +680,61 @@ impl ExprCollector { } } +pub(crate) fn collect_fn_body_syntax(node: ast::FnDef) -> BodySyntaxMapping { + let mut collector = ExprCollector::new(); + + let args = if let Some(param_list) = node.param_list() { + let mut args = Vec::new(); + + if let Some(self_param) = param_list.self_param() { + let self_param = LocalSyntaxPtr::new( + self_param + .self_kw() + .expect("self param without self keyword") + .syntax(), + ); + let arg = collector.alloc_pat( + Pat::Bind { + name: Name::self_param(), + }, + self_param, + ); + args.push(arg); + } + + for param in param_list.params() { + let pat = if let Some(pat) = param.pat() { + pat + } else { + continue; + }; + args.push(collector.collect_pat(pat)); + } + args + } else { + Vec::new() + }; + + let body = collector.collect_block_opt(node.body()); + collector.into_body_syntax_mapping(args, body) +} + pub(crate) fn body_syntax_mapping( db: &impl HirDatabase, def_id: DefId, ) -> Cancelable> { let def = def_id.resolve(db)?; - let mut collector = ExprCollector { - exprs: Arena::default(), - pats: Arena::default(), - expr_syntax_mapping: FxHashMap::default(), - expr_syntax_mapping_back: FxHashMap::default(), - pat_syntax_mapping: FxHashMap::default(), - pat_syntax_mapping_back: FxHashMap::default(), - }; - let (body, args) = match def { + let body_syntax_mapping = match def { Def::Function(f) => { let node = f.syntax(db); let node = node.borrowed(); - let args = if let Some(param_list) = node.param_list() { - let mut args = Vec::new(); - // TODO self param - for param in param_list.params() { - let pat = if let Some(pat) = param.pat() { - pat - } else { - continue; - }; - args.push(collector.collect_pat(pat)); - } - args - } else { - Vec::new() - }; - - let body = collector.collect_block_opt(node.body()); - (body, args) + collect_fn_body_syntax(node) } // TODO: consts, etc. _ => panic!("Trying to get body for item type without body"), }; - Ok(Arc::new(collector.into_body_syntax_mapping(args, body))) + Ok(Arc::new(body_syntax_mapping)) } diff --git a/crates/ra_hir/src/function.rs b/crates/ra_hir/src/function.rs index 75ef308ae..4dbdf81d8 100644 --- a/crates/ra_hir/src/function.rs +++ b/crates/ra_hir/src/function.rs @@ -11,9 +11,9 @@ use ra_syntax::{ ast::{self, AstNode, DocCommentsOwner, NameOwner}, }; -use crate::{DefId, DefKind, HirDatabase, ty::InferenceResult, Module, Crate, impl_block::ImplBlock}; +use crate::{DefId, DefKind, HirDatabase, ty::InferenceResult, Module, Crate, impl_block::ImplBlock, expr::{Body, BodySyntaxMapping}}; -pub use self::scope::FnScopes; +pub use self::scope::{FnScopes, ScopesWithSyntaxMapping}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Function { @@ -36,8 +36,21 @@ impl Function { ast::FnDef::cast(syntax.borrowed()).unwrap().owned() } - pub fn scopes(&self, db: &impl HirDatabase) -> Arc { - db.fn_scopes(self.def_id) + pub fn body(&self, db: &impl HirDatabase) -> Cancelable> { + db.body_hir(self.def_id) + } + + pub fn body_syntax_mapping(&self, db: &impl HirDatabase) -> Cancelable> { + db.body_syntax_mapping(self.def_id) + } + + pub fn scopes(&self, db: &impl HirDatabase) -> Cancelable { + let scopes = db.fn_scopes(self.def_id)?; + let syntax_mapping = db.body_syntax_mapping(self.def_id)?; + Ok(ScopesWithSyntaxMapping { + scopes, + syntax_mapping, + }) } pub fn signature_info(&self, db: &impl HirDatabase) -> Option { diff --git a/crates/ra_hir/src/function/scope.rs b/crates/ra_hir/src/function/scope.rs index 42bfe4f32..0607a99cb 100644 --- a/crates/ra_hir/src/function/scope.rs +++ b/crates/ra_hir/src/function/scope.rs @@ -1,14 +1,16 @@ +use std::sync::Arc; + use rustc_hash::{FxHashMap, FxHashSet}; use ra_syntax::{ AstNode, SyntaxNodeRef, TextUnit, TextRange, algo::generate, - ast::{self, ArgListOwner, LoopBodyOwner, NameOwner}, + ast, }; use ra_arena::{Arena, RawId, impl_arena_id}; use ra_db::LocalSyntaxPtr; -use crate::{Name, AsName}; +use crate::{Name, AsName, expr::{PatId, ExprId, Pat, Expr, Body, Statement, BodySyntaxMapping}}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ScopeId(RawId); @@ -16,15 +18,15 @@ impl_arena_id!(ScopeId); #[derive(Debug, PartialEq, Eq)] pub struct FnScopes { - pub self_param: Option, + body: Arc, scopes: Arena, - scope_for: FxHashMap, + scope_for: FxHashMap, } #[derive(Debug, PartialEq, Eq)] pub struct ScopeEntry { name: Name, - ptr: LocalSyntaxPtr, + pat: PatId, } #[derive(Debug, PartialEq, Eq)] @@ -34,28 +36,101 @@ pub struct ScopeData { } impl FnScopes { - pub(crate) fn new(fn_def: ast::FnDef) -> FnScopes { + pub(crate) fn new(body: Arc) -> FnScopes { let mut scopes = FnScopes { - self_param: fn_def - .param_list() - .and_then(|it| it.self_param()) - .map(|it| LocalSyntaxPtr::new(it.syntax())), + body: body.clone(), scopes: Arena::default(), scope_for: FxHashMap::default(), }; let root = scopes.root_scope(); - scopes.add_params_bindings(root, fn_def.param_list()); - if let Some(body) = fn_def.body() { - compute_block_scopes(body, &mut scopes, root) - } + scopes.add_params_bindings(root, body.args()); + compute_expr_scopes(body.body_expr(), &body, &mut scopes, root); scopes } pub fn entries(&self, scope: ScopeId) -> &[ScopeEntry] { &self.scopes[scope].entries } + pub fn scope_chain_for<'a>(&'a self, expr: ExprId) -> impl Iterator + 'a { + generate(self.scope_for(expr), move |&scope| { + self.scopes[scope].parent + }) + } + + pub fn resolve_local_name<'a>( + &'a self, + context_expr: ExprId, + name: Name, + ) -> Option<&'a ScopeEntry> { + let mut shadowed = FxHashSet::default(); + let ret = self + .scope_chain_for(context_expr) + .flat_map(|scope| self.entries(scope).iter()) + .filter(|entry| shadowed.insert(entry.name())) + .filter(|entry| entry.name() == &name) + .nth(0); + ret + } + + fn root_scope(&mut self) -> ScopeId { + self.scopes.alloc(ScopeData { + parent: None, + entries: vec![], + }) + } + fn new_scope(&mut self, parent: ScopeId) -> ScopeId { + self.scopes.alloc(ScopeData { + parent: Some(parent), + entries: vec![], + }) + } + fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) { + match body.pat(pat) { + Pat::Bind { name } => self.scopes[scope].entries.push(ScopeEntry { + name: name.clone(), + pat, + }), + p => p.walk_child_pats(|pat| self.add_bindings(body, scope, pat)), + } + } + fn add_params_bindings(&mut self, scope: ScopeId, params: &[PatId]) { + let body = Arc::clone(&self.body); + params + .into_iter() + .for_each(|it| self.add_bindings(&body, scope, *it)); + } + fn set_scope(&mut self, node: ExprId, scope: ScopeId) { + self.scope_for.insert(node, scope); + } + fn scope_for(&self, expr: ExprId) -> Option { + self.scope_for.get(&expr).map(|&scope| scope) + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ScopesWithSyntaxMapping { + pub syntax_mapping: Arc, + pub scopes: Arc, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ScopeEntryWithSyntax { + name: Name, + ptr: LocalSyntaxPtr, +} + +impl ScopeEntryWithSyntax { + pub fn name(&self) -> &Name { + &self.name + } + pub fn ptr(&self) -> LocalSyntaxPtr { + self.ptr + } +} + +impl ScopesWithSyntaxMapping { pub fn scope_chain<'a>(&'a self, node: SyntaxNodeRef) -> impl Iterator + 'a { generate(self.scope_for(node), move |&scope| { - self.scopes[scope].parent + self.scopes.scopes[scope].parent }) } pub fn scope_chain_for_offset<'a>( @@ -63,26 +138,30 @@ impl FnScopes { offset: TextUnit, ) -> impl Iterator + 'a { let scope = self + .scopes .scope_for .iter() - // find containin scope + .filter_map(|(id, scope)| Some((self.syntax_mapping.expr_syntax(*id)?, scope))) + // find containing scope .min_by_key(|(ptr, _scope)| { ( !(ptr.range().start() <= offset && offset <= ptr.range().end()), ptr.range().len(), ) }) - .map(|(ptr, scope)| self.adjust(*ptr, *scope, offset)); + .map(|(ptr, scope)| self.adjust(ptr, *scope, offset)); - generate(scope, move |&scope| self.scopes[scope].parent) + generate(scope, move |&scope| self.scopes.scopes[scope].parent) } // XXX: during completion, cursor might be outside of any particular // expression. Try to figure out the correct scope... fn adjust(&self, ptr: LocalSyntaxPtr, original_scope: ScopeId, offset: TextUnit) -> ScopeId { let r = ptr.range(); let child_scopes = self + .scopes .scope_for .iter() + .filter_map(|(id, scope)| Some((self.syntax_mapping.expr_syntax(*id)?, scope))) .map(|(ptr, scope)| (ptr.range(), scope)) .filter(|(range, _)| range.start() <= offset && range.is_subrange(&r) && *range != r); @@ -100,22 +179,27 @@ impl FnScopes { .unwrap_or(original_scope) } - pub fn resolve_local_name<'a>(&'a self, name_ref: ast::NameRef) -> Option<&'a ScopeEntry> { + pub fn resolve_local_name(&self, name_ref: ast::NameRef) -> Option { let mut shadowed = FxHashSet::default(); let name = name_ref.as_name(); let ret = self .scope_chain(name_ref.syntax()) - .flat_map(|scope| self.entries(scope).iter()) + .flat_map(|scope| self.scopes.entries(scope).iter()) .filter(|entry| shadowed.insert(entry.name())) .filter(|entry| entry.name() == &name) .nth(0); - ret + ret.and_then(|entry| { + Some(ScopeEntryWithSyntax { + name: entry.name().clone(), + ptr: self.syntax_mapping.pat_syntax(entry.pat())?, + }) + }) } pub fn find_all_refs(&self, pat: ast::BindPat) -> Vec { let fn_def = pat.syntax().ancestors().find_map(ast::FnDef::cast).unwrap(); let name_ptr = LocalSyntaxPtr::new(pat.syntax()); - let refs: Vec<_> = fn_def + fn_def .syntax() .descendants() .filter_map(ast::NameRef::cast) @@ -127,203 +211,95 @@ impl FnScopes { name: name_ref.syntax().text().to_string(), range: name_ref.syntax().range(), }) - .collect(); - - refs + .collect() } - fn root_scope(&mut self) -> ScopeId { - self.scopes.alloc(ScopeData { - parent: None, - entries: vec![], - }) - } - fn new_scope(&mut self, parent: ScopeId) -> ScopeId { - self.scopes.alloc(ScopeData { - parent: Some(parent), - entries: vec![], - }) - } - fn add_bindings(&mut self, scope: ScopeId, pat: ast::Pat) { - let entries = pat - .syntax() - .descendants() - .filter_map(ast::BindPat::cast) - .filter_map(ScopeEntry::new); - self.scopes[scope].entries.extend(entries); - } - fn add_params_bindings(&mut self, scope: ScopeId, params: Option) { - params - .into_iter() - .flat_map(|it| it.params()) - .filter_map(|it| it.pat()) - .for_each(|it| self.add_bindings(scope, it)); - } - fn set_scope(&mut self, node: SyntaxNodeRef, scope: ScopeId) { - self.scope_for.insert(LocalSyntaxPtr::new(node), scope); - } fn scope_for(&self, node: SyntaxNodeRef) -> Option { node.ancestors() .map(LocalSyntaxPtr::new) - .filter_map(|it| self.scope_for.get(&it).map(|&scope| scope)) + .filter_map(|ptr| self.syntax_mapping.syntax_expr(ptr)) + .filter_map(|it| self.scopes.scope_for(it)) .next() } } impl ScopeEntry { - fn new(pat: ast::BindPat) -> Option { - let name = pat.name()?.as_name(); - let res = ScopeEntry { - name, - ptr: LocalSyntaxPtr::new(pat.syntax()), - }; - Some(res) - } pub fn name(&self) -> &Name { &self.name } - pub fn ptr(&self) -> LocalSyntaxPtr { - self.ptr + pub fn pat(&self) -> PatId { + self.pat } } -fn compute_block_scopes(block: ast::Block, scopes: &mut FnScopes, mut scope: ScopeId) { - // A hack for completion :( - scopes.set_scope(block.syntax(), scope); - for stmt in block.statements() { +fn compute_block_scopes( + statements: &[Statement], + tail: Option, + body: &Body, + scopes: &mut FnScopes, + mut scope: ScopeId, +) { + for stmt in statements { match stmt { - ast::Stmt::LetStmt(stmt) => { - if let Some(expr) = stmt.initializer() { - scopes.set_scope(expr.syntax(), scope); - compute_expr_scopes(expr, scopes, scope); + Statement::Let { + pat, initializer, .. + } => { + if let Some(expr) = initializer { + scopes.set_scope(*expr, scope); + compute_expr_scopes(*expr, body, scopes, scope); } scope = scopes.new_scope(scope); - if let Some(pat) = stmt.pat() { - scopes.add_bindings(scope, pat); - } + scopes.add_bindings(body, scope, *pat); } - ast::Stmt::ExprStmt(expr_stmt) => { - if let Some(expr) = expr_stmt.expr() { - scopes.set_scope(expr.syntax(), scope); - compute_expr_scopes(expr, scopes, scope); - } + Statement::Expr(expr) => { + scopes.set_scope(*expr, scope); + compute_expr_scopes(*expr, body, scopes, scope); } } } - if let Some(expr) = block.expr() { - scopes.set_scope(expr.syntax(), scope); - compute_expr_scopes(expr, scopes, scope); + if let Some(expr) = tail { + compute_expr_scopes(expr, body, scopes, scope); } } -fn compute_expr_scopes(expr: ast::Expr, scopes: &mut FnScopes, scope: ScopeId) { - match expr { - ast::Expr::IfExpr(e) => { - let cond_scope = e - .condition() - .and_then(|cond| compute_cond_scopes(cond, scopes, scope)); - if let Some(block) = e.then_branch() { - compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope)); - } - if let Some(block) = e.else_branch() { - compute_block_scopes(block, scopes, scope); - } - } - ast::Expr::BlockExpr(e) => { - if let Some(block) = e.block() { - compute_block_scopes(block, scopes, scope); - } - } - ast::Expr::LoopExpr(e) => { - if let Some(block) = e.loop_body() { - compute_block_scopes(block, scopes, scope); - } - } - ast::Expr::WhileExpr(e) => { - let cond_scope = e - .condition() - .and_then(|cond| compute_cond_scopes(cond, scopes, scope)); - if let Some(block) = e.loop_body() { - compute_block_scopes(block, scopes, cond_scope.unwrap_or(scope)); - } +fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut FnScopes, scope: ScopeId) { + scopes.set_scope(expr, scope); + match body.expr(expr) { + Expr::Block { statements, tail } => { + compute_block_scopes(&statements, *tail, body, scopes, scope); } - ast::Expr::ForExpr(e) => { - if let Some(expr) = e.iterable() { - compute_expr_scopes(expr, scopes, scope); - } - let mut scope = scope; - if let Some(pat) = e.pat() { - scope = scopes.new_scope(scope); - scopes.add_bindings(scope, pat); - } - if let Some(block) = e.loop_body() { - compute_block_scopes(block, scopes, scope); - } - } - ast::Expr::LambdaExpr(e) => { + Expr::For { + iterable, + pat, + body: body_expr, + } => { + compute_expr_scopes(*iterable, body, scopes, scope); let scope = scopes.new_scope(scope); - scopes.add_params_bindings(scope, e.param_list()); - if let Some(body) = e.body() { - scopes.set_scope(body.syntax(), scope); - compute_expr_scopes(body, scopes, scope); - } + scopes.add_bindings(body, scope, *pat); + compute_expr_scopes(*body_expr, body, scopes, scope); } - ast::Expr::CallExpr(e) => { - compute_call_scopes(e.expr(), e.arg_list(), scopes, scope); - } - ast::Expr::MethodCallExpr(e) => { - compute_call_scopes(e.expr(), e.arg_list(), scopes, scope); + Expr::Lambda { + args, + body: body_expr, + .. + } => { + let scope = scopes.new_scope(scope); + scopes.add_params_bindings(scope, &args); + compute_expr_scopes(*body_expr, body, scopes, scope); } - ast::Expr::MatchExpr(e) => { - if let Some(expr) = e.expr() { - compute_expr_scopes(expr, scopes, scope); - } - for arm in e.match_arm_list().into_iter().flat_map(|it| it.arms()) { + Expr::Match { expr, arms } => { + compute_expr_scopes(*expr, body, scopes, scope); + for arm in arms { let scope = scopes.new_scope(scope); - for pat in arm.pats() { - scopes.add_bindings(scope, pat); - } - if let Some(expr) = arm.expr() { - compute_expr_scopes(expr, scopes, scope); + for pat in &arm.pats { + scopes.add_bindings(body, scope, *pat); } + scopes.set_scope(arm.expr, scope); + compute_expr_scopes(arm.expr, body, scopes, scope); } } - _ => expr - .syntax() - .children() - .filter_map(ast::Expr::cast) - .for_each(|expr| compute_expr_scopes(expr, scopes, scope)), + e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)), }; - - fn compute_call_scopes( - receiver: Option, - arg_list: Option, - scopes: &mut FnScopes, - scope: ScopeId, - ) { - arg_list - .into_iter() - .flat_map(|it| it.args()) - .chain(receiver) - .for_each(|expr| compute_expr_scopes(expr, scopes, scope)); - } - - fn compute_cond_scopes( - cond: ast::Condition, - scopes: &mut FnScopes, - scope: ScopeId, - ) -> Option { - if let Some(expr) = cond.expr() { - compute_expr_scopes(expr, scopes, scope); - } - if let Some(pat) = cond.pat() { - let s = scopes.new_scope(scope); - scopes.add_bindings(s, pat); - Some(s) - } else { - None - } - } } #[derive(Debug)] @@ -338,6 +314,8 @@ mod tests { use ra_syntax::SourceFileNode; use test_utils::{extract_offset, assert_eq_text}; + use crate::expr; + use super::*; fn do_check(code: &str, expected: &[&str]) { @@ -353,15 +331,20 @@ mod tests { let file = SourceFileNode::parse(&code); 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 scopes = FnScopes::new(fn_def); + let body_hir = expr::collect_fn_body_syntax(fn_def); + let scopes = FnScopes::new(Arc::clone(body_hir.body())); + let scopes = ScopesWithSyntaxMapping { + scopes: Arc::new(scopes), + syntax_mapping: Arc::new(body_hir), + }; let actual = scopes .scope_chain(marker.syntax()) - .flat_map(|scope| scopes.entries(scope)) + .flat_map(|scope| scopes.scopes.entries(scope)) .map(|it| it.name().to_string()) .collect::>() .join("\n"); let expected = expected.join("\n"); - assert_eq_text!(&actual, &expected); + assert_eq_text!(&expected, &actual); } #[test] @@ -389,7 +372,7 @@ mod tests { } #[test] - fn test_metod_call_scope() { + fn test_method_call_scope() { do_check( r" fn quux() { @@ -445,10 +428,15 @@ mod tests { let fn_def: ast::FnDef = find_node_at_offset(file.syntax(), off).unwrap(); let name_ref: ast::NameRef = find_node_at_offset(file.syntax(), off).unwrap(); - let scopes = FnScopes::new(fn_def); + let body_hir = expr::collect_fn_body_syntax(fn_def); + let scopes = FnScopes::new(Arc::clone(body_hir.body())); + let scopes = ScopesWithSyntaxMapping { + scopes: Arc::new(scopes), + syntax_mapping: Arc::new(body_hir), + }; let local_name_entry = scopes.resolve_local_name(name_ref).unwrap(); - let local_name = local_name_entry.ptr().resolve(&file); + let local_name = local_name_entry.ptr(); let expected_name = find_node_at_offset::(file.syntax(), expected_offset.into()).unwrap(); assert_eq!(local_name.range(), expected_name.syntax().range()); diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index fea9e141b..82dc287de 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -47,7 +47,7 @@ pub use self::{ ids::{HirFileId, DefId, DefLoc, MacroCallId, MacroCallLoc}, macros::{MacroDef, MacroInput, MacroExpansion}, module::{Module, ModuleId, Problem, nameres::{ItemMap, PerNs, Namespace}, ModuleScope, Resolution}, - function::{Function, FnScopes}, + function::{Function, FnScopes, ScopesWithSyntaxMapping}, adt::{Struct, Enum}, ty::Ty, impl_block::{ImplBlock, ImplItem}, diff --git a/crates/ra_hir/src/name.rs b/crates/ra_hir/src/name.rs index 017caf442..6f95b168f 100644 --- a/crates/ra_hir/src/name.rs +++ b/crates/ra_hir/src/name.rs @@ -31,6 +31,10 @@ impl Name { Name::new("[missing name]".into()) } + pub(crate) fn self_param() -> Name { + Name::new("self".into()) + } + pub(crate) fn tuple_field_name(idx: usize) -> Name { Name::new(idx.to_string().into()) } @@ -51,7 +55,8 @@ impl Name { "u128" => KnownName::U128, "f32" => KnownName::F32, "f64" => KnownName::F64, - "Self" => KnownName::Self_, + "Self" => KnownName::SelfType, + "self" => KnownName::SelfParam, _ => return None, }; Some(name) @@ -104,5 +109,6 @@ pub(crate) enum KnownName { F32, F64, - Self_, + SelfType, + SelfParam, } diff --git a/crates/ra_hir/src/query_definitions.rs b/crates/ra_hir/src/query_definitions.rs index a5d99beda..d9ee9d37f 100644 --- a/crates/ra_hir/src/query_definitions.rs +++ b/crates/ra_hir/src/query_definitions.rs @@ -11,7 +11,7 @@ use ra_syntax::{ use ra_db::{SourceRootId, Cancelable,}; use crate::{ - SourceFileItems, SourceItemId, DefKind, Function, DefId, Name, AsName, HirFileId, + SourceFileItems, SourceItemId, DefKind, DefId, Name, AsName, HirFileId, MacroCallLoc, db::HirDatabase, function::FnScopes, @@ -23,11 +23,10 @@ use crate::{ adt::{StructData, EnumData}, }; -pub(super) fn fn_scopes(db: &impl HirDatabase, def_id: DefId) -> Arc { - let function = Function::new(def_id); - let syntax = function.syntax(db); - let res = FnScopes::new(syntax.borrowed()); - Arc::new(res) +pub(super) fn fn_scopes(db: &impl HirDatabase, def_id: DefId) -> Cancelable> { + let body = db.body_hir(def_id)?; + let res = FnScopes::new(body); + Ok(Arc::new(res)) } pub(super) fn struct_data(db: &impl HirDatabase, def_id: DefId) -> Cancelable> { diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index e33762e0d..8c320a705 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -31,10 +31,11 @@ use ra_syntax::{ }; use crate::{ - Def, DefId, FnScopes, Module, Function, Struct, Enum, Path, Name, AsName, ImplBlock, + Def, DefId, Module, Function, Struct, Enum, Path, Name, AsName, ImplBlock, db::HirDatabase, type_ref::{TypeRef, Mutability}, name::KnownName, + ScopesWithSyntaxMapping, }; /// The ID of a type variable. @@ -305,7 +306,7 @@ impl Ty { return Ok(Ty::Uint(uint_ty)); } else if let Some(float_ty) = primitive::FloatTy::from_name(name) { return Ok(Ty::Float(float_ty)); - } else if name.as_known_name() == Some(KnownName::Self_) { + } else if name.as_known_name() == Some(KnownName::SelfType) { return Ty::from_hir_opt(db, module, None, impl_block.map(|i| i.target_type())); } } @@ -515,7 +516,7 @@ impl InferenceResult { #[derive(Clone, Debug)] struct InferenceContext<'a, D: HirDatabase> { db: &'a D, - scopes: Arc, + scopes: ScopesWithSyntaxMapping, /// The self param for the current method, if it exists. self_param: Option, module: Module, @@ -529,7 +530,7 @@ struct InferenceContext<'a, D: HirDatabase> { impl<'a, D: HirDatabase> InferenceContext<'a, D> { fn new( db: &'a D, - scopes: Arc, + scopes: ScopesWithSyntaxMapping, module: Module, impl_block: Option, ) -> Self { @@ -826,10 +827,6 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { self.infer_expr_opt(e.expr(), &Expectation::none())?; Ty::Never } - ast::Expr::MatchArmList(_) | ast::Expr::MatchArm(_) | ast::Expr::MatchGuard(_) => { - // Can this even occur outside of a match expression? - Ty::Unknown - } ast::Expr::StructLit(e) => { let (ty, def_id) = self.resolve_variant(e.path())?; if let Some(nfl) = e.named_field_list() { @@ -845,10 +842,6 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { } ty } - ast::Expr::NamedFieldList(_) | ast::Expr::NamedField(_) => { - // Can this even occur outside of a struct literal? - Ty::Unknown - } ast::Expr::IndexExpr(_e) => Ty::Unknown, ast::Expr::FieldExpr(e) => { let receiver_ty = self.infer_expr_opt(e.expr(), &Expectation::none())?; @@ -1016,7 +1009,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { pub fn infer(db: &impl HirDatabase, def_id: DefId) -> Cancelable> { let function = Function::new(def_id); // TODO: consts also need inference - let scopes = function.scopes(db); + let scopes = function.scopes(db)?; let module = function.module(db)?; let impl_block = function.impl_block(db)?; let mut ctx = InferenceContext::new(db, scopes, module, impl_block); -- cgit v1.2.3 From e5a6cf815372150ad40dee995b7b89f29e701427 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Sun, 6 Jan 2019 00:33:58 +0100 Subject: Various small code review improvements --- crates/ra_hir/src/expr.rs | 31 ++++++++++++++++++------------- crates/ra_hir/src/function/scope.rs | 12 +++++------- crates/ra_hir/src/source_binder.rs | 10 ++++------ 3 files changed, 27 insertions(+), 26 deletions(-) (limited to 'crates/ra_hir') diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs index 5cf0f5e3f..6866fc2ac 100644 --- a/crates/ra_hir/src/expr.rs +++ b/crates/ra_hir/src/expr.rs @@ -1,3 +1,4 @@ +use std::ops::Index; use std::sync::Arc; use rustc_hash::FxHashMap; @@ -44,14 +45,6 @@ pub struct BodySyntaxMapping { } impl Body { - pub fn expr(&self, expr: ExprId) -> &Expr { - &self.exprs[expr] - } - - pub fn pat(&self, pat: PatId) -> &Pat { - &self.pats[pat] - } - pub fn args(&self) -> &[PatId] { &self.args } @@ -61,6 +54,22 @@ impl Body { } } +impl Index for Body { + type Output = Expr; + + fn index(&self, expr: ExprId) -> &Expr { + &self.exprs[expr] + } +} + +impl Index for Body { + type Output = Pat; + + fn index(&self, pat: PatId) -> &Pat { + &self.pats[pat] + } +} + impl BodySyntaxMapping { pub fn expr_syntax(&self, expr: ExprId) -> Option { self.expr_syntax_mapping_back.get(&expr).cloned() @@ -377,11 +386,7 @@ impl ExprCollector { syntax_ptr, ) } else { - let condition = if let Some(condition) = e.condition() { - self.collect_expr_opt(condition.expr()) - } else { - self.exprs.alloc(Expr::Missing) - }; + let condition = self.collect_expr_opt(e.condition().and_then(|c| c.expr())); let then_branch = self.collect_block_opt(e.then_branch()); let else_branch = e.else_branch().map(|e| self.collect_block(e)); self.alloc_expr( diff --git a/crates/ra_hir/src/function/scope.rs b/crates/ra_hir/src/function/scope.rs index 0607a99cb..0a12f0b35 100644 --- a/crates/ra_hir/src/function/scope.rs +++ b/crates/ra_hir/src/function/scope.rs @@ -66,8 +66,7 @@ impl FnScopes { .scope_chain_for(context_expr) .flat_map(|scope| self.entries(scope).iter()) .filter(|entry| shadowed.insert(entry.name())) - .filter(|entry| entry.name() == &name) - .nth(0); + .find(|entry| entry.name() == &name); ret } @@ -84,7 +83,7 @@ impl FnScopes { }) } fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) { - match body.pat(pat) { + match &body[pat] { Pat::Bind { name } => self.scopes[scope].entries.push(ScopeEntry { name: name.clone(), pat, @@ -96,7 +95,7 @@ impl FnScopes { let body = Arc::clone(&self.body); params .into_iter() - .for_each(|it| self.add_bindings(&body, scope, *it)); + .for_each(|pat| self.add_bindings(&body, scope, *pat)); } fn set_scope(&mut self, node: ExprId, scope: ScopeId) { self.scope_for.insert(node, scope); @@ -218,8 +217,7 @@ impl ScopesWithSyntaxMapping { node.ancestors() .map(LocalSyntaxPtr::new) .filter_map(|ptr| self.syntax_mapping.syntax_expr(ptr)) - .filter_map(|it| self.scopes.scope_for(it)) - .next() + .find_map(|it| self.scopes.scope_for(it)) } } @@ -264,7 +262,7 @@ fn compute_block_scopes( fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut FnScopes, scope: ScopeId) { scopes.set_scope(expr, scope); - match body.expr(expr) { + match &body[expr] { Expr::Block { statements, tail } => { compute_block_scopes(&statements, *tail, body, scopes, scope); } diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 551f44d4e..29a3960e9 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -92,12 +92,10 @@ pub fn function_from_position( position: FilePosition, ) -> Cancelable> { let file = db.source_file(position.file_id); - let fn_def = if let Some(f) = find_node_at_offset::(file.syntax(), position.offset) - { - f - } else { - return Ok(None); - }; + let fn_def = ctry!(find_node_at_offset::( + file.syntax(), + position.offset + )); function_from_source(db, position.file_id, fn_def) } -- cgit v1.2.3 From 98957f4e6f66469310072dff5dfc3e521a7cd555 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Sun, 6 Jan 2019 01:00:34 +0100 Subject: Add fn signature query --- crates/ra_hir/src/db.rs | 7 ++++- crates/ra_hir/src/function.rs | 60 ++++++++++++++++++++++++++++++++++++++++++- crates/ra_hir/src/lib.rs | 2 +- crates/ra_hir/src/mock.rs | 1 + crates/ra_hir/src/name.rs | 4 +++ crates/ra_hir/src/path.rs | 14 +++++++--- crates/ra_hir/src/type_ref.rs | 4 +++ 7 files changed, 85 insertions(+), 7 deletions(-) (limited to 'crates/ra_hir') diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index aaf367e08..96a3c60b9 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -7,7 +7,7 @@ use crate::{ DefLoc, DefId, MacroCallLoc, MacroCallId, Name, HirFileId, SourceFileItems, SourceItemId, query_definitions, - FnScopes, + FnSignature, FnScopes, macros::MacroExpansion, module::{ModuleId, ModuleTree, ModuleSource, nameres::{ItemMap, InputModuleItems}}, @@ -103,6 +103,11 @@ pub trait HirDatabase: SyntaxDatabase type BodySyntaxMappingQuery; use fn crate::expr::body_syntax_mapping; } + + fn fn_signature(def_id: DefId) -> Arc { + type FnSignatureQuery; + use fn crate::function::fn_signature; + } } } diff --git a/crates/ra_hir/src/function.rs b/crates/ra_hir/src/function.rs index 4dbdf81d8..4627be071 100644 --- a/crates/ra_hir/src/function.rs +++ b/crates/ra_hir/src/function.rs @@ -11,7 +11,7 @@ use ra_syntax::{ ast::{self, AstNode, DocCommentsOwner, NameOwner}, }; -use crate::{DefId, DefKind, HirDatabase, ty::InferenceResult, Module, Crate, impl_block::ImplBlock, expr::{Body, BodySyntaxMapping}}; +use crate::{DefId, DefKind, HirDatabase, ty::InferenceResult, Module, Crate, impl_block::ImplBlock, expr::{Body, BodySyntaxMapping}, type_ref::{TypeRef, Mutability}, Name}; pub use self::scope::{FnScopes, ScopesWithSyntaxMapping}; @@ -53,6 +53,10 @@ impl Function { }) } + pub fn signature(&self, db: &impl HirDatabase) -> Arc { + db.fn_signature(self.def_id) + } + pub fn signature_info(&self, db: &impl HirDatabase) -> Option { let syntax = self.syntax(db); FnSignatureInfo::new(syntax.borrowed()) @@ -76,6 +80,60 @@ impl Function { } } +/// The declared signature of a function. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FnSignature { + args: Vec, + ret_type: TypeRef, +} + +impl FnSignature { + pub fn args(&self) -> &[TypeRef] { + &self.args + } + + pub fn ret_type(&self) -> &TypeRef { + &self.ret_type + } +} + +pub(crate) fn fn_signature(db: &impl HirDatabase, def_id: DefId) -> Arc { + let func = Function::new(def_id); + let syntax = func.syntax(db); + let node = syntax.borrowed(); + let mut args = Vec::new(); + if let Some(param_list) = node.param_list() { + if let Some(self_param) = param_list.self_param() { + let self_type = if let Some(type_ref) = self_param.type_ref() { + TypeRef::from_ast(type_ref) + } else { + let self_type = TypeRef::Path(Name::self_type().into()); + match self_param.flavor() { + ast::SelfParamFlavor::Owned => self_type, + ast::SelfParamFlavor::Ref => { + TypeRef::Reference(Box::new(self_type), Mutability::Shared) + } + ast::SelfParamFlavor::MutRef => { + TypeRef::Reference(Box::new(self_type), Mutability::Mut) + } + } + }; + args.push(self_type); + } + for param in param_list.params() { + let type_ref = TypeRef::from_ast_opt(param.type_ref()); + args.push(type_ref); + } + } + let ret_type = if let Some(type_ref) = node.ret_type().and_then(|rt| rt.type_ref()) { + TypeRef::from_ast(type_ref) + } else { + TypeRef::unit() + }; + let sig = FnSignature { args, ret_type }; + Arc::new(sig) +} + #[derive(Debug, Clone)] pub struct FnSignatureInfo { pub name: String, diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index 82dc287de..d600b91df 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -47,7 +47,7 @@ pub use self::{ ids::{HirFileId, DefId, DefLoc, MacroCallId, MacroCallLoc}, macros::{MacroDef, MacroInput, MacroExpansion}, module::{Module, ModuleId, Problem, nameres::{ItemMap, PerNs, Namespace}, ModuleScope, Resolution}, - function::{Function, FnScopes, ScopesWithSyntaxMapping}, + function::{Function, FnSignature, FnScopes, ScopesWithSyntaxMapping}, adt::{Struct, Enum}, ty::Ty, impl_block::{ImplBlock, ImplItem}, diff --git a/crates/ra_hir/src/mock.rs b/crates/ra_hir/src/mock.rs index 661a5a26b..8d176662c 100644 --- a/crates/ra_hir/src/mock.rs +++ b/crates/ra_hir/src/mock.rs @@ -210,6 +210,7 @@ salsa::database_storage! { fn impls_in_module() for db::ImplsInModuleQuery; fn body_hir() for db::BodyHirQuery; fn body_syntax_mapping() for db::BodySyntaxMappingQuery; + fn fn_signature() for db::FnSignatureQuery; } } } diff --git a/crates/ra_hir/src/name.rs b/crates/ra_hir/src/name.rs index 6f95b168f..90229bc54 100644 --- a/crates/ra_hir/src/name.rs +++ b/crates/ra_hir/src/name.rs @@ -35,6 +35,10 @@ impl Name { Name::new("self".into()) } + pub(crate) fn self_type() -> Name { + Name::new("Self".into()) + } + pub(crate) fn tuple_field_name(idx: usize) -> Name { Name::new(idx.to_string().into()) } diff --git a/crates/ra_hir/src/path.rs b/crates/ra_hir/src/path.rs index 2e42caffe..dcf4cf8b6 100644 --- a/crates/ra_hir/src/path.rs +++ b/crates/ra_hir/src/path.rs @@ -67,10 +67,7 @@ impl Path { /// Converts an `ast::NameRef` into a single-identifier `Path`. pub fn from_name_ref(name_ref: ast::NameRef) -> Path { - Path { - kind: PathKind::Plain, - segments: vec![name_ref.as_name()], - } + name_ref.as_name().into() } /// `true` is this path is a single identifier, like `foo` @@ -92,6 +89,15 @@ impl Path { } } +impl From for Path { + fn from(name: Name) -> Path { + Path { + kind: PathKind::Plain, + segments: vec![name], + } + } +} + fn expand_use_tree( prefix: Option, tree: ast::UseTree, diff --git a/crates/ra_hir/src/type_ref.rs b/crates/ra_hir/src/type_ref.rs index b36bb35d8..859f330c2 100644 --- a/crates/ra_hir/src/type_ref.rs +++ b/crates/ra_hir/src/type_ref.rs @@ -107,4 +107,8 @@ impl TypeRef { TypeRef::Error } } + + pub fn unit() -> TypeRef { + TypeRef::Tuple(Vec::new()) + } } -- cgit v1.2.3