From a6146d35b1615cf5fb908b29f34e58bfde3bf96d Mon Sep 17 00:00:00 2001 From: Marcus Klaas de Vries Date: Thu, 10 Jan 2019 13:54:58 +0100 Subject: Implement type inference for literals (WIP) --- crates/ra_hir/src/expr.rs | 78 +++++++++++++++++++++++++++- crates/ra_hir/src/ty.rs | 33 +++++++++++- crates/ra_hir/src/ty/tests.rs | 20 +++++++ crates/ra_hir/src/ty/tests/data/basics.txt | 2 +- crates/ra_hir/src/ty/tests/data/literals.txt | 10 ++++ crates/ra_syntax/src/lib.rs | 5 ++ crates/ra_syntax/src/yellow.rs | 12 +++++ crates/ra_syntax/src/yellow/syntax_text.rs | 11 +++- 8 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 crates/ra_hir/src/ty/tests/data/literals.txt diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs index f0936e9f3..e07725d05 100644 --- a/crates/ra_hir/src/expr.rs +++ b/crates/ra_hir/src/expr.rs @@ -5,7 +5,10 @@ use rustc_hash::FxHashMap; use ra_arena::{Arena, RawId, impl_arena_id, map::ArenaMap}; use ra_db::{LocalSyntaxPtr, Cancelable}; -use ra_syntax::ast::{self, AstNode, LoopBodyOwner, ArgListOwner, NameOwner}; +use ra_syntax::{ + SyntaxKind, + ast::{self, AstNode, LoopBodyOwner, ArgListOwner, NameOwner} +}; use crate::{Path, type_ref::{Mutability, TypeRef}, Name, HirDatabase, DefId, Def, name::AsName}; @@ -103,6 +106,19 @@ impl BodySyntaxMapping { } } +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Literal { + String(String), + ByteString(Vec), + Char(char), + Bool(bool), + Byte(u8), + Int, // this and float need additional information + Float, + Tuple { values: Vec }, + Array { values: Vec }, +} + #[derive(Debug, Clone, Eq, PartialEq)] pub enum Expr { /// This is produced if syntax tree does not have a required expression piece. @@ -186,6 +202,7 @@ pub enum Expr { Tuple { exprs: Vec, }, + Literal(Literal), } pub use ra_syntax::ast::PrefixOp as UnaryOp; @@ -305,6 +322,20 @@ impl Expr { f(*expr); } } + Expr::Literal(l) => match l { + Literal::Array { values } | Literal::Tuple { values } => { + for &val in values { + f(val); + } + } + Literal::String(..) + | Literal::ByteString(..) + | Literal::Byte(..) + | Literal::Bool(..) + | Literal::Char(..) + | Literal::Int + | Literal::Float => {} + }, } } } @@ -633,13 +664,56 @@ impl ExprCollector { let exprs = e.exprs().map(|expr| self.collect_expr(expr)).collect(); self.alloc_expr(Expr::Tuple { exprs }, syntax_ptr) } + ast::ExprKind::Literal(e) => { + let child = e.syntax().children().next(); + + if let Some(c) = child { + let lit = match c.kind() { + SyntaxKind::INT_NUMBER => Literal::Int, + SyntaxKind::FLOAT_NUMBER => Literal::Float, + SyntaxKind::STRING => { + // FIXME: this likely includes the " characters + let text = c.text().to_string(); + Literal::String(text) + } + SyntaxKind::ARRAY_EXPR => { + // TODO: recursively call to self + Literal::Array { values: vec![] } + } + SyntaxKind::PAREN_EXPR => { + // TODO: recursively call to self + Literal::Tuple { values: vec![] } + } + SyntaxKind::TRUE_KW => Literal::Bool(true), + SyntaxKind::FALSE_KW => Literal::Bool(false), + SyntaxKind::BYTE_STRING => { + // FIXME: this is completely incorrect for a variety + // of reasons, but at least it gives the right type + let bytes = c.text().to_string().into_bytes(); + Literal::ByteString(bytes) + } + SyntaxKind::CHAR => { + let character = c.text().char_at(1).unwrap_or('X'); + Literal::Char(character) + } + SyntaxKind::BYTE => { + let character = c.text().char_at(1).unwrap_or('X'); + Literal::Byte(character as u8) + } + _ => return self.alloc_expr(Expr::Missing, syntax_ptr), + }; + + self.alloc_expr(Expr::Literal(lit), syntax_ptr) + } else { + self.alloc_expr(Expr::Missing, syntax_ptr) + } + } // TODO implement HIR for these: ast::ExprKind::Label(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), ast::ExprKind::IndexExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), ast::ExprKind::ArrayExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), ast::ExprKind::RangeExpr(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), - ast::ExprKind::Literal(_e) => self.alloc_expr(Expr::Missing, syntax_ptr), } } diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index fa46ddfe9..0baa205a1 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -38,7 +38,7 @@ use crate::{ db::HirDatabase, type_ref::{TypeRef, Mutability}, name::KnownName, - expr::{Body, Expr, ExprId, PatId, UnaryOp, BinaryOp, Statement}, + expr::{Body, Expr, Literal, ExprId, PatId, UnaryOp, BinaryOp, Statement}, }; fn transpose(x: Cancelable>) -> Option> { @@ -1067,6 +1067,37 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { Ty::Tuple(Arc::from(ty_vec)) } + Expr::Literal(lit) => match lit { + Literal::Bool(..) => Ty::Bool, + Literal::String(..) => Ty::Ref(Arc::new(Ty::Str), Mutability::Shared), + Literal::ByteString(..) => { + let byte_type = Arc::new(Ty::Uint(primitive::UintTy::U8)); + let slice_type = Arc::new(Ty::Slice(byte_type)); + Ty::Ref(slice_type, Mutability::Shared) + } + Literal::Byte(..) => Ty::Uint(primitive::UintTy::U8), + Literal::Char(..) => Ty::Char, + Literal::Tuple { values } => { + let mut inner_tys = Vec::new(); + for &expr in values { + let inner_ty = self.infer_expr(expr, &Expectation::none())?; + inner_tys.push(inner_ty); + } + Ty::Tuple(Arc::from(inner_tys)) + } + Literal::Array { values } => { + // simply take the type of the first element for now + let inner_ty = match values.get(0) { + Some(&expr) => self.infer_expr(expr, &Expectation::none())?, + None => Ty::Unknown, + }; + // TODO: we should return a Ty::Array when it becomes + // available + Ty::Slice(Arc::new(inner_ty)) + } + // TODO + Literal::Int | Literal::Float => Ty::Unknown, + }, }; // use a new type variable if we got Ty::Unknown here let ty = self.insert_type_vars_shallow(ty); diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index 920188fc9..97d3d222f 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs @@ -132,6 +132,26 @@ fn test(a: &u32, b: &mut u32, c: *const u32, d: *mut u32) { ); } +#[test] +fn infer_literals() { + check_inference( + r#" +fn test() { + 5i32; + "hello"; + b"bytes"; + 'c'; + b'b'; + 3.14; + 5000; + (0u32, -5isize); + [true, true, false] +} +"#, + "literals.txt", + ); +} + #[test] fn infer_backwards() { check_inference( diff --git a/crates/ra_hir/src/ty/tests/data/basics.txt b/crates/ra_hir/src/ty/tests/data/basics.txt index ac7faae0a..e02947ba8 100644 --- a/crates/ra_hir/src/ty/tests/data/basics.txt +++ b/crates/ra_hir/src/ty/tests/data/basics.txt @@ -9,5 +9,5 @@ [69; 70) 'd': &str [76; 82) '1usize': [unknown] [88; 94) '1isize': [unknown] -[100; 106) '"test"': [unknown] +[100; 106) '"test"': &str [112; 118) '1.0f32': [unknown] diff --git a/crates/ra_hir/src/ty/tests/data/literals.txt b/crates/ra_hir/src/ty/tests/data/literals.txt new file mode 100644 index 000000000..8b22eee31 --- /dev/null +++ b/crates/ra_hir/src/ty/tests/data/literals.txt @@ -0,0 +1,10 @@ +[11; 135) '{ ...lse] }': () +[17; 21) '5i32': [unknown] +[27; 34) '"hello"': &str +[40; 48) 'b"bytes"': &[u8] +[54; 57) ''c'': char +[63; 67) 'b'b'': u8 +[73; 77) '3.14': [unknown] +[83; 87) '5000': [unknown] +[93; 108) '(0u32, -5isize)': [unknown] +[114; 133) '[true,...false]': () diff --git a/crates/ra_syntax/src/lib.rs b/crates/ra_syntax/src/lib.rs index 2a095817a..6181df9d7 100644 --- a/crates/ra_syntax/src/lib.rs +++ b/crates/ra_syntax/src/lib.rs @@ -59,24 +59,29 @@ impl SourceFile { assert_eq!(root.kind(), SyntaxKind::SOURCE_FILE); TreeArc::cast(root) } + pub fn parse(text: &str) -> TreeArc { let tokens = tokenize(&text); let (green, errors) = parser_impl::parse_with(yellow::GreenBuilder::new(), text, &tokens, grammar::root); SourceFile::new(green, errors) } + pub fn reparse(&self, edit: &AtomTextEdit) -> TreeArc { self.incremental_reparse(edit) .unwrap_or_else(|| self.full_reparse(edit)) } + pub fn incremental_reparse(&self, edit: &AtomTextEdit) -> Option> { reparsing::incremental_reparse(self.syntax(), edit, self.errors()) .map(|(green_node, errors)| SourceFile::new(green_node, errors)) } + fn full_reparse(&self, edit: &AtomTextEdit) -> TreeArc { let text = edit.apply(self.syntax().text().to_string()); SourceFile::parse(&text) } + pub fn errors(&self) -> Vec { let mut errors = self.syntax.root_data().clone(); errors.extend(validation::validate(self)); diff --git a/crates/ra_syntax/src/yellow.rs b/crates/ra_syntax/src/yellow.rs index 93621d08a..03df00fc6 100644 --- a/crates/ra_syntax/src/yellow.rs +++ b/crates/ra_syntax/src/yellow.rs @@ -128,40 +128,52 @@ impl SyntaxNode { pub(crate) fn root_data(&self) -> &Vec { self.0.root_data() } + pub(crate) fn replace_with(&self, replacement: GreenNode) -> GreenNode { self.0.replace_self(replacement) } + pub fn to_owned(&self) -> TreeArc { let ptr = TreeArc(self.0.to_owned()); TreeArc::cast(ptr) } + pub fn kind(&self) -> SyntaxKind { self.0.kind() } + pub fn range(&self) -> TextRange { self.0.range() } + pub fn text(&self) -> SyntaxText { SyntaxText::new(self) } + pub fn is_leaf(&self) -> bool { self.0.is_leaf() } + pub fn parent(&self) -> Option<&SyntaxNode> { self.0.parent().map(SyntaxNode::from_repr) } + pub fn first_child(&self) -> Option<&SyntaxNode> { self.0.first_child().map(SyntaxNode::from_repr) } + pub fn last_child(&self) -> Option<&SyntaxNode> { self.0.last_child().map(SyntaxNode::from_repr) } + pub fn next_sibling(&self) -> Option<&SyntaxNode> { self.0.next_sibling().map(SyntaxNode::from_repr) } + pub fn prev_sibling(&self) -> Option<&SyntaxNode> { self.0.prev_sibling().map(SyntaxNode::from_repr) } + pub fn children(&self) -> SyntaxNodeChildren { SyntaxNodeChildren(self.0.children()) } diff --git a/crates/ra_syntax/src/yellow/syntax_text.rs b/crates/ra_syntax/src/yellow/syntax_text.rs index 08dbe57a2..378cd1b2e 100644 --- a/crates/ra_syntax/src/yellow/syntax_text.rs +++ b/crates/ra_syntax/src/yellow/syntax_text.rs @@ -15,6 +15,7 @@ impl<'a> SyntaxText<'a> { range: node.range(), } } + pub fn chunks(&self) -> impl Iterator { let range = self.range; self.node.descendants().filter_map(move |node| { @@ -24,15 +25,19 @@ impl<'a> SyntaxText<'a> { Some(&text[range]) }) } + pub fn push_to(&self, buf: &mut String) { self.chunks().for_each(|it| buf.push_str(it)); } + pub fn to_string(&self) -> String { self.chunks().collect() } + pub fn contains(&self, c: char) -> bool { self.chunks().any(|it| it.contains(c)) } + pub fn find(&self, c: char) -> Option { let mut acc: TextUnit = 0.into(); for chunk in self.chunks() { @@ -44,9 +49,11 @@ impl<'a> SyntaxText<'a> { } None } + pub fn len(&self) -> TextUnit { self.range.len() } + pub fn slice(&self, range: impl SyntaxTextSlice) -> SyntaxText<'a> { let range = range.restrict(self.range).unwrap_or_else(|| { panic!("invalid slice, range: {:?}, slice: {:?}", self.range, range) @@ -56,8 +63,10 @@ impl<'a> SyntaxText<'a> { range, } } - pub fn char_at(&self, offset: TextUnit) -> Option { + + pub fn char_at(&self, offset: impl Into) -> Option { let mut start: TextUnit = 0.into(); + let offset = offset.into(); for chunk in self.chunks() { let end = start + TextUnit::of_str(chunk); if start <= offset && offset < end { -- cgit v1.2.3