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_analysis/src/db.rs | 2 + crates/ra_db/src/syntax_ptr.rs | 2 +- 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 + crates/ra_syntax/src/ast/generated.rs | 10 +- crates/ra_syntax/src/grammar.ron | 4 +- 9 files changed, 542 insertions(+), 4 deletions(-) create mode 100644 crates/ra_hir/src/expr.rs diff --git a/crates/ra_analysis/src/db.rs b/crates/ra_analysis/src/db.rs index 5422a400b..074a7a7f6 100644 --- a/crates/ra_analysis/src/db.rs +++ b/crates/ra_analysis/src/db.rs @@ -106,6 +106,8 @@ salsa::database_storage! { fn struct_data() for hir::db::StructDataQuery; fn enum_data() for hir::db::EnumDataQuery; fn impls_in_module() for hir::db::ImplsInModuleQuery; + fn body_hir() for hir::db::BodyHirQuery; + fn body_syntax_mapping() for hir::db::BodySyntaxMappingQuery; } } } diff --git a/crates/ra_db/src/syntax_ptr.rs b/crates/ra_db/src/syntax_ptr.rs index 744cb2352..5bfcedf2b 100644 --- a/crates/ra_db/src/syntax_ptr.rs +++ b/crates/ra_db/src/syntax_ptr.rs @@ -1,6 +1,6 @@ use ra_syntax::{SourceFileNode, SyntaxKind, SyntaxNode, SyntaxNodeRef, TextRange}; -/// A pionter to a syntax node inside a file. +/// A pointer to a syntax node inside a file. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct LocalSyntaxPtr { range: TextRange, 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 diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs index 7df6a9c46..deb4dea88 100644 --- a/crates/ra_syntax/src/ast/generated.rs +++ b/crates/ra_syntax/src/ast/generated.rs @@ -378,7 +378,11 @@ impl> BreakExprNode { } -impl<'a> BreakExpr<'a> {} +impl<'a> BreakExpr<'a> { + pub fn expr(self) -> Option> { + super::child_opt(self) + } +} // Byte #[derive(Debug, Clone, Copy,)] @@ -3880,6 +3884,10 @@ impl<'a> StructLit<'a> { pub fn named_field_list(self) -> Option> { super::child_opt(self) } + + pub fn spread(self) -> Option> { + super::child_opt(self) + } } // StructPat diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron index c55e9e07a..5bcdf3f1d 100644 --- a/crates/ra_syntax/src/grammar.ron +++ b/crates/ra_syntax/src/grammar.ron @@ -384,7 +384,7 @@ Grammar( options: [ "Condition" ] ), "ContinueExpr": (), - "BreakExpr": (), + "BreakExpr": (options: ["Expr"]), "Label": (), "BlockExpr": ( options: [ "Block" ] @@ -404,7 +404,7 @@ Grammar( collections: [ [ "pats", "Pat" ] ] ), "MatchGuard": (), - "StructLit": (options: ["Path", "NamedFieldList"]), + "StructLit": (options: ["Path", "NamedFieldList", ["spread", "Expr"]]), "NamedFieldList": (collections: [ ["fields", "NamedField"] ]), "NamedField": (options: ["NameRef", "Expr"]), "CallExpr": ( -- cgit v1.2.3