From c84a7bfa923f469d04d12448f9d92fab97367209 Mon Sep 17 00:00:00 2001 From: Wodann Date: Sat, 23 Nov 2019 18:33:51 +0100 Subject: [PATCH 01/21] feat: parsing of data structures --- crates/mun_hir/src/adt.rs | 51 +++++++++ crates/mun_hir/src/code_model.rs | 58 +++++++++- crates/mun_hir/src/code_model/src.rs | 32 +++++- crates/mun_hir/src/db.rs | 8 ++ crates/mun_hir/src/ids.rs | 14 +++ crates/mun_hir/src/lib.rs | 3 +- crates/mun_hir/src/raw.rs | 4 + crates/mun_hir/src/ty.rs | 7 +- crates/mun_hir/src/ty/lower.rs | 32 +++++- crates/mun_hir/src/type_ref.rs | 25 +++++ crates/mun_syntax/src/ast.rs | 6 +- crates/mun_syntax/src/ast/extensions.rs | 25 ++++- crates/mun_syntax/src/ast/generated.rs | 101 +++++++++++++++++- crates/mun_syntax/src/grammar.ron | 23 +++- crates/mun_syntax/src/lib.rs | 1 + crates/mun_syntax/src/parsing/grammar.rs | 1 + crates/mun_syntax/src/parsing/grammar/adt.rs | 50 +++++++++ .../src/parsing/grammar/declarations.rs | 17 ++- .../mun_syntax/src/syntax_kind/generated.rs | 11 ++ crates/mun_syntax/src/tests/lexer.rs | 2 +- crates/mun_syntax/src/tests/parser.rs | 14 +++ .../src/tests/snapshots/lexer__keywords.snap | 6 +- .../tests/snapshots/parser__structures.snap | 57 ++++++++++ 23 files changed, 534 insertions(+), 14 deletions(-) create mode 100644 crates/mun_hir/src/adt.rs create mode 100644 crates/mun_syntax/src/parsing/grammar/adt.rs create mode 100644 crates/mun_syntax/src/tests/snapshots/parser__structures.snap diff --git a/crates/mun_hir/src/adt.rs b/crates/mun_hir/src/adt.rs new file mode 100644 index 000000000..d973aab61 --- /dev/null +++ b/crates/mun_hir/src/adt.rs @@ -0,0 +1,51 @@ +use std::sync::Arc; + +use crate::{ + arena::{Arena, RawId}, + ids::{AstItemDef, StructId}, + type_ref::TypeRef, + AsName, DefDatabase, Name, +}; +use mun_syntax::ast::{self, NameOwner, TypeAscriptionOwner}; + +/// A single field of an enum variant or struct +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FieldData { + pub name: Name, + pub type_ref: TypeRef, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct LocalStructFieldId(RawId); +impl_arena_id!(LocalStructFieldId); + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct StructData { + pub name: Name, + pub fields: Option>>, +} + +impl StructData { + pub(crate) fn struct_data_query(db: &impl DefDatabase, id: StructId) -> Arc { + let src = id.source(db); + let name = src + .ast + .name() + .map(|n| n.as_name()) + .unwrap_or_else(Name::missing); + + let fields = if let ast::StructKind::Record(r) = src.ast.kind() { + let fields = r + .fields() + .map(|fd| FieldData { + name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing), + type_ref: TypeRef::from_ast_opt(fd.ascribed_type()), + }) + .collect(); + Some(Arc::new(fields)) + } else { + None + }; + Arc::new(StructData { name, fields }) + } +} diff --git a/crates/mun_hir/src/code_model.rs b/crates/mun_hir/src/code_model.rs index 7d09f035d..81974cbec 100644 --- a/crates/mun_hir/src/code_model.rs +++ b/crates/mun_hir/src/code_model.rs @@ -1,6 +1,7 @@ pub(crate) mod src; use self::src::HasSource; +use crate::adt::{LocalStructFieldId, StructData}; use crate::diagnostics::DiagnosticSink; use crate::expr::{Body, BodySourceMap}; use crate::ids::AstItemDef; @@ -10,7 +11,10 @@ use crate::raw::{DefKind, RawFileItem}; use crate::resolve::{Resolution, Resolver}; use crate::ty::InferenceResult; use crate::type_ref::{TypeRefBuilder, TypeRefId, TypeRefMap, TypeRefSourceMap}; -use crate::{ids::FunctionId, AsName, DefDatabase, FileId, HirDatabase, Name, Ty}; +use crate::{ + ids::{FunctionId, StructId}, + AsName, DefDatabase, FileId, HirDatabase, Name, Ty, +}; use mun_syntax::ast::{NameOwner, TypeAscriptionOwner, VisibilityOwner}; use rustc_hash::FxHashMap; use std::sync::Arc; @@ -91,6 +95,11 @@ impl ModuleData { id: FunctionId::from_ast_id(loc_ctx, ast_id), })) } + DefKind::Struct(ast_id) => { + data.definitions.push(ModuleDef::Struct(Struct { + id: StructId::from_ast_id(loc_ctx, ast_id), + })) + } } } }; @@ -107,6 +116,7 @@ impl ModuleData { pub enum ModuleDef { Function(Function), BuiltinType(BuiltinType), + Struct(Struct), } impl From for ModuleDef { @@ -121,6 +131,12 @@ impl From for ModuleDef { } } +impl From for ModuleDef { + fn from(t: Struct) -> Self { + ModuleDef::Struct(t) + } +} + /// The definitions that have a body. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DefWithBody { @@ -309,6 +325,43 @@ impl BuiltinType { ]; } +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct Struct { + pub(crate) id: StructId, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct StructField { + pub(crate) parent: Struct, + pub(crate) id: LocalStructFieldId, +} + +impl Struct { + pub fn module(self, db: &impl DefDatabase) -> Module { + Module { + file_id: self.id.file_id(db), + } + } + + pub fn data(self, db: &impl DefDatabase) -> Arc { + db.struct_data(self.id) + } + + pub fn name(self, db: &impl DefDatabase) -> Name { + self.data(db).name.clone() + } + + pub fn fields(self, db: &impl HirDatabase) -> Vec { + self.data(db) + .fields + .as_ref() + .into_iter() + .flat_map(|it| it.iter()) + .map(|(id, _)| StructField { parent: self, id }) + .collect() + } +} + mod diagnostics { use super::Module; use crate::diagnostics::{DiagnosticSink, DuplicateDefinition}; @@ -330,6 +383,9 @@ mod diagnostics { DefKind::Function(id) => { SyntaxNodePtr::new(id.with_file_id(owner.file_id).to_node(db).syntax()) } + DefKind::Struct(id) => { + SyntaxNodePtr::new(id.with_file_id(owner.file_id).to_node(db).syntax()) + } } } diff --git a/crates/mun_hir/src/code_model/src.rs b/crates/mun_hir/src/code_model/src.rs index 040602d96..95eafce8d 100644 --- a/crates/mun_hir/src/code_model/src.rs +++ b/crates/mun_hir/src/code_model/src.rs @@ -1,4 +1,4 @@ -use crate::code_model::Function; +use crate::code_model::{Function, Struct, StructField}; use crate::ids::AstItemDef; use crate::{DefDatabase, FileId, SourceDatabase}; use mun_syntax::{ast, AstNode, SyntaxNode}; @@ -21,6 +21,36 @@ impl HasSource for Function { } } +impl HasSource for Struct { + type Ast = ast::StructDef; + fn source(self, db: &impl DefDatabase) -> Source { + self.id.source(db) + } +} + +impl HasSource for StructField { + type Ast = ast::RecordFieldDef; + + fn source(self, db: &impl DefDatabase) -> Source { + let src = self.parent.source(db); + let file_id = src.file_id; + let field_sources = if let ast::StructKind::Record(r) = src.ast.kind() { + r.fields().collect() + } else { + Vec::new() + }; + + let ast = field_sources + .into_iter() + .zip(self.parent.data(db).fields.as_ref().unwrap().iter()) + .find(|(_syntax, (id, _))| *id == self.id) + .unwrap() + .0; + + Source { file_id, ast } + } +} + impl Source { pub(crate) fn map U, U>(self, f: F) -> Source { Source { diff --git a/crates/mun_hir/src/db.rs b/crates/mun_hir/src/db.rs index 03fa9d65e..0b78c61e8 100644 --- a/crates/mun_hir/src/db.rs +++ b/crates/mun_hir/src/db.rs @@ -4,6 +4,7 @@ use crate::input::{SourceRoot, SourceRootId}; use crate::name_resolution::Namespace; use crate::ty::{FnSig, Ty, TypableDef}; use crate::{ + adt::StructData, code_model::{DefWithBody, FnData, Function, ModuleData}, ids, line_index::LineIndex, @@ -59,9 +60,16 @@ pub trait DefDatabase: SourceDatabase { #[salsa::invoke(RawItems::raw_file_items_query)] fn raw_items(&self, file_id: FileId) -> Arc; + #[salsa::invoke(StructData::struct_data_query)] + fn struct_data(&self, id: ids::StructId) -> Arc; + /// Interns a function definition #[salsa::interned] fn intern_function(&self, loc: ids::ItemLoc) -> ids::FunctionId; + + /// Interns a struct definition + #[salsa::interned] + fn intern_struct(&self, loc: ids::ItemLoc) -> ids::StructId; } #[salsa::query_group(HirDatabaseStorage)] diff --git a/crates/mun_hir/src/ids.rs b/crates/mun_hir/src/ids.rs index d5681f216..b41cd201c 100644 --- a/crates/mun_hir/src/ids.rs +++ b/crates/mun_hir/src/ids.rs @@ -104,3 +104,17 @@ impl AstItemDef for FunctionId { db.lookup_intern_function(self) } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct StructId(salsa::InternId); +impl_intern_key!(StructId); + +impl AstItemDef for StructId { + fn intern(db: &impl DefDatabase, loc: ItemLoc) -> Self { + db.intern_struct(loc) + } + + fn lookup_intern(self, db: &impl DefDatabase) -> ItemLoc { + db.lookup_intern_struct(self) + } +} diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index 9f451c542..c444f3656 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -7,6 +7,7 @@ #[macro_use] mod arena; +mod adt; mod code_model; mod db; pub mod diagnostics; @@ -60,4 +61,4 @@ use crate::{ source_id::{AstIdMap, FileAstId}, }; -pub use self::code_model::{FnData, Function, Module, ModuleDef, Visibility}; +pub use self::code_model::{FnData, Function, Module, ModuleDef, Struct, Visibility}; diff --git a/crates/mun_hir/src/raw.rs b/crates/mun_hir/src/raw.rs index aa1dc661c..6d1dddc76 100644 --- a/crates/mun_hir/src/raw.rs +++ b/crates/mun_hir/src/raw.rs @@ -26,6 +26,7 @@ pub(super) struct DefData { #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub(super) enum DefKind { Function(FileAstId), + Struct(FileAstId), } #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -54,6 +55,9 @@ impl RawItems { ast::ModuleItemKind::FunctionDef(it) => { (DefKind::Function((*ast_id_map).ast_id(&it)), it.name()) } + ast::ModuleItemKind::StructDef(it) => { + (DefKind::Struct((*ast_id_map).ast_id(&it)), it.name()) + } }; // If no name is provided an error is already emitted diff --git a/crates/mun_hir/src/ty.rs b/crates/mun_hir/src/ty.rs index 7288abb3d..df9c842ce 100644 --- a/crates/mun_hir/src/ty.rs +++ b/crates/mun_hir/src/ty.rs @@ -3,7 +3,7 @@ mod lower; use crate::display::{HirDisplay, HirFormatter}; use crate::ty::infer::TypeVarId; -use crate::{Function, HirDatabase}; +use crate::{Function, HirDatabase, Struct}; pub(crate) use infer::infer_query; pub use infer::InferenceResult; pub(crate) use lower::{fn_sig_for_fn, type_for_def, TypableDef}; @@ -55,6 +55,10 @@ pub enum TypeCtor { /// The primitive boolean type. Written as `bool`. Bool, + /// An abstract datatype (structures, tuples, or enumerations) + /// TODO: Add tuples and enumerations + Struct(Struct), + /// The never type `never`. Never, @@ -177,6 +181,7 @@ impl HirDisplay for ApplicationTy { TypeCtor::Float => write!(f, "float"), TypeCtor::Int => write!(f, "int"), TypeCtor::Bool => write!(f, "bool"), + TypeCtor::Struct(def) => write!(f, "{}", def.name(f.db)), TypeCtor::Never => write!(f, "never"), TypeCtor::FnDef(def) => { let sig = f.db.fn_signature(def); diff --git a/crates/mun_hir/src/ty/lower.rs b/crates/mun_hir/src/ty/lower.rs index 085dafa39..e22f4e0f7 100644 --- a/crates/mun_hir/src/ty/lower.rs +++ b/crates/mun_hir/src/ty/lower.rs @@ -4,7 +4,7 @@ use crate::name_resolution::Namespace; use crate::resolve::{Resolution, Resolver}; use crate::ty::{FnSig, Ty, TypeCtor}; use crate::type_ref::{TypeRef, TypeRefId, TypeRefMap}; -use crate::{Function, HirDatabase, ModuleDef, Path}; +use crate::{Function, HirDatabase, ModuleDef, Path, Struct}; #[derive(Clone, PartialEq, Eq, Debug)] pub(crate) struct LowerResult { @@ -78,6 +78,7 @@ impl Ty { pub enum TypableDef { Function(Function), BuiltinType(BuiltinType), + Struct(Struct), } impl From for TypableDef { @@ -92,15 +93,28 @@ impl From for TypableDef { } } +impl From for TypableDef { + fn from(f: Struct) -> Self { + TypableDef::Struct(f) + } +} + impl From for Option { fn from(d: ModuleDef) -> Self { match d { ModuleDef::Function(f) => Some(TypableDef::Function(f)), ModuleDef::BuiltinType(t) => Some(TypableDef::BuiltinType(t)), + ModuleDef::Struct(t) => Some(TypableDef::Struct(t)), } } } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum CallableDef { + Function(Function), + Struct(Struct), +} + /// Build the declared type of an item. This depends on the namespace; e.g. for /// `struct Foo(usize)`, we have two types: The type of the struct itself, and /// the constructor function `(usize) -> Foo` which lives in the values @@ -109,6 +123,8 @@ pub(crate) fn type_for_def(db: &impl HirDatabase, def: TypableDef, ns: Namespace match (def, ns) { (TypableDef::Function(f), Namespace::Values) => type_for_fn(db, f), (TypableDef::BuiltinType(t), Namespace::Types) => type_for_builtin(t), + (TypableDef::Struct(s), Namespace::Values) => type_for_struct_constructor(db, s), + (TypableDef::Struct(s), Namespace::Types) => type_for_struct(db, s), // 'error' cases: (TypableDef::Function(_), Namespace::Types) => Ty::Unknown, @@ -143,6 +159,20 @@ pub fn fn_sig_for_fn(db: &impl HirDatabase, def: Function) -> FnSig { FnSig::from_params_and_return(params, ret) } +/// Build the type of a struct constructor. +fn type_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> Ty { + let struct_data = db.struct_data(def.id); + if struct_data.fields.is_none() { + type_for_struct(db, def) // Unit struct + } else { + unreachable!(); + } +} + +fn type_for_struct(_db: &impl HirDatabase, def: Struct) -> Ty { + Ty::simple(TypeCtor::Struct(def)) +} + pub mod diagnostics { use crate::type_ref::TypeRefId; diff --git a/crates/mun_hir/src/type_ref.rs b/crates/mun_hir/src/type_ref.rs index 16a30b53d..55c2fcd69 100644 --- a/crates/mun_hir/src/type_ref.rs +++ b/crates/mun_hir/src/type_ref.rs @@ -21,6 +21,31 @@ pub enum TypeRef { Error, } +impl TypeRef { + /// Converts an `ast::TypeRef` to a `hir::TypeRef`. + pub fn from_ast(node: ast::TypeRef) -> Self { + match node.kind() { + ast::TypeRefKind::NeverType(..) => TypeRef::Never, + ast::TypeRefKind::PathType(inner) => { + // FIXME: Use `Path::from_src` + inner + .path() + .and_then(Path::from_ast) + .map(TypeRef::Path) + .unwrap_or(TypeRef::Error) + } + } + } + + pub fn from_ast_opt(node: Option) -> Self { + if let Some(node) = node { + TypeRef::from_ast(node) + } else { + TypeRef::Error + } + } +} + #[derive(Default, Debug, Eq, PartialEq)] pub struct TypeRefSourceMap { type_ref_map: FxHashMap, TypeRefId>, diff --git a/crates/mun_syntax/src/ast.rs b/crates/mun_syntax/src/ast.rs index 7fe85bf24..4c3e58f79 100644 --- a/crates/mun_syntax/src/ast.rs +++ b/crates/mun_syntax/src/ast.rs @@ -8,7 +8,11 @@ mod traits; use crate::{syntax_node::SyntaxNodeChildren, SmolStr, SyntaxKind, SyntaxNode, SyntaxToken}; pub use self::{ - expr_extensions::*, extensions::PathSegmentKind, generated::*, tokens::*, traits::*, + expr_extensions::*, + extensions::{PathSegmentKind, StructKind}, + generated::*, + tokens::*, + traits::*, }; use std::marker::PhantomData; diff --git a/crates/mun_syntax/src/ast/extensions.rs b/crates/mun_syntax/src/ast/extensions.rs index d0d408a4c..53f3e3811 100644 --- a/crates/mun_syntax/src/ast/extensions.rs +++ b/crates/mun_syntax/src/ast/extensions.rs @@ -1,6 +1,5 @@ -use crate::ast::NameOwner; use crate::{ - ast::{self, AstNode}, + ast::{self, child_opt, AstNode, NameOwner}, T, }; use crate::{SmolStr, SyntaxNode}; @@ -88,3 +87,25 @@ impl ast::PathSegment { } } } + +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum StructKind { + Record(ast::RecordFieldDefList), + Unit, +} + +impl StructKind { + fn from_node(node: &N) -> StructKind { + if let Some(r) = child_opt::<_, ast::RecordFieldDefList>(node) { + StructKind::Record(r) + } else { + StructKind::Unit + } + } +} + +impl ast::StructDef { + pub fn kind(&self) -> StructKind { + StructKind::from_node(self) + } +} diff --git a/crates/mun_syntax/src/ast/generated.rs b/crates/mun_syntax/src/ast/generated.rs index f2f7fabd2..83dc40c12 100644 --- a/crates/mun_syntax/src/ast/generated.rs +++ b/crates/mun_syntax/src/ast/generated.rs @@ -567,7 +567,7 @@ pub struct ModuleItem { impl AstNode for ModuleItem { fn can_cast(kind: SyntaxKind) -> bool { match kind { - FUNCTION_DEF => true, + FUNCTION_DEF | STRUCT_DEF => true, _ => false, } } @@ -585,12 +585,18 @@ impl AstNode for ModuleItem { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ModuleItemKind { FunctionDef(FunctionDef), + StructDef(StructDef), } impl From for ModuleItem { fn from(n: FunctionDef) -> ModuleItem { ModuleItem { syntax: n.syntax } } } +impl From for ModuleItem { + fn from(n: StructDef) -> ModuleItem { + ModuleItem { syntax: n.syntax } + } +} impl ModuleItem { pub fn kind(&self) -> ModuleItemKind { @@ -598,6 +604,7 @@ impl ModuleItem { FUNCTION_DEF => { ModuleItemKind::FunctionDef(FunctionDef::cast(self.syntax.clone()).unwrap()) } + STRUCT_DEF => ModuleItemKind::StructDef(StructDef::cast(self.syntax.clone()).unwrap()), _ => unreachable!(), } } @@ -1021,6 +1028,68 @@ impl PrefixExpr { } } +// RecordFieldDef + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct RecordFieldDef { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for RecordFieldDef { + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + RECORD_FIELD_DEF => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(RecordFieldDef { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl ast::NameOwner for RecordFieldDef {} +impl ast::VisibilityOwner for RecordFieldDef {} +impl ast::DocCommentsOwner for RecordFieldDef {} +impl ast::TypeAscriptionOwner for RecordFieldDef {} +impl RecordFieldDef {} + +// RecordFieldDefList + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct RecordFieldDefList { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for RecordFieldDefList { + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + RECORD_FIELD_DEF_LIST => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(RecordFieldDefList { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl RecordFieldDefList { + pub fn fields(&self) -> impl Iterator { + super::children(self) + } +} + // RetType #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1165,6 +1234,36 @@ impl Stmt { impl Stmt {} +// StructDef + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct StructDef { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for StructDef { + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + STRUCT_DEF => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(StructDef { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl ast::NameOwner for StructDef {} +impl ast::VisibilityOwner for StructDef {} +impl ast::DocCommentsOwner for StructDef {} +impl StructDef {} + // TypeRef #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/mun_syntax/src/grammar.ron b/crates/mun_syntax/src/grammar.ron index d758be6bf..bf0f06dcf 100644 --- a/crates/mun_syntax/src/grammar.ron +++ b/crates/mun_syntax/src/grammar.ron @@ -80,6 +80,7 @@ Grammar( "let", "mut", "class", + "struct", "never", "pub" ], @@ -103,6 +104,10 @@ Grammar( "PARAM_LIST", "PARAM", + + "STRUCT_DEF", + "RECORD_FIELD_DEF_LIST", + "RECORD_FIELD_DEF", "PATH_TYPE", "NEVER_TYPE", @@ -140,7 +145,7 @@ Grammar( traits: [ "ModuleItemOwner", "FunctionDefOwner" ], ), "ModuleItem": ( - enum: ["FunctionDef"] + enum: ["FunctionDef", "StructDef"] ), "Visibility": (), "FunctionDef": ( @@ -163,6 +168,22 @@ Grammar( "TypeAscriptionOwner" ], ), + "StructDef": ( + traits: [ + "NameOwner", + "VisibilityOwner", + "DocCommentsOwner", + ] + ), + "RecordFieldDefList": (collections: [("fields", "RecordFieldDef")]), + "RecordFieldDef": ( + traits: [ + "NameOwner", + "VisibilityOwner", + "DocCommentsOwner", + "TypeAscriptionOwner" + ] + ), "LetStmt": ( options: [ ["pat", "Pat"], diff --git a/crates/mun_syntax/src/lib.rs b/crates/mun_syntax/src/lib.rs index d9c41b28d..2307976f9 100644 --- a/crates/mun_syntax/src/lib.rs +++ b/crates/mun_syntax/src/lib.rs @@ -162,6 +162,7 @@ fn api_walkthrough() { for item in file.items() { match item.kind() { ast::ModuleItemKind::FunctionDef(f) => func = Some(f), + ast::ModuleItemKind::StructDef(_) => (), } } diff --git a/crates/mun_syntax/src/parsing/grammar.rs b/crates/mun_syntax/src/parsing/grammar.rs index 3d309f165..154eb0e62 100644 --- a/crates/mun_syntax/src/parsing/grammar.rs +++ b/crates/mun_syntax/src/parsing/grammar.rs @@ -1,3 +1,4 @@ +mod adt; mod declarations; mod expressions; mod params; diff --git a/crates/mun_syntax/src/parsing/grammar/adt.rs b/crates/mun_syntax/src/parsing/grammar/adt.rs new file mode 100644 index 000000000..629d02420 --- /dev/null +++ b/crates/mun_syntax/src/parsing/grammar/adt.rs @@ -0,0 +1,50 @@ +use super::*; + +pub(super) fn struct_def(p: &mut Parser, m: Marker) { + assert!(p.at(T![struct])); + p.bump(T![struct]); + + name_recovery(p, declarations::DECLARATION_RECOVERY_SET); + match p.current() { + T![;] => { + p.bump(T![;]); + } + T!['{'] => record_field_def_list(p), + _ => { + p.error("expected ';', pr '{'"); + } + } + m.complete(p, STRUCT_DEF); +} + +pub(super) fn record_field_def_list(p: &mut Parser) { + assert!(p.at(T!['{'])); + let m = p.start(); + p.bump(T!['{']); + while !p.at(T!['}']) && !p.at(EOF) { + if p.at(T!['{']) { + error_block(p, "expected field"); + continue; + } + record_field_def(p); + if !p.at(T!['}']) { + p.expect(T![,]); + } + } + p.expect(T!['}']); + m.complete(p, RECORD_FIELD_DEF_LIST); +} + +fn record_field_def(p: &mut Parser) { + let m = p.start(); + opt_visibility(p); + if p.at(IDENT) { + name(p); + p.expect(T![:]); + types::type_(p); + m.complete(p, RECORD_FIELD_DEF); + } else { + m.abandon(p); + p.error_and_bump("expected field declaration"); + } +} diff --git a/crates/mun_syntax/src/parsing/grammar/declarations.rs b/crates/mun_syntax/src/parsing/grammar/declarations.rs index 83c4f1328..d9a23cd2f 100644 --- a/crates/mun_syntax/src/parsing/grammar/declarations.rs +++ b/crates/mun_syntax/src/parsing/grammar/declarations.rs @@ -1,7 +1,7 @@ use super::*; use crate::T; -pub(super) const DECLARATION_RECOVERY_SET: TokenSet = token_set![FN_KW, PUB_KW]; +pub(super) const DECLARATION_RECOVERY_SET: TokenSet = token_set![FN_KW, PUB_KW, STRUCT_KW]; pub(super) fn mod_contents(p: &mut Parser) { while !p.at(EOF) { @@ -34,6 +34,11 @@ pub(super) fn declaration(p: &mut Parser) { pub(super) fn maybe_declaration(p: &mut Parser, m: Marker) -> Result<(), Marker> { opt_visibility(p); + let m = match declarations_without_modifiers(p, m) { + Ok(()) => return Ok(()), + Err(m) => m, + }; + match p.current() { T![fn] => { fn_def(p); @@ -44,6 +49,16 @@ pub(super) fn maybe_declaration(p: &mut Parser, m: Marker) -> Result<(), Marker> Ok(()) } +fn declarations_without_modifiers(p: &mut Parser, m: Marker) -> Result<(), Marker> { + match p.current() { + T![struct] => { + adt::struct_def(p, m); + } + _ => return Err(m), + }; + Ok(()) +} + pub(super) fn fn_def(p: &mut Parser) { assert!(p.at(T![fn])); p.bump(T![fn]); diff --git a/crates/mun_syntax/src/syntax_kind/generated.rs b/crates/mun_syntax/src/syntax_kind/generated.rs index 3317cd9c5..d360ac603 100644 --- a/crates/mun_syntax/src/syntax_kind/generated.rs +++ b/crates/mun_syntax/src/syntax_kind/generated.rs @@ -72,6 +72,7 @@ pub enum SyntaxKind { LET_KW, MUT_KW, CLASS_KW, + STRUCT_KW, NEVER_KW, PUB_KW, INT_NUMBER, @@ -87,6 +88,9 @@ pub enum SyntaxKind { VISIBILITY, PARAM_LIST, PARAM, + STRUCT_DEF, + RECORD_FIELD_DEF_LIST, + RECORD_FIELD_DEF, PATH_TYPE, NEVER_TYPE, LET_STMT, @@ -174,6 +178,7 @@ macro_rules! T { (let) => { $crate::SyntaxKind::LET_KW }; (mut) => { $crate::SyntaxKind::MUT_KW }; (class) => { $crate::SyntaxKind::CLASS_KW }; + (struct) => { $crate::SyntaxKind::STRUCT_KW }; (never) => { $crate::SyntaxKind::NEVER_KW }; (pub) => { $crate::SyntaxKind::PUB_KW }; } @@ -213,6 +218,7 @@ impl SyntaxKind { | LET_KW | MUT_KW | CLASS_KW + | STRUCT_KW | NEVER_KW | PUB_KW => true, @@ -330,6 +336,7 @@ impl SyntaxKind { LET_KW => &SyntaxInfo { name: "LET_KW" }, MUT_KW => &SyntaxInfo { name: "MUT_KW" }, CLASS_KW => &SyntaxInfo { name: "CLASS_KW" }, + STRUCT_KW => &SyntaxInfo { name: "STRUCT_KW" }, NEVER_KW => &SyntaxInfo { name: "NEVER_KW" }, PUB_KW => &SyntaxInfo { name: "PUB_KW" }, INT_NUMBER => &SyntaxInfo { name: "INT_NUMBER" }, @@ -345,6 +352,9 @@ impl SyntaxKind { VISIBILITY => &SyntaxInfo { name: "VISIBILITY" }, PARAM_LIST => &SyntaxInfo { name: "PARAM_LIST" }, PARAM => &SyntaxInfo { name: "PARAM" }, + STRUCT_DEF => &SyntaxInfo { name: "STRUCT_DEF" }, + RECORD_FIELD_DEF_LIST => &SyntaxInfo { name: "RECORD_FIELD_DEF_LIST" }, + RECORD_FIELD_DEF => &SyntaxInfo { name: "RECORD_FIELD_DEF" }, PATH_TYPE => &SyntaxInfo { name: "PATH_TYPE" }, NEVER_TYPE => &SyntaxInfo { name: "NEVER_TYPE" }, LET_STMT => &SyntaxInfo { name: "LET_STMT" }, @@ -396,6 +406,7 @@ impl SyntaxKind { "let" => LET_KW, "mut" => MUT_KW, "class" => CLASS_KW, + "struct" => STRUCT_KW, "never" => NEVER_KW, "pub" => PUB_KW, _ => return None, diff --git a/crates/mun_syntax/src/tests/lexer.rs b/crates/mun_syntax/src/tests/lexer.rs index 804f1a1e5..a12f5858b 100644 --- a/crates/mun_syntax/src/tests/lexer.rs +++ b/crates/mun_syntax/src/tests/lexer.rs @@ -109,7 +109,7 @@ fn keywords() { lex_snapshot( r#" and break do else false for fn if in nil - return true while let mut class + return true while let mut struct class never loop pub "#, ) diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index f979504bc..a80336f7e 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -60,6 +60,20 @@ fn literals() { ); } +#[test] +fn structures() { + ok_snapshot_test( + r#" + struct Foo; + struct Foo {} + struct Foo { + a: float, + b: int, + } + "#, + ) +} + #[test] fn unary_expr() { ok_snapshot_test( diff --git a/crates/mun_syntax/src/tests/snapshots/lexer__keywords.snap b/crates/mun_syntax/src/tests/snapshots/lexer__keywords.snap index cbaecb148..8ac1a983e 100644 --- a/crates/mun_syntax/src/tests/snapshots/lexer__keywords.snap +++ b/crates/mun_syntax/src/tests/snapshots/lexer__keywords.snap @@ -1,6 +1,6 @@ --- source: crates/mun_syntax/src/tests/lexer.rs -expression: "and break do else false for fn if in nil\nreturn true while let mut class \nnever loop pub" +expression: "and break do else false for fn if in nil\nreturn true while let mut struct class\nnever loop pub" --- AND_KW 3 "and" WHITESPACE 1 " " @@ -32,8 +32,10 @@ LET_KW 3 "let" WHITESPACE 1 " " MUT_KW 3 "mut" WHITESPACE 1 " " +STRUCT_KW 6 "struct" +WHITESPACE 1 " " CLASS_KW 5 "class" -WHITESPACE 2 " \n" +WHITESPACE 1 "\n" NEVER_KW 5 "never" WHITESPACE 1 " " LOOP_KW 4 "loop" diff --git a/crates/mun_syntax/src/tests/snapshots/parser__structures.snap b/crates/mun_syntax/src/tests/snapshots/parser__structures.snap new file mode 100644 index 000000000..a4250a268 --- /dev/null +++ b/crates/mun_syntax/src/tests/snapshots/parser__structures.snap @@ -0,0 +1,57 @@ +--- +source: crates/mun_syntax/src/tests/parser.rs +expression: "struct Foo;\nstruct Foo {}\nstruct Foo {\n a: float,\n b: int,\n}" +--- +SOURCE_FILE@[0; 66) + STRUCT_DEF@[0; 11) + STRUCT_KW@[0; 6) "struct" + WHITESPACE@[6; 7) " " + NAME@[7; 10) + IDENT@[7; 10) "Foo" + SEMI@[10; 11) ";" + WHITESPACE@[11; 12) "\n" + STRUCT_DEF@[12; 25) + STRUCT_KW@[12; 18) "struct" + WHITESPACE@[18; 19) " " + NAME@[19; 22) + IDENT@[19; 22) "Foo" + WHITESPACE@[22; 23) " " + RECORD_FIELD_DEF_LIST@[23; 25) + L_CURLY@[23; 24) "{" + R_CURLY@[24; 25) "}" + WHITESPACE@[25; 26) "\n" + STRUCT_DEF@[26; 66) + STRUCT_KW@[26; 32) "struct" + WHITESPACE@[32; 33) " " + NAME@[33; 36) + IDENT@[33; 36) "Foo" + WHITESPACE@[36; 37) " " + RECORD_FIELD_DEF_LIST@[37; 66) + L_CURLY@[37; 38) "{" + WHITESPACE@[38; 43) "\n " + RECORD_FIELD_DEF@[43; 51) + NAME@[43; 44) + IDENT@[43; 44) "a" + COLON@[44; 45) ":" + WHITESPACE@[45; 46) " " + PATH_TYPE@[46; 51) + PATH@[46; 51) + PATH_SEGMENT@[46; 51) + NAME_REF@[46; 51) + IDENT@[46; 51) "float" + COMMA@[51; 52) "," + WHITESPACE@[52; 57) "\n " + RECORD_FIELD_DEF@[57; 63) + NAME@[57; 58) + IDENT@[57; 58) "b" + COLON@[58; 59) ":" + WHITESPACE@[59; 60) " " + PATH_TYPE@[60; 63) + PATH@[60; 63) + PATH_SEGMENT@[60; 63) + NAME_REF@[60; 63) + IDENT@[60; 63) "int" + COMMA@[63; 64) "," + WHITESPACE@[64; 65) "\n" + R_CURLY@[65; 66) "}" + From 9a86797d49d558b141af68fad1175511a0430d7f Mon Sep 17 00:00:00 2001 From: Wodann Date: Sat, 23 Nov 2019 18:33:51 +0100 Subject: [PATCH 02/21] feat(hir): resolve struct names --- crates/mun_hir/src/name_resolution.rs | 8 ++++++++ .../snapshots/tests__struct_declaration.snap | 8 ++++++++ crates/mun_hir/src/ty/tests.rs | 20 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 crates/mun_hir/src/ty/snapshots/tests__struct_declaration.snap diff --git a/crates/mun_hir/src/name_resolution.rs b/crates/mun_hir/src/name_resolution.rs index e5fa777bb..ca5883d9a 100644 --- a/crates/mun_hir/src/name_resolution.rs +++ b/crates/mun_hir/src/name_resolution.rs @@ -57,6 +57,14 @@ pub(crate) fn module_scope_query(db: &impl HirDatabase, file_id: FileId) -> Arc< }, ); } + ModuleDef::Struct(s) => { + scope.items.insert( + s.name(db), + Resolution { + def: PerNs::types(*def), + }, + ); + } _ => {} } } diff --git a/crates/mun_hir/src/ty/snapshots/tests__struct_declaration.snap b/crates/mun_hir/src/ty/snapshots/tests__struct_declaration.snap new file mode 100644 index 000000000..03c318842 --- /dev/null +++ b/crates/mun_hir/src/ty/snapshots/tests__struct_declaration.snap @@ -0,0 +1,8 @@ +--- +source: crates/mun_hir/src/ty/tests.rs +expression: "struct Foo;\nstruct Bar {}\nstruct Baz {\n f: float,\n i: int,\n}\n\nfn main() {\n let foo: Foo;\n let bar: Bar;\n let baz: Baz;\n}" +--- +[78; 135) '{ ...Baz; }': nothing +[88; 91) 'foo': Foo +[106; 109) 'bar': Bar +[124; 127) 'baz': Baz diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index df6ff1693..f69bdf6cd 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -172,6 +172,26 @@ fn invalid_binary_ops() { ) } +#[test] +fn struct_declaration() { + infer_snapshot( + r#" + struct Foo; + struct Bar {} + struct Baz { + f: float, + i: int, + } + + fn main() { + let foo: Foo; + let bar: Bar; + let baz: Baz; + } + "#, + ) +} + fn infer_snapshot(text: &str) { let text = text.trim().replace("\n ", "\n"); insta::assert_snapshot!(insta::_macro_support::AutoName, infer(&text), &text); From e241e8604a95aaaa6ce86eca4785d448dd893dd8 Mon Sep 17 00:00:00 2001 From: Wodann Date: Fri, 29 Nov 2019 20:07:08 +0100 Subject: [PATCH 03/21] improvement(struct): allow trailing semicolon --- crates/mun_syntax/src/parsing/grammar/adt.rs | 3 +- crates/mun_syntax/src/tests/parser.rs | 46 ++--- .../tests/snapshots/parser__structures.snap | 166 ++++++++++++------ 3 files changed, 142 insertions(+), 73 deletions(-) diff --git a/crates/mun_syntax/src/parsing/grammar/adt.rs b/crates/mun_syntax/src/parsing/grammar/adt.rs index 629d02420..66f7d7100 100644 --- a/crates/mun_syntax/src/parsing/grammar/adt.rs +++ b/crates/mun_syntax/src/parsing/grammar/adt.rs @@ -11,7 +11,7 @@ pub(super) fn struct_def(p: &mut Parser, m: Marker) { } T!['{'] => record_field_def_list(p), _ => { - p.error("expected ';', pr '{'"); + p.error("expected ';', or '{'"); } } m.complete(p, STRUCT_DEF); @@ -32,6 +32,7 @@ pub(super) fn record_field_def_list(p: &mut Parser) { } } p.expect(T!['}']); + p.eat(T![;]); m.complete(p, RECORD_FIELD_DEF_LIST); } diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index a80336f7e..e80ae37f1 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -1,26 +1,19 @@ use crate::SourceFile; -fn ok_snapshot_test(text: &str) { +fn snapshot_test(text: &str) { let text = text.trim().replace("\n ", "\n"); let file = SourceFile::parse(&text); - let errors = file.errors(); - assert_eq!( - &*errors, - &[] as &[crate::SyntaxError], - "There should be no errors\nAST:\n{}", - file.debug_dump() - ); insta::assert_snapshot!(insta::_macro_support::AutoName, file.debug_dump(), &text); } #[test] fn empty() { - ok_snapshot_test(r#""#); + snapshot_test(r#""#); } #[test] fn function() { - ok_snapshot_test( + snapshot_test( r#" // Source file comment @@ -35,7 +28,7 @@ fn function() { #[test] fn block() { - ok_snapshot_test( + snapshot_test( r#" fn foo() { let a; @@ -47,7 +40,7 @@ fn block() { #[test] fn literals() { - ok_snapshot_test( + snapshot_test( r#" fn foo() { let a = true; @@ -62,21 +55,28 @@ fn literals() { #[test] fn structures() { - ok_snapshot_test( + snapshot_test( r#" + struct Foo // error: expected ';', or '{' struct Foo; + struct Foo;; // error: expected a declaration struct Foo {} + struct Foo {}; struct Foo { a: float, b: int, } + struct Foo { + a: float, + b: int, + }; "#, ) } #[test] fn unary_expr() { - ok_snapshot_test( + snapshot_test( r#" fn foo() { let a = --3; @@ -88,7 +88,7 @@ fn unary_expr() { #[test] fn binary_expr() { - ok_snapshot_test( + snapshot_test( r#" fn foo() { let a = 3+4*5 @@ -100,7 +100,7 @@ fn binary_expr() { #[test] fn expression_statement() { - ok_snapshot_test( + snapshot_test( r#" fn foo() { let a = "hello" @@ -116,7 +116,7 @@ fn expression_statement() { #[test] fn function_calls() { - ok_snapshot_test( + snapshot_test( r#" fn bar(i:number) { } fn foo(i:number) { @@ -128,7 +128,7 @@ fn function_calls() { #[test] fn patterns() { - ok_snapshot_test( + snapshot_test( r#" fn main(_:number) { let a = 0; @@ -140,7 +140,7 @@ fn patterns() { #[test] fn compare_operands() { - ok_snapshot_test( + snapshot_test( r#" fn main() { let _ = a==b; @@ -157,7 +157,7 @@ fn compare_operands() { #[test] fn if_expr() { - ok_snapshot_test( + snapshot_test( r#" fn bar() { if true {}; @@ -171,7 +171,7 @@ fn if_expr() { #[test] fn block_expr() { - ok_snapshot_test( + snapshot_test( r#" fn bar() { {3} @@ -182,7 +182,7 @@ fn block_expr() { #[test] fn return_expr() { - ok_snapshot_test( + snapshot_test( r#" fn foo() { return; @@ -194,7 +194,7 @@ fn return_expr() { #[test] fn loop_expr() { - ok_snapshot_test( + snapshot_test( r#" fn foo() { loop {} diff --git a/crates/mun_syntax/src/tests/snapshots/parser__structures.snap b/crates/mun_syntax/src/tests/snapshots/parser__structures.snap index a4250a268..a4a4c1580 100644 --- a/crates/mun_syntax/src/tests/snapshots/parser__structures.snap +++ b/crates/mun_syntax/src/tests/snapshots/parser__structures.snap @@ -1,57 +1,125 @@ --- source: crates/mun_syntax/src/tests/parser.rs -expression: "struct Foo;\nstruct Foo {}\nstruct Foo {\n a: float,\n b: int,\n}" +expression: "struct Foo // error: expected ';', or '{'\nstruct Foo;\nstruct Foo;; // error: expected a declaration\nstruct Foo {}\nstruct Foo {};\nstruct Foo {\n a: float,\n b: int,\n}\nstruct Foo {\n a: float,\n b: int,\n};" --- -SOURCE_FILE@[0; 66) - STRUCT_DEF@[0; 11) +SOURCE_FILE@[0; 219) + STRUCT_DEF@[0; 10) STRUCT_KW@[0; 6) "struct" WHITESPACE@[6; 7) " " NAME@[7; 10) IDENT@[7; 10) "Foo" - SEMI@[10; 11) ";" - WHITESPACE@[11; 12) "\n" - STRUCT_DEF@[12; 25) - STRUCT_KW@[12; 18) "struct" - WHITESPACE@[18; 19) " " - NAME@[19; 22) - IDENT@[19; 22) "Foo" - WHITESPACE@[22; 23) " " - RECORD_FIELD_DEF_LIST@[23; 25) - L_CURLY@[23; 24) "{" - R_CURLY@[24; 25) "}" - WHITESPACE@[25; 26) "\n" - STRUCT_DEF@[26; 66) - STRUCT_KW@[26; 32) "struct" - WHITESPACE@[32; 33) " " - NAME@[33; 36) - IDENT@[33; 36) "Foo" - WHITESPACE@[36; 37) " " - RECORD_FIELD_DEF_LIST@[37; 66) - L_CURLY@[37; 38) "{" - WHITESPACE@[38; 43) "\n " - RECORD_FIELD_DEF@[43; 51) - NAME@[43; 44) - IDENT@[43; 44) "a" - COLON@[44; 45) ":" - WHITESPACE@[45; 46) " " - PATH_TYPE@[46; 51) - PATH@[46; 51) - PATH_SEGMENT@[46; 51) - NAME_REF@[46; 51) - IDENT@[46; 51) "float" - COMMA@[51; 52) "," - WHITESPACE@[52; 57) "\n " - RECORD_FIELD_DEF@[57; 63) - NAME@[57; 58) - IDENT@[57; 58) "b" - COLON@[58; 59) ":" - WHITESPACE@[59; 60) " " - PATH_TYPE@[60; 63) - PATH@[60; 63) - PATH_SEGMENT@[60; 63) - NAME_REF@[60; 63) - IDENT@[60; 63) "int" - COMMA@[63; 64) "," - WHITESPACE@[64; 65) "\n" - R_CURLY@[65; 66) "}" + WHITESPACE@[10; 16) " " + COMMENT@[16; 46) "// error: expected \'; ..." + WHITESPACE@[46; 47) "\n" + STRUCT_DEF@[47; 58) + STRUCT_KW@[47; 53) "struct" + WHITESPACE@[53; 54) " " + NAME@[54; 57) + IDENT@[54; 57) "Foo" + SEMI@[57; 58) ";" + WHITESPACE@[58; 59) "\n" + STRUCT_DEF@[59; 70) + STRUCT_KW@[59; 65) "struct" + WHITESPACE@[65; 66) " " + NAME@[66; 69) + IDENT@[66; 69) "Foo" + SEMI@[69; 70) ";" + ERROR@[70; 71) + SEMI@[70; 71) ";" + WHITESPACE@[71; 75) " " + COMMENT@[75; 107) "// error: expected a ..." + WHITESPACE@[107; 108) "\n" + STRUCT_DEF@[108; 121) + STRUCT_KW@[108; 114) "struct" + WHITESPACE@[114; 115) " " + NAME@[115; 118) + IDENT@[115; 118) "Foo" + WHITESPACE@[118; 119) " " + RECORD_FIELD_DEF_LIST@[119; 121) + L_CURLY@[119; 120) "{" + R_CURLY@[120; 121) "}" + WHITESPACE@[121; 122) "\n" + STRUCT_DEF@[122; 136) + STRUCT_KW@[122; 128) "struct" + WHITESPACE@[128; 129) " " + NAME@[129; 132) + IDENT@[129; 132) "Foo" + WHITESPACE@[132; 133) " " + RECORD_FIELD_DEF_LIST@[133; 136) + L_CURLY@[133; 134) "{" + R_CURLY@[134; 135) "}" + SEMI@[135; 136) ";" + WHITESPACE@[136; 137) "\n" + STRUCT_DEF@[137; 177) + STRUCT_KW@[137; 143) "struct" + WHITESPACE@[143; 144) " " + NAME@[144; 147) + IDENT@[144; 147) "Foo" + WHITESPACE@[147; 148) " " + RECORD_FIELD_DEF_LIST@[148; 177) + L_CURLY@[148; 149) "{" + WHITESPACE@[149; 154) "\n " + RECORD_FIELD_DEF@[154; 162) + NAME@[154; 155) + IDENT@[154; 155) "a" + COLON@[155; 156) ":" + WHITESPACE@[156; 157) " " + PATH_TYPE@[157; 162) + PATH@[157; 162) + PATH_SEGMENT@[157; 162) + NAME_REF@[157; 162) + IDENT@[157; 162) "float" + COMMA@[162; 163) "," + WHITESPACE@[163; 168) "\n " + RECORD_FIELD_DEF@[168; 174) + NAME@[168; 169) + IDENT@[168; 169) "b" + COLON@[169; 170) ":" + WHITESPACE@[170; 171) " " + PATH_TYPE@[171; 174) + PATH@[171; 174) + PATH_SEGMENT@[171; 174) + NAME_REF@[171; 174) + IDENT@[171; 174) "int" + COMMA@[174; 175) "," + WHITESPACE@[175; 176) "\n" + R_CURLY@[176; 177) "}" + WHITESPACE@[177; 178) "\n" + STRUCT_DEF@[178; 219) + STRUCT_KW@[178; 184) "struct" + WHITESPACE@[184; 185) " " + NAME@[185; 188) + IDENT@[185; 188) "Foo" + WHITESPACE@[188; 189) " " + RECORD_FIELD_DEF_LIST@[189; 219) + L_CURLY@[189; 190) "{" + WHITESPACE@[190; 195) "\n " + RECORD_FIELD_DEF@[195; 203) + NAME@[195; 196) + IDENT@[195; 196) "a" + COLON@[196; 197) ":" + WHITESPACE@[197; 198) " " + PATH_TYPE@[198; 203) + PATH@[198; 203) + PATH_SEGMENT@[198; 203) + NAME_REF@[198; 203) + IDENT@[198; 203) "float" + COMMA@[203; 204) "," + WHITESPACE@[204; 209) "\n " + RECORD_FIELD_DEF@[209; 215) + NAME@[209; 210) + IDENT@[209; 210) "b" + COLON@[210; 211) ":" + WHITESPACE@[211; 212) " " + PATH_TYPE@[212; 215) + PATH@[212; 215) + PATH_SEGMENT@[212; 215) + NAME_REF@[212; 215) + IDENT@[212; 215) "int" + COMMA@[215; 216) "," + WHITESPACE@[216; 217) "\n" + R_CURLY@[217; 218) "}" + SEMI@[218; 219) ";" +error Offset(10): expected ';', or '{' +error Offset(70): expected a declaration From 0b10bb165fcc49a952515e0e30d44e6fc44224d6 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Sat, 30 Nov 2019 10:19:16 +0100 Subject: [PATCH 04/21] feat(tuple): add parsing and HIR lowering for tuples --- crates/mun_hir/src/adt.rs | 67 +++- crates/mun_hir/src/code_model.rs | 8 +- crates/mun_hir/src/code_model/src.rs | 2 +- crates/mun_hir/src/name.rs | 67 +++- crates/mun_hir/src/ty/lower.rs | 5 +- .../snapshots/tests__struct_declaration.snap | 10 +- crates/mun_hir/src/ty/tests.rs | 5 +- crates/mun_syntax/src/ast/extensions.rs | 16 +- crates/mun_syntax/src/ast/generated.rs | 63 ++++ crates/mun_syntax/src/grammar.ron | 11 + crates/mun_syntax/src/parsing/grammar/adt.rs | 30 +- .../mun_syntax/src/parsing/grammar/types.rs | 2 + .../mun_syntax/src/syntax_kind/generated.rs | 4 + crates/mun_syntax/src/tests/parser.rs | 10 +- .../tests/snapshots/parser__structures.snap | 317 +++++++++++------- 15 files changed, 450 insertions(+), 167 deletions(-) diff --git a/crates/mun_hir/src/adt.rs b/crates/mun_hir/src/adt.rs index d973aab61..0e599c0e4 100644 --- a/crates/mun_hir/src/adt.rs +++ b/crates/mun_hir/src/adt.rs @@ -8,21 +8,42 @@ use crate::{ }; use mun_syntax::ast::{self, NameOwner, TypeAscriptionOwner}; -/// A single field of an enum variant or struct +/// A single field of a record +/// ```mun +/// struct Foo { +/// a: int, // <- this +/// } +/// ``` +/// or +/// ```mun +/// struct Foo( +/// int, // <- this +/// ) +/// ``` #[derive(Debug, Clone, PartialEq, Eq)] -pub struct FieldData { +pub struct StructFieldData { pub name: Name, pub type_ref: TypeRef, } +/// An identifier for a struct's or tuple's field #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct LocalStructFieldId(RawId); -impl_arena_id!(LocalStructFieldId); +pub struct StructFieldId(RawId); +impl_arena_id!(StructFieldId); + +/// A struct's fields' data (record, tuple, or unit struct) +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum StructKind { + Record, + Tuple, + Unit, +} #[derive(Debug, Clone, PartialEq, Eq)] pub struct StructData { pub name: Name, - pub fields: Option>>, + pub fields: Arena, + pub kind: StructKind, } impl StructData { @@ -34,18 +55,30 @@ impl StructData { .map(|n| n.as_name()) .unwrap_or_else(Name::missing); - let fields = if let ast::StructKind::Record(r) = src.ast.kind() { - let fields = r - .fields() - .map(|fd| FieldData { - name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing), - type_ref: TypeRef::from_ast_opt(fd.ascribed_type()), - }) - .collect(); - Some(Arc::new(fields)) - } else { - None + let (fields, kind) = match src.ast.kind() { + ast::StructKind::Record(r) => { + let fields = r + .fields() + .map(|fd| StructFieldData { + name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing), + type_ref: TypeRef::from_ast_opt(fd.ascribed_type()), + }) + .collect(); + (fields, StructKind::Record) + } + ast::StructKind::Tuple(t) => { + let fields = t + .fields() + .enumerate() + .map(|(index, fd)| StructFieldData { + name: Name::new_tuple_field(index), + type_ref: TypeRef::from_ast_opt(fd.type_ref()), + }) + .collect(); + (fields, StructKind::Tuple) + } + ast::StructKind::Unit => (Arena::default(), StructKind::Unit), }; - Arc::new(StructData { name, fields }) + Arc::new(StructData { name, fields, kind }) } } diff --git a/crates/mun_hir/src/code_model.rs b/crates/mun_hir/src/code_model.rs index 81974cbec..1d5b69d79 100644 --- a/crates/mun_hir/src/code_model.rs +++ b/crates/mun_hir/src/code_model.rs @@ -1,7 +1,7 @@ pub(crate) mod src; use self::src::HasSource; -use crate::adt::{LocalStructFieldId, StructData}; +use crate::adt::{StructData, StructFieldId}; use crate::diagnostics::DiagnosticSink; use crate::expr::{Body, BodySourceMap}; use crate::ids::AstItemDef; @@ -333,7 +333,7 @@ pub struct Struct { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct StructField { pub(crate) parent: Struct, - pub(crate) id: LocalStructFieldId, + pub(crate) id: StructFieldId, } impl Struct { @@ -354,9 +354,7 @@ impl Struct { pub fn fields(self, db: &impl HirDatabase) -> Vec { self.data(db) .fields - .as_ref() - .into_iter() - .flat_map(|it| it.iter()) + .iter() .map(|(id, _)| StructField { parent: self, id }) .collect() } diff --git a/crates/mun_hir/src/code_model/src.rs b/crates/mun_hir/src/code_model/src.rs index 95eafce8d..bb0843e96 100644 --- a/crates/mun_hir/src/code_model/src.rs +++ b/crates/mun_hir/src/code_model/src.rs @@ -42,7 +42,7 @@ impl HasSource for StructField { let ast = field_sources .into_iter() - .zip(self.parent.data(db).fields.as_ref().unwrap().iter()) + .zip(self.parent.data(db).fields.iter()) .find(|(_syntax, (id, _))| *id == self.id) .unwrap() .0; diff --git a/crates/mun_hir/src/name.rs b/crates/mun_hir/src/name.rs index 67d429677..ab9855343 100644 --- a/crates/mun_hir/src/name.rs +++ b/crates/mun_hir/src/name.rs @@ -3,30 +3,60 @@ use std::fmt; /// `Name` is a wrapper around string, which is used in hir for both references /// and declarations. -#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -pub struct Name { - text: SmolStr, +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Name(Repr); + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +enum Repr { + Text(SmolStr), + TupleField(usize), } impl fmt::Display for Name { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.text, f) + match &self.0 { + Repr::Text(text) => fmt::Display::fmt(&text, f), + Repr::TupleField(idx) => fmt::Display::fmt(&idx, f), + } } } -impl fmt::Debug for Name { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(&self.text, f) +impl Name { + /// Note: this is private to make creating name from random string hard. + /// Hopefully, this should allow us to integrate hygiene cleaner in the + /// future, and to switch to interned representation of names. + const fn new_text(text: SmolStr) -> Name { + Name(Repr::Text(text)) } -} -impl Name { - const fn new(text: SmolStr) -> Name { - Name { text } + pub(crate) fn new_tuple_field(idx: usize) -> Name { + Name(Repr::TupleField(idx)) + } + + /// Shortcut to create inline plain text name + const fn new_inline_ascii(len: usize, text: &[u8]) -> Name { + Name::new_text(SmolStr::new_inline_from_ascii(len, text)) + } + + /// Resolve a name from the text of token. + fn resolve(raw_text: &SmolStr) -> Name { + let raw_start = "r#"; + if raw_text.as_str().starts_with(raw_start) { + Name::new_text(SmolStr::new(&raw_text[raw_start.len()..])) + } else { + Name::new_text(raw_text.clone()) + } } pub(crate) fn missing() -> Name { - Name::new("[missing name]".into()) + Name::new_text("[missing name]".into()) + } + + pub(crate) fn as_tuple_index(&self) -> Option { + match self.0 { + Repr::TupleField(idx) => Some(idx), + _ => None, + } } } @@ -36,16 +66,19 @@ pub(crate) trait AsName { impl AsName for ast::NameRef { fn as_name(&self) -> Name { - Name::new(self.text().clone()) + match self.as_tuple_field() { + Some(idx) => Name::new_tuple_field(idx), + None => Name::resolve(self.text()), + } } } impl AsName for ast::Name { fn as_name(&self) -> Name { - Name::new(self.text().clone()) + Name::resolve(self.text()) } } -pub(crate) const FLOAT: Name = Name::new(SmolStr::new_inline_from_ascii(5, b"float")); -pub(crate) const INT: Name = Name::new(SmolStr::new_inline_from_ascii(3, b"int")); -pub(crate) const BOOLEAN: Name = Name::new(SmolStr::new_inline_from_ascii(4, b"bool")); +pub(crate) const FLOAT: Name = Name::new_inline_ascii(5, b"float"); +pub(crate) const INT: Name = Name::new_inline_ascii(3, b"int"); +pub(crate) const BOOLEAN: Name = Name::new_inline_ascii(4, b"bool"); diff --git a/crates/mun_hir/src/ty/lower.rs b/crates/mun_hir/src/ty/lower.rs index e22f4e0f7..13d9e4939 100644 --- a/crates/mun_hir/src/ty/lower.rs +++ b/crates/mun_hir/src/ty/lower.rs @@ -1,4 +1,5 @@ pub(crate) use self::diagnostics::LowerDiagnostic; +use crate::adt::StructKind; use crate::code_model::BuiltinType; use crate::name_resolution::Namespace; use crate::resolve::{Resolution, Resolver}; @@ -162,8 +163,8 @@ pub fn fn_sig_for_fn(db: &impl HirDatabase, def: Function) -> FnSig { /// Build the type of a struct constructor. fn type_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> Ty { let struct_data = db.struct_data(def.id); - if struct_data.fields.is_none() { - type_for_struct(db, def) // Unit struct + if struct_data.kind == StructKind::Unit { + type_for_struct(db, def) } else { unreachable!(); } diff --git a/crates/mun_hir/src/ty/snapshots/tests__struct_declaration.snap b/crates/mun_hir/src/ty/snapshots/tests__struct_declaration.snap index 03c318842..bbe9a1ef8 100644 --- a/crates/mun_hir/src/ty/snapshots/tests__struct_declaration.snap +++ b/crates/mun_hir/src/ty/snapshots/tests__struct_declaration.snap @@ -1,8 +1,8 @@ --- source: crates/mun_hir/src/ty/tests.rs -expression: "struct Foo;\nstruct Bar {}\nstruct Baz {\n f: float,\n i: int,\n}\n\nfn main() {\n let foo: Foo;\n let bar: Bar;\n let baz: Baz;\n}" +expression: "struct Foo;\nstruct Bar {\n f: float,\n i: int,\n}\nstruct Baz(float, int);\n\n\nfn main() {\n let foo: Foo;\n let bar: Bar;\n let baz: Baz;\n}" --- -[78; 135) '{ ...Baz; }': nothing -[88; 91) 'foo': Foo -[106; 109) 'bar': Bar -[124; 127) 'baz': Baz +[89; 146) '{ ...Baz; }': nothing +[99; 102) 'foo': Foo +[117; 120) 'bar': Bar +[135; 138) 'baz': Baz diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index f69bdf6cd..941cde3fd 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -177,11 +177,12 @@ fn struct_declaration() { infer_snapshot( r#" struct Foo; - struct Bar {} - struct Baz { + struct Bar { f: float, i: int, } + struct Baz(float, int); + fn main() { let foo: Foo; diff --git a/crates/mun_syntax/src/ast/extensions.rs b/crates/mun_syntax/src/ast/extensions.rs index 53f3e3811..bca795b6c 100644 --- a/crates/mun_syntax/src/ast/extensions.rs +++ b/crates/mun_syntax/src/ast/extensions.rs @@ -1,6 +1,6 @@ use crate::{ ast::{self, child_opt, AstNode, NameOwner}, - T, + SyntaxKind, T, }; use crate::{SmolStr, SyntaxNode}; use text_unit::TextRange; @@ -15,6 +15,17 @@ impl ast::NameRef { pub fn text(&self) -> &SmolStr { text_of_first_token(self.syntax()) } + + pub fn as_tuple_field(&self) -> Option { + self.syntax().children_with_tokens().find_map(|c| { + if c.kind() == SyntaxKind::INT_NUMBER { + c.as_token() + .and_then(|tok| tok.text().as_str().parse().ok()) + } else { + None + } + }) + } } impl ast::FunctionDef { @@ -91,6 +102,7 @@ impl ast::PathSegment { #[derive(Debug, Clone, PartialEq, Eq)] pub enum StructKind { Record(ast::RecordFieldDefList), + Tuple(ast::TupleFieldDefList), Unit, } @@ -98,6 +110,8 @@ impl StructKind { fn from_node(node: &N) -> StructKind { if let Some(r) = child_opt::<_, ast::RecordFieldDefList>(node) { StructKind::Record(r) + } else if let Some(t) = child_opt::<_, ast::TupleFieldDefList>(node) { + StructKind::Tuple(t) } else { StructKind::Unit } diff --git a/crates/mun_syntax/src/ast/generated.rs b/crates/mun_syntax/src/ast/generated.rs index 83dc40c12..6b22a4210 100644 --- a/crates/mun_syntax/src/ast/generated.rs +++ b/crates/mun_syntax/src/ast/generated.rs @@ -1264,6 +1264,69 @@ impl ast::VisibilityOwner for StructDef {} impl ast::DocCommentsOwner for StructDef {} impl StructDef {} +// TupleFieldDef + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TupleFieldDef { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for TupleFieldDef { + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + TUPLE_FIELD_DEF => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(TupleFieldDef { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl ast::VisibilityOwner for TupleFieldDef {} +impl TupleFieldDef { + pub fn type_ref(&self) -> Option { + super::child_opt(self) + } +} + +// TupleFieldDefList + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct TupleFieldDefList { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for TupleFieldDefList { + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + TUPLE_FIELD_DEF_LIST => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(TupleFieldDefList { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl TupleFieldDefList { + pub fn fields(&self) -> impl Iterator { + super::children(self) + } +} + // TypeRef #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/mun_syntax/src/grammar.ron b/crates/mun_syntax/src/grammar.ron index bf0f06dcf..c02138ade 100644 --- a/crates/mun_syntax/src/grammar.ron +++ b/crates/mun_syntax/src/grammar.ron @@ -108,6 +108,8 @@ Grammar( "STRUCT_DEF", "RECORD_FIELD_DEF_LIST", "RECORD_FIELD_DEF", + "TUPLE_FIELD_DEF_LIST", + "TUPLE_FIELD_DEF", "PATH_TYPE", "NEVER_TYPE", @@ -184,6 +186,15 @@ Grammar( "TypeAscriptionOwner" ] ), + "TupleFieldDefList": (collections: [("fields", "TupleFieldDef")]), + "TupleFieldDef": ( + options: [ + "TypeRef", + ], + traits: [ + "VisibilityOwner", + ] + ), "LetStmt": ( options: [ ["pat", "Pat"], diff --git a/crates/mun_syntax/src/parsing/grammar/adt.rs b/crates/mun_syntax/src/parsing/grammar/adt.rs index 66f7d7100..7665b0da5 100644 --- a/crates/mun_syntax/src/parsing/grammar/adt.rs +++ b/crates/mun_syntax/src/parsing/grammar/adt.rs @@ -10,8 +10,9 @@ pub(super) fn struct_def(p: &mut Parser, m: Marker) { p.bump(T![;]); } T!['{'] => record_field_def_list(p), + T!['('] => tuple_field_def_list(p), _ => { - p.error("expected ';', or '{'"); + p.error("expected a ';', or '{'"); } } m.complete(p, STRUCT_DEF); @@ -23,7 +24,7 @@ pub(super) fn record_field_def_list(p: &mut Parser) { p.bump(T!['{']); while !p.at(T!['}']) && !p.at(EOF) { if p.at(T!['{']) { - error_block(p, "expected field"); + error_block(p, "expected a field"); continue; } record_field_def(p); @@ -36,6 +37,29 @@ pub(super) fn record_field_def_list(p: &mut Parser) { m.complete(p, RECORD_FIELD_DEF_LIST); } +pub(super) fn tuple_field_def_list(p: &mut Parser) { + assert!(p.at(T!['('])); + let m = p.start(); + p.bump(T!['(']); + while !p.at(T![')']) && !p.at(EOF) { + let m = p.start(); + if !p.at_ts(types::TYPE_FIRST) { + m.abandon(p); + p.error_and_bump("expected a type"); + break; + } + types::type_(p); + m.complete(p, TUPLE_FIELD_DEF); + + if !p.at(T![')']) { + p.expect(T![,]); + } + } + p.expect(T![')']); + p.eat(T![;]); + m.complete(p, TUPLE_FIELD_DEF_LIST); +} + fn record_field_def(p: &mut Parser) { let m = p.start(); opt_visibility(p); @@ -46,6 +70,6 @@ fn record_field_def(p: &mut Parser) { m.complete(p, RECORD_FIELD_DEF); } else { m.abandon(p); - p.error_and_bump("expected field declaration"); + p.error_and_bump("expected a field declaration"); } } diff --git a/crates/mun_syntax/src/parsing/grammar/types.rs b/crates/mun_syntax/src/parsing/grammar/types.rs index 87f02d358..410e27085 100644 --- a/crates/mun_syntax/src/parsing/grammar/types.rs +++ b/crates/mun_syntax/src/parsing/grammar/types.rs @@ -1,5 +1,7 @@ use super::*; +pub(super) const TYPE_FIRST: TokenSet = paths::PATH_FIRST.union(token_set![T![never],]); + pub(super) const TYPE_RECOVERY_SET: TokenSet = token_set![R_PAREN, COMMA]; pub(super) fn ascription(p: &mut Parser) { diff --git a/crates/mun_syntax/src/syntax_kind/generated.rs b/crates/mun_syntax/src/syntax_kind/generated.rs index d360ac603..dad0229cd 100644 --- a/crates/mun_syntax/src/syntax_kind/generated.rs +++ b/crates/mun_syntax/src/syntax_kind/generated.rs @@ -91,6 +91,8 @@ pub enum SyntaxKind { STRUCT_DEF, RECORD_FIELD_DEF_LIST, RECORD_FIELD_DEF, + TUPLE_FIELD_DEF_LIST, + TUPLE_FIELD_DEF, PATH_TYPE, NEVER_TYPE, LET_STMT, @@ -355,6 +357,8 @@ impl SyntaxKind { STRUCT_DEF => &SyntaxInfo { name: "STRUCT_DEF" }, RECORD_FIELD_DEF_LIST => &SyntaxInfo { name: "RECORD_FIELD_DEF_LIST" }, RECORD_FIELD_DEF => &SyntaxInfo { name: "RECORD_FIELD_DEF" }, + TUPLE_FIELD_DEF_LIST => &SyntaxInfo { name: "TUPLE_FIELD_DEF_LIST" }, + TUPLE_FIELD_DEF => &SyntaxInfo { name: "TUPLE_FIELD_DEF" }, PATH_TYPE => &SyntaxInfo { name: "PATH_TYPE" }, NEVER_TYPE => &SyntaxInfo { name: "NEVER_TYPE" }, LET_STMT => &SyntaxInfo { name: "LET_STMT" }, diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index e80ae37f1..8e1de7312 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -57,19 +57,25 @@ fn literals() { fn structures() { snapshot_test( r#" - struct Foo // error: expected ';', or '{' + struct Foo // error: expected a ';', or a '{' struct Foo; struct Foo;; // error: expected a declaration struct Foo {} struct Foo {}; + struct Foo {,}; // error: expected a field declaration struct Foo { a: float, - b: int, } struct Foo { a: float, b: int, }; + struct Foo() + struct Foo(); + struct Foo(,); // error: expected a type + struct Foo(float) + struct Foo(float,); + struct Foo(float, int) "#, ) } diff --git a/crates/mun_syntax/src/tests/snapshots/parser__structures.snap b/crates/mun_syntax/src/tests/snapshots/parser__structures.snap index a4a4c1580..cdb5aed26 100644 --- a/crates/mun_syntax/src/tests/snapshots/parser__structures.snap +++ b/crates/mun_syntax/src/tests/snapshots/parser__structures.snap @@ -1,125 +1,218 @@ --- source: crates/mun_syntax/src/tests/parser.rs -expression: "struct Foo // error: expected ';', or '{'\nstruct Foo;\nstruct Foo;; // error: expected a declaration\nstruct Foo {}\nstruct Foo {};\nstruct Foo {\n a: float,\n b: int,\n}\nstruct Foo {\n a: float,\n b: int,\n};" +expression: "struct Foo // error: expected a ';', or a '{'\nstruct Foo;\nstruct Foo;; // error: expected a declaration\nstruct Foo {}\nstruct Foo {};\nstruct Foo {,}; // error: expected a field declaration\nstruct Foo {\n a: float,\n}\nstruct Foo {\n a: float,\n b: int,\n};\nstruct Foo()\nstruct Foo();\nstruct Foo(,); // error: expected a type\nstruct Foo(float)\nstruct Foo(float,);\nstruct Foo(float, int)" --- -SOURCE_FILE@[0; 219) +SOURCE_FILE@[0; 396) STRUCT_DEF@[0; 10) STRUCT_KW@[0; 6) "struct" WHITESPACE@[6; 7) " " NAME@[7; 10) IDENT@[7; 10) "Foo" WHITESPACE@[10; 16) " " - COMMENT@[16; 46) "// error: expected \'; ..." - WHITESPACE@[46; 47) "\n" - STRUCT_DEF@[47; 58) - STRUCT_KW@[47; 53) "struct" - WHITESPACE@[53; 54) " " - NAME@[54; 57) - IDENT@[54; 57) "Foo" - SEMI@[57; 58) ";" - WHITESPACE@[58; 59) "\n" - STRUCT_DEF@[59; 70) - STRUCT_KW@[59; 65) "struct" - WHITESPACE@[65; 66) " " - NAME@[66; 69) - IDENT@[66; 69) "Foo" - SEMI@[69; 70) ";" - ERROR@[70; 71) - SEMI@[70; 71) ";" - WHITESPACE@[71; 75) " " - COMMENT@[75; 107) "// error: expected a ..." - WHITESPACE@[107; 108) "\n" - STRUCT_DEF@[108; 121) - STRUCT_KW@[108; 114) "struct" - WHITESPACE@[114; 115) " " - NAME@[115; 118) - IDENT@[115; 118) "Foo" + COMMENT@[16; 50) "// error: expected a ..." + WHITESPACE@[50; 51) "\n" + STRUCT_DEF@[51; 62) + STRUCT_KW@[51; 57) "struct" + WHITESPACE@[57; 58) " " + NAME@[58; 61) + IDENT@[58; 61) "Foo" + SEMI@[61; 62) ";" + WHITESPACE@[62; 63) "\n" + STRUCT_DEF@[63; 74) + STRUCT_KW@[63; 69) "struct" + WHITESPACE@[69; 70) " " + NAME@[70; 73) + IDENT@[70; 73) "Foo" + SEMI@[73; 74) ";" + ERROR@[74; 75) + SEMI@[74; 75) ";" + WHITESPACE@[75; 79) " " + COMMENT@[79; 111) "// error: expected a ..." + WHITESPACE@[111; 112) "\n" + STRUCT_DEF@[112; 125) + STRUCT_KW@[112; 118) "struct" WHITESPACE@[118; 119) " " - RECORD_FIELD_DEF_LIST@[119; 121) - L_CURLY@[119; 120) "{" - R_CURLY@[120; 121) "}" - WHITESPACE@[121; 122) "\n" - STRUCT_DEF@[122; 136) - STRUCT_KW@[122; 128) "struct" - WHITESPACE@[128; 129) " " - NAME@[129; 132) - IDENT@[129; 132) "Foo" + NAME@[119; 122) + IDENT@[119; 122) "Foo" + WHITESPACE@[122; 123) " " + RECORD_FIELD_DEF_LIST@[123; 125) + L_CURLY@[123; 124) "{" + R_CURLY@[124; 125) "}" + WHITESPACE@[125; 126) "\n" + STRUCT_DEF@[126; 140) + STRUCT_KW@[126; 132) "struct" WHITESPACE@[132; 133) " " - RECORD_FIELD_DEF_LIST@[133; 136) - L_CURLY@[133; 134) "{" - R_CURLY@[134; 135) "}" - SEMI@[135; 136) ";" - WHITESPACE@[136; 137) "\n" - STRUCT_DEF@[137; 177) - STRUCT_KW@[137; 143) "struct" - WHITESPACE@[143; 144) " " - NAME@[144; 147) - IDENT@[144; 147) "Foo" + NAME@[133; 136) + IDENT@[133; 136) "Foo" + WHITESPACE@[136; 137) " " + RECORD_FIELD_DEF_LIST@[137; 140) + L_CURLY@[137; 138) "{" + R_CURLY@[138; 139) "}" + SEMI@[139; 140) ";" + WHITESPACE@[140; 141) "\n" + STRUCT_DEF@[141; 156) + STRUCT_KW@[141; 147) "struct" WHITESPACE@[147; 148) " " - RECORD_FIELD_DEF_LIST@[148; 177) - L_CURLY@[148; 149) "{" - WHITESPACE@[149; 154) "\n " - RECORD_FIELD_DEF@[154; 162) - NAME@[154; 155) - IDENT@[154; 155) "a" - COLON@[155; 156) ":" - WHITESPACE@[156; 157) " " - PATH_TYPE@[157; 162) - PATH@[157; 162) - PATH_SEGMENT@[157; 162) - NAME_REF@[157; 162) - IDENT@[157; 162) "float" - COMMA@[162; 163) "," - WHITESPACE@[163; 168) "\n " - RECORD_FIELD_DEF@[168; 174) - NAME@[168; 169) - IDENT@[168; 169) "b" - COLON@[169; 170) ":" - WHITESPACE@[170; 171) " " - PATH_TYPE@[171; 174) - PATH@[171; 174) - PATH_SEGMENT@[171; 174) - NAME_REF@[171; 174) - IDENT@[171; 174) "int" - COMMA@[174; 175) "," - WHITESPACE@[175; 176) "\n" - R_CURLY@[176; 177) "}" - WHITESPACE@[177; 178) "\n" - STRUCT_DEF@[178; 219) - STRUCT_KW@[178; 184) "struct" - WHITESPACE@[184; 185) " " - NAME@[185; 188) - IDENT@[185; 188) "Foo" - WHITESPACE@[188; 189) " " - RECORD_FIELD_DEF_LIST@[189; 219) - L_CURLY@[189; 190) "{" - WHITESPACE@[190; 195) "\n " - RECORD_FIELD_DEF@[195; 203) - NAME@[195; 196) - IDENT@[195; 196) "a" - COLON@[196; 197) ":" - WHITESPACE@[197; 198) " " - PATH_TYPE@[198; 203) - PATH@[198; 203) - PATH_SEGMENT@[198; 203) - NAME_REF@[198; 203) - IDENT@[198; 203) "float" - COMMA@[203; 204) "," - WHITESPACE@[204; 209) "\n " - RECORD_FIELD_DEF@[209; 215) - NAME@[209; 210) - IDENT@[209; 210) "b" - COLON@[210; 211) ":" - WHITESPACE@[211; 212) " " - PATH_TYPE@[212; 215) - PATH@[212; 215) - PATH_SEGMENT@[212; 215) - NAME_REF@[212; 215) - IDENT@[212; 215) "int" - COMMA@[215; 216) "," - WHITESPACE@[216; 217) "\n" - R_CURLY@[217; 218) "}" - SEMI@[218; 219) ";" -error Offset(10): expected ';', or '{' -error Offset(70): expected a declaration + NAME@[148; 151) + IDENT@[148; 151) "Foo" + WHITESPACE@[151; 152) " " + RECORD_FIELD_DEF_LIST@[152; 156) + L_CURLY@[152; 153) "{" + ERROR@[153; 154) + COMMA@[153; 154) "," + R_CURLY@[154; 155) "}" + SEMI@[155; 156) ";" + WHITESPACE@[156; 157) " " + COMMENT@[157; 195) "// error: expected a ..." + WHITESPACE@[195; 196) "\n" + STRUCT_DEF@[196; 224) + STRUCT_KW@[196; 202) "struct" + WHITESPACE@[202; 203) " " + NAME@[203; 206) + IDENT@[203; 206) "Foo" + WHITESPACE@[206; 207) " " + RECORD_FIELD_DEF_LIST@[207; 224) + L_CURLY@[207; 208) "{" + WHITESPACE@[208; 213) "\n " + RECORD_FIELD_DEF@[213; 221) + NAME@[213; 214) + IDENT@[213; 214) "a" + COLON@[214; 215) ":" + WHITESPACE@[215; 216) " " + PATH_TYPE@[216; 221) + PATH@[216; 221) + PATH_SEGMENT@[216; 221) + NAME_REF@[216; 221) + IDENT@[216; 221) "float" + COMMA@[221; 222) "," + WHITESPACE@[222; 223) "\n" + R_CURLY@[223; 224) "}" + WHITESPACE@[224; 225) "\n" + STRUCT_DEF@[225; 266) + STRUCT_KW@[225; 231) "struct" + WHITESPACE@[231; 232) " " + NAME@[232; 235) + IDENT@[232; 235) "Foo" + WHITESPACE@[235; 236) " " + RECORD_FIELD_DEF_LIST@[236; 266) + L_CURLY@[236; 237) "{" + WHITESPACE@[237; 242) "\n " + RECORD_FIELD_DEF@[242; 250) + NAME@[242; 243) + IDENT@[242; 243) "a" + COLON@[243; 244) ":" + WHITESPACE@[244; 245) " " + PATH_TYPE@[245; 250) + PATH@[245; 250) + PATH_SEGMENT@[245; 250) + NAME_REF@[245; 250) + IDENT@[245; 250) "float" + COMMA@[250; 251) "," + WHITESPACE@[251; 256) "\n " + RECORD_FIELD_DEF@[256; 262) + NAME@[256; 257) + IDENT@[256; 257) "b" + COLON@[257; 258) ":" + WHITESPACE@[258; 259) " " + PATH_TYPE@[259; 262) + PATH@[259; 262) + PATH_SEGMENT@[259; 262) + NAME_REF@[259; 262) + IDENT@[259; 262) "int" + COMMA@[262; 263) "," + WHITESPACE@[263; 264) "\n" + R_CURLY@[264; 265) "}" + SEMI@[265; 266) ";" + WHITESPACE@[266; 267) "\n" + STRUCT_DEF@[267; 279) + STRUCT_KW@[267; 273) "struct" + WHITESPACE@[273; 274) " " + NAME@[274; 277) + IDENT@[274; 277) "Foo" + TUPLE_FIELD_DEF_LIST@[277; 279) + L_PAREN@[277; 278) "(" + R_PAREN@[278; 279) ")" + WHITESPACE@[279; 280) "\n" + STRUCT_DEF@[280; 293) + STRUCT_KW@[280; 286) "struct" + WHITESPACE@[286; 287) " " + NAME@[287; 290) + IDENT@[287; 290) "Foo" + TUPLE_FIELD_DEF_LIST@[290; 293) + L_PAREN@[290; 291) "(" + R_PAREN@[291; 292) ")" + SEMI@[292; 293) ";" + WHITESPACE@[293; 294) "\n" + STRUCT_DEF@[294; 308) + STRUCT_KW@[294; 300) "struct" + WHITESPACE@[300; 301) " " + NAME@[301; 304) + IDENT@[301; 304) "Foo" + TUPLE_FIELD_DEF_LIST@[304; 308) + L_PAREN@[304; 305) "(" + ERROR@[305; 306) + COMMA@[305; 306) "," + R_PAREN@[306; 307) ")" + SEMI@[307; 308) ";" + WHITESPACE@[308; 310) " " + COMMENT@[310; 335) "// error: expected a ..." + WHITESPACE@[335; 336) "\n" + STRUCT_DEF@[336; 353) + STRUCT_KW@[336; 342) "struct" + WHITESPACE@[342; 343) " " + NAME@[343; 346) + IDENT@[343; 346) "Foo" + TUPLE_FIELD_DEF_LIST@[346; 353) + L_PAREN@[346; 347) "(" + TUPLE_FIELD_DEF@[347; 352) + PATH_TYPE@[347; 352) + PATH@[347; 352) + PATH_SEGMENT@[347; 352) + NAME_REF@[347; 352) + IDENT@[347; 352) "float" + R_PAREN@[352; 353) ")" + WHITESPACE@[353; 354) "\n" + STRUCT_DEF@[354; 373) + STRUCT_KW@[354; 360) "struct" + WHITESPACE@[360; 361) " " + NAME@[361; 364) + IDENT@[361; 364) "Foo" + TUPLE_FIELD_DEF_LIST@[364; 373) + L_PAREN@[364; 365) "(" + TUPLE_FIELD_DEF@[365; 370) + PATH_TYPE@[365; 370) + PATH@[365; 370) + PATH_SEGMENT@[365; 370) + NAME_REF@[365; 370) + IDENT@[365; 370) "float" + COMMA@[370; 371) "," + R_PAREN@[371; 372) ")" + SEMI@[372; 373) ";" + WHITESPACE@[373; 374) "\n" + STRUCT_DEF@[374; 396) + STRUCT_KW@[374; 380) "struct" + WHITESPACE@[380; 381) " " + NAME@[381; 384) + IDENT@[381; 384) "Foo" + TUPLE_FIELD_DEF_LIST@[384; 396) + L_PAREN@[384; 385) "(" + TUPLE_FIELD_DEF@[385; 390) + PATH_TYPE@[385; 390) + PATH@[385; 390) + PATH_SEGMENT@[385; 390) + NAME_REF@[385; 390) + IDENT@[385; 390) "float" + COMMA@[390; 391) "," + WHITESPACE@[391; 392) " " + TUPLE_FIELD_DEF@[392; 395) + PATH_TYPE@[392; 395) + PATH@[392; 395) + PATH_SEGMENT@[392; 395) + NAME_REF@[392; 395) + IDENT@[392; 395) "int" + R_PAREN@[395; 396) ")" +error Offset(10): expected a ';', or '{' +error Offset(74): expected a declaration +error Offset(153): expected a field declaration +error Offset(305): expected a type From 0ecdd6b52283e923830329e3297aa744abf7f7de Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Sat, 30 Nov 2019 14:03:49 +0100 Subject: [PATCH 05/21] feat(structs): generate type ir for structs --- crates/mun_codegen/Cargo.toml | 2 +- crates/mun_codegen/src/code_gen.rs | 2 +- crates/mun_codegen/src/code_gen/symbols.rs | 6 +- crates/mun_codegen/src/db.rs | 2 +- crates/mun_codegen/src/ir.rs | 1 + crates/mun_codegen/src/ir/adt.rs | 24 ++++++ crates/mun_codegen/src/ir/body.rs | 16 ++-- crates/mun_codegen/src/ir/dispatch_table.rs | 6 +- crates/mun_codegen/src/ir/function.rs | 4 +- crates/mun_codegen/src/ir/module.rs | 14 +++- crates/mun_codegen/src/ir/ty.rs | 6 +- crates/mun_codegen/src/mock.rs | 11 ++- .../src/snapshots/test__struct_test.snap | 19 +++++ crates/mun_codegen/src/test.rs | 33 ++++++-- crates/mun_hir/src/adt.rs | 31 +++++-- crates/mun_hir/src/code_model.rs | 40 ++++++++- crates/mun_hir/src/db.rs | 6 +- crates/mun_hir/src/ty.rs | 5 +- crates/mun_hir/src/ty/lower.rs | 82 ++++++++++++++++++- crates/mun_hir/src/type_ref.rs | 7 ++ crates/mun_syntax/src/tests/parser.rs | 4 +- 21 files changed, 271 insertions(+), 50 deletions(-) create mode 100644 crates/mun_codegen/src/ir/adt.rs create mode 100644 crates/mun_codegen/src/snapshots/test__struct_test.snap diff --git a/crates/mun_codegen/Cargo.toml b/crates/mun_codegen/Cargo.toml index 2e8ae06a8..428823178 100644 --- a/crates/mun_codegen/Cargo.toml +++ b/crates/mun_codegen/Cargo.toml @@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0" description = "LLVM IR code generation for Mun" [dependencies] -mun_hir = { path = "../mun_hir" } +hir = { path = "../mun_hir", package = "mun_hir" } mun_target = { path = "../mun_target" } mun_lld = { path = "../mun_lld" } failure = "0.1.5" diff --git a/crates/mun_codegen/src/code_gen.rs b/crates/mun_codegen/src/code_gen.rs index 28fa1e2f9..891b86787 100644 --- a/crates/mun_codegen/src/code_gen.rs +++ b/crates/mun_codegen/src/code_gen.rs @@ -1,11 +1,11 @@ use crate::code_gen::linker::LinkerError; use crate::IrDatabase; use failure::Fail; +use hir::FileId; use inkwell::module::Module; use inkwell::passes::{PassManager, PassManagerBuilder}; use inkwell::targets::{CodeModel, FileType, InitializationConfig, RelocMode, Target}; use inkwell::OptimizationLevel; -use mun_hir::FileId; use std::io::{self, Write}; use std::path::Path; diff --git a/crates/mun_codegen/src/code_gen/symbols.rs b/crates/mun_codegen/src/code_gen/symbols.rs index a7f605f61..fb6195a24 100644 --- a/crates/mun_codegen/src/code_gen/symbols.rs +++ b/crates/mun_codegen/src/code_gen/symbols.rs @@ -3,6 +3,7 @@ use crate::ir::dispatch_table::DispatchTable; use crate::ir::function; use crate::values::{BasicValue, GlobalValue}; use crate::IrDatabase; +use hir::{Ty, TypeCtor}; use inkwell::attributes::Attribute; use inkwell::values::{IntValue, PointerValue, UnnamedAddress}; use inkwell::{ @@ -10,7 +11,6 @@ use inkwell::{ values::{FunctionValue, StructValue}, AddressSpace, }; -use mun_hir::{self as hir, Ty, TypeCtor}; use std::collections::HashMap; use std::hash::{Hash, Hasher}; @@ -161,7 +161,7 @@ fn gen_function_info_array<'a, D: IrDatabase>( db: &D, types: &AbiTypes, module: &Module, - functions: impl Iterator, + functions: impl Iterator, ) -> GlobalValue { let function_infos: Vec = functions .map(|(f, value)| { @@ -254,7 +254,7 @@ fn gen_dispatch_table( /// for the ABI that `get_info` exposes. pub(super) fn gen_reflection_ir( db: &impl IrDatabase, - function_map: &HashMap, + function_map: &HashMap, dispatch_table: &DispatchTable, module: &Module, ) { diff --git a/crates/mun_codegen/src/db.rs b/crates/mun_codegen/src/db.rs index 01561b8de..17203db37 100644 --- a/crates/mun_codegen/src/db.rs +++ b/crates/mun_codegen/src/db.rs @@ -1,6 +1,6 @@ #![allow(clippy::type_repetition_in_bounds)] -use mun_hir as hir; + use crate::{code_gen::symbols::TypeInfo, ir::module::ModuleIR, Context}; use inkwell::{types::AnyTypeEnum, OptimizationLevel}; diff --git a/crates/mun_codegen/src/ir.rs b/crates/mun_codegen/src/ir.rs index 807768dba..1dd2bf525 100644 --- a/crates/mun_codegen/src/ir.rs +++ b/crates/mun_codegen/src/ir.rs @@ -1,5 +1,6 @@ use inkwell::types::{AnyTypeEnum, BasicTypeEnum}; +pub mod adt; pub mod body; pub(crate) mod dispatch_table; pub mod function; diff --git a/crates/mun_codegen/src/ir/adt.rs b/crates/mun_codegen/src/ir/adt.rs new file mode 100644 index 000000000..c761cc1ac --- /dev/null +++ b/crates/mun_codegen/src/ir/adt.rs @@ -0,0 +1,24 @@ +//use crate::ir::module::Types; +use crate::ir::try_convert_any_to_basic; +use crate::IrDatabase; +use inkwell::types::{AnyTypeEnum, BasicTypeEnum}; + +pub(super) fn gen_struct_decl(db: &impl IrDatabase, s: hir::Struct) { + if let AnyTypeEnum::StructType(struct_type) = db.type_ir(s.ty(db)) { + if struct_type.is_opaque() { + let field_types: Vec = s + .fields(db) + .iter() + .map(|field| { + let field_type = field.ty(db); + try_convert_any_to_basic(db.type_ir(field_type)) + .expect("could not convert field type") + }) + .collect(); + + struct_type.set_body(&field_types, false); + } + } else { + unreachable!() + } +} diff --git a/crates/mun_codegen/src/ir/body.rs b/crates/mun_codegen/src/ir/body.rs index ce95d50ac..613a047f6 100644 --- a/crates/mun_codegen/src/ir/body.rs +++ b/crates/mun_codegen/src/ir/body.rs @@ -1,14 +1,14 @@ use crate::{ir::dispatch_table::DispatchTable, ir::try_convert_any_to_basic, IrDatabase}; +use hir::{ + ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, HirDisplay, InferenceResult, Literal, Ordering, + Pat, PatId, Path, Resolution, Resolver, Statement, TypeCtor, +}; use inkwell::{ builder::Builder, module::Module, values::{BasicValueEnum, CallSiteValue, FloatValue, FunctionValue, IntValue}, FloatPredicate, IntPredicate, }; -use mun_hir::{ - self as hir, ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, HirDisplay, InferenceResult, - Literal, Ordering, Pat, PatId, Path, Resolution, Resolver, Statement, TypeCtor, -}; use std::{collections::HashMap, mem, sync::Arc}; use inkwell::basic_block::BasicBlock; @@ -32,7 +32,7 @@ pub(crate) struct BodyIrGenerator<'a, 'b, D: IrDatabase> { pat_to_param: HashMap, pat_to_local: HashMap, pat_to_name: HashMap, - function_map: &'a HashMap, + function_map: &'a HashMap, dispatch_table: &'b DispatchTable, active_loop: Option, hir_function: hir::Function, @@ -44,7 +44,7 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { module: &'a Module, hir_function: hir::Function, ir_function: FunctionValue, - function_map: &'a HashMap, + function_map: &'a HashMap, dispatch_table: &'b DispatchTable, ) -> Self { // Get the type information from the `hir::Function` @@ -136,7 +136,7 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { tail, } => self.gen_block(expr, statements, *tail), Expr::Path(ref p) => { - let resolver = mun_hir::resolver_for_expr(self.body.clone(), self.db, expr); + let resolver = hir::resolver_for_expr(self.body.clone(), self.db, expr); Some(self.gen_path_expr(p, expr, &resolver)) } Expr::Literal(lit) => Some(self.gen_literal(lit)), @@ -471,7 +471,7 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { let body = self.body.clone(); match &body[expr] { Expr::Path(ref p) => { - let resolver = mun_hir::resolver_for_expr(self.body.clone(), self.db, expr); + let resolver = hir::resolver_for_expr(self.body.clone(), self.db, expr); self.gen_path_place_expr(p, expr, &resolver) } _ => unreachable!("invalid place expression"), diff --git a/crates/mun_codegen/src/ir/dispatch_table.rs b/crates/mun_codegen/src/ir/dispatch_table.rs index 0fa09f162..a2eb4ea37 100644 --- a/crates/mun_codegen/src/ir/dispatch_table.rs +++ b/crates/mun_codegen/src/ir/dispatch_table.rs @@ -3,8 +3,8 @@ use crate::IrDatabase; use inkwell::module::Module; use inkwell::types::BasicTypeEnum; use inkwell::values::{BasicValueEnum, PointerValue}; -use mun_hir as hir; -use mun_hir::{Body, Expr, ExprId, InferenceResult}; + +use hir::{Body, Expr, ExprId, InferenceResult}; use std::collections::HashMap; /// A dispatch table in IR is a struct that contains pointers to all functions that are called from @@ -147,7 +147,7 @@ impl<'a, D: IrDatabase> DispatchTableBuilder<'a, D> { /// This creates the final DispatchTable with all *called* functions from within the module /// # Parameters /// * **functions**: Mapping of *defined* Mun functions to their respective IR values. - pub fn finalize(self, functions: &HashMap) -> DispatchTable { + pub fn finalize(self, functions: &HashMap) -> DispatchTable { // Construct the table body from all the entries in the dispatch table let table_body: Vec = self .entries diff --git a/crates/mun_codegen/src/ir/function.rs b/crates/mun_codegen/src/ir/function.rs index 1ccaa257b..357d0b92d 100644 --- a/crates/mun_codegen/src/ir/function.rs +++ b/crates/mun_codegen/src/ir/function.rs @@ -4,7 +4,7 @@ use crate::values::FunctionValue; use crate::{IrDatabase, Module, OptimizationLevel}; use inkwell::passes::{PassManager, PassManagerBuilder}; use inkwell::types::AnyTypeEnum; -use mun_hir as hir; + use std::collections::HashMap; /// Constructs a PassManager to optimize functions for the given optimization level. @@ -45,7 +45,7 @@ pub(crate) fn gen_body<'a, 'b, D: IrDatabase>( hir_function: hir::Function, llvm_function: FunctionValue, module: &'a Module, - llvm_functions: &'a HashMap, + llvm_functions: &'a HashMap, dispatch_table: &'b DispatchTable, ) -> FunctionValue { let mut code_gen = BodyIrGenerator::new( diff --git a/crates/mun_codegen/src/ir/module.rs b/crates/mun_codegen/src/ir/module.rs index e2e3199b7..4db54b3b6 100644 --- a/crates/mun_codegen/src/ir/module.rs +++ b/crates/mun_codegen/src/ir/module.rs @@ -1,8 +1,8 @@ use crate::ir::dispatch_table::{DispatchTable, DispatchTableBuilder}; -use crate::ir::function; +use crate::ir::{adt, function}; use crate::IrDatabase; +use hir::{FileId, ModuleDef}; use inkwell::{module::Module, values::FunctionValue}; -use mun_hir::{FileId, ModuleDef}; use std::collections::HashMap; use std::sync::Arc; @@ -15,7 +15,7 @@ pub struct ModuleIR { pub llvm_module: Module, /// A mapping from HIR functions to LLVM IR values - pub functions: HashMap, + pub functions: HashMap, /// The dispatch table pub dispatch_table: DispatchTable, @@ -27,6 +27,14 @@ pub(crate) fn ir_query(db: &impl IrDatabase, file_id: FileId) -> Arc { .context() .create_module(db.file_relative_path(file_id).as_str()); + // Generate all type definitions + for def in db.module_data(file_id).definitions() { + match def { + ModuleDef::Struct(s) => adt::gen_struct_decl(db, *s), + ModuleDef::BuiltinType(_) | ModuleDef::Function(_) => (), + } + } + // Generate all the function signatures let mut functions = HashMap::new(); let mut dispatch_table_builder = DispatchTableBuilder::new(db, &llvm_module); diff --git a/crates/mun_codegen/src/ir/ty.rs b/crates/mun_codegen/src/ir/ty.rs index d5cab68e5..52a3a599f 100644 --- a/crates/mun_codegen/src/ir/ty.rs +++ b/crates/mun_codegen/src/ir/ty.rs @@ -1,7 +1,7 @@ use super::try_convert_any_to_basic; use crate::IrDatabase; +use hir::{ApplicationTy, Ty, TypeCtor}; use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum}; -use mun_hir::{ApplicationTy, Ty, TypeCtor}; /// Given a mun type, construct an LLVM IR type pub(crate) fn ir_query(db: &impl IrDatabase, ty: Ty) -> AnyTypeEnum { @@ -29,6 +29,10 @@ pub(crate) fn ir_query(db: &impl IrDatabase, ty: Ty) -> AnyTypeEnum { AnyTypeEnum::FunctionType(fn_type) } + TypeCtor::Struct(s) => { + let name = s.name(db).to_string(); + context.opaque_struct_type(&name).into() + } _ => unreachable!(), }, _ => unreachable!("unknown type can not be converted"), diff --git a/crates/mun_codegen/src/mock.rs b/crates/mun_codegen/src/mock.rs index 555aec346..e8d4c2a9f 100644 --- a/crates/mun_codegen/src/mock.rs +++ b/crates/mun_codegen/src/mock.rs @@ -1,13 +1,12 @@ use crate::{IrDatabase, OptimizationLevel}; -use mun_hir::{FileId, RelativePathBuf}; -use mun_hir::{SourceDatabase, SourceRoot, SourceRootId}; +use hir::{FileId, RelativePathBuf, SourceDatabase, SourceRoot, SourceRootId}; use std::sync::Arc; /// A mock implementation of the IR database. It can be used to set up a simple test case. #[salsa::database( - mun_hir::SourceDatabaseStorage, - mun_hir::DefDatabaseStorage, - mun_hir::HirDatabaseStorage, + hir::SourceDatabaseStorage, + hir::DefDatabaseStorage, + hir::HirDatabaseStorage, crate::IrDatabaseStorage )] #[derive(Default, Debug)] @@ -38,7 +37,7 @@ impl MockDatabase { source_root.insert_file(rel_path, file_id); db.set_source_root(source_root_id, Arc::new(source_root)); - db.set_optimization_lvl(OptimizationLevel::Default); + db.set_optimization_lvl(OptimizationLevel::None); let context = crate::Context::create(); db.set_context(Arc::new(context)); diff --git a/crates/mun_codegen/src/snapshots/test__struct_test.snap b/crates/mun_codegen/src/snapshots/test__struct_test.snap new file mode 100644 index 000000000..8f7a45259 --- /dev/null +++ b/crates/mun_codegen/src/snapshots/test__struct_test.snap @@ -0,0 +1,19 @@ +--- +source: crates/mun_codegen/src/test.rs +expression: "struct Bar(float, int, bool, Foo);\nstruct Foo { a: int };\nstruct Baz;\nfn foo() {\n let a: Foo;\n let b: Bar;\n let c: Baz;\n}" +--- +; ModuleID = 'main.mun' +source_filename = "main.mun" + +%Baz = type {} +%Bar = type { double, i64, i1, %Foo } +%Foo = type { i64 } + +define void @foo() { +body: + %c = alloca %Baz + %b = alloca %Bar + %a = alloca %Foo + ret void +} + diff --git a/crates/mun_codegen/src/test.rs b/crates/mun_codegen/src/test.rs index 9e1f2c61e..48897bb9a 100644 --- a/crates/mun_codegen/src/test.rs +++ b/crates/mun_codegen/src/test.rs @@ -1,8 +1,6 @@ use crate::{mock::MockDatabase, IrDatabase}; -use mun_hir::diagnostics::DiagnosticSink; -use mun_hir::line_index::LineIndex; -use mun_hir::Module; -use mun_hir::SourceDatabase; +use hir::{diagnostics::DiagnosticSink, line_index::LineIndex, Module, SourceDatabase}; +use inkwell::OptimizationLevel; use std::cell::RefCell; use std::sync::Arc; @@ -359,15 +357,40 @@ fn while_expr() { while n<4 { break; }; + } + "#, + ) +} + +#[test] +fn struct_test() { + test_snapshot_unoptimized( + r#" + struct Bar(float, int, bool, Foo); + struct Foo { a: int }; + struct Baz; + fn foo() { + let a: Foo; + let b: Bar; + let c: Baz; } "#, ) } fn test_snapshot(text: &str) { + test_snapshot_with_optimization(text, OptimizationLevel::Default); +} + +fn test_snapshot_unoptimized(text: &str) { + test_snapshot_with_optimization(text, OptimizationLevel::None); +} + +fn test_snapshot_with_optimization(text: &str, opt: OptimizationLevel) { let text = text.trim().replace("\n ", "\n"); - let (db, file_id) = MockDatabase::with_single_file(&text); + let (mut db, file_id) = MockDatabase::with_single_file(&text); + db.set_optimization_lvl(opt); let line_index: Arc = db.line_index(file_id); let messages = RefCell::new(Vec::new()); diff --git a/crates/mun_hir/src/adt.rs b/crates/mun_hir/src/adt.rs index 0e599c0e4..ceee776f2 100644 --- a/crates/mun_hir/src/adt.rs +++ b/crates/mun_hir/src/adt.rs @@ -1,9 +1,9 @@ use std::sync::Arc; +use crate::type_ref::{TypeRefBuilder, TypeRefId, TypeRefMap, TypeRefSourceMap}; use crate::{ arena::{Arena, RawId}, ids::{AstItemDef, StructId}, - type_ref::TypeRef, AsName, DefDatabase, Name, }; use mun_syntax::ast::{self, NameOwner, TypeAscriptionOwner}; @@ -23,7 +23,7 @@ use mun_syntax::ast::{self, NameOwner, TypeAscriptionOwner}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct StructFieldData { pub name: Name, - pub type_ref: TypeRef, + pub type_ref: TypeRefId, } /// An identifier for a struct's or tuple's field @@ -39,11 +39,13 @@ pub enum StructKind { Unit, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq)] pub struct StructData { pub name: Name, pub fields: Arena, pub kind: StructKind, + type_ref_map: TypeRefMap, + type_ref_source_map: TypeRefSourceMap, } impl StructData { @@ -55,13 +57,14 @@ impl StructData { .map(|n| n.as_name()) .unwrap_or_else(Name::missing); + let mut type_ref_builder = TypeRefBuilder::default(); let (fields, kind) = match src.ast.kind() { ast::StructKind::Record(r) => { let fields = r .fields() .map(|fd| StructFieldData { name: fd.name().map(|n| n.as_name()).unwrap_or_else(Name::missing), - type_ref: TypeRef::from_ast_opt(fd.ascribed_type()), + type_ref: type_ref_builder.alloc_from_node_opt(fd.ascribed_type().as_ref()), }) .collect(); (fields, StructKind::Record) @@ -72,13 +75,29 @@ impl StructData { .enumerate() .map(|(index, fd)| StructFieldData { name: Name::new_tuple_field(index), - type_ref: TypeRef::from_ast_opt(fd.type_ref()), + type_ref: type_ref_builder.alloc_from_node_opt(fd.type_ref().as_ref()), }) .collect(); (fields, StructKind::Tuple) } ast::StructKind::Unit => (Arena::default(), StructKind::Unit), }; - Arc::new(StructData { name, fields, kind }) + + let (type_ref_map, type_ref_source_map) = type_ref_builder.finish(); + Arc::new(StructData { + name, + fields, + kind, + type_ref_map, + type_ref_source_map, + }) + } + + pub fn type_ref_source_map(&self) -> &TypeRefSourceMap { + &self.type_ref_source_map + } + + pub fn type_ref_map(&self) -> &TypeRefMap { + &self.type_ref_map } } diff --git a/crates/mun_hir/src/code_model.rs b/crates/mun_hir/src/code_model.rs index 1d5b69d79..1cc1ed955 100644 --- a/crates/mun_hir/src/code_model.rs +++ b/crates/mun_hir/src/code_model.rs @@ -9,7 +9,7 @@ use crate::ids::LocationCtx; use crate::name_resolution::Namespace; use crate::raw::{DefKind, RawFileItem}; use crate::resolve::{Resolution, Resolver}; -use crate::ty::InferenceResult; +use crate::ty::{lower::LowerBatchResult, InferenceResult}; use crate::type_ref::{TypeRefBuilder, TypeRefId, TypeRefMap, TypeRefSourceMap}; use crate::{ ids::{FunctionId, StructId}, @@ -52,6 +52,7 @@ impl Module { #[allow(clippy::single_match)] match decl { ModuleDef::Function(f) => f.diagnostics(db, sink), + ModuleDef::Struct(s) => s.diagnostics(db, sink), _ => (), } } @@ -336,6 +337,19 @@ pub struct StructField { pub(crate) id: StructFieldId, } +impl StructField { + pub fn ty(self, db: &impl HirDatabase) -> Ty { + let data = self.parent.data(db); + let type_ref_id = data.fields[self.id].type_ref; + let lower = self.parent.lower(db); + lower[type_ref_id].clone() + } + + pub fn name(self, db: &impl HirDatabase) -> Name { + self.parent.data(db).fields[self.id].name.clone() + } +} + impl Struct { pub fn module(self, db: &impl DefDatabase) -> Module { Module { @@ -358,6 +372,30 @@ impl Struct { .map(|(id, _)| StructField { parent: self, id }) .collect() } + + pub fn ty(self, db: &impl HirDatabase) -> Ty { + db.type_for_def(self.into(), Namespace::Types) + } + + pub fn lower(self, db: &impl HirDatabase) -> Arc { + db.lower_struct(self) + } + + pub(crate) fn resolver(self, db: &impl HirDatabase) -> Resolver { + // take the outer scope... + self.module(db).resolver(db) + } + + pub fn diagnostics(self, db: &impl HirDatabase, sink: &mut DiagnosticSink) { + let data = self.data(db); + let lower = self.lower(db); + lower.add_diagnostics( + db, + self.module(db).file_id, + data.type_ref_source_map(), + sink, + ); + } } mod diagnostics { diff --git a/crates/mun_hir/src/db.rs b/crates/mun_hir/src/db.rs index 0b78c61e8..28e10a70e 100644 --- a/crates/mun_hir/src/db.rs +++ b/crates/mun_hir/src/db.rs @@ -2,6 +2,7 @@ use crate::input::{SourceRoot, SourceRootId}; use crate::name_resolution::Namespace; +use crate::ty::lower::LowerBatchResult; use crate::ty::{FnSig, Ty, TypableDef}; use crate::{ adt::StructData, @@ -11,7 +12,7 @@ use crate::{ name_resolution::ModuleScope, source_id::ErasedFileAstId, ty::InferenceResult, - AstIdMap, ExprScopes, FileId, RawItems, + AstIdMap, ExprScopes, FileId, RawItems, Struct, }; use mun_syntax::{ast, Parse, SourceFile, SyntaxNode}; pub use relative_path::RelativePathBuf; @@ -83,6 +84,9 @@ pub trait HirDatabase: DefDatabase { #[salsa::invoke(crate::ty::infer_query)] fn infer(&self, def: DefWithBody) -> Arc; + #[salsa::invoke(crate::ty::lower::lower_struct_query)] + fn lower_struct(&self, def: Struct) -> Arc; + #[salsa::invoke(crate::FnData::fn_data_query)] fn fn_data(&self, func: Function) -> Arc; diff --git a/crates/mun_hir/src/ty.rs b/crates/mun_hir/src/ty.rs index df9c842ce..c1a17783b 100644 --- a/crates/mun_hir/src/ty.rs +++ b/crates/mun_hir/src/ty.rs @@ -1,5 +1,6 @@ mod infer; -mod lower; +pub(super) mod lower; +mod op; use crate::display::{HirDisplay, HirFormatter}; use crate::ty::infer::TypeVarId; @@ -10,8 +11,6 @@ pub(crate) use lower::{fn_sig_for_fn, type_for_def, TypableDef}; use std::fmt; use std::sync::Arc; -mod op; - #[cfg(test)] mod tests; diff --git a/crates/mun_hir/src/ty/lower.rs b/crates/mun_hir/src/ty/lower.rs index 13d9e4939..22f322848 100644 --- a/crates/mun_hir/src/ty/lower.rs +++ b/crates/mun_hir/src/ty/lower.rs @@ -1,11 +1,15 @@ pub(crate) use self::diagnostics::LowerDiagnostic; use crate::adt::StructKind; +use crate::arena::map::ArenaMap; use crate::code_model::BuiltinType; +use crate::diagnostics::DiagnosticSink; use crate::name_resolution::Namespace; use crate::resolve::{Resolution, Resolver}; use crate::ty::{FnSig, Ty, TypeCtor}; -use crate::type_ref::{TypeRef, TypeRefId, TypeRefMap}; -use crate::{Function, HirDatabase, ModuleDef, Path, Struct}; +use crate::type_ref::{TypeRef, TypeRefId, TypeRefMap, TypeRefSourceMap}; +use crate::{FileId, Function, HirDatabase, ModuleDef, Path, Struct}; +use std::ops::Index; +use std::sync::Arc; #[derive(Clone, PartialEq, Eq, Debug)] pub(crate) struct LowerResult { @@ -13,6 +17,34 @@ pub(crate) struct LowerResult { pub(crate) diagnostics: Vec, } +#[derive(Clone, Debug, Default, PartialEq, Eq)] +pub struct LowerBatchResult { + pub(crate) type_ref_to_type: ArenaMap, + pub(crate) diagnostics: Vec, +} + +impl Index for LowerBatchResult { + type Output = Ty; + fn index(&self, expr: TypeRefId) -> &Ty { + self.type_ref_to_type.get(expr).unwrap_or(&Ty::Unknown) + } +} + +impl LowerBatchResult { + /// Adds all the `LowerDiagnostic`s of the result to the `DiagnosticSink`. + pub(crate) fn add_diagnostics( + &self, + db: &impl HirDatabase, + file_id: FileId, + source_map: &TypeRefSourceMap, + sink: &mut DiagnosticSink, + ) { + self.diagnostics + .iter() + .for_each(|it| it.add_to(db, file_id, source_map, sink)) + } +} + impl Ty { pub(crate) fn from_hir( db: &impl HirDatabase, @@ -75,6 +107,28 @@ impl Ty { } } +pub fn types_from_hir( + db: &impl HirDatabase, + resolver: &Resolver, + type_ref_map: &TypeRefMap, +) -> Arc { + let mut result = LowerBatchResult::default(); + for (id, _) in type_ref_map.iter() { + let LowerResult { ty, diagnostics } = Ty::from_hir(db, resolver, type_ref_map, id); + for diagnostic in diagnostics { + result.diagnostics.push(diagnostic); + } + // TODO: Add detection of cyclic types + result.type_ref_to_type.insert(id, ty); + } + Arc::new(result) +} + +pub fn lower_struct_query(db: &impl HirDatabase, s: Struct) -> Arc { + let data = s.data(db); + types_from_hir(db, &s.resolver(db), data.type_ref_map()) +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum TypableDef { Function(Function), @@ -175,10 +229,32 @@ fn type_for_struct(_db: &impl HirDatabase, def: Struct) -> Ty { } pub mod diagnostics { - use crate::type_ref::TypeRefId; + use crate::diagnostics::UnresolvedType; + use crate::{ + diagnostics::DiagnosticSink, + type_ref::{TypeRefId, TypeRefSourceMap}, + FileId, HirDatabase, + }; #[derive(Debug, PartialEq, Eq, Clone)] pub(crate) enum LowerDiagnostic { UnresolvedType { id: TypeRefId }, } + + impl LowerDiagnostic { + pub(crate) fn add_to( + &self, + _db: &impl HirDatabase, + file_id: FileId, + source_map: &TypeRefSourceMap, + sink: &mut DiagnosticSink, + ) { + match self { + LowerDiagnostic::UnresolvedType { id } => sink.push(UnresolvedType { + file: file_id, + type_ref: source_map.type_ref_syntax(*id).unwrap(), + }), + } + } + } } diff --git a/crates/mun_hir/src/type_ref.rs b/crates/mun_hir/src/type_ref.rs index 55c2fcd69..37ad763d8 100644 --- a/crates/mun_hir/src/type_ref.rs +++ b/crates/mun_hir/src/type_ref.rs @@ -67,6 +67,13 @@ pub struct TypeRefMap { type_refs: Arena, } +impl TypeRefMap { + /// Iterate over the elements in the map + pub fn iter(&self) -> impl Iterator { + self.type_refs.iter() + } +} + impl Index for TypeRefMap { type Output = TypeRef; diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index 8e1de7312..e012c050c 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -210,7 +210,7 @@ fn loop_expr() { #[test] fn break_expr() { - ok_snapshot_test( + snapshot_test( r#" fn foo() { break; @@ -223,7 +223,7 @@ fn break_expr() { #[test] fn while_expr() { - ok_snapshot_test( + snapshot_test( r#" fn foo() { while true {}; From 7976f072bbcddbff0580c0e576e0fe6387a888c3 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Sun, 1 Dec 2019 23:32:51 +0100 Subject: [PATCH 06/21] feat(struct): parsing of record literals --- crates/mun_syntax/src/ast/generated.rs | 105 ++++++++++++++++++ crates/mun_syntax/src/grammar.ron | 11 ++ crates/mun_syntax/src/parsing/grammar.rs | 10 ++ .../src/parsing/grammar/expressions.rs | 38 ++++++- .../mun_syntax/src/syntax_kind/generated.rs | 6 + crates/mun_syntax/src/tests/parser.rs | 13 +++ .../tests/snapshots/parser__record_lit.snap | 79 +++++++++++++ 7 files changed, 259 insertions(+), 3 deletions(-) create mode 100644 crates/mun_syntax/src/tests/snapshots/parser__record_lit.snap diff --git a/crates/mun_syntax/src/ast/generated.rs b/crates/mun_syntax/src/ast/generated.rs index 6b22a4210..c1ab95658 100644 --- a/crates/mun_syntax/src/ast/generated.rs +++ b/crates/mun_syntax/src/ast/generated.rs @@ -1028,6 +1028,41 @@ impl PrefixExpr { } } +// RecordField + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct RecordField { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for RecordField { + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + RECORD_FIELD => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(RecordField { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl RecordField { + pub fn name_ref(&self) -> Option { + super::child_opt(self) + } + + pub fn expr(&self) -> Option { + super::child_opt(self) + } +} + // RecordFieldDef #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1090,6 +1125,76 @@ impl RecordFieldDefList { } } +// RecordFieldList + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct RecordFieldList { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for RecordFieldList { + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + RECORD_FIELD_LIST => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(RecordFieldList { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl RecordFieldList { + pub fn fields(&self) -> impl Iterator { + super::children(self) + } + + pub fn spread(&self) -> Option { + super::child_opt(self) + } +} + +// RecordLit + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct RecordLit { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for RecordLit { + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + RECORD_LIT => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(RecordLit { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl RecordLit { + pub fn path(&self) -> Option { + super::child_opt(self) + } + + pub fn record_field_list(&self) -> Option { + super::child_opt(self) + } +} + // RetType #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/mun_syntax/src/grammar.ron b/crates/mun_syntax/src/grammar.ron index c02138ade..9782c1839 100644 --- a/crates/mun_syntax/src/grammar.ron +++ b/crates/mun_syntax/src/grammar.ron @@ -141,6 +141,10 @@ Grammar( "PATH", "PATH_SEGMENT", + + "RECORD_LIT", + "RECORD_FIELD_LIST", + "RECORD_FIELD", ], ast: { "SourceFile": ( @@ -298,5 +302,12 @@ Grammar( "PlaceholderPat" ], ), + + "RecordLit": (options: ["Path", "RecordFieldList"]), + "RecordFieldList": ( + collections: [ ("fields", "RecordField") ], + options: [["spread", "Expr"]] + ), + "RecordField": (options: ["NameRef", "Expr"]), } ) diff --git a/crates/mun_syntax/src/parsing/grammar.rs b/crates/mun_syntax/src/parsing/grammar.rs index 154eb0e62..d1783138b 100644 --- a/crates/mun_syntax/src/parsing/grammar.rs +++ b/crates/mun_syntax/src/parsing/grammar.rs @@ -65,6 +65,16 @@ fn opt_visibility(p: &mut Parser) -> bool { } } +fn name_ref_or_index(p: &mut Parser) { + if p.at(IDENT) || p.at(INT_NUMBER) { + let m = p.start(); + p.bump_any(); + m.complete(p, NAME_REF); + } else { + p.error_and_bump("expected identifier"); + } +} + fn error_block(p: &mut Parser, message: &str) { assert!(p.at(T!['{'])); let m = p.start(); diff --git a/crates/mun_syntax/src/parsing/grammar/expressions.rs b/crates/mun_syntax/src/parsing/grammar/expressions.rs index 1599aa59b..f658989d1 100644 --- a/crates/mun_syntax/src/parsing/grammar/expressions.rs +++ b/crates/mun_syntax/src/parsing/grammar/expressions.rs @@ -226,7 +226,7 @@ fn atom_expr(p: &mut Parser, r: Restrictions) -> Option { } if paths::is_path_start(p) { - return Some(path_expr(p)); + return Some(path_expr(p, r)); } let marker = match p.current() { @@ -245,10 +245,17 @@ fn atom_expr(p: &mut Parser, r: Restrictions) -> Option { Some(marker) } -fn path_expr(p: &mut Parser) -> CompletedMarker { +fn path_expr(p: &mut Parser, r: Restrictions) -> CompletedMarker { + assert!(paths::is_path_start(p)); let m = p.start(); paths::expr_path(p); - m.complete(p, PATH_EXPR) + match p.current() { + T!['{'] if !r.forbid_structs => { + record_field_list(p); + m.complete(p, RECORD_LIT) + } + _ => m.complete(p, PATH_EXPR), + } } fn literal(p: &mut Parser) -> Option { @@ -328,3 +335,28 @@ fn while_expr(p: &mut Parser) -> CompletedMarker { block(p); m.complete(p, WHILE_EXPR) } + +fn record_field_list(p: &mut Parser) { + assert!(p.at(T!['{'])); + let m = p.start(); + p.bump(T!['{']); + while !p.at(EOF) && !p.at(T!['}']) { + match p.current() { + IDENT | INT_NUMBER => { + let m = p.start(); + name_ref_or_index(p); + if p.eat(T![:]) { + expr(p); + } + m.complete(p, RECORD_FIELD); + } + T!['{'] => error_block(p, "expected a field"), + _ => p.error_and_bump("expected identifier"), + } + if !p.at(T!['}']) { + p.expect(T![,]); + } + } + p.expect(T!['}']); + m.complete(p, RECORD_FIELD_LIST); +} diff --git a/crates/mun_syntax/src/syntax_kind/generated.rs b/crates/mun_syntax/src/syntax_kind/generated.rs index dad0229cd..6f910b0e3 100644 --- a/crates/mun_syntax/src/syntax_kind/generated.rs +++ b/crates/mun_syntax/src/syntax_kind/generated.rs @@ -117,6 +117,9 @@ pub enum SyntaxKind { NAME_REF, PATH, PATH_SEGMENT, + RECORD_LIT, + RECORD_FIELD_LIST, + RECORD_FIELD, // Technical kind so that we can cast from u16 safely #[doc(hidden)] __LAST, @@ -383,6 +386,9 @@ impl SyntaxKind { NAME_REF => &SyntaxInfo { name: "NAME_REF" }, PATH => &SyntaxInfo { name: "PATH" }, PATH_SEGMENT => &SyntaxInfo { name: "PATH_SEGMENT" }, + RECORD_LIT => &SyntaxInfo { name: "RECORD_LIT" }, + RECORD_FIELD_LIST => &SyntaxInfo { name: "RECORD_FIELD_LIST" }, + RECORD_FIELD => &SyntaxInfo { name: "RECORD_FIELD" }, TOMBSTONE => &SyntaxInfo { name: "TOMBSTONE" }, EOF => &SyntaxInfo { name: "EOF" }, __LAST => &SyntaxInfo { name: "__LAST" }, diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index e012c050c..7628c05c4 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -232,3 +232,16 @@ fn while_expr() { "#, ) } + +#[test] +fn record_lit() { + snapshot_test( + r#" + fn foo() { + S {}; + S { x, y: 32, }; + TupleStruct { 0: 1 }; + } + "#, + ) +} diff --git a/crates/mun_syntax/src/tests/snapshots/parser__record_lit.snap b/crates/mun_syntax/src/tests/snapshots/parser__record_lit.snap new file mode 100644 index 000000000..41c167f1b --- /dev/null +++ b/crates/mun_syntax/src/tests/snapshots/parser__record_lit.snap @@ -0,0 +1,79 @@ +--- +source: crates/mun_syntax/src/tests/parser.rs +expression: "fn foo() {\n S {};\n S { x, y: 32, };\n TupleStruct { 0: 1 };\n}" +--- +SOURCE_FILE@[0; 69) + FUNCTION_DEF@[0; 69) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 6) + IDENT@[3; 6) "foo" + PARAM_LIST@[6; 8) + L_PAREN@[6; 7) "(" + R_PAREN@[7; 8) ")" + WHITESPACE@[8; 9) " " + BLOCK_EXPR@[9; 69) + L_CURLY@[9; 10) "{" + WHITESPACE@[10; 15) "\n " + EXPR_STMT@[15; 20) + RECORD_LIT@[15; 19) + PATH@[15; 16) + PATH_SEGMENT@[15; 16) + NAME_REF@[15; 16) + IDENT@[15; 16) "S" + WHITESPACE@[16; 17) " " + RECORD_FIELD_LIST@[17; 19) + L_CURLY@[17; 18) "{" + R_CURLY@[18; 19) "}" + SEMI@[19; 20) ";" + WHITESPACE@[20; 25) "\n " + EXPR_STMT@[25; 41) + RECORD_LIT@[25; 40) + PATH@[25; 26) + PATH_SEGMENT@[25; 26) + NAME_REF@[25; 26) + IDENT@[25; 26) "S" + WHITESPACE@[26; 27) " " + RECORD_FIELD_LIST@[27; 40) + L_CURLY@[27; 28) "{" + WHITESPACE@[28; 29) " " + RECORD_FIELD@[29; 30) + NAME_REF@[29; 30) + IDENT@[29; 30) "x" + COMMA@[30; 31) "," + WHITESPACE@[31; 32) " " + RECORD_FIELD@[32; 37) + NAME_REF@[32; 33) + IDENT@[32; 33) "y" + COLON@[33; 34) ":" + WHITESPACE@[34; 35) " " + LITERAL@[35; 37) + INT_NUMBER@[35; 37) "32" + COMMA@[37; 38) "," + WHITESPACE@[38; 39) " " + R_CURLY@[39; 40) "}" + SEMI@[40; 41) ";" + WHITESPACE@[41; 46) "\n " + EXPR_STMT@[46; 67) + RECORD_LIT@[46; 66) + PATH@[46; 57) + PATH_SEGMENT@[46; 57) + NAME_REF@[46; 57) + IDENT@[46; 57) "TupleStruct" + WHITESPACE@[57; 58) " " + RECORD_FIELD_LIST@[58; 66) + L_CURLY@[58; 59) "{" + WHITESPACE@[59; 60) " " + RECORD_FIELD@[60; 64) + NAME_REF@[60; 61) + INT_NUMBER@[60; 61) "0" + COLON@[61; 62) ":" + WHITESPACE@[62; 63) " " + LITERAL@[63; 64) + INT_NUMBER@[63; 64) "1" + WHITESPACE@[64; 65) " " + R_CURLY@[65; 66) "}" + SEMI@[66; 67) ";" + WHITESPACE@[67; 68) "\n" + R_CURLY@[68; 69) "}" + From 76e918859049dd81ce1b9666746676fd47aa1f7c Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 4 Dec 2019 20:20:05 +0100 Subject: [PATCH 07/21] feat(struct): type inference of record literals --- crates/mun_hir/src/code_model.rs | 42 ++++- crates/mun_hir/src/diagnostics.rs | 24 +++ crates/mun_hir/src/expr.rs | 95 ++++++++++- crates/mun_hir/src/lib.rs | 2 + crates/mun_hir/src/macros.rs | 18 ++ crates/mun_hir/src/name_resolution.rs | 2 +- crates/mun_hir/src/ty/infer.rs | 155 ++++++++++++++++-- ...claration.snap => tests__struct_decl.snap} | 0 .../src/ty/snapshots/tests__struct_lit.snap | 10 ++ crates/mun_hir/src/ty/tests.rs | 23 ++- crates/mun_syntax/src/ast/generated.rs | 9 +- crates/mun_syntax/src/grammar.ron | 1 + crates/mun_syntax/src/parsing/grammar.rs | 32 ++-- .../src/parsing/grammar/expressions.rs | 51 +++--- crates/mun_syntax/src/tests/parser.rs | 2 +- ...tructures.snap => parser__struct_def.snap} | 0 16 files changed, 407 insertions(+), 59 deletions(-) create mode 100644 crates/mun_hir/src/macros.rs rename crates/mun_hir/src/ty/snapshots/{tests__struct_declaration.snap => tests__struct_decl.snap} (100%) create mode 100644 crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap rename crates/mun_syntax/src/tests/snapshots/{parser__structures.snap => parser__struct_def.snap} (100%) diff --git a/crates/mun_hir/src/code_model.rs b/crates/mun_hir/src/code_model.rs index 1cc1ed955..c6ddb878b 100644 --- a/crates/mun_hir/src/code_model.rs +++ b/crates/mun_hir/src/code_model.rs @@ -143,6 +143,7 @@ impl From for ModuleDef { pub enum DefWithBody { Function(Function), } +impl_froms!(DefWithBody: Function); #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Visibility { @@ -171,9 +172,36 @@ impl DefWithBody { } } -impl From for DefWithBody { - fn from(f: Function) -> Self { - DefWithBody::Function(f) +/// Definitions that have a struct. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum DefWithStruct { + Struct(Struct), +} +impl_froms!(DefWithStruct: Struct); + +impl DefWithStruct { + pub fn fields(self, db: &impl HirDatabase) -> Vec { + match self { + DefWithStruct::Struct(s) => s.fields(db), + } + } + + pub fn field(self, db: &impl HirDatabase, name: &Name) -> Option { + match self { + DefWithStruct::Struct(s) => s.field(db, name), + } + } + + pub fn module(self, db: &impl HirDatabase) -> Module { + match self { + DefWithStruct::Struct(s) => s.module(db), + } + } + + pub fn data(self, db: &impl HirDatabase) -> Arc { + match self { + DefWithStruct::Struct(s) => s.data(db), + } } } @@ -373,6 +401,14 @@ impl Struct { .collect() } + pub fn field(self, db: &impl HirDatabase, name: &Name) -> Option { + self.data(db) + .fields + .iter() + .find(|(_, data)| data.name == *name) + .map(|(id, _)| StructField { parent: self, id }) + } + pub fn ty(self, db: &impl HirDatabase) -> Ty { db.type_for_def(self.into(), Namespace::Types) } diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs index 28f3bb9b3..4fb9e0507 100644 --- a/crates/mun_hir/src/diagnostics.rs +++ b/crates/mun_hir/src/diagnostics.rs @@ -408,3 +408,27 @@ impl Diagnostic for BreakWithValueOutsideLoop { self } } + +#[derive(Debug)] +pub struct NoSuchField { + pub file: FileId, + pub field: SyntaxNodePtr, +} + +impl Diagnostic for NoSuchField { + fn message(&self) -> String { + "no such field".to_string() + } + + fn file(&self) -> FileId { + self.file + } + + fn syntax_node_ptr(&self) -> SyntaxNodePtr { + self.field + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} diff --git a/crates/mun_hir/src/expr.rs b/crates/mun_hir/src/expr.rs index 1dcaedd8d..caf8cd25d 100644 --- a/crates/mun_hir/src/expr.rs +++ b/crates/mun_hir/src/expr.rs @@ -9,6 +9,7 @@ use crate::{ use crate::code_model::src::{HasSource, Source}; use crate::name::AsName; use crate::type_ref::{TypeRef, TypeRefBuilder, TypeRefId, TypeRefMap, TypeRefSourceMap}; +use either::Either; pub use mun_syntax::ast::PrefixOp as UnaryOp; use mun_syntax::ast::{ArgListOwner, BinOp, LoopBodyOwner, NameOwner, TypeAscriptionOwner}; use mun_syntax::{ast, AstNode, AstPtr, T}; @@ -102,12 +103,14 @@ impl Index for Body { } } -type ExprPtr = AstPtr; //Either, AstPtr>; +type ExprPtr = Either, AstPtr>; type ExprSource = Source; type PatPtr = AstPtr; //Either, AstPtr>; type PatSource = Source; +type RecordPtr = AstPtr; + /// 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 action positions of @@ -119,6 +122,7 @@ pub struct BodySourceMap { pat_map: FxHashMap, pat_map_back: ArenaMap, type_refs: TypeRefSourceMap, + field_map: FxHashMap<(ExprId, usize), RecordPtr>, } impl BodySourceMap { @@ -135,7 +139,7 @@ impl BodySourceMap { } pub(crate) fn node_expr(&self, node: &ast::Expr) -> Option { - self.expr_map.get(&AstPtr::new(node)).cloned() + self.expr_map.get(&Either::Left(AstPtr::new(node))).cloned() } pub(crate) fn pat_syntax(&self, pat: PatId) -> Option { @@ -149,6 +153,16 @@ impl BodySourceMap { pub fn type_refs(&self) -> &TypeRefSourceMap { &self.type_refs } + + pub fn field_syntax(&self, expr: ExprId, field: usize) -> RecordPtr { + self.field_map[&(expr, field)] + } +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct RecordLitField { + pub name: Name, + pub expr: ExprId, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -211,6 +225,11 @@ pub enum Expr { condition: ExprId, body: ExprId, }, + RecordLit { + path: Option, + fields: Vec, + spread: Option, + }, Literal(Literal), } @@ -312,6 +331,14 @@ impl Expr { f(*condition); f(*body); } + Expr::RecordLit { fields, spread, .. } => { + for field in fields { + f(field.expr); + } + if let Some(expr) = spread { + f(*expr); + } + } } } } @@ -376,7 +403,22 @@ where id } - fn alloc_expr(&mut self, expr: Expr, ptr: ExprPtr) -> ExprId { + fn alloc_expr(&mut self, expr: Expr, ptr: AstPtr) -> ExprId { + let ptr = Either::Left(ptr); + let id = self.exprs.alloc(expr); + self.source_map.expr_map.insert(ptr, id); + self.source_map.expr_map_back.insert( + id, + Source { + file_id: self.current_file_id, + ast: ptr, + }, + ); + id + } + + fn alloc_expr_field_shorthand(&mut self, expr: Expr, ptr: RecordPtr) -> ExprId { + let ptr = Either::Right(ptr); let id = self.exprs.alloc(expr); self.source_map.expr_map.insert(ptr, id); self.source_map.expr_map_back.insert( @@ -583,6 +625,50 @@ where .unwrap_or(Expr::Missing); self.alloc_expr(path, syntax_ptr) } + ast::ExprKind::RecordLit(e) => { + let path = e.path().and_then(Path::from_ast); + let mut field_ptrs = Vec::new(); + let record_lit = if let Some(r) = e.record_field_list() { + let fields = r + .fields() + .inspect(|field| field_ptrs.push(AstPtr::new(field))) + .map(|field| RecordLitField { + 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() { + self.alloc_expr_field_shorthand( + Expr::Path(Path::from_name_ref(&nr)), + AstPtr::new(&field), + ) + } else { + self.missing_expr() + }, + }) + .collect(); + let spread = r.spread().map(|s| self.collect_expr(s)); + Expr::RecordLit { + path, + fields, + spread, + } + } else { + Expr::RecordLit { + path, + fields: Vec::new(), + spread: None, + } + }; + + let res = self.alloc_expr(record_lit, syntax_ptr); + for (idx, ptr) in field_ptrs.into_iter().enumerate() { + self.source_map.field_map.insert((res, idx), ptr); + } + res + } ast::ExprKind::IfExpr(e) => { let then_branch = self.collect_block_opt(e.then_branch()); @@ -608,7 +694,8 @@ where ast::ExprKind::ParenExpr(e) => { let inner = self.collect_expr_opt(e.expr()); // make the paren expr point to the inner expression as well - self.source_map.expr_map.insert(syntax_ptr, inner); + let src = Either::Left(syntax_ptr); + self.source_map.expr_map.insert(src, inner); inner } ast::ExprKind::CallExpr(e) => { diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index c444f3656..a9e0d9b2b 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -5,6 +5,8 @@ #![allow(dead_code)] +#[macro_use] +mod macros; #[macro_use] mod arena; mod adt; diff --git a/crates/mun_hir/src/macros.rs b/crates/mun_hir/src/macros.rs new file mode 100644 index 000000000..3fdf91489 --- /dev/null +++ b/crates/mun_hir/src/macros.rs @@ -0,0 +1,18 @@ +macro_rules! impl_froms { + ($e:ident: $($v:ident $(($($sv:ident),*))?),*) => { + $( + impl From<$v> for $e { + fn from(it: $v) -> $e { + $e::$v(it) + } + } + $($( + impl From<$sv> for $e { + fn from(it: $sv) -> $e { + $e::$v($v::$sv(it)) + } + } + )*)? + )* + } +} diff --git a/crates/mun_hir/src/name_resolution.rs b/crates/mun_hir/src/name_resolution.rs index ca5883d9a..961bfa21d 100644 --- a/crates/mun_hir/src/name_resolution.rs +++ b/crates/mun_hir/src/name_resolution.rs @@ -61,7 +61,7 @@ pub(crate) fn module_scope_query(db: &impl HirDatabase, file_id: FileId) -> Arc< scope.items.insert( s.name(db), Resolution { - def: PerNs::types(*def), + def: PerNs::both(*def, *def), }, ); } diff --git a/crates/mun_hir/src/ty/infer.rs b/crates/mun_hir/src/ty/infer.rs index 06777a8a9..c03332dc6 100644 --- a/crates/mun_hir/src/ty/infer.rs +++ b/crates/mun_hir/src/ty/infer.rs @@ -1,6 +1,6 @@ use crate::{ arena::map::ArenaMap, - code_model::DefWithBody, + code_model::{DefWithBody, DefWithStruct}, diagnostics::DiagnosticSink, expr, expr::{Body, Expr, ExprId, Literal, Pat, PatId, Statement}, @@ -333,7 +333,36 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { Expr::While { condition, body } => { self.infer_while_expr(tgt_expr, *condition, *body, expected) } - _ => Ty::Unknown, + Expr::RecordLit { + path, + fields, + spread, + } => { + let (ty, def_id) = self.resolve_struct(path.as_ref()); + self.unify(&ty, &expected.ty); + + for (idx, field) in fields.iter().enumerate() { + let field_ty = def_id + .as_ref() + .and_then(|it| match it.field(self.db, &field.name) { + Some(field) => Some(field), + None => { + self.diagnostics.push(InferenceDiagnostic::NoSuchField { + expr: tgt_expr, + field: idx, + }); + None + } + }) + .map_or(Ty::Unknown, |field| field.ty(self.db)); + self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); + } + if let Some(expr) = spread { + self.infer_expr(*expr, &Expectation::has_type(ty.clone())); + } + ty + } + Expr::UnaryOp { .. } => Ty::Unknown, // Expr::UnaryOp { expr: _, op: _ } => {} // Expr::Block { statements: _, tail: _ } => {} }; @@ -482,6 +511,42 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { } } + fn resolve_struct(&mut self, path: Option<&Path>) -> (Ty, Option) { + let path = match path { + Some(path) => path, + None => return (Ty::Unknown, None), + }; + let resolver = &self.resolver; + let resolution = match resolver + .resolve_path_without_assoc_items(self.db, &path) + .take_types() + { + Some(resolution) => resolution, + None => return (Ty::Unknown, None), + }; + + match resolution { + Resolution::LocalBinding(pat) => { + let ty = self + .type_of_pat + .get(pat) + .map_or(Ty::Unknown, |ty| ty.clone()); + //let ty = self.resolve_ty_as_possible(&mut vec![], ty); + (ty, None) + } + Resolution::Def(def) => { + if let Some(typable) = def.into() { + match typable { + TypableDef::Struct(s) => (s.ty(self.db), Some(s.into())), + TypableDef::BuiltinType(_) | TypableDef::Function(_) => (Ty::Unknown, None), + } + } else { + unreachable!(); + } + } + } + } + fn infer_block( &mut self, statements: &[Statement], @@ -676,8 +741,8 @@ impl From for ExprOrPatId { mod diagnostics { use crate::diagnostics::{ BreakOutsideLoop, BreakWithValueOutsideLoop, CannotApplyBinaryOp, ExpectedFunction, - IncompatibleBranch, InvalidLHS, MismatchedType, MissingElseBranch, ParameterCountMismatch, - ReturnMissingExpression, + IncompatibleBranch, InvalidLHS, MismatchedType, MissingElseBranch, NoSuchField, + ParameterCountMismatch, ReturnMissingExpression, }; use crate::{ code_model::src::HasSource, @@ -736,6 +801,10 @@ mod diagnostics { BreakWithValueOutsideLoop { id: ExprId, }, + NoSuchField { + expr: ExprId, + field: usize, + }, } impl InferenceDiagnostic { @@ -750,9 +819,10 @@ mod diagnostics { match self { InferenceDiagnostic::UnresolvedValue { id } => { let expr = match id { - ExprOrPatId::ExprId(id) => { - body.expr_syntax(*id).map(|ptr| ptr.ast.syntax_node_ptr()) - } + ExprOrPatId::ExprId(id) => body.expr_syntax(*id).map(|ptr| { + ptr.ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()) + }), ExprOrPatId::PatId(id) => { body.pat_syntax(*id).map(|ptr| ptr.ast.syntax_node_ptr()) } @@ -770,7 +840,11 @@ mod diagnostics { expected, found, } => { - let expr = body.expr_syntax(*id).unwrap().ast.syntax_node_ptr(); + let expr = body + .expr_syntax(*id) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(ParameterCountMismatch { file, expr, @@ -779,7 +853,11 @@ mod diagnostics { }) } InferenceDiagnostic::ExpectedFunction { id, found } => { - let expr = body.expr_syntax(*id).unwrap().ast.syntax_node_ptr(); + let expr = body + .expr_syntax(*id) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(ExpectedFunction { file, expr, @@ -791,7 +869,11 @@ mod diagnostics { found, expected, } => { - let expr = body.expr_syntax(*id).unwrap().ast.syntax_node_ptr(); + let expr = body + .expr_syntax(*id) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(MismatchedType { file, expr, @@ -804,7 +886,11 @@ mod diagnostics { then_ty, else_ty, } => { - let expr = body.expr_syntax(*id).unwrap().ast.syntax_node_ptr(); + let expr = body + .expr_syntax(*id) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(IncompatibleBranch { file, if_expr: expr, @@ -813,7 +899,11 @@ mod diagnostics { }); } InferenceDiagnostic::MissingElseBranch { id, then_ty } => { - let expr = body.expr_syntax(*id).unwrap().ast.syntax_node_ptr(); + let expr = body + .expr_syntax(*id) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(MissingElseBranch { file, if_expr: expr, @@ -821,7 +911,11 @@ mod diagnostics { }); } InferenceDiagnostic::CannotApplyBinaryOp { id, lhs, rhs } => { - let expr = body.expr_syntax(*id).unwrap().ast.syntax_node_ptr(); + let expr = body + .expr_syntax(*id) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(CannotApplyBinaryOp { file, expr, @@ -830,8 +924,16 @@ mod diagnostics { }); } InferenceDiagnostic::InvalidLHS { id, lhs } => { - let id = body.expr_syntax(*id).unwrap().ast.syntax_node_ptr(); - let lhs = body.expr_syntax(*lhs).unwrap().ast.syntax_node_ptr(); + let id = body + .expr_syntax(*id) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); + let lhs = body + .expr_syntax(*lhs) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(InvalidLHS { file, expr: id, @@ -839,26 +941,43 @@ mod diagnostics { }); } InferenceDiagnostic::ReturnMissingExpression { id } => { - let id = body.expr_syntax(*id).unwrap().ast.syntax_node_ptr(); + let id = body + .expr_syntax(*id) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(ReturnMissingExpression { file, return_expr: id, }); } InferenceDiagnostic::BreakOutsideLoop { id } => { - let id = body.expr_syntax(*id).unwrap().ast.syntax_node_ptr(); + let id = body + .expr_syntax(*id) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(BreakOutsideLoop { file, break_expr: id, }); } InferenceDiagnostic::BreakWithValueOutsideLoop { id } => { - let id = body.expr_syntax(*id).unwrap().ast.syntax_node_ptr(); + let id = body + .expr_syntax(*id) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(BreakWithValueOutsideLoop { file, break_expr: id, }); } + InferenceDiagnostic::NoSuchField { expr, field } => { + let file = owner.source(db).file_id; + let field = owner.body_source_map(db).field_syntax(*expr, *field).into(); + sink.push(NoSuchField { file, field }) + } } } } diff --git a/crates/mun_hir/src/ty/snapshots/tests__struct_declaration.snap b/crates/mun_hir/src/ty/snapshots/tests__struct_decl.snap similarity index 100% rename from crates/mun_hir/src/ty/snapshots/tests__struct_declaration.snap rename to crates/mun_hir/src/ty/snapshots/tests__struct_decl.snap diff --git a/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap b/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap new file mode 100644 index 000000000..ba7f72ddf --- /dev/null +++ b/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap @@ -0,0 +1,10 @@ +--- +source: crates/mun_hir/src/ty/tests.rs +expression: "struct Foo;\nstruct Bar {\n a: float,\n}\n\nfn main() {\n let a: Foo = Foo;\n let b: Bar = Bar { a: 1.23, };\n}" +--- +[52; 112) '{ ..., }; }': nothing +[62; 63) 'a': Foo +[71; 74) 'Foo': Foo +[84; 85) 'b': Bar +[93; 109) 'Bar { ....23, }': Bar +[102; 106) '1.23': float diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index 941cde3fd..f7a5c0b49 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -173,7 +173,7 @@ fn invalid_binary_ops() { } #[test] -fn struct_declaration() { +fn struct_decl() { infer_snapshot( r#" struct Foo; @@ -193,6 +193,23 @@ fn struct_declaration() { ) } +#[test] +fn struct_lit() { + infer_snapshot( + r#" + struct Foo; + struct Bar { + a: float, + } + + fn main() { + let a: Foo = Foo; + let b: Bar = Bar { a: 1.23, }; + } + "#, + ) +} + fn infer_snapshot(text: &str) { let text = text.trim().replace("\n ", "\n"); insta::assert_snapshot!(insta::_macro_support::AutoName, infer(&text), &text); @@ -218,7 +235,9 @@ fn infer(content: &str) -> String { for (expr, ty) in infer_result.type_of_expr.iter() { let syntax_ptr = match body_source_map.expr_syntax(expr) { - Some(sp) => sp.map(|ast| ast.syntax_node_ptr()), + Some(sp) => { + sp.map(|ast| ast.either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr())) + } None => continue, }; types.push((syntax_ptr, ty)); diff --git a/crates/mun_syntax/src/ast/generated.rs b/crates/mun_syntax/src/ast/generated.rs index c1ab95658..a31f85d81 100644 --- a/crates/mun_syntax/src/ast/generated.rs +++ b/crates/mun_syntax/src/ast/generated.rs @@ -249,7 +249,7 @@ impl AstNode for Expr { fn can_cast(kind: SyntaxKind) -> bool { match kind { LITERAL | PREFIX_EXPR | PATH_EXPR | BIN_EXPR | PAREN_EXPR | CALL_EXPR | IF_EXPR - | LOOP_EXPR | WHILE_EXPR | RETURN_EXPR | BREAK_EXPR | BLOCK_EXPR => true, + | LOOP_EXPR | WHILE_EXPR | RETURN_EXPR | BREAK_EXPR | BLOCK_EXPR | RECORD_LIT => true, _ => false, } } @@ -278,6 +278,7 @@ pub enum ExprKind { ReturnExpr(ReturnExpr), BreakExpr(BreakExpr), BlockExpr(BlockExpr), + RecordLit(RecordLit), } impl From for Expr { fn from(n: Literal) -> Expr { @@ -339,6 +340,11 @@ impl From for Expr { Expr { syntax: n.syntax } } } +impl From for Expr { + fn from(n: RecordLit) -> Expr { + Expr { syntax: n.syntax } + } +} impl Expr { pub fn kind(&self) -> ExprKind { @@ -355,6 +361,7 @@ impl Expr { RETURN_EXPR => ExprKind::ReturnExpr(ReturnExpr::cast(self.syntax.clone()).unwrap()), BREAK_EXPR => ExprKind::BreakExpr(BreakExpr::cast(self.syntax.clone()).unwrap()), BLOCK_EXPR => ExprKind::BlockExpr(BlockExpr::cast(self.syntax.clone()).unwrap()), + RECORD_LIT => ExprKind::RecordLit(RecordLit::cast(self.syntax.clone()).unwrap()), _ => unreachable!(), } } diff --git a/crates/mun_syntax/src/grammar.ron b/crates/mun_syntax/src/grammar.ron index 9782c1839..54eaba651 100644 --- a/crates/mun_syntax/src/grammar.ron +++ b/crates/mun_syntax/src/grammar.ron @@ -261,6 +261,7 @@ Grammar( "ReturnExpr", "BreakExpr", "BlockExpr", + "RecordLit", ] ), diff --git a/crates/mun_syntax/src/parsing/grammar.rs b/crates/mun_syntax/src/parsing/grammar.rs index d1783138b..2a1b24457 100644 --- a/crates/mun_syntax/src/parsing/grammar.rs +++ b/crates/mun_syntax/src/parsing/grammar.rs @@ -12,6 +12,18 @@ use super::{ SyntaxKind::{self, *}, }; +#[derive(Clone, Copy, PartialEq, Eq)] +enum BlockLike { + Block, + NotBlock, +} + +impl BlockLike { + fn is_block(self) -> bool { + self == BlockLike::Block + } +} + pub(crate) fn root(p: &mut Parser) { let m = p.start(); declarations::mod_contents(p); @@ -54,6 +66,16 @@ fn name_ref(p: &mut Parser) { } } +fn name_ref_or_index(p: &mut Parser) { + if p.at(IDENT) || p.at(INT_NUMBER) { + let m = p.start(); + p.bump_any(); + m.complete(p, NAME_REF); + } else { + p.error_and_bump("expected an identifier"); + } +} + fn opt_visibility(p: &mut Parser) -> bool { if p.at(PUB_KW) { let m = p.start(); @@ -65,16 +87,6 @@ fn opt_visibility(p: &mut Parser) -> bool { } } -fn name_ref_or_index(p: &mut Parser) { - if p.at(IDENT) || p.at(INT_NUMBER) { - let m = p.start(); - p.bump_any(); - m.complete(p, NAME_REF); - } else { - p.error_and_bump("expected identifier"); - } -} - fn error_block(p: &mut Parser, message: &str) { assert!(p.at(T!['{'])); let m = p.start(); diff --git a/crates/mun_syntax/src/parsing/grammar/expressions.rs b/crates/mun_syntax/src/parsing/grammar/expressions.rs index f658989d1..e2e01e4b1 100644 --- a/crates/mun_syntax/src/parsing/grammar/expressions.rs +++ b/crates/mun_syntax/src/parsing/grammar/expressions.rs @@ -71,7 +71,7 @@ pub(super) fn stmt(p: &mut Parser) { return; } - let cm = expr_stmt(p); + let (cm, _blocklike) = expr_stmt(p); let kind = cm.as_ref().map(|cm| cm.kind()).unwrap_or(ERROR); if p.at(T!['}']) { @@ -116,18 +116,23 @@ fn expr_no_struct(p: &mut Parser) { expr_bp(p, r, 1); } -fn expr_stmt(p: &mut Parser) -> Option { +fn expr_stmt(p: &mut Parser) -> (Option, BlockLike) { let r = Restrictions { forbid_structs: false, }; expr_bp(p, r, 1) } -fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> Option { +fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> (Option, BlockLike) { // Parse left hand side of the expression let mut lhs = match lhs(p, r) { - Some(lhs) => lhs, - None => return None, + Some((lhs, blocklike)) => { + if blocklike.is_block() { + return (Some(lhs), BlockLike::Block); + } + lhs + } + None => return (None, BlockLike::NotBlock), }; loop { @@ -143,7 +148,7 @@ fn expr_bp(p: &mut Parser, r: Restrictions, bp: u8) -> Option { lhs = m.complete(p, BIN_EXPR); } - Some(lhs) + (Some(lhs), BlockLike::NotBlock) } fn current_op(p: &Parser) -> (u8, SyntaxKind) { @@ -167,7 +172,7 @@ fn current_op(p: &Parser) -> (u8, SyntaxKind) { } } -fn lhs(p: &mut Parser, r: Restrictions) -> Option { +fn lhs(p: &mut Parser, r: Restrictions) -> Option<(CompletedMarker, BlockLike)> { let m; let kind = match p.current() { T![-] | T![!] => { @@ -176,22 +181,26 @@ fn lhs(p: &mut Parser, r: Restrictions) -> Option { PREFIX_EXPR } _ => { - let lhs = atom_expr(p, r)?; - return Some(postfix_expr(p, lhs)); + let (lhs, blocklike) = atom_expr(p, r)?; + return Some(postfix_expr(p, lhs, blocklike)); } }; expr_bp(p, r, 255); - Some(m.complete(p, kind)) + Some((m.complete(p, kind), BlockLike::NotBlock)) } -fn postfix_expr(p: &mut Parser, mut lhs: CompletedMarker) -> CompletedMarker { +fn postfix_expr( + p: &mut Parser, + mut lhs: CompletedMarker, + _blocklike: BlockLike, +) -> (CompletedMarker, BlockLike) { loop { lhs = match p.current() { T!['('] => call_expr(p, lhs), _ => break, } } - lhs + (lhs, BlockLike::NotBlock) } fn call_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker { @@ -220,9 +229,9 @@ fn arg_list(p: &mut Parser) { m.complete(p, ARG_LIST); } -fn atom_expr(p: &mut Parser, r: Restrictions) -> Option { +fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMarker, BlockLike)> { if let Some(m) = literal(p) { - return Some(m); + return Some((m, BlockLike::NotBlock)); } if paths::is_path_start(p) { @@ -242,19 +251,23 @@ fn atom_expr(p: &mut Parser, r: Restrictions) -> Option { return None; } }; - Some(marker) + let blocklike = match marker.kind() { + IF_EXPR | WHILE_EXPR | LOOP_EXPR | BLOCK_EXPR => BlockLike::Block, + _ => BlockLike::NotBlock, + }; + Some((marker, blocklike)) } -fn path_expr(p: &mut Parser, r: Restrictions) -> CompletedMarker { +fn path_expr(p: &mut Parser, r: Restrictions) -> (CompletedMarker, BlockLike) { assert!(paths::is_path_start(p)); let m = p.start(); paths::expr_path(p); match p.current() { T!['{'] if !r.forbid_structs => { record_field_list(p); - m.complete(p, RECORD_LIT) + (m.complete(p, RECORD_LIT), BlockLike::NotBlock) } - _ => m.complete(p, PATH_EXPR), + _ => (m.complete(p, PATH_EXPR), BlockLike::NotBlock), } } @@ -351,7 +364,7 @@ fn record_field_list(p: &mut Parser) { m.complete(p, RECORD_FIELD); } T!['{'] => error_block(p, "expected a field"), - _ => p.error_and_bump("expected identifier"), + _ => p.error_and_bump("expected an identifier"), } if !p.at(T!['}']) { p.expect(T![,]); diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index 7628c05c4..8313cda25 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -54,7 +54,7 @@ fn literals() { } #[test] -fn structures() { +fn struct_def() { snapshot_test( r#" struct Foo // error: expected a ';', or a '{' diff --git a/crates/mun_syntax/src/tests/snapshots/parser__structures.snap b/crates/mun_syntax/src/tests/snapshots/parser__struct_def.snap similarity index 100% rename from crates/mun_syntax/src/tests/snapshots/parser__structures.snap rename to crates/mun_syntax/src/tests/snapshots/parser__struct_def.snap From b2aea8bd204240b2dbd75842184f4f147849e0a4 Mon Sep 17 00:00:00 2001 From: Wodann Date: Wed, 4 Dec 2019 20:26:47 +0100 Subject: [PATCH 08/21] feat(struct): type inference of tuple literals --- crates/mun_codegen/src/ir/ty.rs | 8 +- crates/mun_hir/src/db.rs | 6 +- crates/mun_hir/src/lib.rs | 2 +- crates/mun_hir/src/name_resolution.rs | 1 - crates/mun_hir/src/ty.rs | 16 +- crates/mun_hir/src/ty/lower.rs | 24 ++- .../src/ty/snapshots/tests__struct_lit.snap | 19 ++- crates/mun_hir/src/ty/tests.rs | 2 + crates/mun_syntax/src/tests/parser.rs | 6 +- .../tests/snapshots/parser__record_lit.snap | 79 ---------- .../tests/snapshots/parser__struct_lit.snap | 148 ++++++++++++++++++ 11 files changed, 206 insertions(+), 105 deletions(-) delete mode 100644 crates/mun_syntax/src/tests/snapshots/parser__record_lit.snap create mode 100644 crates/mun_syntax/src/tests/snapshots/parser__struct_lit.snap diff --git a/crates/mun_codegen/src/ir/ty.rs b/crates/mun_codegen/src/ir/ty.rs index 52a3a599f..2ab1cfb9e 100644 --- a/crates/mun_codegen/src/ir/ty.rs +++ b/crates/mun_codegen/src/ir/ty.rs @@ -1,6 +1,6 @@ use super::try_convert_any_to_basic; use crate::IrDatabase; -use hir::{ApplicationTy, Ty, TypeCtor}; +use hir::{ApplicationTy, CallableDef, Ty, TypeCtor}; use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum}; /// Given a mun type, construct an LLVM IR type @@ -12,8 +12,8 @@ pub(crate) fn ir_query(db: &impl IrDatabase, ty: Ty) -> AnyTypeEnum { TypeCtor::Float => AnyTypeEnum::FloatType(context.f64_type()), TypeCtor::Int => AnyTypeEnum::IntType(context.i64_type()), TypeCtor::Bool => AnyTypeEnum::IntType(context.bool_type()), - TypeCtor::FnDef(f) => { - let ty = db.fn_signature(f); + TypeCtor::FnDef(def @ CallableDef::Function(_)) => { + let ty = db.callable_sig(def); let params: Vec = ty .params() .iter() @@ -29,7 +29,7 @@ pub(crate) fn ir_query(db: &impl IrDatabase, ty: Ty) -> AnyTypeEnum { AnyTypeEnum::FunctionType(fn_type) } - TypeCtor::Struct(s) => { + TypeCtor::FnDef(CallableDef::Struct(s)) | TypeCtor::Struct(s) => { let name = s.name(db).to_string(); context.opaque_struct_type(&name).into() } diff --git a/crates/mun_hir/src/db.rs b/crates/mun_hir/src/db.rs index 28e10a70e..bbea16214 100644 --- a/crates/mun_hir/src/db.rs +++ b/crates/mun_hir/src/db.rs @@ -3,7 +3,7 @@ use crate::input::{SourceRoot, SourceRootId}; use crate::name_resolution::Namespace; use crate::ty::lower::LowerBatchResult; -use crate::ty::{FnSig, Ty, TypableDef}; +use crate::ty::{CallableDef, FnSig, Ty, TypableDef}; use crate::{ adt::StructData, code_model::{DefWithBody, FnData, Function, ModuleData}, @@ -90,8 +90,8 @@ pub trait HirDatabase: DefDatabase { #[salsa::invoke(crate::FnData::fn_data_query)] fn fn_data(&self, func: Function) -> Arc; - #[salsa::invoke(crate::ty::fn_sig_for_fn)] - fn fn_signature(&self, func: Function) -> FnSig; + #[salsa::invoke(crate::ty::callable_item_sig)] + fn callable_sig(&self, def: CallableDef) -> FnSig; #[salsa::invoke(crate::ty::type_for_def)] fn type_for_def(&self, def: TypableDef, ns: Namespace) -> Ty; diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index a9e0d9b2b..21413c9fc 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -54,7 +54,7 @@ pub use crate::{ path::{Path, PathKind}, raw::RawItems, resolve::{Resolution, Resolver}, - ty::{ApplicationTy, InferenceResult, Ty, TypeCtor}, + ty::{lower::CallableDef, ApplicationTy, InferenceResult, Ty, TypeCtor}, }; use crate::{ diff --git a/crates/mun_hir/src/name_resolution.rs b/crates/mun_hir/src/name_resolution.rs index 961bfa21d..8f3c804cb 100644 --- a/crates/mun_hir/src/name_resolution.rs +++ b/crates/mun_hir/src/name_resolution.rs @@ -47,7 +47,6 @@ pub(crate) fn module_scope_query(db: &impl HirDatabase, file_id: FileId) -> Arc< let mut scope = ModuleScope::default(); let defs = db.module_data(file_id); for def in defs.definitions() { - #[allow(clippy::single_match)] match def { ModuleDef::Function(f) => { scope.items.insert( diff --git a/crates/mun_hir/src/ty.rs b/crates/mun_hir/src/ty.rs index c1a17783b..989b7dc92 100644 --- a/crates/mun_hir/src/ty.rs +++ b/crates/mun_hir/src/ty.rs @@ -7,7 +7,7 @@ use crate::ty::infer::TypeVarId; use crate::{Function, HirDatabase, Struct}; pub(crate) use infer::infer_query; pub use infer::InferenceResult; -pub(crate) use lower::{fn_sig_for_fn, type_for_def, TypableDef}; +pub(crate) use lower::{callable_item_sig, fn_sig_for_fn, type_for_def, CallableDef, TypableDef}; use std::fmt; use std::sync::Arc; @@ -73,7 +73,7 @@ pub enum TypeCtor { /// function foo() -> number { 1 } /// let bar = foo; // bar: function() -> number {foo} /// ``` - FnDef(Function), + FnDef(CallableDef), } impl Ty { @@ -107,7 +107,7 @@ impl Ty { pub fn as_function_def(&self) -> Option { match self { Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::FnDef(def) => Some(def), + TypeCtor::FnDef(CallableDef::Function(def)) => Some(def), _ => None, }, _ => None, @@ -117,7 +117,7 @@ impl Ty { pub fn callable_sig(&self, db: &impl HirDatabase) -> Option { match self { Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::FnDef(def) => Some(db.fn_signature(def)), + TypeCtor::FnDef(def) => Some(db.callable_sig(def)), _ => None, }, _ => None, @@ -180,10 +180,12 @@ impl HirDisplay for ApplicationTy { TypeCtor::Float => write!(f, "float"), TypeCtor::Int => write!(f, "int"), TypeCtor::Bool => write!(f, "bool"), - TypeCtor::Struct(def) => write!(f, "{}", def.name(f.db)), + TypeCtor::Struct(def) | TypeCtor::FnDef(CallableDef::Struct(def)) => { + write!(f, "{}", def.name(f.db)) + } TypeCtor::Never => write!(f, "never"), - TypeCtor::FnDef(def) => { - let sig = f.db.fn_signature(def); + TypeCtor::FnDef(CallableDef::Function(def)) => { + let sig = fn_sig_for_fn(f.db, def); let name = def.name(f.db); write!(f, "function {}", name)?; write!(f, "(")?; diff --git a/crates/mun_hir/src/ty/lower.rs b/crates/mun_hir/src/ty/lower.rs index 22f322848..df8b71583 100644 --- a/crates/mun_hir/src/ty/lower.rs +++ b/crates/mun_hir/src/ty/lower.rs @@ -169,6 +169,7 @@ pub enum CallableDef { Function(Function), Struct(Struct), } +impl_froms!(CallableDef: Function, Struct); /// Build the declared type of an item. This depends on the namespace; e.g. for /// `struct Foo(usize)`, we have two types: The type of the struct itself, and @@ -199,7 +200,14 @@ fn type_for_builtin(def: BuiltinType) -> Ty { /// Build the declared type of a function. This should not need to look at the /// function body. fn type_for_fn(_db: &impl HirDatabase, def: Function) -> Ty { - Ty::simple(TypeCtor::FnDef(def)) + Ty::simple(TypeCtor::FnDef(def.into())) +} + +pub(crate) fn callable_item_sig(db: &impl HirDatabase, def: CallableDef) -> FnSig { + match def { + CallableDef::Function(f) => fn_sig_for_fn(db, f), + CallableDef::Struct(s) => fn_sig_for_struct_constructor(db, s), + } } pub fn fn_sig_for_fn(db: &impl HirDatabase, def: Function) -> FnSig { @@ -214,13 +222,25 @@ pub fn fn_sig_for_fn(db: &impl HirDatabase, def: Function) -> FnSig { FnSig::from_params_and_return(params, ret) } +fn fn_sig_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> FnSig { + let data = def.data(db); + let resolver = def.resolver(db); + let params = data + .fields + .iter() + .map(|(_, field)| Ty::from_hir(db, &resolver, data.type_ref_map(), field.type_ref).ty) + .collect::>(); + let ret = type_for_struct(db, def); + FnSig::from_params_and_return(params, ret) +} + /// Build the type of a struct constructor. fn type_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> Ty { let struct_data = db.struct_data(def.id); if struct_data.kind == StructKind::Unit { type_for_struct(db, def) } else { - unreachable!(); + Ty::simple(TypeCtor::FnDef(def.into())) } } diff --git a/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap b/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap index ba7f72ddf..b419ff135 100644 --- a/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap +++ b/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap @@ -1,10 +1,15 @@ --- source: crates/mun_hir/src/ty/tests.rs -expression: "struct Foo;\nstruct Bar {\n a: float,\n}\n\nfn main() {\n let a: Foo = Foo;\n let b: Bar = Bar { a: 1.23, };\n}" +expression: "struct Foo;\nstruct Bar {\n a: float,\n}\nstruct Baz(float, int);\n\nfn main() {\n let a: Foo = Foo;\n let b: Bar = Bar { a: 1.23, };\n let c = Baz(1.23, 1);\n}" --- -[52; 112) '{ ..., }; }': nothing -[62; 63) 'a': Foo -[71; 74) 'Foo': Foo -[84; 85) 'b': Bar -[93; 109) 'Bar { ....23, }': Bar -[102; 106) '1.23': float +[76; 162) '{ ... 1); }': nothing +[86; 87) 'a': Foo +[95; 98) 'Foo': Foo +[108; 109) 'b': Bar +[117; 133) 'Bar { ....23, }': Bar +[126; 130) '1.23': float +[143; 144) 'c': Baz +[147; 150) 'Baz': Baz +[147; 159) 'Baz(1.23, 1)': Baz +[151; 155) '1.23': float +[157; 158) '1': int diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index f7a5c0b49..c59884313 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -201,10 +201,12 @@ fn struct_lit() { struct Bar { a: float, } + struct Baz(float, int); fn main() { let a: Foo = Foo; let b: Bar = Bar { a: 1.23, }; + let c = Baz(1.23, 1); } "#, ) diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index 8313cda25..017b43e60 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -234,13 +234,17 @@ fn while_expr() { } #[test] -fn record_lit() { +fn struct_lit() { snapshot_test( r#" fn foo() { + U; S {}; S { x, y: 32, }; + S { x: 32, y: 64 }; TupleStruct { 0: 1 }; + T(1.23); + T(1.23, 4,) } "#, ) diff --git a/crates/mun_syntax/src/tests/snapshots/parser__record_lit.snap b/crates/mun_syntax/src/tests/snapshots/parser__record_lit.snap deleted file mode 100644 index 41c167f1b..000000000 --- a/crates/mun_syntax/src/tests/snapshots/parser__record_lit.snap +++ /dev/null @@ -1,79 +0,0 @@ ---- -source: crates/mun_syntax/src/tests/parser.rs -expression: "fn foo() {\n S {};\n S { x, y: 32, };\n TupleStruct { 0: 1 };\n}" ---- -SOURCE_FILE@[0; 69) - FUNCTION_DEF@[0; 69) - FN_KW@[0; 2) "fn" - WHITESPACE@[2; 3) " " - NAME@[3; 6) - IDENT@[3; 6) "foo" - PARAM_LIST@[6; 8) - L_PAREN@[6; 7) "(" - R_PAREN@[7; 8) ")" - WHITESPACE@[8; 9) " " - BLOCK_EXPR@[9; 69) - L_CURLY@[9; 10) "{" - WHITESPACE@[10; 15) "\n " - EXPR_STMT@[15; 20) - RECORD_LIT@[15; 19) - PATH@[15; 16) - PATH_SEGMENT@[15; 16) - NAME_REF@[15; 16) - IDENT@[15; 16) "S" - WHITESPACE@[16; 17) " " - RECORD_FIELD_LIST@[17; 19) - L_CURLY@[17; 18) "{" - R_CURLY@[18; 19) "}" - SEMI@[19; 20) ";" - WHITESPACE@[20; 25) "\n " - EXPR_STMT@[25; 41) - RECORD_LIT@[25; 40) - PATH@[25; 26) - PATH_SEGMENT@[25; 26) - NAME_REF@[25; 26) - IDENT@[25; 26) "S" - WHITESPACE@[26; 27) " " - RECORD_FIELD_LIST@[27; 40) - L_CURLY@[27; 28) "{" - WHITESPACE@[28; 29) " " - RECORD_FIELD@[29; 30) - NAME_REF@[29; 30) - IDENT@[29; 30) "x" - COMMA@[30; 31) "," - WHITESPACE@[31; 32) " " - RECORD_FIELD@[32; 37) - NAME_REF@[32; 33) - IDENT@[32; 33) "y" - COLON@[33; 34) ":" - WHITESPACE@[34; 35) " " - LITERAL@[35; 37) - INT_NUMBER@[35; 37) "32" - COMMA@[37; 38) "," - WHITESPACE@[38; 39) " " - R_CURLY@[39; 40) "}" - SEMI@[40; 41) ";" - WHITESPACE@[41; 46) "\n " - EXPR_STMT@[46; 67) - RECORD_LIT@[46; 66) - PATH@[46; 57) - PATH_SEGMENT@[46; 57) - NAME_REF@[46; 57) - IDENT@[46; 57) "TupleStruct" - WHITESPACE@[57; 58) " " - RECORD_FIELD_LIST@[58; 66) - L_CURLY@[58; 59) "{" - WHITESPACE@[59; 60) " " - RECORD_FIELD@[60; 64) - NAME_REF@[60; 61) - INT_NUMBER@[60; 61) "0" - COLON@[61; 62) ":" - WHITESPACE@[62; 63) " " - LITERAL@[63; 64) - INT_NUMBER@[63; 64) "1" - WHITESPACE@[64; 65) " " - R_CURLY@[65; 66) "}" - SEMI@[66; 67) ";" - WHITESPACE@[67; 68) "\n" - R_CURLY@[68; 69) "}" - diff --git a/crates/mun_syntax/src/tests/snapshots/parser__struct_lit.snap b/crates/mun_syntax/src/tests/snapshots/parser__struct_lit.snap new file mode 100644 index 000000000..6d7987689 --- /dev/null +++ b/crates/mun_syntax/src/tests/snapshots/parser__struct_lit.snap @@ -0,0 +1,148 @@ +--- +source: crates/mun_syntax/src/tests/parser.rs +expression: "fn foo() {\n U;\n S {};\n S { x, y: 32, };\n S { x: 32, y: 64 };\n TupleStruct { 0: 1 };\n T(1.23);\n T(1.23, 4,)\n}" +--- +SOURCE_FILE@[0; 129) + FUNCTION_DEF@[0; 129) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 6) + IDENT@[3; 6) "foo" + PARAM_LIST@[6; 8) + L_PAREN@[6; 7) "(" + R_PAREN@[7; 8) ")" + WHITESPACE@[8; 9) " " + BLOCK_EXPR@[9; 129) + L_CURLY@[9; 10) "{" + WHITESPACE@[10; 15) "\n " + EXPR_STMT@[15; 17) + PATH_EXPR@[15; 16) + PATH@[15; 16) + PATH_SEGMENT@[15; 16) + NAME_REF@[15; 16) + IDENT@[15; 16) "U" + SEMI@[16; 17) ";" + WHITESPACE@[17; 22) "\n " + EXPR_STMT@[22; 27) + RECORD_LIT@[22; 26) + PATH@[22; 23) + PATH_SEGMENT@[22; 23) + NAME_REF@[22; 23) + IDENT@[22; 23) "S" + WHITESPACE@[23; 24) " " + RECORD_FIELD_LIST@[24; 26) + L_CURLY@[24; 25) "{" + R_CURLY@[25; 26) "}" + SEMI@[26; 27) ";" + WHITESPACE@[27; 32) "\n " + EXPR_STMT@[32; 48) + RECORD_LIT@[32; 47) + PATH@[32; 33) + PATH_SEGMENT@[32; 33) + NAME_REF@[32; 33) + IDENT@[32; 33) "S" + WHITESPACE@[33; 34) " " + RECORD_FIELD_LIST@[34; 47) + L_CURLY@[34; 35) "{" + WHITESPACE@[35; 36) " " + RECORD_FIELD@[36; 37) + NAME_REF@[36; 37) + IDENT@[36; 37) "x" + COMMA@[37; 38) "," + WHITESPACE@[38; 39) " " + RECORD_FIELD@[39; 44) + NAME_REF@[39; 40) + IDENT@[39; 40) "y" + COLON@[40; 41) ":" + WHITESPACE@[41; 42) " " + LITERAL@[42; 44) + INT_NUMBER@[42; 44) "32" + COMMA@[44; 45) "," + WHITESPACE@[45; 46) " " + R_CURLY@[46; 47) "}" + SEMI@[47; 48) ";" + WHITESPACE@[48; 53) "\n " + EXPR_STMT@[53; 72) + RECORD_LIT@[53; 71) + PATH@[53; 54) + PATH_SEGMENT@[53; 54) + NAME_REF@[53; 54) + IDENT@[53; 54) "S" + WHITESPACE@[54; 55) " " + RECORD_FIELD_LIST@[55; 71) + L_CURLY@[55; 56) "{" + WHITESPACE@[56; 57) " " + RECORD_FIELD@[57; 62) + NAME_REF@[57; 58) + IDENT@[57; 58) "x" + COLON@[58; 59) ":" + WHITESPACE@[59; 60) " " + LITERAL@[60; 62) + INT_NUMBER@[60; 62) "32" + COMMA@[62; 63) "," + WHITESPACE@[63; 64) " " + RECORD_FIELD@[64; 69) + NAME_REF@[64; 65) + IDENT@[64; 65) "y" + COLON@[65; 66) ":" + WHITESPACE@[66; 67) " " + LITERAL@[67; 69) + INT_NUMBER@[67; 69) "64" + WHITESPACE@[69; 70) " " + R_CURLY@[70; 71) "}" + SEMI@[71; 72) ";" + WHITESPACE@[72; 77) "\n " + EXPR_STMT@[77; 98) + RECORD_LIT@[77; 97) + PATH@[77; 88) + PATH_SEGMENT@[77; 88) + NAME_REF@[77; 88) + IDENT@[77; 88) "TupleStruct" + WHITESPACE@[88; 89) " " + RECORD_FIELD_LIST@[89; 97) + L_CURLY@[89; 90) "{" + WHITESPACE@[90; 91) " " + RECORD_FIELD@[91; 95) + NAME_REF@[91; 92) + INT_NUMBER@[91; 92) "0" + COLON@[92; 93) ":" + WHITESPACE@[93; 94) " " + LITERAL@[94; 95) + INT_NUMBER@[94; 95) "1" + WHITESPACE@[95; 96) " " + R_CURLY@[96; 97) "}" + SEMI@[97; 98) ";" + WHITESPACE@[98; 103) "\n " + EXPR_STMT@[103; 111) + CALL_EXPR@[103; 110) + PATH_EXPR@[103; 104) + PATH@[103; 104) + PATH_SEGMENT@[103; 104) + NAME_REF@[103; 104) + IDENT@[103; 104) "T" + ARG_LIST@[104; 110) + L_PAREN@[104; 105) "(" + LITERAL@[105; 109) + FLOAT_NUMBER@[105; 109) "1.23" + R_PAREN@[109; 110) ")" + SEMI@[110; 111) ";" + WHITESPACE@[111; 116) "\n " + CALL_EXPR@[116; 127) + PATH_EXPR@[116; 117) + PATH@[116; 117) + PATH_SEGMENT@[116; 117) + NAME_REF@[116; 117) + IDENT@[116; 117) "T" + ARG_LIST@[117; 127) + L_PAREN@[117; 118) "(" + LITERAL@[118; 122) + FLOAT_NUMBER@[118; 122) "1.23" + COMMA@[122; 123) "," + WHITESPACE@[123; 124) " " + LITERAL@[124; 125) + INT_NUMBER@[124; 125) "4" + COMMA@[125; 126) "," + R_PAREN@[126; 127) ")" + WHITESPACE@[127; 128) "\n" + R_CURLY@[128; 129) "}" + From a6303a1c4f98c75c89e8f2c320b7e89e7a187cd9 Mon Sep 17 00:00:00 2001 From: Wodann Date: Thu, 5 Dec 2019 20:44:05 +0100 Subject: [PATCH 09/21] feat(struct): add lexing of indices, and parsing and type inferencing of fields --- crates/mun_hir/src/expr.rs | 14 ++- crates/mun_hir/src/name.rs | 9 ++ crates/mun_hir/src/ty/infer.rs | 14 +++ .../snapshots/tests__struct_field_index.snap | 22 ++++ crates/mun_hir/src/ty/tests.rs | 22 ++++ crates/mun_syntax/src/ast/expr_extensions.rs | 25 ++++ crates/mun_syntax/src/ast/generated.rs | 47 ++++++- crates/mun_syntax/src/grammar.ron | 6 + .../src/parsing/grammar/expressions.rs | 34 ++++++ crates/mun_syntax/src/parsing/lexer.rs | 34 ++++++ .../mun_syntax/src/syntax_kind/generated.rs | 4 + crates/mun_syntax/src/tests/parser.rs | 18 +++ .../snapshots/parser__struct_field_index.snap | 115 ++++++++++++++++++ 13 files changed, 361 insertions(+), 3 deletions(-) create mode 100644 crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap create mode 100644 crates/mun_syntax/src/tests/snapshots/parser__struct_field_index.snap diff --git a/crates/mun_hir/src/expr.rs b/crates/mun_hir/src/expr.rs index caf8cd25d..4cab6e97f 100644 --- a/crates/mun_hir/src/expr.rs +++ b/crates/mun_hir/src/expr.rs @@ -230,6 +230,10 @@ pub enum Expr { fields: Vec, spread: Option, }, + Field { + expr: ExprId, + name: Name, + }, Literal(Literal), } @@ -299,7 +303,7 @@ impl Expr { f(*lhs); f(*rhs); } - Expr::UnaryOp { expr, .. } => { + Expr::Field { expr, .. } | Expr::UnaryOp { expr, .. } => { f(*expr); } Expr::Literal(_) => {} @@ -669,6 +673,14 @@ where } res } + ast::ExprKind::FieldExpr(e) => { + let expr = self.collect_expr_opt(e.expr()); + let name = match e.field_access() { + Some(kind) => kind.as_name(), + None => Name::missing(), + }; + self.alloc_expr(Expr::Field { expr, name }, syntax_ptr) + } ast::ExprKind::IfExpr(e) => { let then_branch = self.collect_block_opt(e.then_branch()); diff --git a/crates/mun_hir/src/name.rs b/crates/mun_hir/src/name.rs index ab9855343..9df501272 100644 --- a/crates/mun_hir/src/name.rs +++ b/crates/mun_hir/src/name.rs @@ -79,6 +79,15 @@ impl AsName for ast::Name { } } +impl AsName for ast::FieldKind { + fn as_name(&self) -> Name { + match self { + ast::FieldKind::Name(nr) => nr.as_name(), + ast::FieldKind::Index(idx) => Name::new_tuple_field(idx.text()[1..].parse().unwrap()), + } + } +} + pub(crate) const FLOAT: Name = Name::new_inline_ascii(5, b"float"); pub(crate) const INT: Name = Name::new_inline_ascii(3, b"int"); pub(crate) const BOOLEAN: Name = Name::new_inline_ascii(4, b"bool"); diff --git a/crates/mun_hir/src/ty/infer.rs b/crates/mun_hir/src/ty/infer.rs index c03332dc6..ebb72d3ca 100644 --- a/crates/mun_hir/src/ty/infer.rs +++ b/crates/mun_hir/src/ty/infer.rs @@ -362,6 +362,20 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { } ty } + Expr::Field { expr, name } => { + let receiver_ty = self.infer_expr(*expr, &Expectation::none()); + match receiver_ty { + Ty::Apply(a_ty) => { + if let TypeCtor::Struct(s) = a_ty.ctor { + s.field(self.db, name).map(|field| field.ty(self.db)) + } else { + None + } + } + _ => None, + } + .unwrap_or(Ty::Unknown) + } Expr::UnaryOp { .. } => Ty::Unknown, // Expr::UnaryOp { expr: _, op: _ } => {} // Expr::Block { statements: _, tail: _ } => {} diff --git a/crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap b/crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap new file mode 100644 index 000000000..e3dcc68d2 --- /dev/null +++ b/crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap @@ -0,0 +1,22 @@ +--- +source: crates/mun_hir/src/ty/tests.rs +expression: "struct Foo {\n a: float,\n b: int,\n}\nstruct Bar(float, int)\n\nfn main() {\n let foo = Foo { a: 1.23, b: 4 };\n foo.a\n foo.b\n let bar = Bar(1.23, 4);\n bar.0\n bar.1;\n}" +--- +[75; 184) '{ ...r.1; }': nothing +[85; 88) 'foo': Foo +[91; 112) 'Foo { ...b: 4 }': Foo +[100; 104) '1.23': float +[109; 110) '4': int +[118; 121) 'foo': Foo +[118; 123) 'foo.a': float +[128; 131) 'foo': Foo +[128; 133) 'foo.b': int +[142; 145) 'bar': Bar +[148; 151) 'Bar': Bar +[148; 160) 'Bar(1.23, 4)': Bar +[152; 156) '1.23': float +[158; 159) '4': int +[166; 169) 'bar': Bar +[166; 171) 'bar.0': float +[176; 179) 'bar': Bar +[176; 181) 'bar.1': int diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index c59884313..d70372624 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -212,6 +212,28 @@ fn struct_lit() { ) } +#[test] +fn struct_field_index() { + infer_snapshot( + r#" + struct Foo { + a: float, + b: int, + } + struct Bar(float, int) + + fn main() { + let foo = Foo { a: 1.23, b: 4 }; + foo.a + foo.b + let bar = Bar(1.23, 4); + bar.0 + bar.1; + } + "#, + ) +} + fn infer_snapshot(text: &str) { let text = text.trim().replace("\n ", "\n"); insta::assert_snapshot!(insta::_macro_support::AutoName, infer(&text), &text); diff --git a/crates/mun_syntax/src/ast/expr_extensions.rs b/crates/mun_syntax/src/ast/expr_extensions.rs index c4970c296..e20fb88cf 100644 --- a/crates/mun_syntax/src/ast/expr_extensions.rs +++ b/crates/mun_syntax/src/ast/expr_extensions.rs @@ -105,6 +105,31 @@ impl BinExpr { } } +#[derive(PartialEq, Eq)] +pub enum FieldKind { + Name(ast::NameRef), + Index(SyntaxToken), +} + +impl ast::FieldExpr { + pub fn index_token(&self) -> Option { + self.syntax + .children_with_tokens() + .find(|e| e.kind() == SyntaxKind::INDEX) + .and_then(|e| e.into_token()) + } + + pub fn field_access(&self) -> Option { + if let Some(nr) = self.name_ref() { + Some(FieldKind::Name(nr)) + } else if let Some(tok) = self.index_token() { + Some(FieldKind::Index(tok)) + } else { + None + } + } +} + #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum LiteralKind { String, diff --git a/crates/mun_syntax/src/ast/generated.rs b/crates/mun_syntax/src/ast/generated.rs index a31f85d81..979742b5e 100644 --- a/crates/mun_syntax/src/ast/generated.rs +++ b/crates/mun_syntax/src/ast/generated.rs @@ -248,8 +248,9 @@ pub struct Expr { impl AstNode for Expr { fn can_cast(kind: SyntaxKind) -> bool { match kind { - LITERAL | PREFIX_EXPR | PATH_EXPR | BIN_EXPR | PAREN_EXPR | CALL_EXPR | IF_EXPR - | LOOP_EXPR | WHILE_EXPR | RETURN_EXPR | BREAK_EXPR | BLOCK_EXPR | RECORD_LIT => true, + LITERAL | PREFIX_EXPR | PATH_EXPR | BIN_EXPR | PAREN_EXPR | CALL_EXPR | FIELD_EXPR + | IF_EXPR | LOOP_EXPR | WHILE_EXPR | RETURN_EXPR | BREAK_EXPR | BLOCK_EXPR + | RECORD_LIT => true, _ => false, } } @@ -272,6 +273,7 @@ pub enum ExprKind { BinExpr(BinExpr), ParenExpr(ParenExpr), CallExpr(CallExpr), + FieldExpr(FieldExpr), IfExpr(IfExpr), LoopExpr(LoopExpr), WhileExpr(WhileExpr), @@ -310,6 +312,11 @@ impl From for Expr { Expr { syntax: n.syntax } } } +impl From for Expr { + fn from(n: FieldExpr) -> Expr { + Expr { syntax: n.syntax } + } +} impl From for Expr { fn from(n: IfExpr) -> Expr { Expr { syntax: n.syntax } @@ -355,6 +362,7 @@ impl Expr { BIN_EXPR => ExprKind::BinExpr(BinExpr::cast(self.syntax.clone()).unwrap()), PAREN_EXPR => ExprKind::ParenExpr(ParenExpr::cast(self.syntax.clone()).unwrap()), CALL_EXPR => ExprKind::CallExpr(CallExpr::cast(self.syntax.clone()).unwrap()), + FIELD_EXPR => ExprKind::FieldExpr(FieldExpr::cast(self.syntax.clone()).unwrap()), IF_EXPR => ExprKind::IfExpr(IfExpr::cast(self.syntax.clone()).unwrap()), LOOP_EXPR => ExprKind::LoopExpr(LoopExpr::cast(self.syntax.clone()).unwrap()), WHILE_EXPR => ExprKind::WhileExpr(WhileExpr::cast(self.syntax.clone()).unwrap()), @@ -400,6 +408,41 @@ impl ExprStmt { } } +// FieldExpr + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct FieldExpr { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for FieldExpr { + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + FIELD_EXPR => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(FieldExpr { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl FieldExpr { + pub fn expr(&self) -> Option { + super::child_opt(self) + } + + pub fn name_ref(&self) -> Option { + super::child_opt(self) + } +} + // FunctionDef #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/mun_syntax/src/grammar.ron b/crates/mun_syntax/src/grammar.ron index 54eaba651..e3a82cf5e 100644 --- a/crates/mun_syntax/src/grammar.ron +++ b/crates/mun_syntax/src/grammar.ron @@ -92,6 +92,7 @@ Grammar( tokens: [ "ERROR", "IDENT", + "INDEX", "WHITESPACE", "COMMENT", ], @@ -123,6 +124,7 @@ Grammar( "BIN_EXPR", "PAREN_EXPR", "CALL_EXPR", + "FIELD_EXPR", "IF_EXPR", "BLOCK_EXPR", "RETURN_EXPR", @@ -237,6 +239,9 @@ Grammar( traits: ["ArgListOwner"], options: [ "Expr" ], ), + "FieldExpr": ( + options: ["Expr", "NameRef"] + ), "IfExpr": ( options: [ "Condition" ] ), @@ -255,6 +260,7 @@ Grammar( "BinExpr", "ParenExpr", "CallExpr", + "FieldExpr", "IfExpr", "LoopExpr", "WhileExpr", diff --git a/crates/mun_syntax/src/parsing/grammar/expressions.rs b/crates/mun_syntax/src/parsing/grammar/expressions.rs index e2e01e4b1..fc61e3bfa 100644 --- a/crates/mun_syntax/src/parsing/grammar/expressions.rs +++ b/crates/mun_syntax/src/parsing/grammar/expressions.rs @@ -197,6 +197,14 @@ fn postfix_expr( loop { lhs = match p.current() { T!['('] => call_expr(p, lhs), + T![.] => match postfix_dot_expr(p, lhs) { + Ok(it) => it, + Err(it) => { + lhs = it; + break; + } + }, + INDEX => field_expr(p, lhs), _ => break, } } @@ -229,6 +237,32 @@ fn arg_list(p: &mut Parser) { m.complete(p, ARG_LIST); } +fn postfix_dot_expr( + p: &mut Parser, + lhs: CompletedMarker, +) -> Result { + assert!(p.at(T![.])); + if p.nth(1) == IDENT && p.nth(2) == T!['('] { + unimplemented!("Method calls are not supported yet."); + } + + Ok(field_expr(p, lhs)) +} + +fn field_expr(p: &mut Parser, lhs: CompletedMarker) -> CompletedMarker { + assert!(p.at(T![.]) || p.at(INDEX)); + let m = lhs.precede(p); + if p.at(T![.]) { + p.bump(T![.]); + name_ref_or_index(p); + } else if p.at(INDEX) { + p.bump(INDEX); + } else { + p.error("expected field name or number"); + } + m.complete(p, FIELD_EXPR) +} + fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMarker, BlockLike)> { if let Some(m) = literal(p) { return Some((m, BlockLike::NotBlock)); diff --git a/crates/mun_syntax/src/parsing/lexer.rs b/crates/mun_syntax/src/parsing/lexer.rs index c3709b22a..7610e9c4b 100644 --- a/crates/mun_syntax/src/parsing/lexer.rs +++ b/crates/mun_syntax/src/parsing/lexer.rs @@ -66,6 +66,10 @@ fn next_token_inner(c: char, cursor: &mut Cursor) -> SyntaxKind { return scan_number(c, cursor); } + if let Some(kind) = scan_index(c, cursor) { + return kind; + } + if let Some(kind) = SyntaxKind::from_char(c) { return kind; } @@ -96,3 +100,33 @@ fn scan_identifier_or_keyword(c: char, cursor: &mut Cursor) -> SyntaxKind { } IDENT } + +fn scan_index(c: char, cursor: &mut Cursor) -> Option { + if c == '.' { + let mut is_first = true; + while let Some(cc) = cursor.current() { + match cc { + '0' => { + cursor.bump(); + if is_first { + break; + } + } + '1'..='9' => { + cursor.bump(); + } + _ => { + if is_first { + return None; + } else { + break; + } + } + } + is_first = false; + } + Some(SyntaxKind::INDEX) + } else { + None + } +} diff --git a/crates/mun_syntax/src/syntax_kind/generated.rs b/crates/mun_syntax/src/syntax_kind/generated.rs index 6f910b0e3..12d4b2ba9 100644 --- a/crates/mun_syntax/src/syntax_kind/generated.rs +++ b/crates/mun_syntax/src/syntax_kind/generated.rs @@ -80,6 +80,7 @@ pub enum SyntaxKind { STRING, ERROR, IDENT, + INDEX, WHITESPACE, COMMENT, SOURCE_FILE, @@ -103,6 +104,7 @@ pub enum SyntaxKind { BIN_EXPR, PAREN_EXPR, CALL_EXPR, + FIELD_EXPR, IF_EXPR, BLOCK_EXPR, RETURN_EXPR, @@ -349,6 +351,7 @@ impl SyntaxKind { STRING => &SyntaxInfo { name: "STRING" }, ERROR => &SyntaxInfo { name: "ERROR" }, IDENT => &SyntaxInfo { name: "IDENT" }, + INDEX => &SyntaxInfo { name: "INDEX" }, WHITESPACE => &SyntaxInfo { name: "WHITESPACE" }, COMMENT => &SyntaxInfo { name: "COMMENT" }, SOURCE_FILE => &SyntaxInfo { name: "SOURCE_FILE" }, @@ -372,6 +375,7 @@ impl SyntaxKind { BIN_EXPR => &SyntaxInfo { name: "BIN_EXPR" }, PAREN_EXPR => &SyntaxInfo { name: "PAREN_EXPR" }, CALL_EXPR => &SyntaxInfo { name: "CALL_EXPR" }, + FIELD_EXPR => &SyntaxInfo { name: "FIELD_EXPR" }, IF_EXPR => &SyntaxInfo { name: "IF_EXPR" }, BLOCK_EXPR => &SyntaxInfo { name: "BLOCK_EXPR" }, RETURN_EXPR => &SyntaxInfo { name: "RETURN_EXPR" }, diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index 017b43e60..acae97863 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -249,3 +249,21 @@ fn struct_lit() { "#, ) } + +#[test] +fn struct_field_index() { + snapshot_test( + r#" + fn main() { + foo.a + foo.a.b + foo.0 + foo.0.1 + foo.10 + foo.01 // index: .0 + foo.0 1 // index: .0 + foo.a.0 + } + "#, + ) +} diff --git a/crates/mun_syntax/src/tests/snapshots/parser__struct_field_index.snap b/crates/mun_syntax/src/tests/snapshots/parser__struct_field_index.snap new file mode 100644 index 000000000..aabaf31a3 --- /dev/null +++ b/crates/mun_syntax/src/tests/snapshots/parser__struct_field_index.snap @@ -0,0 +1,115 @@ +--- +source: crates/mun_syntax/src/tests/parser.rs +expression: "fn main() {\n foo.a\n foo.a.b\n foo.0\n foo.0.1\n foo.10\n foo.01 // index: .0\n foo.0 1 // index: .0 \n foo.a.0\n}" +--- +SOURCE_FILE@[0; 131) + FUNCTION_DEF@[0; 131) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 7) + IDENT@[3; 7) "main" + PARAM_LIST@[7; 9) + L_PAREN@[7; 8) "(" + R_PAREN@[8; 9) ")" + WHITESPACE@[9; 10) " " + BLOCK_EXPR@[10; 131) + L_CURLY@[10; 11) "{" + WHITESPACE@[11; 16) "\n " + EXPR_STMT@[16; 21) + FIELD_EXPR@[16; 21) + PATH_EXPR@[16; 19) + PATH@[16; 19) + PATH_SEGMENT@[16; 19) + NAME_REF@[16; 19) + IDENT@[16; 19) "foo" + DOT@[19; 20) "." + NAME_REF@[20; 21) + IDENT@[20; 21) "a" + WHITESPACE@[21; 26) "\n " + EXPR_STMT@[26; 33) + FIELD_EXPR@[26; 33) + FIELD_EXPR@[26; 31) + PATH_EXPR@[26; 29) + PATH@[26; 29) + PATH_SEGMENT@[26; 29) + NAME_REF@[26; 29) + IDENT@[26; 29) "foo" + DOT@[29; 30) "." + NAME_REF@[30; 31) + IDENT@[30; 31) "a" + DOT@[31; 32) "." + NAME_REF@[32; 33) + IDENT@[32; 33) "b" + WHITESPACE@[33; 38) "\n " + EXPR_STMT@[38; 43) + FIELD_EXPR@[38; 43) + PATH_EXPR@[38; 41) + PATH@[38; 41) + PATH_SEGMENT@[38; 41) + NAME_REF@[38; 41) + IDENT@[38; 41) "foo" + INDEX@[41; 43) ".0" + WHITESPACE@[43; 48) "\n " + EXPR_STMT@[48; 55) + FIELD_EXPR@[48; 55) + FIELD_EXPR@[48; 53) + PATH_EXPR@[48; 51) + PATH@[48; 51) + PATH_SEGMENT@[48; 51) + NAME_REF@[48; 51) + IDENT@[48; 51) "foo" + INDEX@[51; 53) ".0" + INDEX@[53; 55) ".1" + WHITESPACE@[55; 60) "\n " + EXPR_STMT@[60; 66) + FIELD_EXPR@[60; 66) + PATH_EXPR@[60; 63) + PATH@[60; 63) + PATH_SEGMENT@[60; 63) + NAME_REF@[60; 63) + IDENT@[60; 63) "foo" + INDEX@[63; 66) ".10" + WHITESPACE@[66; 71) "\n " + EXPR_STMT@[71; 76) + FIELD_EXPR@[71; 76) + PATH_EXPR@[71; 74) + PATH@[71; 74) + PATH_SEGMENT@[71; 74) + NAME_REF@[71; 74) + IDENT@[71; 74) "foo" + INDEX@[74; 76) ".0" + EXPR_STMT@[76; 77) + LITERAL@[76; 77) + INT_NUMBER@[76; 77) "1" + WHITESPACE@[77; 79) " " + COMMENT@[79; 91) "// index: .0" + WHITESPACE@[91; 96) "\n " + EXPR_STMT@[96; 101) + FIELD_EXPR@[96; 101) + PATH_EXPR@[96; 99) + PATH@[96; 99) + PATH_SEGMENT@[96; 99) + NAME_REF@[96; 99) + IDENT@[96; 99) "foo" + INDEX@[99; 101) ".0" + WHITESPACE@[101; 102) " " + EXPR_STMT@[102; 103) + LITERAL@[102; 103) + INT_NUMBER@[102; 103) "1" + WHITESPACE@[103; 104) " " + COMMENT@[104; 117) "// index: .0 " + WHITESPACE@[117; 122) "\n " + FIELD_EXPR@[122; 129) + FIELD_EXPR@[122; 127) + PATH_EXPR@[122; 125) + PATH@[122; 125) + PATH_SEGMENT@[122; 125) + NAME_REF@[122; 125) + IDENT@[122; 125) "foo" + DOT@[125; 126) "." + NAME_REF@[126; 127) + IDENT@[126; 127) "a" + INDEX@[127; 129) ".0" + WHITESPACE@[129; 130) "\n" + R_CURLY@[130; 131) "}" + From 9c9758e32eccfb2f08592d521e636c6f5fadc8a8 Mon Sep 17 00:00:00 2001 From: Wodann Date: Thu, 12 Dec 2019 17:29:15 +0100 Subject: [PATCH 10/21] feat(struct): add IR generation for record, tuple, and unit struct literals --- crates/mun_codegen/src/ir/adt.rs | 13 ++++ crates/mun_codegen/src/ir/body.rs | 59 ++++++++++++++++--- crates/mun_codegen/src/ir/dispatch_table.rs | 29 +++++---- .../src/snapshots/test__struct_test.snap | 6 +- crates/mun_codegen/src/test.rs | 6 +- crates/mun_hir/src/lib.rs | 2 +- crates/mun_hir/src/ty.rs | 10 ++-- 7 files changed, 94 insertions(+), 31 deletions(-) diff --git a/crates/mun_codegen/src/ir/adt.rs b/crates/mun_codegen/src/ir/adt.rs index c761cc1ac..9b1cfb7ea 100644 --- a/crates/mun_codegen/src/ir/adt.rs +++ b/crates/mun_codegen/src/ir/adt.rs @@ -2,6 +2,7 @@ use crate::ir::try_convert_any_to_basic; use crate::IrDatabase; use inkwell::types::{AnyTypeEnum, BasicTypeEnum}; +use inkwell::values::BasicValueEnum; pub(super) fn gen_struct_decl(db: &impl IrDatabase, s: hir::Struct) { if let AnyTypeEnum::StructType(struct_type) = db.type_ir(s.ty(db)) { @@ -22,3 +23,15 @@ pub(super) fn gen_struct_decl(db: &impl IrDatabase, s: hir::Struct) { unreachable!() } } + +pub(crate) fn gen_named_struct_lit( + db: &impl IrDatabase, + ty: hir::Ty, + values: &[BasicValueEnum], +) -> BasicValueEnum { + if let inkwell::types::AnyTypeEnum::StructType(struct_type) = db.type_ir(ty) { + struct_type.const_named_struct(&values).into() + } else { + unreachable!("at this stage there must be a struct type"); + } +} diff --git a/crates/mun_codegen/src/ir/body.rs b/crates/mun_codegen/src/ir/body.rs index 613a047f6..dff643b15 100644 --- a/crates/mun_codegen/src/ir/body.rs +++ b/crates/mun_codegen/src/ir/body.rs @@ -1,4 +1,7 @@ -use crate::{ir::dispatch_table::DispatchTable, ir::try_convert_any_to_basic, IrDatabase}; +use crate::{ + ir::adt::gen_named_struct_lit, ir::dispatch_table::DispatchTable, ir::try_convert_any_to_basic, + IrDatabase, +}; use hir::{ ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, HirDisplay, InferenceResult, Literal, Ordering, Pat, PatId, Path, Resolution, Resolver, Statement, TypeCtor, @@ -140,13 +143,23 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { Some(self.gen_path_expr(p, expr, &resolver)) } Expr::Literal(lit) => Some(self.gen_literal(lit)), + Expr::RecordLit { fields, .. } => Some(self.gen_record_lit(expr, fields)), Expr::BinaryOp { lhs, rhs, op } => { self.gen_binary_op(expr, *lhs, *rhs, op.expect("missing op")) } Expr::Call { ref callee, ref args, - } => self.gen_call(*callee, &args).try_as_basic_value().left(), + } => { + // Get the callable definition from the map + match self.infer[*callee].as_callable_def() { + Some(hir::CallableDef::Function(def)) => { + self.gen_call(def, &args).try_as_basic_value().left() + } + Some(hir::CallableDef::Struct(_)) => Some(self.gen_named_tuple_lit(expr, args)), + None => panic!("expected a callable expression"), + } + } Expr::If { condition, then_branch, @@ -195,6 +208,38 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { self.module.get_context().const_struct(&[], false).into() } + /// Generates IR for a record literal, e.g. `Foo { a: 1.23, b: 4 }` + fn gen_record_lit( + &mut self, + type_expr: ExprId, + fields: &[hir::RecordLitField], + ) -> BasicValueEnum { + let struct_ty = self.infer[type_expr].clone(); + let fields: Vec = fields + .iter() + .map(|field| self.gen_expr(field.expr).expect("expected a field value")) + .collect(); + + gen_named_struct_lit(self.db, struct_ty, &fields) + } + + /// Generates IR for a named tuple literal, e.g. `Foo(1.23, 4)` + fn gen_named_tuple_lit(&mut self, type_expr: ExprId, args: &[ExprId]) -> BasicValueEnum { + let struct_ty = self.infer[type_expr].clone(); + let args: Vec = args + .iter() + .map(|expr| self.gen_expr(*expr).expect("expected a field value")) + .collect(); + + gen_named_struct_lit(self.db, struct_ty, &args) + } + + /// Generates IR for a unit struct literal, e.g `Foo` + fn gen_unit_struct_lit(&self, type_expr: ExprId) -> BasicValueEnum { + let struct_ty = self.infer[type_expr].clone(); + gen_named_struct_lit(self.db, struct_ty, &[]) + } + /// Generates IR for the specified block expression. fn gen_block( &mut self, @@ -263,7 +308,7 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { fn gen_path_expr( &self, path: &Path, - _expr: ExprId, + expr: ExprId, resolver: &Resolver, ) -> inkwell::values::BasicValueEnum { let resolution = resolver @@ -282,6 +327,7 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { unreachable!("could not find the pattern.."); } } + Resolution::Def(hir::ModuleDef::Struct(_)) => self.gen_unit_struct_lit(expr), Resolution::Def(_) => panic!("no support for module definitions"), } } @@ -484,12 +530,7 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { } /// Generates IR for a function call. - fn gen_call(&mut self, callee: ExprId, args: &[ExprId]) -> CallSiteValue { - // Get the function value from the map - let function = self.infer[callee] - .as_function_def() - .expect("expected a function expression"); - + fn gen_call(&mut self, function: hir::Function, args: &[ExprId]) -> CallSiteValue { // Get all the arguments let args: Vec = args .iter() diff --git a/crates/mun_codegen/src/ir/dispatch_table.rs b/crates/mun_codegen/src/ir/dispatch_table.rs index a2eb4ea37..c23fb9568 100644 --- a/crates/mun_codegen/src/ir/dispatch_table.rs +++ b/crates/mun_codegen/src/ir/dispatch_table.rs @@ -119,18 +119,10 @@ impl<'a, D: IrDatabase> DispatchTableBuilder<'a, D> { // If this expression is a call, store it in the dispatch table if let Expr::Call { callee, .. } = expr { - self.ensure_table_ref(); - - // Get the function from the expression - let function = &infer[*callee] - .as_function_def() - .expect("expected a function expression"); - - // If the function is not yet contained in the table, add it - if !self.function_to_idx.contains_key(function) { - self.entries.push(*function); - self.function_to_idx - .insert(*function, self.function_to_idx.len()); + match infer[*callee].as_callable_def() { + Some(hir::CallableDef::Function(def)) => self.collect_fn_def(def), + Some(hir::CallableDef::Struct(_)) => (), + None => panic!("expected a callable expression"), } } @@ -138,6 +130,19 @@ impl<'a, D: IrDatabase> DispatchTableBuilder<'a, D> { expr.walk_child_exprs(|expr_id| self.collect_expr(expr_id, body, infer)) } + /// Collects function call expression from the given expression. + #[allow(clippy::map_entry)] + fn collect_fn_def(&mut self, function: hir::Function) { + self.ensure_table_ref(); + + // If the function is not yet contained in the table, add it + if !self.function_to_idx.contains_key(&function) { + self.entries.push(function); + self.function_to_idx + .insert(function, self.function_to_idx.len()); + } + } + /// Collect all the call expressions from the specified body with the given type inference /// result. pub fn collect_body(&mut self, body: &Body, infer: &InferenceResult) { diff --git a/crates/mun_codegen/src/snapshots/test__struct_test.snap b/crates/mun_codegen/src/snapshots/test__struct_test.snap index 8f7a45259..67b75900f 100644 --- a/crates/mun_codegen/src/snapshots/test__struct_test.snap +++ b/crates/mun_codegen/src/snapshots/test__struct_test.snap @@ -1,6 +1,6 @@ --- source: crates/mun_codegen/src/test.rs -expression: "struct Bar(float, int, bool, Foo);\nstruct Foo { a: int };\nstruct Baz;\nfn foo() {\n let a: Foo;\n let b: Bar;\n let c: Baz;\n}" +expression: "struct Bar(float, int, bool, Foo);\nstruct Foo { a: int };\nstruct Baz;\nfn foo() {\n let a: Foo = Foo { a: 5 };\n let b: Bar = Bar(1.23, 4, true, a);\n let c: Baz = Baz;\n}" --- ; ModuleID = 'main.mun' source_filename = "main.mun" @@ -14,6 +14,10 @@ body: %c = alloca %Baz %b = alloca %Bar %a = alloca %Foo + store %Foo { i64 5 }, %Foo* %a + %a1 = load %Foo, %Foo* %a + store %Bar { double 1.230000e+00, i64 4, i1 true, %Foo %a1 }, %Bar* %b + store %Baz zeroinitializer, %Baz* %c ret void } diff --git a/crates/mun_codegen/src/test.rs b/crates/mun_codegen/src/test.rs index 48897bb9a..0d1569524 100644 --- a/crates/mun_codegen/src/test.rs +++ b/crates/mun_codegen/src/test.rs @@ -370,9 +370,9 @@ fn struct_test() { struct Foo { a: int }; struct Baz; fn foo() { - let a: Foo; - let b: Bar; - let c: Baz; + let a: Foo = Foo { a: 5 }; + let b: Bar = Bar(1.23, 4, true, a); + let c: Baz = Baz; } "#, ) diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index 21413c9fc..f3a1045c4 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -45,7 +45,7 @@ pub use crate::{ display::HirDisplay, expr::{ resolver_for_expr, ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, ExprScopes, Literal, - LogicOp, Ordering, Pat, PatId, Statement, + LogicOp, Ordering, Pat, PatId, RecordLitField, Statement, }, ids::ItemLoc, input::{FileId, SourceRoot, SourceRootId}, diff --git a/crates/mun_hir/src/ty.rs b/crates/mun_hir/src/ty.rs index 989b7dc92..bb2d9ef0c 100644 --- a/crates/mun_hir/src/ty.rs +++ b/crates/mun_hir/src/ty.rs @@ -4,7 +4,7 @@ mod op; use crate::display::{HirDisplay, HirFormatter}; use crate::ty::infer::TypeVarId; -use crate::{Function, HirDatabase, Struct}; +use crate::{HirDatabase, Struct}; pub(crate) use infer::infer_query; pub use infer::InferenceResult; pub(crate) use lower::{callable_item_sig, fn_sig_for_fn, type_for_def, CallableDef, TypableDef}; @@ -102,12 +102,12 @@ impl Ty { } } - /// Returns the function definition for the given expression or `None` if the type does not - /// represent a function. - pub fn as_function_def(&self) -> Option { + /// Returns the callable definition for the given expression or `None` if the type does not + /// represent a callable. + pub fn as_callable_def(&self) -> Option { match self { Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::FnDef(CallableDef::Function(def)) => Some(def), + TypeCtor::FnDef(def) => Some(def), _ => None, }, _ => None, From 5d34607c8dde74bbec8a0343575d8da6841f99db Mon Sep 17 00:00:00 2001 From: Wodann Date: Mon, 16 Dec 2019 11:42:57 +0100 Subject: [PATCH 11/21] feat(struct): add struct to ABI and runtime dispatch table --- crates/mun_abi/c | 2 +- crates/mun_abi/src/autogen.rs | 90 ++++++++++++++- crates/mun_abi/src/autogen_impl.rs | 115 ++++++++++++++++--- crates/mun_codegen/src/code_gen.rs | 1 + crates/mun_codegen/src/code_gen/abi_types.rs | 15 +++ crates/mun_codegen/src/code_gen/symbols.rs | 109 ++++++++++++++---- crates/mun_codegen/src/ir/adt.rs | 5 +- crates/mun_codegen/src/ir/module.rs | 11 +- crates/mun_runtime/src/assembly.rs | 14 ++- crates/mun_runtime/src/lib.rs | 46 ++++++-- crates/mun_runtime/src/test.rs | 36 ++++++ 11 files changed, 387 insertions(+), 57 deletions(-) diff --git a/crates/mun_abi/c b/crates/mun_abi/c index 5db0081b8..420fc752f 160000 --- a/crates/mun_abi/c +++ b/crates/mun_abi/c @@ -1 +1 @@ -Subproject commit 5db0081b833c0b2b4d1a82206d56e55278c3af94 +Subproject commit 420fc752f88626cd21bc6ac232afb522402060b8 diff --git a/crates/mun_abi/src/autogen.rs b/crates/mun_abi/src/autogen.rs index 8a9e862b8..10d58b52e 100644 --- a/crates/mun_abi/src/autogen.rs +++ b/crates/mun_abi/src/autogen.rs @@ -206,6 +206,62 @@ fn bindgen_test_layout_FunctionInfo() { ) ); } +#[doc = " Represents a struct declaration."] +#[doc = ""] +#[doc = "
"] +#[repr(C)] +#[derive(Clone, Debug)] +pub struct StructInfo { + #[doc = " Struct name"] + pub name: *const ::std::os::raw::c_char, + #[doc = " Struct fields' information"] + pub field_types: *const TypeInfo, + #[doc = " Number of fields"] + pub num_fields: u16, +} +#[test] +fn bindgen_test_layout_StructInfo() { + assert_eq!( + ::std::mem::size_of::(), + 24usize, + concat!("Size of: ", stringify!(StructInfo)) + ); + assert_eq!( + ::std::mem::align_of::(), + 8usize, + concat!("Alignment of ", stringify!(StructInfo)) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).name as *const _ as usize }, + 0usize, + concat!( + "Offset of field: ", + stringify!(StructInfo), + "::", + stringify!(name) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).field_types as *const _ as usize }, + 8usize, + concat!( + "Offset of field: ", + stringify!(StructInfo), + "::", + stringify!(field_types) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).num_fields as *const _ as usize }, + 16usize, + concat!( + "Offset of field: ", + stringify!(StructInfo), + "::", + stringify!(num_fields) + ) + ); +} #[doc = " Represents a module declaration."] #[doc = ""] #[doc = "
"] @@ -218,12 +274,16 @@ pub struct ModuleInfo { pub functions: *const FunctionInfo, #[doc = " Number of module functions"] pub num_functions: u32, + #[doc = " Module structs"] + pub structs: *const StructInfo, + #[doc = " Number of module structs"] + pub num_structs: u32, } #[test] fn bindgen_test_layout_ModuleInfo() { assert_eq!( ::std::mem::size_of::(), - 24usize, + 40usize, concat!("Size of: ", stringify!(ModuleInfo)) ); assert_eq!( @@ -261,6 +321,26 @@ fn bindgen_test_layout_ModuleInfo() { stringify!(num_functions) ) ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).structs as *const _ as usize }, + 24usize, + concat!( + "Offset of field: ", + stringify!(ModuleInfo), + "::", + stringify!(structs) + ) + ); + assert_eq!( + unsafe { &(*(::std::ptr::null::())).num_structs as *const _ as usize }, + 32usize, + concat!( + "Offset of field: ", + stringify!(ModuleInfo), + "::", + stringify!(num_structs) + ) + ); } #[doc = " Represents a function dispatch table. This is used for runtime linking."] #[doc = ""] @@ -339,7 +419,7 @@ pub struct AssemblyInfo { fn bindgen_test_layout_AssemblyInfo() { assert_eq!( ::std::mem::size_of::(), - 64usize, + 80usize, concat!("Size of: ", stringify!(AssemblyInfo)) ); assert_eq!( @@ -359,7 +439,7 @@ fn bindgen_test_layout_AssemblyInfo() { ); assert_eq!( unsafe { &(*(::std::ptr::null::())).dispatch_table as *const _ as usize }, - 24usize, + 40usize, concat!( "Offset of field: ", stringify!(AssemblyInfo), @@ -369,7 +449,7 @@ fn bindgen_test_layout_AssemblyInfo() { ); assert_eq!( unsafe { &(*(::std::ptr::null::())).dependencies as *const _ as usize }, - 48usize, + 64usize, concat!( "Offset of field: ", stringify!(AssemblyInfo), @@ -379,7 +459,7 @@ fn bindgen_test_layout_AssemblyInfo() { ); assert_eq!( unsafe { &(*(::std::ptr::null::())).num_dependencies as *const _ as usize }, - 56usize, + 72usize, concat!( "Offset of field: ", stringify!(AssemblyInfo), diff --git a/crates/mun_abi/src/autogen_impl.rs b/crates/mun_abi/src/autogen_impl.rs index 62dee9125..5ce5100e8 100644 --- a/crates/mun_abi/src/autogen_impl.rs +++ b/crates/mun_abi/src/autogen_impl.rs @@ -53,6 +53,24 @@ unsafe impl Sync for FunctionSignature {} unsafe impl Send for FunctionInfo {} unsafe impl Sync for FunctionInfo {} +impl StructInfo { + /// Returns the struct's name. + pub fn name(&self) -> &str { + unsafe { CStr::from_ptr(self.name) } + .to_str() + .expect("Function name contains invalid UTF8") + } + + /// Returns the struct's fields' types. + pub fn field_types(&self) -> &[TypeInfo] { + if self.num_fields == 0 { + &[] + } else { + unsafe { slice::from_raw_parts(self.field_types, self.num_fields as usize) } + } + } +} + impl ModuleInfo { /// Returns the module's full path. pub fn path(&self) -> &str { @@ -82,6 +100,15 @@ impl ModuleInfo { unsafe { slice::from_raw_parts(self.functions, self.num_functions as usize) } } } + + /// Returns the module's structs. + pub fn structs(&self) -> &[StructInfo] { + if self.num_structs == 0 { + &[] + } else { + unsafe { slice::from_raw_parts(self.structs, self.num_structs as usize) } + } + } } unsafe impl Send for ModuleInfo {} @@ -202,7 +229,7 @@ mod tests { b: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], }; - const FAKE_TYPE_NAME: &'static str = "type-name"; + const FAKE_TYPE_NAME: &str = "type-name"; #[test] fn test_type_info_name() { @@ -235,7 +262,7 @@ mod tests { } } - const FAKE_FN_NAME: &'static str = "fn-name"; + const FAKE_FN_NAME: &str = "fn-name"; #[test] fn test_fn_signature_name() { @@ -296,35 +323,82 @@ mod tests { assert_eq!(fn_signature.return_type(), return_type); } - fn fake_module_info(path: &CStr, functions: &[FunctionInfo]) -> ModuleInfo { + fn fake_struct_info(name: &CStr, field_types: &[TypeInfo]) -> StructInfo { + StructInfo { + name: name.as_ptr(), + field_types: field_types.as_ptr(), + num_fields: field_types.len() as u16, + } + } + + const FAKE_STRUCT_NAME: &str = "struct-name"; + + #[test] + fn test_struct_info_name() { + let struct_name = CString::new(FAKE_STRUCT_NAME).expect("Invalid fake struct name."); + let struct_info = fake_struct_info(&struct_name, &[]); + + assert_eq!(struct_info.name(), FAKE_STRUCT_NAME); + } + + #[test] + fn test_struct_info_field_types_none() { + let field_types = &[]; + let struct_name = CString::new(FAKE_STRUCT_NAME).expect("Invalid fake struct name."); + let struct_info = fake_struct_info(&struct_name, field_types); + + assert_eq!(struct_info.field_types(), field_types); + } + + #[test] + fn test_struct_info_field_types_some() { + let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); + let type_info = fake_type_info(&type_name); + + let field_types = &[type_info]; + let struct_name = CString::new(FAKE_STRUCT_NAME).expect("Invalid fake struct name."); + let struct_info = fake_struct_info(&struct_name, field_types); + + assert_eq!(struct_info.field_types(), field_types); + } + + fn fake_module_info( + path: &CStr, + functions: &[FunctionInfo], + structs: &[StructInfo], + ) -> ModuleInfo { ModuleInfo { path: path.as_ptr(), functions: functions.as_ptr(), num_functions: functions.len() as u32, + structs: structs.as_ptr(), + num_structs: structs.len() as u32, } } - const FAKE_MODULE_PATH: &'static str = "path::to::module"; + const FAKE_MODULE_PATH: &str = "path::to::module"; #[test] fn test_module_info_path() { let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); - let module = fake_module_info(&module_path, &[]); + let module = fake_module_info(&module_path, &[], &[]); assert_eq!(module.path(), FAKE_MODULE_PATH); } #[test] - fn test_module_info_functions_none() { + fn test_module_info_types_none() { let functions = &[]; + let structs = &[]; let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); - let module = fake_module_info(&module_path, functions); + let module = fake_module_info(&module_path, functions, structs); assert_eq!(module.functions().len(), functions.len()); + assert_eq!(module.structs().len(), structs.len()); } #[test] - fn test_module_info_functions_some() { + fn test_module_info_types_some() { let type_name = CString::new(FAKE_TYPE_NAME).expect("Invalid fake type name."); let type_info = fake_type_info(&type_name); @@ -336,20 +410,31 @@ mod tests { signature: fn_signature, fn_ptr: ptr::null(), }; - let functions = &[fn_info]; + + let struct_name = CString::new(FAKE_STRUCT_NAME).expect("Invalid fake struct name"); + let struct_info = fake_struct_info(&struct_name, &[]); + let structs = &[struct_info]; + let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); - let module = fake_module_info(&module_path, functions); + let module = fake_module_info(&module_path, functions, structs); - let result = module.functions(); - assert_eq!(result.len(), functions.len()); - for (lhs, rhs) in result.iter().zip(functions.iter()) { + let result_functions = module.functions(); + assert_eq!(result_functions.len(), functions.len()); + for (lhs, rhs) in result_functions.iter().zip(functions.iter()) { assert_eq!(lhs.fn_ptr, rhs.fn_ptr); assert_eq!(lhs.signature.name(), rhs.signature.name()); assert_eq!(lhs.signature.arg_types(), rhs.signature.arg_types()); assert_eq!(lhs.signature.return_type(), rhs.signature.return_type()); assert_eq!(lhs.signature.privacy(), rhs.signature.privacy()); } + + let result_structs = module.structs(); + assert_eq!(result_structs.len(), structs.len()); + for (lhs, rhs) in result_structs.iter().zip(structs.iter()) { + assert_eq!(lhs.name(), rhs.name()); + assert_eq!(lhs.field_types(), rhs.field_types()); + } } fn fake_dispatch_table( @@ -555,12 +640,12 @@ mod tests { } } - const FAKE_DEPENDENCY: &'static str = "path/to/dependency.dylib"; + const FAKE_DEPENDENCY: &str = "path/to/dependency.dylib"; #[test] fn test_assembly_info_dependencies() { let module_path = CString::new(FAKE_MODULE_PATH).expect("Invalid fake module path."); - let module = fake_module_info(&module_path, &[]); + let module = fake_module_info(&module_path, &[], &[]); let dispatch_table = fake_dispatch_table(&[], &mut []); diff --git a/crates/mun_codegen/src/code_gen.rs b/crates/mun_codegen/src/code_gen.rs index 891b86787..8ec606896 100644 --- a/crates/mun_codegen/src/code_gen.rs +++ b/crates/mun_codegen/src/code_gen.rs @@ -61,6 +61,7 @@ pub fn write_module_shared_object( symbols::gen_reflection_ir( db, &module.functions, + &module.structs, &module.dispatch_table, &assembly_module, ); diff --git a/crates/mun_codegen/src/code_gen/abi_types.rs b/crates/mun_codegen/src/code_gen/abi_types.rs index 6dd9f6411..0ab97d407 100644 --- a/crates/mun_codegen/src/code_gen/abi_types.rs +++ b/crates/mun_codegen/src/code_gen/abi_types.rs @@ -8,6 +8,7 @@ pub(super) struct AbiTypes { pub type_info_type: StructType, pub function_signature_type: StructType, pub function_info_type: StructType, + pub struct_info_type: StructType, pub module_info_type: StructType, pub dispatch_table_type: StructType, pub assembly_info_type: StructType, @@ -60,6 +61,17 @@ pub(super) fn gen_abi_types(context: ContextRef) -> AbiTypes { false, ); + // Construct the `MunStructInfo` struct + let struct_info_type = context.opaque_struct_type("struct.MunStructInfo"); + struct_info_type.set_body( + &[ + str_type.into(), // name + type_info_type.ptr_type(AddressSpace::Const).into(), // field_types + context.i16_type().into(), // num_fields + ], + false, + ); + // Construct the `MunModuleInfo` struct let module_info_type = context.opaque_struct_type("struct.MunModuleInfo"); module_info_type.set_body( @@ -67,6 +79,8 @@ pub(super) fn gen_abi_types(context: ContextRef) -> AbiTypes { str_type.into(), // path function_info_type.ptr_type(AddressSpace::Const).into(), // functions context.i32_type().into(), // num_functions + struct_info_type.ptr_type(AddressSpace::Const).into(), // structs + context.i32_type().into(), // num_structs ], false, ); @@ -105,6 +119,7 @@ pub(super) fn gen_abi_types(context: ContextRef) -> AbiTypes { type_info_type, function_signature_type, function_info_type, + struct_info_type, module_info_type, dispatch_table_type, assembly_info_type, diff --git a/crates/mun_codegen/src/code_gen/symbols.rs b/crates/mun_codegen/src/code_gen/symbols.rs index fb6195a24..3f73b3142 100644 --- a/crates/mun_codegen/src/code_gen/symbols.rs +++ b/crates/mun_codegen/src/code_gen/symbols.rs @@ -3,12 +3,12 @@ use crate::ir::dispatch_table::DispatchTable; use crate::ir::function; use crate::values::{BasicValue, GlobalValue}; use crate::IrDatabase; -use hir::{Ty, TypeCtor}; -use inkwell::attributes::Attribute; -use inkwell::values::{IntValue, PointerValue, UnnamedAddress}; +use hir::{CallableDef, Ty, TypeCtor}; use inkwell::{ + attributes::Attribute, module::{Linkage, Module}, - values::{FunctionValue, StructValue}, + types::{AnyTypeEnum, StructType}, + values::{FunctionValue, IntValue, PointerValue, StructValue, UnnamedAddress}, AddressSpace, }; use std::collections::HashMap; @@ -43,12 +43,15 @@ impl TypeInfo { } } -pub fn type_info_query(_db: &impl IrDatabase, ty: Ty) -> TypeInfo { +pub fn type_info_query(db: &impl IrDatabase, ty: Ty) -> TypeInfo { match ty { Ty::Apply(ctor) => match ctor.ctor { TypeCtor::Float => TypeInfo::from_name("@core::float"), TypeCtor::Int => TypeInfo::from_name("@core::int"), TypeCtor::Bool => TypeInfo::from_name("@core::bool"), + TypeCtor::Struct(s) | TypeCtor::FnDef(CallableDef::Struct(s)) => { + TypeInfo::from_name(s.name(db).to_string()) + } _ => unreachable!("{:?} unhandled", ctor), }, _ => unreachable!(), @@ -91,9 +94,8 @@ fn gen_signature_from_function( _ => 1, }; let ret_type_ir = gen_signature_return_type(db, module, types, function); - let params_type_ir = gen_signature_argument_types(db, module, types, function); + let (params_type_ir, num_params) = gen_signature_argument_types(db, module, types, function); - let body = function.body(db); types.function_signature_type.const_named_struct(&[ name_str.into(), params_type_ir.into(), @@ -101,7 +103,7 @@ fn gen_signature_from_function( module .get_context() .i16_type() - .const_int(body.params().len() as u64, false) + .const_int(num_params as u64, false) .into(), module.get_context().i8_type().const_int(0, false).into(), ]) @@ -114,23 +116,41 @@ fn gen_signature_argument_types( module: &Module, types: &AbiTypes, function: hir::Function, -) -> PointerValue { +) -> (PointerValue, usize) { let body = function.body(db); let infer = function.infer(db); - if body.params().is_empty() { - types - .type_info_type - .ptr_type(AddressSpace::Const) - .const_null() + + let hir_types = body.params().iter().map(|(p, _)| infer[*p].clone()); + + gen_type_info_array(db, module, types, hir_types) +} + +/// Generates +fn gen_type_info_array( + db: &D, + module: &Module, + types: &AbiTypes, + hir_types: impl Iterator, +) -> (PointerValue, usize) { + let mut hir_types = hir_types.peekable(); + if hir_types.peek().is_none() { + ( + types + .type_info_type + .ptr_type(AddressSpace::Const) + .const_null(), + 0, + ) } else { - let params_type_array_ir = types.type_info_type.const_array( - &body - .params() - .iter() - .map(|(p, _)| type_info_ir(&db.type_info(infer[*p].clone()), &module)) - .collect::>(), - ); - gen_global(module, ¶ms_type_array_ir, "").as_pointer_value() + let type_infos = hir_types + .map(|ty| type_info_ir(&db.type_info(ty), &module)) + .collect::>(); + + let type_array_ir = types.type_info_type.const_array(&type_infos); + ( + gen_global(module, &type_array_ir, "").as_pointer_value(), + type_infos.len(), + ) } } @@ -185,6 +205,42 @@ fn gen_function_info_array<'a, D: IrDatabase>( gen_global(module, &function_infos, "fn.get_info.functions") } +/// Construct a global that holds a reference to all structs. e.g.: +/// MunStructInfo[] info = { ... } +fn gen_struct_info_array<'a, D: IrDatabase>( + db: &D, + types: &AbiTypes, + module: &Module, + structs: impl Iterator, +) -> GlobalValue { + let struct_infos: Vec = structs + .map(|(s, _)| { + if let AnyTypeEnum::StructType(_) = db.type_ir(s.ty(db)) { + let name_str = intern_string(&module, &s.name(db).to_string()); + + let fields = s.fields(db); + let field_types = fields.iter().map(|field| field.ty(db)); + let (fields, num_fields) = gen_type_info_array(db, module, types, field_types); + + types.struct_info_type.const_named_struct(&[ + name_str.into(), + fields.into(), + module + .get_context() + .i16_type() + .const_int(num_fields as u64, false) + .into(), + ]) + } else { + unreachable!(); + } + }) + .collect(); + + let struct_infos = types.struct_info_type.const_array(&struct_infos); + gen_global(module, &struct_infos, "fn.get_info.structs") +} + /// Construct a global from the specified value fn gen_global(module: &Module, value: &dyn BasicValue, name: &str) -> GlobalValue { let global = module.add_global(value.as_basic_value_enum().get_type(), None, name); @@ -255,6 +311,7 @@ fn gen_dispatch_table( pub(super) fn gen_reflection_ir( db: &impl IrDatabase, function_map: &HashMap, + struct_map: &HashMap, dispatch_table: &DispatchTable, module: &Module, ) { @@ -272,6 +329,14 @@ pub(super) fn gen_reflection_ir( .i32_type() .const_int(function_map.len() as u64, false) .into(), + gen_struct_info_array(db, &abi_types, module, struct_map.iter()) + .as_pointer_value() + .into(), + module + .get_context() + .i32_type() + .const_int(struct_map.len() as u64, false) + .into(), ]); // Construct the dispatch table struct diff --git a/crates/mun_codegen/src/ir/adt.rs b/crates/mun_codegen/src/ir/adt.rs index 9b1cfb7ea..b477bb79c 100644 --- a/crates/mun_codegen/src/ir/adt.rs +++ b/crates/mun_codegen/src/ir/adt.rs @@ -1,10 +1,10 @@ //use crate::ir::module::Types; use crate::ir::try_convert_any_to_basic; use crate::IrDatabase; -use inkwell::types::{AnyTypeEnum, BasicTypeEnum}; +use inkwell::types::{AnyTypeEnum, BasicTypeEnum, StructType}; use inkwell::values::BasicValueEnum; -pub(super) fn gen_struct_decl(db: &impl IrDatabase, s: hir::Struct) { +pub(super) fn gen_struct_decl(db: &impl IrDatabase, s: hir::Struct) -> StructType { if let AnyTypeEnum::StructType(struct_type) = db.type_ir(s.ty(db)) { if struct_type.is_opaque() { let field_types: Vec = s @@ -19,6 +19,7 @@ pub(super) fn gen_struct_decl(db: &impl IrDatabase, s: hir::Struct) { struct_type.set_body(&field_types, false); } + struct_type } else { unreachable!() } diff --git a/crates/mun_codegen/src/ir/module.rs b/crates/mun_codegen/src/ir/module.rs index 4db54b3b6..37f98d869 100644 --- a/crates/mun_codegen/src/ir/module.rs +++ b/crates/mun_codegen/src/ir/module.rs @@ -2,7 +2,7 @@ use crate::ir::dispatch_table::{DispatchTable, DispatchTableBuilder}; use crate::ir::{adt, function}; use crate::IrDatabase; use hir::{FileId, ModuleDef}; -use inkwell::{module::Module, values::FunctionValue}; +use inkwell::{module::Module, types::StructType, values::FunctionValue}; use std::collections::HashMap; use std::sync::Arc; @@ -17,6 +17,9 @@ pub struct ModuleIR { /// A mapping from HIR functions to LLVM IR values pub functions: HashMap, + /// A mapping from HIR structs to LLVM IR types + pub structs: HashMap, + /// The dispatch table pub dispatch_table: DispatchTable, } @@ -28,9 +31,12 @@ pub(crate) fn ir_query(db: &impl IrDatabase, file_id: FileId) -> Arc { .create_module(db.file_relative_path(file_id).as_str()); // Generate all type definitions + let mut structs = HashMap::new(); for def in db.module_data(file_id).definitions() { match def { - ModuleDef::Struct(s) => adt::gen_struct_decl(db, *s), + ModuleDef::Struct(s) => { + structs.insert(*s, adt::gen_struct_decl(db, *s)); + } ModuleDef::BuiltinType(_) | ModuleDef::Function(_) => (), } } @@ -77,6 +83,7 @@ pub(crate) fn ir_query(db: &impl IrDatabase, file_id: FileId) -> Arc { file_id, llvm_module, functions, + structs, dispatch_table, }) } diff --git a/crates/mun_runtime/src/assembly.rs b/crates/mun_runtime/src/assembly.rs index 2554786e4..b7ee2499a 100644 --- a/crates/mun_runtime/src/assembly.rs +++ b/crates/mun_runtime/src/assembly.rs @@ -32,7 +32,11 @@ impl Assembly { let info = get_info(); for function in info.symbols.functions() { - runtime_dispatch_table.insert(function.signature.name(), function.clone()); + runtime_dispatch_table.insert_fn(function.signature.name(), function.clone()); + } + + for s in info.symbols.structs() { + runtime_dispatch_table.insert_struct(s.name(), s.clone()); } Ok(Assembly { @@ -46,7 +50,7 @@ impl Assembly { pub fn link(&mut self, runtime_dispatch_table: &DispatchTable) -> Result<(), Error> { for (dispatch_ptr, fn_signature) in self.info.dispatch_table.iter_mut() { let fn_ptr = runtime_dispatch_table - .get(fn_signature.name()) + .get_fn(fn_signature.name()) .map(|f| f.fn_ptr) .ok_or_else(|| { io::Error::new( @@ -72,7 +76,11 @@ impl Assembly { // let library_path = library_path.canonicalize()?; for function in self.info.symbols.functions() { - runtime_dispatch_table.remove(function.signature.name()); + runtime_dispatch_table.remove_fn(function.signature.name()); + } + + for s in self.info.symbols.structs() { + runtime_dispatch_table.remove_struct(s.name()); } // Drop the old library, as some operating systems don't allow editing of in-use shared diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index 10a3645ab..8b860f1a2 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -18,7 +18,7 @@ use std::sync::mpsc::{channel, Receiver}; use std::time::Duration; use failure::Error; -use mun_abi::{FunctionInfo, Reflection}; +use mun_abi::{FunctionInfo, Reflection, StructInfo}; use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher}; pub use crate::assembly::Assembly; @@ -60,15 +60,16 @@ impl RuntimeBuilder { } } -/// A runtime dispatch table that maps full function paths to function information. +/// A runtime dispatch table that maps full paths to function and struct information. #[derive(Default)] pub struct DispatchTable { functions: HashMap, + structs: HashMap, } impl DispatchTable { /// Retrieves the [`FunctionInfo`] corresponding to `fn_path`, if it exists. - pub fn get(&self, fn_path: &str) -> Option<&FunctionInfo> { + pub fn get_fn(&self, fn_path: &str) -> Option<&FunctionInfo> { self.functions.get(fn_path) } @@ -76,13 +77,39 @@ impl DispatchTable { /// /// If the dispatch table already contained this `fn_path`, the value is updated, and the old /// value is returned. - pub fn insert(&mut self, fn_path: &str, fn_info: FunctionInfo) -> Option { + pub fn insert_fn( + &mut self, + fn_path: T, + fn_info: FunctionInfo, + ) -> Option { self.functions.insert(fn_path.to_string(), fn_info) } /// Removes and returns the `fn_info` corresponding to `fn_path`, if it exists. - pub fn remove(&mut self, fn_path: &str) -> Option { - self.functions.remove(fn_path) + pub fn remove_fn>(&mut self, fn_path: T) -> Option { + self.functions.remove(fn_path.as_ref()) + } + + /// Retrieves the [`StructInfo`] corresponding to `struct_path`, if it exists. + pub fn get_struct>(&self, struct_path: T) -> Option<&StructInfo> { + self.structs.get(struct_path.as_ref()) + } + + /// Inserts the `struct_info` for `struct_path` into the dispatch table. + /// + /// If the dispatch table already contained this `struct_path`, the value is updated, and the + /// old value is returned. + pub fn insert_struct( + &mut self, + struct_path: T, + struct_info: StructInfo, + ) -> Option { + self.structs.insert(struct_path.to_string(), struct_info) + } + + /// Removes and returns the `struct_info` corresponding to `struct_path`, if it exists. + pub fn remove_struct>(&mut self, struct_path: T) -> Option { + self.structs.remove(struct_path.as_ref()) } } @@ -139,7 +166,12 @@ impl Runtime { /// Retrieves the function information corresponding to `function_name`, if available. pub fn get_function_info(&self, function_name: &str) -> Option<&FunctionInfo> { - self.dispatch_table.get(function_name) + self.dispatch_table.get_fn(function_name) + } + + /// Retrieves the struct information corresponding to `struct_name`, if available. + pub fn get_struct_info(&self, struct_name: &str) -> Option<&StructInfo> { + self.dispatch_table.get_struct(struct_name) } /// Updates the state of the runtime. This includes checking for file changes, and reloading diff --git a/crates/mun_runtime/src/test.rs b/crates/mun_runtime/src/test.rs index bbe6ae4f9..c661448c5 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -334,3 +334,39 @@ fn compiler_valid_utf8() { assert_invoke_eq!(bool, false, driver, "foo", 10i64); } + +#[test] +fn struct_info() { + use mun_abi::{Guid, Reflection}; + + let driver = TestDriver::new( + r" + struct Foo; + struct Bar(bool); + struct Baz { a: int, b: float }; + ", + ); + + assert_struct_info_eq(&driver, "Foo", &[]); + assert_struct_info_eq(&driver, "Bar", &[bool::type_guid()]); + assert_struct_info_eq(&driver, "Baz", &[i64::type_guid(), f64::type_guid()]); + + fn assert_struct_info_eq( + driver: &TestDriver, + expected_name: &str, + expected_field_types: &[Guid], + ) { + let struct_info = driver.runtime.get_struct_info(expected_name).unwrap(); + + assert_eq!(struct_info.name(), expected_name); + if expected_field_types.is_empty() { + assert_eq!(struct_info.field_types(), &[]); + } else { + let field_types = struct_info.field_types(); + assert_eq!(field_types.len(), expected_field_types.len()); + for (idx, field_type) in expected_field_types.iter().enumerate() { + assert_eq!(field_types[idx].guid, *field_type); + } + } + } +} From 1069b9ef696fafa33202a125d1277b67f1045c35 Mon Sep 17 00:00:00 2001 From: Wodann Date: Tue, 17 Dec 2019 23:21:26 +0100 Subject: [PATCH 12/21] feat(struct): add IR generation for fields --- crates/mun_codegen/src/ir/body.rs | 37 ++++++++++++- .../src/snapshots/test__field_expr.snap | 54 +++++++++++++++++++ crates/mun_codegen/src/test.rs | 38 +++++++++++++ crates/mun_hir/src/code_model.rs | 4 ++ crates/mun_hir/src/lib.rs | 3 +- crates/mun_hir/src/ty.rs | 10 ++++ crates/mun_hir/src/ty/infer.rs | 18 ++++--- crates/mun_hir/src/ty/infer/place_expr.rs | 1 + 8 files changed, 155 insertions(+), 10 deletions(-) create mode 100644 crates/mun_codegen/src/snapshots/test__field_expr.snap diff --git a/crates/mun_codegen/src/ir/body.rs b/crates/mun_codegen/src/ir/body.rs index dff643b15..c98f294c7 100644 --- a/crates/mun_codegen/src/ir/body.rs +++ b/crates/mun_codegen/src/ir/body.rs @@ -3,8 +3,8 @@ use crate::{ IrDatabase, }; use hir::{ - ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, HirDisplay, InferenceResult, Literal, Ordering, - Pat, PatId, Path, Resolution, Resolver, Statement, TypeCtor, + ArenaId, ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, HirDisplay, InferenceResult, Literal, + Name, Ordering, Pat, PatId, Path, Resolution, Resolver, Statement, TypeCtor, }; use inkwell::{ builder::Builder, @@ -169,6 +169,13 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { Expr::Loop { body } => self.gen_loop(expr, *body), Expr::While { condition, body } => self.gen_while(expr, *condition, *body), Expr::Break { expr: break_expr } => self.gen_break(expr, *break_expr), + Expr::Field { + expr: receiver_expr, + name, + } => { + let ptr = self.gen_field(expr, *receiver_expr, name); + Some(self.builder.build_load(ptr, &name.to_string())) + } _ => unimplemented!("unimplemented expr type {:?}", &body[expr]), } } @@ -520,6 +527,10 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { let resolver = hir::resolver_for_expr(self.body.clone(), self.db, expr); self.gen_path_place_expr(p, expr, &resolver) } + Expr::Field { + expr: receiver_expr, + name, + } => self.gen_field(expr, *receiver_expr, name), _ => unreachable!("invalid place expression"), } } @@ -752,4 +763,26 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { None } } + + fn gen_field(&mut self, _expr: ExprId, receiver_expr: ExprId, name: &Name) -> PointerValue { + let receiver_ty = &self.infer[receiver_expr] + .as_struct() + .expect("expected a struct"); + + let field_idx = receiver_ty + .field(self.db, name) + .expect("expected a struct field") + .id() + .into_raw() + .into(); + + let receiver_ptr = self.gen_place_expr(receiver_expr); + unsafe { + self.builder.build_struct_gep( + receiver_ptr, + field_idx, + &format!("{}.{}", receiver_ty.name(self.db), name), + ) + } + } } diff --git a/crates/mun_codegen/src/snapshots/test__field_expr.snap b/crates/mun_codegen/src/snapshots/test__field_expr.snap new file mode 100644 index 000000000..8668bcf45 --- /dev/null +++ b/crates/mun_codegen/src/snapshots/test__field_expr.snap @@ -0,0 +1,54 @@ +--- +source: crates/mun_codegen/src/test.rs +expression: "struct Bar(float, Foo);\nstruct Foo { a: int };\n\nfn bar_0(bar: Bar): float {\n bar.0\n}\n\nfn bar_1(bar: Bar): Foo {\n bar.1\n}\n\nfn bar_1_a(bar: Bar): int {\n bar.1.a\n}\n\nfn foo_a(foo: Foo): int {\n foo.a\n}\n\nfn bar_1_foo_a(bar: Bar): int {\n foo_a(bar_1(bar))\n}\n\nfn main(): int {\n let a: Foo = Foo { a: 5 };\n let b: Bar = Bar(1.23, a);\n let aa_lhs = a.a + 2;\n let aa_rhs = 2 + a.a;\n aa_lhs + aa_rhs\n}" +--- +; ModuleID = 'main.mun' +source_filename = "main.mun" + +%DispatchTable = type { i64 (%Foo)*, %Foo (%Bar)* } +%Foo = type { i64 } +%Bar = type { double, %Foo } + +@dispatchTable = global %DispatchTable { i64 (%Foo)* @foo_a, %Foo (%Bar)* @bar_1 } + +define double @bar_0(%Bar) { +body: + %.fca.0.extract = extractvalue %Bar %0, 0 + ret double %.fca.0.extract +} + +define %Foo @bar_1(%Bar) { +body: + %.fca.1.0.extract = extractvalue %Bar %0, 1, 0 + %"1.fca.0.insert" = insertvalue %Foo undef, i64 %.fca.1.0.extract, 0 + ret %Foo %"1.fca.0.insert" +} + +define i64 @bar_1_a(%Bar) { +body: + %.fca.1.0.extract = extractvalue %Bar %0, 1, 0 + ret i64 %.fca.1.0.extract +} + +define i64 @foo_a(%Foo) { +body: + %.fca.0.extract = extractvalue %Foo %0, 0 + ret i64 %.fca.0.extract +} + +define i64 @bar_1_foo_a(%Bar) { +body: + %.fca.0.extract = extractvalue %Bar %0, 0 + %.fca.1.0.extract = extractvalue %Bar %0, 1, 0 + %bar_1_ptr = load %Foo (%Bar)*, %Foo (%Bar)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 1) + %bar_1 = call %Foo %bar_1_ptr(%Bar %0) + %foo_a_ptr = load i64 (%Foo)*, i64 (%Foo)** getelementptr inbounds (%DispatchTable, %DispatchTable* @dispatchTable, i32 0, i32 0) + %foo_a = call i64 %foo_a_ptr(%Foo %bar_1) + ret i64 %foo_a +} + +define i64 @main() { +body: + ret i64 14 +} + diff --git a/crates/mun_codegen/src/test.rs b/crates/mun_codegen/src/test.rs index 0d1569524..b05490cdd 100644 --- a/crates/mun_codegen/src/test.rs +++ b/crates/mun_codegen/src/test.rs @@ -378,6 +378,44 @@ fn struct_test() { ) } +#[test] +fn field_expr() { + test_snapshot( + r#" + struct Bar(float, Foo); + struct Foo { a: int }; + + fn bar_0(bar: Bar): float { + bar.0 + } + + fn bar_1(bar: Bar): Foo { + bar.1 + } + + fn bar_1_a(bar: Bar): int { + bar.1.a + } + + fn foo_a(foo: Foo): int { + foo.a + } + + fn bar_1_foo_a(bar: Bar): int { + foo_a(bar_1(bar)) + } + + fn main(): int { + let a: Foo = Foo { a: 5 }; + let b: Bar = Bar(1.23, a); + let aa_lhs = a.a + 2; + let aa_rhs = 2 + a.a; + aa_lhs + aa_rhs + } + "#, + ) +} + fn test_snapshot(text: &str) { test_snapshot_with_optimization(text, OptimizationLevel::Default); } diff --git a/crates/mun_hir/src/code_model.rs b/crates/mun_hir/src/code_model.rs index c6ddb878b..b54af1b0b 100644 --- a/crates/mun_hir/src/code_model.rs +++ b/crates/mun_hir/src/code_model.rs @@ -376,6 +376,10 @@ impl StructField { pub fn name(self, db: &impl HirDatabase) -> Name { self.parent.data(db).fields[self.id].name.clone() } + + pub fn id(self) -> StructFieldId { + self.id + } } impl Struct { diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index f3a1045c4..59d505143 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -38,6 +38,7 @@ pub use salsa; pub use relative_path::{RelativePath, RelativePathBuf}; pub use crate::{ + arena::{ArenaId, RawId}, db::{ DefDatabase, DefDatabaseStorage, HirDatabase, HirDatabaseStorage, SourceDatabase, SourceDatabaseStorage, @@ -58,7 +59,7 @@ pub use crate::{ }; use crate::{ - arena::{Arena, ArenaId, RawId}, + arena::Arena, name::AsName, source_id::{AstIdMap, FileAstId}, }; diff --git a/crates/mun_hir/src/ty.rs b/crates/mun_hir/src/ty.rs index bb2d9ef0c..af38b12be 100644 --- a/crates/mun_hir/src/ty.rs +++ b/crates/mun_hir/src/ty.rs @@ -114,6 +114,16 @@ impl Ty { } } + pub fn as_struct(&self) -> Option { + match self { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::FnDef(CallableDef::Struct(s)) | TypeCtor::Struct(s) => Some(s), + _ => None, + }, + _ => None, + } + } + pub fn callable_sig(&self, db: &impl HirDatabase) -> Option { match self { Ty::Apply(a_ty) => match a_ty.ctor { diff --git a/crates/mun_hir/src/ty/infer.rs b/crates/mun_hir/src/ty/infer.rs index ebb72d3ca..a1b02e650 100644 --- a/crates/mun_hir/src/ty/infer.rs +++ b/crates/mun_hir/src/ty/infer.rs @@ -365,16 +365,20 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { Expr::Field { expr, name } => { let receiver_ty = self.infer_expr(*expr, &Expectation::none()); match receiver_ty { - Ty::Apply(a_ty) => { - if let TypeCtor::Struct(s) = a_ty.ctor { - s.field(self.db, name).map(|field| field.ty(self.db)) - } else { - None + ty_app!(TypeCtor::Struct(s)) => { + match s.field(self.db, name).map(|field| field.ty(self.db)) { + Some(field_ty) => field_ty, + None => { + // TODO: Unknown struct field + Ty::Unknown + } } } - _ => None, + _ => { + // TODO: Expected receiver to be struct type + Ty::Unknown + } } - .unwrap_or(Ty::Unknown) } Expr::UnaryOp { .. } => Ty::Unknown, // Expr::UnaryOp { expr: _, op: _ } => {} diff --git a/crates/mun_hir/src/ty/infer/place_expr.rs b/crates/mun_hir/src/ty/infer/place_expr.rs index 6358d9a73..e8039f9ea 100644 --- a/crates/mun_hir/src/ty/infer/place_expr.rs +++ b/crates/mun_hir/src/ty/infer/place_expr.rs @@ -10,6 +10,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { let body = Arc::clone(&self.body); // avoid borrow checker problem match &body[expr] { Expr::Path(p) => self.check_place_path(resolver, p), + Expr::Field { .. } => false, // TODO: add support for assigning structs _ => false, } } From d6dda233e1990cf2ed9552d731f67b06eb1cb289 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 10 Jan 2020 17:01:07 +0100 Subject: [PATCH 13/21] misc(mun_hir): expression validator --- crates/mun_hir/src/code_model.rs | 14 ++--- crates/mun_hir/src/db.rs | 2 +- crates/mun_hir/src/expr.rs | 1 + crates/mun_hir/src/expr/scope.rs | 2 +- crates/mun_hir/src/expr/validator.rs | 28 +++++++++ .../expr/validator/uninitialized_access.rs | 57 +++++++++++++++++++ 6 files changed, 95 insertions(+), 9 deletions(-) create mode 100644 crates/mun_hir/src/expr/validator.rs create mode 100644 crates/mun_hir/src/expr/validator/uninitialized_access.rs diff --git a/crates/mun_hir/src/code_model.rs b/crates/mun_hir/src/code_model.rs index b54af1b0b..5c628d177 100644 --- a/crates/mun_hir/src/code_model.rs +++ b/crates/mun_hir/src/code_model.rs @@ -2,10 +2,13 @@ pub(crate) mod src; use self::src::HasSource; use crate::adt::{StructData, StructFieldId}; +use crate::code_model::diagnostics::ModuleDefinitionDiagnostic; use crate::diagnostics::DiagnosticSink; +use crate::expr::validator::ExprValidator; use crate::expr::{Body, BodySourceMap}; use crate::ids::AstItemDef; use crate::ids::LocationCtx; +use crate::name::*; use crate::name_resolution::Namespace; use crate::raw::{DefKind, RawFileItem}; use crate::resolve::{Resolution, Resolver}; @@ -157,7 +160,7 @@ impl DefWithBody { } pub fn body(self, db: &impl HirDatabase) -> Arc { - db.body_hir(self) + db.body(self) } pub fn body_source_map(self, db: &impl HirDatabase) -> Arc { @@ -307,7 +310,7 @@ impl Function { } pub fn body(self, db: &impl HirDatabase) -> Arc { - db.body_hir(self.into()) + db.body(self.into()) } pub fn ty(self, db: &impl HirDatabase) -> Ty { @@ -330,8 +333,8 @@ impl Function { pub fn diagnostics(self, db: &impl HirDatabase, sink: &mut DiagnosticSink) { let infer = self.infer(db); infer.add_diagnostics(db, self, sink); - // let mut validator = ExprValidator::new(self, infer, sink); - // validator.validate_body(db); + let mut validator = ExprValidator::new(self, db, sink); + validator.validate_body(); } } @@ -342,9 +345,6 @@ pub enum BuiltinType { Boolean, } -use crate::code_model::diagnostics::ModuleDefinitionDiagnostic; -use crate::name::*; - impl BuiltinType { #[rustfmt::skip] pub(crate) const ALL: &'static [(Name, BuiltinType)] = &[ diff --git a/crates/mun_hir/src/db.rs b/crates/mun_hir/src/db.rs index bbea16214..3f5ee7fb0 100644 --- a/crates/mun_hir/src/db.rs +++ b/crates/mun_hir/src/db.rs @@ -101,7 +101,7 @@ pub trait HirDatabase: DefDatabase { fn module_data(&self, file_id: FileId) -> Arc; #[salsa::invoke(crate::expr::body_hir_query)] - fn body_hir(&self, def: DefWithBody) -> Arc; + fn body(&self, def: DefWithBody) -> Arc; #[salsa::invoke(crate::expr::body_with_source_map_query)] fn body_with_source_map( diff --git a/crates/mun_hir/src/expr.rs b/crates/mun_hir/src/expr.rs index 4cab6e97f..6280aba0e 100644 --- a/crates/mun_hir/src/expr.rs +++ b/crates/mun_hir/src/expr.rs @@ -22,6 +22,7 @@ use crate::resolve::Resolver; use std::mem; pub(crate) mod scope; +pub(crate) mod validator; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ExprId(RawId); diff --git a/crates/mun_hir/src/expr/scope.rs b/crates/mun_hir/src/expr/scope.rs index 4ad6e04f5..be9feffe7 100644 --- a/crates/mun_hir/src/expr/scope.rs +++ b/crates/mun_hir/src/expr/scope.rs @@ -43,7 +43,7 @@ pub(crate) struct ScopeData { impl ExprScopes { pub(crate) fn expr_scopes_query(db: &impl HirDatabase, def: DefWithBody) -> Arc { - let body = db.body_hir(def); + let body = db.body(def); let res = ExprScopes::new(body); Arc::new(res) } diff --git a/crates/mun_hir/src/expr/validator.rs b/crates/mun_hir/src/expr/validator.rs new file mode 100644 index 000000000..f1bdd7bb0 --- /dev/null +++ b/crates/mun_hir/src/expr/validator.rs @@ -0,0 +1,28 @@ +use crate::{diagnostics::DiagnosticSink, Body, Function, HirDatabase, InferenceResult}; +use std::sync::Arc; + +mod uninitialized_access; + +pub struct ExprValidator<'a, 'b: 'a, 'd, DB: HirDatabase> { + func: Function, + infer: Arc, + body: Arc, + sink: &'a mut DiagnosticSink<'b>, + db: &'d DB, +} + +impl<'a, 'b, 'd, DB: HirDatabase> ExprValidator<'a, 'b, 'd, DB> { + pub fn new(func: Function, db: &'d DB, sink: &'a mut DiagnosticSink<'b>) -> Self { + ExprValidator { + func, + sink, + db, + infer: db.infer(func.into()), + body: db.body(func.into()), + } + } + + pub fn validate_body(&mut self) { + self.validate_uninitialized_access(); + } +} diff --git a/crates/mun_hir/src/expr/validator/uninitialized_access.rs b/crates/mun_hir/src/expr/validator/uninitialized_access.rs new file mode 100644 index 000000000..d92266507 --- /dev/null +++ b/crates/mun_hir/src/expr/validator/uninitialized_access.rs @@ -0,0 +1,57 @@ +use super::ExprValidator; +use crate::HirDatabase; +//use crate::{HirDatabase, ExprId, PatId, Expr, Path}; +//use std::collections::HashSet; + +//enum ExprSide { +// left, +// Expr +//} + +impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { + /// Validates that all binding access has previously been initialized. + pub(super) fn validate_uninitialized_access(&mut self) { + // let mut initialized_patterns = HashSet::new(); + // + // // Add all parameter patterns to the set of initialized patterns (they must have been + // // initialized) + // for (pat, _) in self.body.params.iter() { + // initialized_patterns.insert(*pat) + // } + // + // self.validate_expr_access(&mut initialized_patterns, self.body.body_expr); + } + + // /// Validates that the specified expr does not access unitialized bindings + // fn validate_expr_access(&mut self, initialized_patterns: &mut HashSet, expr: ExprId, exprSide:ExprSide) { + // let body = self.body.clone(); + // match &body[expr] { + // Expr::Call { callee, args } => { + // self.validate_expr_access(initialized_patterns, *callee); + // for arg in args.iter() { + // self.validate_expr_access(initialized_patterns, *callee); + // } + // }, + // Expr::Path(p) => { + // let resolver = expr::resolver_for_expr(self.body.clone(), self.db, tgt_expr); + // self.validate_path_access(initialized_patterns, &resolver, p); + // } + // Expr::If { .. } => {}, + // Expr::UnaryOp { .. } => {}, + // Expr::BinaryOp { .. } => {}, + // Expr::Block { .. } => {}, + // Expr::Return { .. } => {}, + // Expr::Break { .. } => {}, + // Expr::Loop { .. } => {}, + // Expr::While { .. } => {}, + // Expr::RecordLit { .. } => {}, + // Expr::Field { .. } => {}, + // Expr::Literal(_) => {}, + // Expr::Missing => {}, + // } + // } + // + // fn validate_expr_access(&mut self, initialized_patterns: &mut HashSet, p: &Path) { + // + // } +} From afe18e1ffd63729ac9d303f3a0d4819e6f037cad Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 10 Jan 2020 19:08:15 +0100 Subject: [PATCH 14/21] feat: diagnostics for uninitialized variable access --- crates/mun_hir/src/diagnostics.rs | 24 ++ crates/mun_hir/src/expr/validator.rs | 3 + .../tests__uninitialized_access.snap | 6 + .../tests__uninitialized_access_if.snap | 6 + .../tests__uninitialized_access_while.snap | 6 + crates/mun_hir/src/expr/validator/tests.rs | 95 +++++++ .../expr/validator/uninitialized_access.rs | 251 ++++++++++++++---- 7 files changed, 343 insertions(+), 48 deletions(-) create mode 100644 crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access.snap create mode 100644 crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access_if.snap create mode 100644 crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access_while.snap create mode 100644 crates/mun_hir/src/expr/validator/tests.rs diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs index 4fb9e0507..c99ac70af 100644 --- a/crates/mun_hir/src/diagnostics.rs +++ b/crates/mun_hir/src/diagnostics.rs @@ -432,3 +432,27 @@ impl Diagnostic for NoSuchField { self } } + +#[derive(Debug)] +pub struct PossiblyUninitializedVariable { + pub file: FileId, + pub pat: SyntaxNodePtr, +} + +impl Diagnostic for PossiblyUninitializedVariable { + fn message(&self) -> String { + "use of possibly-uninitialized variable".to_string() + } + + fn file(&self) -> FileId { + self.file + } + + fn syntax_node_ptr(&self) -> SyntaxNodePtr { + self.pat + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} diff --git a/crates/mun_hir/src/expr/validator.rs b/crates/mun_hir/src/expr/validator.rs index f1bdd7bb0..35bce9508 100644 --- a/crates/mun_hir/src/expr/validator.rs +++ b/crates/mun_hir/src/expr/validator.rs @@ -3,6 +3,9 @@ use std::sync::Arc; mod uninitialized_access; +#[cfg(test)] +mod tests; + pub struct ExprValidator<'a, 'b: 'a, 'd, DB: HirDatabase> { func: Function, infer: Arc, diff --git a/crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access.snap b/crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access.snap new file mode 100644 index 000000000..498da9907 --- /dev/null +++ b/crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access.snap @@ -0,0 +1,6 @@ +--- +source: crates/mun_hir/src/expr/validator/tests.rs +expression: "fn foo() {\n let a:int;\n let b = a + 3;\n}" +--- +[38; 39): use of possibly-uninitialized variable + diff --git a/crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access_if.snap b/crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access_if.snap new file mode 100644 index 000000000..45e724988 --- /dev/null +++ b/crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access_if.snap @@ -0,0 +1,6 @@ +--- +source: crates/mun_hir/src/expr/validator/tests.rs +expression: "fn foo() {\n let a:int;\n if true { a = 3; } else { a = 4; }\n let b = a + 4; // correct, `a` is initialized either way\n}\n\nfn bar() {\n let a:int;\n if true { a = 3; }\n let b = a + 4; // `a` is possibly-unitialized\n}\n\nfn baz() {\n let a:int;\n if true { return } else { a = 4 };\n let b = a + 4; // correct, `a` is initialized either way\n}\n\nfn foz() {\n let a:int;\n if true { a = 4 } else { return };\n let b = a + 4; // correct, `a` is initialized either way\n}\n\nfn boz() {\n let a:int;\n return;\n let b = a + 4; // `a` is not initialized but this is dead code anyway\n}" +--- +[191; 192): use of possibly-uninitialized variable + diff --git a/crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access_while.snap b/crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access_while.snap new file mode 100644 index 000000000..7256b8aca --- /dev/null +++ b/crates/mun_hir/src/expr/validator/snapshots/tests__uninitialized_access_while.snap @@ -0,0 +1,6 @@ +--- +source: crates/mun_hir/src/expr/validator/tests.rs +expression: "fn foo(b:int) {\n let a:int;\n while b < 4 { b += 1; a = b; a += 1; }\n let c = a + 4; // `a` is possibly-unitialized\n}" +--- +[86; 87): use of possibly-uninitialized variable + diff --git a/crates/mun_hir/src/expr/validator/tests.rs b/crates/mun_hir/src/expr/validator/tests.rs new file mode 100644 index 000000000..7a2cccaa7 --- /dev/null +++ b/crates/mun_hir/src/expr/validator/tests.rs @@ -0,0 +1,95 @@ +use crate::db::SourceDatabase; +use crate::expr::validator::ExprValidator; +use crate::{diagnostics::DiagnosticSink, ids::LocationCtx, mock::MockDatabase, Function}; +use mun_syntax::{ast, AstNode}; +use std::fmt::Write; + +#[test] +fn test_uninitialized_access() { + diagnostics_snapshot( + r#" + fn foo() { + let a:int; + let b = a + 3; + } + "#, + ) +} + +#[test] +fn test_uninitialized_access_if() { + diagnostics_snapshot( + r#" + fn foo() { + let a:int; + if true { a = 3; } else { a = 4; } + let b = a + 4; // correct, `a` is initialized either way + } + + fn bar() { + let a:int; + if true { a = 3; } + let b = a + 4; // `a` is possibly-unitialized + } + + fn baz() { + let a:int; + if true { return } else { a = 4 }; + let b = a + 4; // correct, `a` is initialized either way + } + + fn foz() { + let a:int; + if true { a = 4 } else { return }; + let b = a + 4; // correct, `a` is initialized either way + } + + fn boz() { + let a:int; + return; + let b = a + 4; // `a` is not initialized but this is dead code anyway + } + "#, + ) +} + +#[test] +fn test_uninitialized_access_while() { + diagnostics_snapshot( + r#" + fn foo(b:int) { + let a:int; + while b < 4 { b += 1; a = b; a += 1; } + let c = a + 4; // `a` is possibly-unitialized + } + "#, + ) +} + +fn diagnostics(content: &str) -> String { + let (db, file_id) = MockDatabase::with_single_file(content); + let source_file = db.parse(file_id).ok().unwrap(); + + let mut diags = String::new(); + + let mut diag_sink = DiagnosticSink::new(|diag| { + write!(diags, "{}: {}\n", diag.highlight_range(), diag.message()).unwrap(); + }); + + let ctx = LocationCtx::new(&db, file_id); + for node in source_file.syntax().descendants() { + if let Some(def) = ast::FunctionDef::cast(node.clone()) { + let fun = Function { + id: ctx.to_def(&def), + }; + ExprValidator::new(fun, &db, &mut diag_sink).validate_body(); + } + } + drop(diag_sink); + diags +} + +fn diagnostics_snapshot(text: &str) { + let text = text.trim().replace("\n ", "\n"); + insta::assert_snapshot!(insta::_macro_support::AutoName, diagnostics(&text), &text); +} diff --git a/crates/mun_hir/src/expr/validator/uninitialized_access.rs b/crates/mun_hir/src/expr/validator/uninitialized_access.rs index d92266507..16d92c497 100644 --- a/crates/mun_hir/src/expr/validator/uninitialized_access.rs +++ b/crates/mun_hir/src/expr/validator/uninitialized_access.rs @@ -1,57 +1,212 @@ use super::ExprValidator; -use crate::HirDatabase; -//use crate::{HirDatabase, ExprId, PatId, Expr, Path}; -//use std::collections::HashSet; +use crate::diagnostics::PossiblyUninitializedVariable; +use crate::{BinaryOp, Expr, ExprId, HirDatabase, PatId, Path, Resolution, Resolver, Statement}; +use std::collections::HashSet; -//enum ExprSide { -// left, -// Expr -//} +#[derive(Copy, Clone, PartialEq, Eq)] +enum ExprKind { + Normal, + Place, + Both, +} impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { /// Validates that all binding access has previously been initialized. pub(super) fn validate_uninitialized_access(&mut self) { - // let mut initialized_patterns = HashSet::new(); - // - // // Add all parameter patterns to the set of initialized patterns (they must have been - // // initialized) - // for (pat, _) in self.body.params.iter() { - // initialized_patterns.insert(*pat) - // } - // - // self.validate_expr_access(&mut initialized_patterns, self.body.body_expr); + let mut initialized_patterns = HashSet::new(); + + // Add all parameter patterns to the set of initialized patterns (they must have been + // initialized) + for (pat, _) in self.body.params.iter() { + initialized_patterns.insert(*pat); + } + + self.validate_expr_access( + &mut initialized_patterns, + self.body.body_expr, + ExprKind::Normal, + ); + } + + /// Validates that the specified expr does not access unitialized bindings + fn validate_expr_access( + &mut self, + initialized_patterns: &mut HashSet, + expr: ExprId, + expr_side: ExprKind, + ) { + let body = self.body.clone(); + match &body[expr] { + Expr::Call { callee, args } => { + self.validate_expr_access(initialized_patterns, *callee, expr_side); + for arg in args.iter() { + self.validate_expr_access(initialized_patterns, *arg, expr_side); + } + } + Expr::Path(p) => { + let resolver = crate::expr::resolver_for_expr(self.body.clone(), self.db, expr); + self.validate_path_access(initialized_patterns, &resolver, p, expr, expr_side); + } + Expr::If { + condition, + then_branch, + else_branch, + } => { + self.validate_expr_access(initialized_patterns, *condition, ExprKind::Normal); + let mut then_branch_initialized_patterns = initialized_patterns.clone(); + self.validate_expr_access( + &mut then_branch_initialized_patterns, + *then_branch, + ExprKind::Normal, + ); + if let Some(else_branch) = else_branch { + let mut else_branch_initialized_patterns = initialized_patterns.clone(); + self.validate_expr_access( + &mut else_branch_initialized_patterns, + *else_branch, + ExprKind::Normal, + ); + let then_is_never = self.infer[*then_branch].is_never(); + let else_is_never = self.infer[*else_branch].is_never(); + match (then_is_never, else_is_never) { + (false, false) => { + initialized_patterns.extend( + then_branch_initialized_patterns + .intersection(&else_branch_initialized_patterns), + ); + } + (true, false) => { + initialized_patterns.extend(else_branch_initialized_patterns); + } + (false, true) => { + initialized_patterns.extend(then_branch_initialized_patterns); + } + (true, true) => {} + }; + } + } + Expr::UnaryOp { expr, .. } => { + self.validate_expr_access(initialized_patterns, *expr, ExprKind::Normal); + } + Expr::BinaryOp { lhs, rhs, op } => { + let lhs_expr_kind = match op { + Some(BinaryOp::Assignment { op: Some(_) }) => ExprKind::Both, + Some(BinaryOp::Assignment { op: None }) => ExprKind::Place, + _ => ExprKind::Normal, + }; + self.validate_expr_access(initialized_patterns, *lhs, lhs_expr_kind); + self.validate_expr_access(initialized_patterns, *rhs, ExprKind::Normal) + } + Expr::Block { statements, tail } => { + for statement in statements.iter() { + match statement { + Statement::Let { + pat, initializer, .. + } => { + if let Some(initializer) = initializer { + self.validate_expr_access( + initialized_patterns, + *initializer, + ExprKind::Normal, + ); + initialized_patterns.insert(*pat); + } + } + Statement::Expr(expr) => { + self.validate_expr_access( + initialized_patterns, + *expr, + ExprKind::Normal, + ); + if self.infer[*expr].is_never() { + return; + } + } + } + } + if let Some(tail) = tail { + self.validate_expr_access(initialized_patterns, *tail, ExprKind::Normal) + } + } + Expr::Return { expr } => { + if let Some(expr) = expr { + self.validate_expr_access(initialized_patterns, *expr, ExprKind::Normal) + } + } + Expr::Break { expr } => { + if let Some(expr) = expr { + self.validate_expr_access(initialized_patterns, *expr, ExprKind::Normal) + } + } + Expr::Loop { body } => { + self.validate_expr_access(initialized_patterns, *body, ExprKind::Normal) + } + Expr::While { condition, body } => { + self.validate_expr_access(initialized_patterns, *condition, ExprKind::Normal); + self.validate_expr_access( + &mut initialized_patterns.clone(), + *body, + ExprKind::Normal, + ); + } + Expr::RecordLit { fields, spread, .. } => { + for field in fields.iter() { + self.validate_expr_access(initialized_patterns, field.expr, ExprKind::Normal); + } + if let Some(expr) = spread { + self.validate_expr_access(initialized_patterns, *expr, ExprKind::Normal); + } + } + Expr::Field { expr, .. } => { + self.validate_expr_access(initialized_patterns, *expr, ExprKind::Normal); + } + Expr::Literal(_) => {} + Expr::Missing => {} + } } - // /// Validates that the specified expr does not access unitialized bindings - // fn validate_expr_access(&mut self, initialized_patterns: &mut HashSet, expr: ExprId, exprSide:ExprSide) { - // let body = self.body.clone(); - // match &body[expr] { - // Expr::Call { callee, args } => { - // self.validate_expr_access(initialized_patterns, *callee); - // for arg in args.iter() { - // self.validate_expr_access(initialized_patterns, *callee); - // } - // }, - // Expr::Path(p) => { - // let resolver = expr::resolver_for_expr(self.body.clone(), self.db, tgt_expr); - // self.validate_path_access(initialized_patterns, &resolver, p); - // } - // Expr::If { .. } => {}, - // Expr::UnaryOp { .. } => {}, - // Expr::BinaryOp { .. } => {}, - // Expr::Block { .. } => {}, - // Expr::Return { .. } => {}, - // Expr::Break { .. } => {}, - // Expr::Loop { .. } => {}, - // Expr::While { .. } => {}, - // Expr::RecordLit { .. } => {}, - // Expr::Field { .. } => {}, - // Expr::Literal(_) => {}, - // Expr::Missing => {}, - // } - // } - // - // fn validate_expr_access(&mut self, initialized_patterns: &mut HashSet, p: &Path) { - // - // } + fn validate_path_access( + &mut self, + initialized_patterns: &mut HashSet, + resolver: &Resolver, + path: &Path, + expr: ExprId, + expr_side: ExprKind, + ) { + let resolution = match resolver + .resolve_path_without_assoc_items(self.db, path) + .take_values() + { + Some(resolution) => resolution, + None => { + // This has already been caught by the inferencing step + return; + } + }; + + let pat = match resolution { + Resolution::LocalBinding(pat) => pat, + Resolution::Def(_def) => return, + }; + + if expr_side == ExprKind::Normal || expr_side == ExprKind::Both { + // Check if the binding has already been initialized + if initialized_patterns.get(&pat).is_none() { + let (_, body_source_map) = self.db.body_with_source_map(self.func.into()); + self.sink.push(PossiblyUninitializedVariable { + file: self.func.module(self.db).file_id(), + pat: body_source_map + .expr_syntax(expr) + .unwrap() + .ast + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()), + }) + } + } + + if expr_side == ExprKind::Place { + // The binding should be initialized + initialized_patterns.insert(pat); + } + } } From d53ee2c57e76f0d7b7f1514a10a0690e29f56d00 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 10 Jan 2020 21:16:10 +0100 Subject: [PATCH 15/21] feat: struct memory type specifiers --- crates/mun_hir/src/adt.rs | 10 +++ .../src/ty/snapshots/tests__struct_decl.snap | 10 +-- crates/mun_hir/src/ty/tests.rs | 4 +- crates/mun_syntax/src/ast.rs | 2 +- crates/mun_syntax/src/ast/extensions.rs | 20 +++++ crates/mun_syntax/src/ast/generated.rs | 33 +++++++- crates/mun_syntax/src/grammar.ron | 9 +- crates/mun_syntax/src/parsing/grammar/adt.rs | 22 ++++- crates/mun_syntax/src/parsing/parser.rs | 22 ++++- .../mun_syntax/src/syntax_kind/generated.rs | 6 ++ crates/mun_syntax/src/tests/parser.rs | 13 +++ .../parser__memory_type_specifier.snap | 82 +++++++++++++++++++ 12 files changed, 218 insertions(+), 15 deletions(-) create mode 100644 crates/mun_syntax/src/tests/snapshots/parser__memory_type_specifier.snap diff --git a/crates/mun_hir/src/adt.rs b/crates/mun_hir/src/adt.rs index ceee776f2..19789ed7c 100644 --- a/crates/mun_hir/src/adt.rs +++ b/crates/mun_hir/src/adt.rs @@ -8,6 +8,8 @@ use crate::{ }; use mun_syntax::ast::{self, NameOwner, TypeAscriptionOwner}; +pub use mun_syntax::ast::StructMemoryKind; + /// A single field of a record /// ```mun /// struct Foo { @@ -44,6 +46,7 @@ pub struct StructData { pub name: Name, pub fields: Arena, pub kind: StructKind, + pub memory_kind: StructMemoryKind, type_ref_map: TypeRefMap, type_ref_source_map: TypeRefSourceMap, } @@ -57,6 +60,12 @@ impl StructData { .map(|n| n.as_name()) .unwrap_or_else(Name::missing); + let memory_kind = src + .ast + .memory_type_specifier() + .and_then(|s| s.kind()) + .unwrap_or(StructMemoryKind::GC); + let mut type_ref_builder = TypeRefBuilder::default(); let (fields, kind) = match src.ast.kind() { ast::StructKind::Record(r) => { @@ -88,6 +97,7 @@ impl StructData { name, fields, kind, + memory_kind, type_ref_map, type_ref_source_map, }) diff --git a/crates/mun_hir/src/ty/snapshots/tests__struct_decl.snap b/crates/mun_hir/src/ty/snapshots/tests__struct_decl.snap index bbe9a1ef8..46afc6367 100644 --- a/crates/mun_hir/src/ty/snapshots/tests__struct_decl.snap +++ b/crates/mun_hir/src/ty/snapshots/tests__struct_decl.snap @@ -1,8 +1,8 @@ --- source: crates/mun_hir/src/ty/tests.rs -expression: "struct Foo;\nstruct Bar {\n f: float,\n i: int,\n}\nstruct Baz(float, int);\n\n\nfn main() {\n let foo: Foo;\n let bar: Bar;\n let baz: Baz;\n}" +expression: "struct Foo;\nstruct(gc) Bar {\n f: float,\n i: int,\n}\nstruct(value) Baz(float, int);\n\n\nfn main() {\n let foo: Foo;\n let bar: Bar;\n let baz: Baz;\n}" --- -[89; 146) '{ ...Baz; }': nothing -[99; 102) 'foo': Foo -[117; 120) 'bar': Bar -[135; 138) 'baz': Baz +[100; 157) '{ ...Baz; }': nothing +[110; 113) 'foo': Foo +[128; 131) 'bar': Bar +[146; 149) 'baz': Baz diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index d70372624..b44c18789 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -177,11 +177,11 @@ fn struct_decl() { infer_snapshot( r#" struct Foo; - struct Bar { + struct(gc) Bar { f: float, i: int, } - struct Baz(float, int); + struct(value) Baz(float, int); fn main() { diff --git a/crates/mun_syntax/src/ast.rs b/crates/mun_syntax/src/ast.rs index 4c3e58f79..47fc03d8a 100644 --- a/crates/mun_syntax/src/ast.rs +++ b/crates/mun_syntax/src/ast.rs @@ -9,7 +9,7 @@ use crate::{syntax_node::SyntaxNodeChildren, SmolStr, SyntaxKind, SyntaxNode, Sy pub use self::{ expr_extensions::*, - extensions::{PathSegmentKind, StructKind}, + extensions::{PathSegmentKind, StructKind, StructMemoryKind}, generated::*, tokens::*, traits::*, diff --git a/crates/mun_syntax/src/ast/extensions.rs b/crates/mun_syntax/src/ast/extensions.rs index bca795b6c..ccc0ebb4c 100644 --- a/crates/mun_syntax/src/ast/extensions.rs +++ b/crates/mun_syntax/src/ast/extensions.rs @@ -118,6 +118,26 @@ impl StructKind { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum StructMemoryKind { + /// A garbage collected struct is allocated on the heap and uses reference semantics when passed + /// around. + GC, + + /// A value struct is allocated on the stack and uses value semantics when passed around. + Value, +} + +impl ast::MemoryTypeSpecifier { + pub fn kind(&self) -> Option { + match self.syntax.first_child_or_token().map(|s| s.kind()) { + Some(SyntaxKind::GC_KW) => Some(StructMemoryKind::GC), + Some(SyntaxKind::VALUE_KW) => Some(StructMemoryKind::Value), + _ => None, + } + } +} + impl ast::StructDef { pub fn kind(&self) -> StructKind { StructKind::from_node(self) diff --git a/crates/mun_syntax/src/ast/generated.rs b/crates/mun_syntax/src/ast/generated.rs index 979742b5e..4f1a7592a 100644 --- a/crates/mun_syntax/src/ast/generated.rs +++ b/crates/mun_syntax/src/ast/generated.rs @@ -607,6 +607,33 @@ impl AstNode for LoopExpr { impl ast::LoopBodyOwner for LoopExpr {} impl LoopExpr {} +// MemoryTypeSpecifier + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct MemoryTypeSpecifier { + pub(crate) syntax: SyntaxNode, +} + +impl AstNode for MemoryTypeSpecifier { + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + MEMORY_TYPE_SPECIFIER => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(MemoryTypeSpecifier { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl MemoryTypeSpecifier {} + // ModuleItem #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -1417,7 +1444,11 @@ impl AstNode for StructDef { impl ast::NameOwner for StructDef {} impl ast::VisibilityOwner for StructDef {} impl ast::DocCommentsOwner for StructDef {} -impl StructDef {} +impl StructDef { + pub fn memory_type_specifier(&self) -> Option { + super::child_opt(self) + } +} // TupleFieldDef diff --git a/crates/mun_syntax/src/grammar.ron b/crates/mun_syntax/src/grammar.ron index e3a82cf5e..8a9da0894 100644 --- a/crates/mun_syntax/src/grammar.ron +++ b/crates/mun_syntax/src/grammar.ron @@ -95,6 +95,10 @@ Grammar( "INDEX", "WHITESPACE", "COMMENT", + + // Contextual keywords + "GC_KW", + "VALUE_KW", ], nodes: [ "SOURCE_FILE", @@ -105,8 +109,9 @@ Grammar( "PARAM_LIST", "PARAM", - + "STRUCT_DEF", + "MEMORY_TYPE_SPECIFIER", "RECORD_FIELD_DEF_LIST", "RECORD_FIELD_DEF", "TUPLE_FIELD_DEF_LIST", @@ -177,12 +182,14 @@ Grammar( ], ), "StructDef": ( + options: ["MemoryTypeSpecifier"], traits: [ "NameOwner", "VisibilityOwner", "DocCommentsOwner", ] ), + "MemoryTypeSpecifier": (), "RecordFieldDefList": (collections: [("fields", "RecordFieldDef")]), "RecordFieldDef": ( traits: [ diff --git a/crates/mun_syntax/src/parsing/grammar/adt.rs b/crates/mun_syntax/src/parsing/grammar/adt.rs index 7665b0da5..f760f4394 100644 --- a/crates/mun_syntax/src/parsing/grammar/adt.rs +++ b/crates/mun_syntax/src/parsing/grammar/adt.rs @@ -3,7 +3,7 @@ use super::*; pub(super) fn struct_def(p: &mut Parser, m: Marker) { assert!(p.at(T![struct])); p.bump(T![struct]); - + memory_type_specifier(p); name_recovery(p, declarations::DECLARATION_RECOVERY_SET); match p.current() { T![;] => { @@ -37,6 +37,26 @@ pub(super) fn record_field_def_list(p: &mut Parser) { m.complete(p, RECORD_FIELD_DEF_LIST); } +fn memory_type_specifier(p: &mut Parser) { + if p.at(T!['(']) { + let m = p.start(); + p.bump(T!['(']); + if p.at(IDENT) { + if p.at_contextual_kw("gc") { + p.bump_remap(GC_KW) + } else if p.at_contextual_kw("value") { + p.bump_remap(VALUE_KW) + } else { + p.error_and_bump("expected memory type specifier"); + } + } else { + p.error("expected memory type specifier"); + } + p.expect(T![')']); + m.complete(p, MEMORY_TYPE_SPECIFIER); + } +} + pub(super) fn tuple_field_def_list(p: &mut Parser) { assert!(p.at(T!['('])); let m = p.start(); diff --git a/crates/mun_syntax/src/parsing/parser.rs b/crates/mun_syntax/src/parsing/parser.rs index 401398ae8..e3951c8ff 100644 --- a/crates/mun_syntax/src/parsing/parser.rs +++ b/crates/mun_syntax/src/parsing/parser.rs @@ -103,10 +103,10 @@ impl<'t> Parser<'t> { kinds.contains(self.current()) } - // /// Checks if the current token is contextual keyword with text `t`. - // pub(crate) fn at_contextual_kw(&self, kw: &str) -> bool { - // self.token_source.is_keyword(kw) - // } + /// Checks if the current token is contextual keyword with text `t`. + pub(crate) fn at_contextual_kw(&self, kw: &str) -> bool { + self.token_source.is_keyword(kw) + } /// Starts a new node in the syntax tree. All nodes and tokens consumed between the `start` and /// the corresponding `Marker::complete` belong to the same node. @@ -130,6 +130,20 @@ impl<'t> Parser<'t> { self.do_bump(kind, 1) } + /// Advances the parser by one token, remapping its kind. + /// This is useful to create contextual keywords from + /// identifiers. For example, the lexer creates an `union` + /// *identifier* token, but the parser remaps it to the + /// `union` keyword, and keyword is what ends up in the + /// final tree. + pub(crate) fn bump_remap(&mut self, kind: SyntaxKind) { + if self.nth(0) == EOF { + // FIXME: panic!? + return; + } + self.do_bump(kind, 1); + } + fn do_bump(&mut self, kind: SyntaxKind, n_raw_tokens: u8) { for _ in 0..n_raw_tokens { self.token_source.bump(); diff --git a/crates/mun_syntax/src/syntax_kind/generated.rs b/crates/mun_syntax/src/syntax_kind/generated.rs index 12d4b2ba9..36aea3a2b 100644 --- a/crates/mun_syntax/src/syntax_kind/generated.rs +++ b/crates/mun_syntax/src/syntax_kind/generated.rs @@ -83,6 +83,8 @@ pub enum SyntaxKind { INDEX, WHITESPACE, COMMENT, + GC_KW, + VALUE_KW, SOURCE_FILE, FUNCTION_DEF, RET_TYPE, @@ -90,6 +92,7 @@ pub enum SyntaxKind { PARAM_LIST, PARAM, STRUCT_DEF, + MEMORY_TYPE_SPECIFIER, RECORD_FIELD_DEF_LIST, RECORD_FIELD_DEF, TUPLE_FIELD_DEF_LIST, @@ -354,6 +357,8 @@ impl SyntaxKind { INDEX => &SyntaxInfo { name: "INDEX" }, WHITESPACE => &SyntaxInfo { name: "WHITESPACE" }, COMMENT => &SyntaxInfo { name: "COMMENT" }, + GC_KW => &SyntaxInfo { name: "GC_KW" }, + VALUE_KW => &SyntaxInfo { name: "VALUE_KW" }, SOURCE_FILE => &SyntaxInfo { name: "SOURCE_FILE" }, FUNCTION_DEF => &SyntaxInfo { name: "FUNCTION_DEF" }, RET_TYPE => &SyntaxInfo { name: "RET_TYPE" }, @@ -361,6 +366,7 @@ impl SyntaxKind { PARAM_LIST => &SyntaxInfo { name: "PARAM_LIST" }, PARAM => &SyntaxInfo { name: "PARAM" }, STRUCT_DEF => &SyntaxInfo { name: "STRUCT_DEF" }, + MEMORY_TYPE_SPECIFIER => &SyntaxInfo { name: "MEMORY_TYPE_SPECIFIER" }, RECORD_FIELD_DEF_LIST => &SyntaxInfo { name: "RECORD_FIELD_DEF_LIST" }, RECORD_FIELD_DEF => &SyntaxInfo { name: "RECORD_FIELD_DEF" }, TUPLE_FIELD_DEF_LIST => &SyntaxInfo { name: "TUPLE_FIELD_DEF_LIST" }, diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index acae97863..1bc79ca82 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -267,3 +267,16 @@ fn struct_field_index() { "#, ) } + +#[test] +fn memory_type_specifier() { + snapshot_test( + r#" + struct Foo {}; + struct(gc) Baz {}; + struct(value) Baz {}; + struct() Err1 {}; // expected memory type specifier + struct(foo) Err2 {}; // expected memory type specifier + "#, + ) +} diff --git a/crates/mun_syntax/src/tests/snapshots/parser__memory_type_specifier.snap b/crates/mun_syntax/src/tests/snapshots/parser__memory_type_specifier.snap new file mode 100644 index 000000000..595a4a8de --- /dev/null +++ b/crates/mun_syntax/src/tests/snapshots/parser__memory_type_specifier.snap @@ -0,0 +1,82 @@ +--- +source: crates/mun_syntax/src/tests/parser.rs +expression: "struct Foo {};\nstruct(gc) Baz {};\nstruct(value) Baz {};\nstruct() Err1 {}; // expected memory type specifier\nstruct(foo) Err2 {}; // expected memory type specifier" +--- +SOURCE_FILE@[0; 165) + STRUCT_DEF@[0; 14) + STRUCT_KW@[0; 6) "struct" + WHITESPACE@[6; 7) " " + NAME@[7; 10) + IDENT@[7; 10) "Foo" + WHITESPACE@[10; 11) " " + RECORD_FIELD_DEF_LIST@[11; 14) + L_CURLY@[11; 12) "{" + R_CURLY@[12; 13) "}" + SEMI@[13; 14) ";" + WHITESPACE@[14; 15) "\n" + STRUCT_DEF@[15; 33) + STRUCT_KW@[15; 21) "struct" + MEMORY_TYPE_SPECIFIER@[21; 25) + L_PAREN@[21; 22) "(" + GC_KW@[22; 24) "gc" + R_PAREN@[24; 25) ")" + WHITESPACE@[25; 26) " " + NAME@[26; 29) + IDENT@[26; 29) "Baz" + WHITESPACE@[29; 30) " " + RECORD_FIELD_DEF_LIST@[30; 33) + L_CURLY@[30; 31) "{" + R_CURLY@[31; 32) "}" + SEMI@[32; 33) ";" + WHITESPACE@[33; 34) "\n" + STRUCT_DEF@[34; 55) + STRUCT_KW@[34; 40) "struct" + MEMORY_TYPE_SPECIFIER@[40; 47) + L_PAREN@[40; 41) "(" + VALUE_KW@[41; 46) "value" + R_PAREN@[46; 47) ")" + WHITESPACE@[47; 48) " " + NAME@[48; 51) + IDENT@[48; 51) "Baz" + WHITESPACE@[51; 52) " " + RECORD_FIELD_DEF_LIST@[52; 55) + L_CURLY@[52; 53) "{" + R_CURLY@[53; 54) "}" + SEMI@[54; 55) ";" + WHITESPACE@[55; 56) "\n" + STRUCT_DEF@[56; 73) + STRUCT_KW@[56; 62) "struct" + MEMORY_TYPE_SPECIFIER@[62; 64) + L_PAREN@[62; 63) "(" + R_PAREN@[63; 64) ")" + WHITESPACE@[64; 65) " " + NAME@[65; 69) + IDENT@[65; 69) "Err1" + WHITESPACE@[69; 70) " " + RECORD_FIELD_DEF_LIST@[70; 73) + L_CURLY@[70; 71) "{" + R_CURLY@[71; 72) "}" + SEMI@[72; 73) ";" + WHITESPACE@[73; 77) " " + COMMENT@[77; 110) "// expected memory ty ..." + WHITESPACE@[110; 111) "\n" + STRUCT_DEF@[111; 131) + STRUCT_KW@[111; 117) "struct" + MEMORY_TYPE_SPECIFIER@[117; 122) + L_PAREN@[117; 118) "(" + ERROR@[118; 121) + IDENT@[118; 121) "foo" + R_PAREN@[121; 122) ")" + WHITESPACE@[122; 123) " " + NAME@[123; 127) + IDENT@[123; 127) "Err2" + WHITESPACE@[127; 128) " " + RECORD_FIELD_DEF_LIST@[128; 131) + L_CURLY@[128; 129) "{" + R_CURLY@[129; 130) "}" + SEMI@[130; 131) ";" + WHITESPACE@[131; 132) " " + COMMENT@[132; 165) "// expected memory ty ..." +error Offset(63): expected memory type specifier +error Offset(118): expected memory type specifier + From a288b1fecb11fae2704a533b1c739009101afb10 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 10 Jan 2020 21:48:07 +0100 Subject: [PATCH 16/21] feat: visibility can now include specifiers --- crates/mun_hir/src/adt.rs | 4 +- crates/mun_syntax/src/ast/extensions.rs | 44 ++++++++- crates/mun_syntax/src/grammar.ron | 8 +- crates/mun_syntax/src/parsing/grammar.rs | 25 +++-- crates/mun_syntax/src/parsing/grammar/adt.rs | 4 +- .../mun_syntax/src/syntax_kind/generated.rs | 25 +++-- crates/mun_syntax/src/tests/lexer.rs | 2 +- crates/mun_syntax/src/tests/parser.rs | 13 +++ .../src/tests/snapshots/lexer__keywords.snap | 8 +- .../tests/snapshots/parser__visibility.snap | 94 +++++++++++++++++++ 10 files changed, 197 insertions(+), 30 deletions(-) create mode 100644 crates/mun_syntax/src/tests/snapshots/parser__visibility.snap diff --git a/crates/mun_hir/src/adt.rs b/crates/mun_hir/src/adt.rs index 19789ed7c..e9a78e611 100644 --- a/crates/mun_hir/src/adt.rs +++ b/crates/mun_hir/src/adt.rs @@ -63,8 +63,8 @@ impl StructData { let memory_kind = src .ast .memory_type_specifier() - .and_then(|s| s.kind()) - .unwrap_or(StructMemoryKind::GC); + .map(|s| s.kind()) + .unwrap_or_default(); let mut type_ref_builder = TypeRefBuilder::default(); let (fields, kind) = match src.ast.kind() { diff --git a/crates/mun_syntax/src/ast/extensions.rs b/crates/mun_syntax/src/ast/extensions.rs index ccc0ebb4c..8e3649843 100644 --- a/crates/mun_syntax/src/ast/extensions.rs +++ b/crates/mun_syntax/src/ast/extensions.rs @@ -128,12 +128,18 @@ pub enum StructMemoryKind { Value, } +impl Default for StructMemoryKind { + fn default() -> Self { + StructMemoryKind::GC + } +} + impl ast::MemoryTypeSpecifier { - pub fn kind(&self) -> Option { + pub fn kind(&self) -> StructMemoryKind { match self.syntax.first_child_or_token().map(|s| s.kind()) { - Some(SyntaxKind::GC_KW) => Some(StructMemoryKind::GC), - Some(SyntaxKind::VALUE_KW) => Some(StructMemoryKind::Value), - _ => None, + Some(SyntaxKind::VALUE_KW) => StructMemoryKind::Value, + Some(SyntaxKind::GC_KW) => StructMemoryKind::GC, + _ => StructMemoryKind::default(), } } } @@ -143,3 +149,33 @@ impl ast::StructDef { StructKind::from_node(self) } } + +pub enum VisibilityKind { + PubPackage, + PubSuper, + Pub, +} + +impl ast::Visibility { + pub fn kind(&self) -> VisibilityKind { + if self.is_pub_package() { + VisibilityKind::PubPackage + } else if self.is_pub_super() { + VisibilityKind::PubSuper + } else { + VisibilityKind::Pub + } + } + + fn is_pub_package(&self) -> bool { + self.syntax() + .children_with_tokens() + .any(|it| it.kind() == T![package]) + } + + fn is_pub_super(&self) -> bool { + self.syntax() + .children_with_tokens() + .any(|it| it.kind() == T![super]) + } +} diff --git a/crates/mun_syntax/src/grammar.ron b/crates/mun_syntax/src/grammar.ron index 8a9da0894..fa9b48195 100644 --- a/crates/mun_syntax/src/grammar.ron +++ b/crates/mun_syntax/src/grammar.ron @@ -66,8 +66,6 @@ Grammar( "nil", // "not", // We use ! // "or", - "self", - "super", // "repeat", // Not supported "return", // "then", // Not supported @@ -82,7 +80,11 @@ Grammar( "class", "struct", "never", - "pub" + "pub", + + "package", + "super", + "self" ], literals: [ "INT_NUMBER", diff --git a/crates/mun_syntax/src/parsing/grammar.rs b/crates/mun_syntax/src/parsing/grammar.rs index 2a1b24457..a2deec2ff 100644 --- a/crates/mun_syntax/src/parsing/grammar.rs +++ b/crates/mun_syntax/src/parsing/grammar.rs @@ -77,13 +77,24 @@ fn name_ref_or_index(p: &mut Parser) { } fn opt_visibility(p: &mut Parser) -> bool { - if p.at(PUB_KW) { - let m = p.start(); - p.bump(PUB_KW); - m.complete(p, VISIBILITY); - true - } else { - false + match p.current() { + T![pub] => { + let m = p.start(); + p.bump(T![pub]); + if p.at(T!['(']) { + match p.nth(1) { + T![package] | T![super] => { + p.bump_any(); + p.bump_any(); + p.expect(T![')']); + } + _ => (), + } + } + m.complete(p, VISIBILITY); + true + } + _ => false, } } diff --git a/crates/mun_syntax/src/parsing/grammar/adt.rs b/crates/mun_syntax/src/parsing/grammar/adt.rs index f760f4394..4423bf5a4 100644 --- a/crates/mun_syntax/src/parsing/grammar/adt.rs +++ b/crates/mun_syntax/src/parsing/grammar/adt.rs @@ -3,7 +3,7 @@ use super::*; pub(super) fn struct_def(p: &mut Parser, m: Marker) { assert!(p.at(T![struct])); p.bump(T![struct]); - memory_type_specifier(p); + opt_memory_type_specifier(p); name_recovery(p, declarations::DECLARATION_RECOVERY_SET); match p.current() { T![;] => { @@ -37,7 +37,7 @@ pub(super) fn record_field_def_list(p: &mut Parser) { m.complete(p, RECORD_FIELD_DEF_LIST); } -fn memory_type_specifier(p: &mut Parser) { +fn opt_memory_type_specifier(p: &mut Parser) { if p.at(T!['(']) { let m = p.start(); p.bump(T!['(']); diff --git a/crates/mun_syntax/src/syntax_kind/generated.rs b/crates/mun_syntax/src/syntax_kind/generated.rs index 36aea3a2b..44881199f 100644 --- a/crates/mun_syntax/src/syntax_kind/generated.rs +++ b/crates/mun_syntax/src/syntax_kind/generated.rs @@ -63,8 +63,6 @@ pub enum SyntaxKind { IF_KW, IN_KW, NIL_KW, - SELF_KW, - SUPER_KW, RETURN_KW, TRUE_KW, WHILE_KW, @@ -75,6 +73,9 @@ pub enum SyntaxKind { STRUCT_KW, NEVER_KW, PUB_KW, + PACKAGE_KW, + SUPER_KW, + SELF_KW, INT_NUMBER, FLOAT_NUMBER, STRING, @@ -179,8 +180,6 @@ macro_rules! T { (if) => { $crate::SyntaxKind::IF_KW }; (in) => { $crate::SyntaxKind::IN_KW }; (nil) => { $crate::SyntaxKind::NIL_KW }; - (self) => { $crate::SyntaxKind::SELF_KW }; - (super) => { $crate::SyntaxKind::SUPER_KW }; (return) => { $crate::SyntaxKind::RETURN_KW }; (true) => { $crate::SyntaxKind::TRUE_KW }; (while) => { $crate::SyntaxKind::WHILE_KW }; @@ -191,6 +190,9 @@ macro_rules! T { (struct) => { $crate::SyntaxKind::STRUCT_KW }; (never) => { $crate::SyntaxKind::NEVER_KW }; (pub) => { $crate::SyntaxKind::PUB_KW }; + (package) => { $crate::SyntaxKind::PACKAGE_KW }; + (super) => { $crate::SyntaxKind::SUPER_KW }; + (self) => { $crate::SyntaxKind::SELF_KW }; } impl From for SyntaxKind { @@ -219,8 +221,6 @@ impl SyntaxKind { | IF_KW | IN_KW | NIL_KW - | SELF_KW - | SUPER_KW | RETURN_KW | TRUE_KW | WHILE_KW @@ -231,6 +231,9 @@ impl SyntaxKind { | STRUCT_KW | NEVER_KW | PUB_KW + | PACKAGE_KW + | SUPER_KW + | SELF_KW => true, _ => false } @@ -337,8 +340,6 @@ impl SyntaxKind { IF_KW => &SyntaxInfo { name: "IF_KW" }, IN_KW => &SyntaxInfo { name: "IN_KW" }, NIL_KW => &SyntaxInfo { name: "NIL_KW" }, - SELF_KW => &SyntaxInfo { name: "SELF_KW" }, - SUPER_KW => &SyntaxInfo { name: "SUPER_KW" }, RETURN_KW => &SyntaxInfo { name: "RETURN_KW" }, TRUE_KW => &SyntaxInfo { name: "TRUE_KW" }, WHILE_KW => &SyntaxInfo { name: "WHILE_KW" }, @@ -349,6 +350,9 @@ impl SyntaxKind { STRUCT_KW => &SyntaxInfo { name: "STRUCT_KW" }, NEVER_KW => &SyntaxInfo { name: "NEVER_KW" }, PUB_KW => &SyntaxInfo { name: "PUB_KW" }, + PACKAGE_KW => &SyntaxInfo { name: "PACKAGE_KW" }, + SUPER_KW => &SyntaxInfo { name: "SUPER_KW" }, + SELF_KW => &SyntaxInfo { name: "SELF_KW" }, INT_NUMBER => &SyntaxInfo { name: "INT_NUMBER" }, FLOAT_NUMBER => &SyntaxInfo { name: "FLOAT_NUMBER" }, STRING => &SyntaxInfo { name: "STRING" }, @@ -417,8 +421,6 @@ impl SyntaxKind { "if" => IF_KW, "in" => IN_KW, "nil" => NIL_KW, - "self" => SELF_KW, - "super" => SUPER_KW, "return" => RETURN_KW, "true" => TRUE_KW, "while" => WHILE_KW, @@ -429,6 +431,9 @@ impl SyntaxKind { "struct" => STRUCT_KW, "never" => NEVER_KW, "pub" => PUB_KW, + "package" => PACKAGE_KW, + "super" => SUPER_KW, + "self" => SELF_KW, _ => return None, }; Some(kw) diff --git a/crates/mun_syntax/src/tests/lexer.rs b/crates/mun_syntax/src/tests/lexer.rs index a12f5858b..13ea9fb16 100644 --- a/crates/mun_syntax/src/tests/lexer.rs +++ b/crates/mun_syntax/src/tests/lexer.rs @@ -110,7 +110,7 @@ fn keywords() { r#" and break do else false for fn if in nil return true while let mut struct class - never loop pub + never loop pub super self package "#, ) } diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index 1bc79ca82..a01414a01 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -280,3 +280,16 @@ fn memory_type_specifier() { "#, ) } + +#[test] +fn visibility() { + snapshot_test( + r#" + pub struct Foo {}; + pub(package) struct(gc) Baz {}; + pub(super) fn foo() {} + pub(package) fn bar() {} + pub fn baz() {} + "#, + ) +} diff --git a/crates/mun_syntax/src/tests/snapshots/lexer__keywords.snap b/crates/mun_syntax/src/tests/snapshots/lexer__keywords.snap index 8ac1a983e..0af61c034 100644 --- a/crates/mun_syntax/src/tests/snapshots/lexer__keywords.snap +++ b/crates/mun_syntax/src/tests/snapshots/lexer__keywords.snap @@ -1,6 +1,6 @@ --- source: crates/mun_syntax/src/tests/lexer.rs -expression: "and break do else false for fn if in nil\nreturn true while let mut struct class\nnever loop pub" +expression: "and break do else false for fn if in nil\nreturn true while let mut struct class\nnever loop pub super self package" --- AND_KW 3 "and" WHITESPACE 1 " " @@ -41,4 +41,10 @@ WHITESPACE 1 " " LOOP_KW 4 "loop" WHITESPACE 1 " " PUB_KW 3 "pub" +WHITESPACE 1 " " +SUPER_KW 5 "super" +WHITESPACE 1 " " +SELF_KW 4 "self" +WHITESPACE 1 " " +PACKAGE_KW 7 "package" diff --git a/crates/mun_syntax/src/tests/snapshots/parser__visibility.snap b/crates/mun_syntax/src/tests/snapshots/parser__visibility.snap new file mode 100644 index 000000000..835ccdcb6 --- /dev/null +++ b/crates/mun_syntax/src/tests/snapshots/parser__visibility.snap @@ -0,0 +1,94 @@ +--- +source: crates/mun_syntax/src/tests/parser.rs +expression: "pub struct Foo {};\npub(package) struct(gc) Baz {};\npub(super) fn foo() {}\npub(package) fn bar() {}\npub fn baz() {}" +--- +SOURCE_FILE@[0; 114) + STRUCT_DEF@[0; 18) + VISIBILITY@[0; 3) + PUB_KW@[0; 3) "pub" + WHITESPACE@[3; 4) " " + STRUCT_KW@[4; 10) "struct" + WHITESPACE@[10; 11) " " + NAME@[11; 14) + IDENT@[11; 14) "Foo" + WHITESPACE@[14; 15) " " + RECORD_FIELD_DEF_LIST@[15; 18) + L_CURLY@[15; 16) "{" + R_CURLY@[16; 17) "}" + SEMI@[17; 18) ";" + WHITESPACE@[18; 19) "\n" + STRUCT_DEF@[19; 50) + VISIBILITY@[19; 31) + PUB_KW@[19; 22) "pub" + L_PAREN@[22; 23) "(" + PACKAGE_KW@[23; 30) "package" + R_PAREN@[30; 31) ")" + WHITESPACE@[31; 32) " " + STRUCT_KW@[32; 38) "struct" + MEMORY_TYPE_SPECIFIER@[38; 42) + L_PAREN@[38; 39) "(" + GC_KW@[39; 41) "gc" + R_PAREN@[41; 42) ")" + WHITESPACE@[42; 43) " " + NAME@[43; 46) + IDENT@[43; 46) "Baz" + WHITESPACE@[46; 47) " " + RECORD_FIELD_DEF_LIST@[47; 50) + L_CURLY@[47; 48) "{" + R_CURLY@[48; 49) "}" + SEMI@[49; 50) ";" + FUNCTION_DEF@[50; 73) + WHITESPACE@[50; 51) "\n" + VISIBILITY@[51; 61) + PUB_KW@[51; 54) "pub" + L_PAREN@[54; 55) "(" + SUPER_KW@[55; 60) "super" + R_PAREN@[60; 61) ")" + WHITESPACE@[61; 62) " " + FN_KW@[62; 64) "fn" + WHITESPACE@[64; 65) " " + NAME@[65; 68) + IDENT@[65; 68) "foo" + PARAM_LIST@[68; 70) + L_PAREN@[68; 69) "(" + R_PAREN@[69; 70) ")" + WHITESPACE@[70; 71) " " + BLOCK_EXPR@[71; 73) + L_CURLY@[71; 72) "{" + R_CURLY@[72; 73) "}" + FUNCTION_DEF@[73; 98) + WHITESPACE@[73; 74) "\n" + VISIBILITY@[74; 86) + PUB_KW@[74; 77) "pub" + L_PAREN@[77; 78) "(" + PACKAGE_KW@[78; 85) "package" + R_PAREN@[85; 86) ")" + WHITESPACE@[86; 87) " " + FN_KW@[87; 89) "fn" + WHITESPACE@[89; 90) " " + NAME@[90; 93) + IDENT@[90; 93) "bar" + PARAM_LIST@[93; 95) + L_PAREN@[93; 94) "(" + R_PAREN@[94; 95) ")" + WHITESPACE@[95; 96) " " + BLOCK_EXPR@[96; 98) + L_CURLY@[96; 97) "{" + R_CURLY@[97; 98) "}" + FUNCTION_DEF@[98; 114) + WHITESPACE@[98; 99) "\n" + VISIBILITY@[99; 102) + PUB_KW@[99; 102) "pub" + WHITESPACE@[102; 103) " " + FN_KW@[103; 105) "fn" + WHITESPACE@[105; 106) " " + NAME@[106; 109) + IDENT@[106; 109) "baz" + PARAM_LIST@[109; 111) + L_PAREN@[109; 110) "(" + R_PAREN@[110; 111) ")" + WHITESPACE@[111; 112) " " + BLOCK_EXPR@[112; 114) + L_CURLY@[112; 113) "{" + R_CURLY@[113; 114) "}" + From 0d412011eaa6d1e317dea1c567f8132ae2f5c099 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 10 Jan 2020 22:31:41 +0100 Subject: [PATCH 17/21] refactor: Renamed Source to InFile and added to diagnostics --- crates/mun/test/main.mun | 10 +- crates/mun_hir/src/adt.rs | 6 +- crates/mun_hir/src/code_model.rs | 8 +- crates/mun_hir/src/code_model/src.rs | 36 ++--- crates/mun_hir/src/diagnostics.rs | 130 +++++------------- crates/mun_hir/src/expr.rs | 39 ++---- .../expr/validator/uninitialized_access.rs | 2 +- crates/mun_hir/src/ids.rs | 10 +- crates/mun_hir/src/in_file.rs | 42 ++++++ crates/mun_hir/src/lib.rs | 1 + crates/mun_hir/src/ty/infer.rs | 26 ++-- crates/mun_hir/src/ty/tests.rs | 8 +- 12 files changed, 134 insertions(+), 184 deletions(-) create mode 100644 crates/mun_hir/src/in_file.rs diff --git a/crates/mun/test/main.mun b/crates/mun/test/main.mun index dd9c9eb89..30003d9bf 100644 --- a/crates/mun/test/main.mun +++ b/crates/mun/test/main.mun @@ -1,8 +1,4 @@ fn foo():int { - break; // error: not in a loop - loop { break 3; break 3.0; } // error: mismatched type - let a:int = loop { break 3.0; } // error: mismatched type - loop { break 3; } - let a:int = loop { break loop { break 3; } } - loop { break loop { break 3.0; } } // error: mismatched type - } + let a:float = 3.0; + a +} diff --git a/crates/mun_hir/src/adt.rs b/crates/mun_hir/src/adt.rs index e9a78e611..c1401bbed 100644 --- a/crates/mun_hir/src/adt.rs +++ b/crates/mun_hir/src/adt.rs @@ -55,19 +55,19 @@ impl StructData { pub(crate) fn struct_data_query(db: &impl DefDatabase, id: StructId) -> Arc { let src = id.source(db); let name = src - .ast + .value .name() .map(|n| n.as_name()) .unwrap_or_else(Name::missing); let memory_kind = src - .ast + .value .memory_type_specifier() .map(|s| s.kind()) .unwrap_or_default(); let mut type_ref_builder = TypeRefBuilder::default(); - let (fields, kind) = match src.ast.kind() { + let (fields, kind) = match src.value.kind() { ast::StructKind::Record(r) => { let fields = r .fields() diff --git a/crates/mun_hir/src/code_model.rs b/crates/mun_hir/src/code_model.rs index 5c628d177..958269871 100644 --- a/crates/mun_hir/src/code_model.rs +++ b/crates/mun_hir/src/code_model.rs @@ -228,26 +228,26 @@ impl FnData { let src = func.source(db); let mut type_ref_builder = TypeRefBuilder::default(); let name = src - .ast + .value .name() .map(|n| n.as_name()) .unwrap_or_else(Name::missing); let visibility = src - .ast + .value .visibility() .map(|_v| Visibility::Public) .unwrap_or(Visibility::Private); let mut params = Vec::new(); - if let Some(param_list) = src.ast.param_list() { + if let Some(param_list) = src.value.param_list() { for param in param_list.params() { let type_ref = type_ref_builder.alloc_from_node_opt(param.ascribed_type().as_ref()); params.push(type_ref); } } - let ret_type = if let Some(type_ref) = src.ast.ret_type().and_then(|rt| rt.type_ref()) { + let ret_type = if let Some(type_ref) = src.value.ret_type().and_then(|rt| rt.type_ref()) { type_ref_builder.alloc_from_node(&type_ref) } else { type_ref_builder.unit() diff --git a/crates/mun_hir/src/code_model/src.rs b/crates/mun_hir/src/code_model/src.rs index bb0843e96..51f9550de 100644 --- a/crates/mun_hir/src/code_model/src.rs +++ b/crates/mun_hir/src/code_model/src.rs @@ -1,29 +1,24 @@ use crate::code_model::{Function, Struct, StructField}; use crate::ids::AstItemDef; -use crate::{DefDatabase, FileId, SourceDatabase}; -use mun_syntax::{ast, AstNode, SyntaxNode}; - -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -pub struct Source { - pub file_id: FileId, - pub ast: T, -} +use crate::in_file::InFile; +use crate::DefDatabase; +use mun_syntax::ast; pub trait HasSource { type Ast; - fn source(self, db: &impl DefDatabase) -> Source; + fn source(self, db: &impl DefDatabase) -> InFile; } impl HasSource for Function { type Ast = ast::FunctionDef; - fn source(self, db: &impl DefDatabase) -> Source { + fn source(self, db: &impl DefDatabase) -> InFile { self.id.source(db) } } impl HasSource for Struct { type Ast = ast::StructDef; - fn source(self, db: &impl DefDatabase) -> Source { + fn source(self, db: &impl DefDatabase) -> InFile { self.id.source(db) } } @@ -31,10 +26,10 @@ impl HasSource for Struct { impl HasSource for StructField { type Ast = ast::RecordFieldDef; - fn source(self, db: &impl DefDatabase) -> Source { + fn source(self, db: &impl DefDatabase) -> InFile { let src = self.parent.source(db); let file_id = src.file_id; - let field_sources = if let ast::StructKind::Record(r) = src.ast.kind() { + let field_sources = if let ast::StructKind::Record(r) = src.value.kind() { r.fields().collect() } else { Vec::new() @@ -47,19 +42,6 @@ impl HasSource for StructField { .unwrap() .0; - Source { file_id, ast } - } -} - -impl Source { - pub(crate) fn map U, U>(self, f: F) -> Source { - Source { - file_id: self.file_id, - ast: f(self.ast), - } - } - - pub(crate) fn file_syntax(&self, db: &impl SourceDatabase) -> SyntaxNode { - db.parse(self.file_id).tree().syntax().clone() + InFile::new(file_id, ast) } } diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs index c99ac70af..8bc6bc487 100644 --- a/crates/mun_hir/src/diagnostics.rs +++ b/crates/mun_hir/src/diagnostics.rs @@ -1,3 +1,4 @@ +use crate::in_file::InFile; use crate::{FileId, HirDatabase, Ty}; use mun_syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr, TextRange}; use std::{any::Any, fmt}; @@ -13,10 +14,9 @@ use std::{any::Any, fmt}; /// diagnostics are transformed into an instance of `Diagnostic` on demand. pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { fn message(&self) -> String; - fn file(&self) -> FileId; - fn syntax_node_ptr(&self) -> SyntaxNodePtr; + fn source(&self) -> InFile; fn highlight_range(&self) -> TextRange { - self.syntax_node_ptr().range() + self.source().value.range() } fn as_any(&self) -> &(dyn Any + Send + 'static); } @@ -28,8 +28,8 @@ pub trait AstDiagnostic { impl dyn Diagnostic { pub fn syntax_node(&self, db: &impl HirDatabase) -> SyntaxNode { - let node = db.parse(self.file()).syntax_node(); - self.syntax_node_ptr().to_node(&node) + let node = db.parse(self.source().file_id).syntax_node(); + self.source().value.to_node(&node) } pub fn downcast_ref(&self) -> Option<&D> { @@ -87,12 +87,8 @@ impl Diagnostic for UnresolvedValue { "undefined value".to_string() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.expr + fn source(&self) -> InFile { + InFile::new(self.file, self.expr) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -111,12 +107,8 @@ impl Diagnostic for UnresolvedType { "undefined type".to_string() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.type_ref.syntax_node_ptr() + fn source(&self) -> InFile { + InFile::new(self.file, self.type_ref.syntax_node_ptr()) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -136,12 +128,8 @@ impl Diagnostic for ExpectedFunction { "expected function type".to_string() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.expr + fn source(&self) -> InFile { + InFile::new(self.file, self.expr) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -165,12 +153,8 @@ impl Diagnostic for ParameterCountMismatch { ) } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.expr + fn source(&self) -> InFile { + InFile::new(self.file, self.expr) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -191,12 +175,8 @@ impl Diagnostic for MismatchedType { "mismatched type".to_string() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.expr + fn source(&self) -> InFile { + InFile::new(self.file, self.expr) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -217,12 +197,8 @@ impl Diagnostic for IncompatibleBranch { "mismatched branches".to_string() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.if_expr + fn source(&self) -> InFile { + InFile::new(self.file, self.if_expr) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -247,12 +223,8 @@ impl Diagnostic for InvalidLHS { "invalid left hand side of expression".to_string() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.lhs + fn source(&self) -> InFile { + InFile::new(self.file, self.lhs) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -272,12 +244,8 @@ impl Diagnostic for MissingElseBranch { "missing else branch".to_string() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.if_expr + fn source(&self) -> InFile { + InFile::new(self.file, self.if_expr) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -298,12 +266,8 @@ impl Diagnostic for CannotApplyBinaryOp { "cannot apply binary operator".to_string() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.expr + fn source(&self) -> InFile { + InFile::new(self.file, self.expr) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -324,12 +288,8 @@ impl Diagnostic for DuplicateDefinition { format!("the name `{}` is defined multiple times", self.name) } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.definition + fn source(&self) -> InFile { + InFile::new(self.file, self.definition) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -348,12 +308,8 @@ impl Diagnostic for ReturnMissingExpression { "`return;` in a function whose return type is not `()`".to_owned() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.return_expr + fn source(&self) -> InFile { + InFile::new(self.file, self.return_expr) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -372,12 +328,8 @@ impl Diagnostic for BreakOutsideLoop { "`break` outside of a loop".to_owned() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.break_expr + fn source(&self) -> InFile { + InFile::new(self.file, self.break_expr) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -396,12 +348,8 @@ impl Diagnostic for BreakWithValueOutsideLoop { "`break` with value can only appear in a `loop`".to_owned() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.break_expr + fn source(&self) -> InFile { + InFile::new(self.file, self.break_expr) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -420,12 +368,8 @@ impl Diagnostic for NoSuchField { "no such field".to_string() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.field + fn source(&self) -> InFile { + InFile::new(self.file, self.field) } fn as_any(&self) -> &(dyn Any + Send + 'static) { @@ -444,12 +388,8 @@ impl Diagnostic for PossiblyUninitializedVariable { "use of possibly-uninitialized variable".to_string() } - fn file(&self) -> FileId { - self.file - } - - fn syntax_node_ptr(&self) -> SyntaxNodePtr { - self.pat + fn source(&self) -> InFile { + InFile::new(self.file, self.pat) } fn as_any(&self) -> &(dyn Any + Send + 'static) { diff --git a/crates/mun_hir/src/expr.rs b/crates/mun_hir/src/expr.rs index 6280aba0e..3ac8aeea7 100644 --- a/crates/mun_hir/src/expr.rs +++ b/crates/mun_hir/src/expr.rs @@ -6,7 +6,7 @@ use crate::{ }; //pub use mun_syntax::ast::PrefixOp as UnaryOp; -use crate::code_model::src::{HasSource, Source}; +use crate::code_model::src::HasSource; use crate::name::AsName; use crate::type_ref::{TypeRef, TypeRefBuilder, TypeRefId, TypeRefMap, TypeRefSourceMap}; use either::Either; @@ -18,6 +18,7 @@ use std::ops::Index; use std::sync::Arc; pub use self::scope::ExprScopes; +use crate::in_file::InFile; use crate::resolve::Resolver; use std::mem; @@ -105,10 +106,10 @@ impl Index for Body { } type ExprPtr = Either, AstPtr>; -type ExprSource = Source; +type ExprSource = InFile; type PatPtr = AstPtr; //Either, AstPtr>; -type PatSource = Source; +type PatSource = InFile; type RecordPtr = AstPtr; @@ -398,13 +399,9 @@ where fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId { let id = self.pats.alloc(pat); self.source_map.pat_map.insert(ptr, id); - self.source_map.pat_map_back.insert( - id, - Source { - file_id: self.current_file_id, - ast: ptr, - }, - ); + self.source_map + .pat_map_back + .insert(id, InFile::new(self.current_file_id, ptr)); id } @@ -412,13 +409,9 @@ where let ptr = Either::Left(ptr); let id = self.exprs.alloc(expr); self.source_map.expr_map.insert(ptr, id); - self.source_map.expr_map_back.insert( - id, - Source { - file_id: self.current_file_id, - ast: ptr, - }, - ); + self.source_map + .expr_map_back + .insert(id, InFile::new(self.current_file_id, ptr)); id } @@ -426,13 +419,9 @@ where let ptr = Either::Right(ptr); let id = self.exprs.alloc(expr); self.source_map.expr_map.insert(ptr, id); - self.source_map.expr_map_back.insert( - id, - Source { - file_id: self.current_file_id, - ast: ptr, - }, - ); + self.source_map + .expr_map_back + .insert(id, InFile::new(self.current_file_id, ptr)); id } @@ -806,7 +795,7 @@ pub(crate) fn body_with_source_map_query( DefWithBody::Function(ref f) => { let src = f.source(db); collector = ExprCollector::new(def, src.file_id, db); - collector.collect_fn_body(&src.ast) + collector.collect_fn_body(&src.value) } } diff --git a/crates/mun_hir/src/expr/validator/uninitialized_access.rs b/crates/mun_hir/src/expr/validator/uninitialized_access.rs index 16d92c497..3c57c4605 100644 --- a/crates/mun_hir/src/expr/validator/uninitialized_access.rs +++ b/crates/mun_hir/src/expr/validator/uninitialized_access.rs @@ -198,7 +198,7 @@ impl<'a, 'b, 'd, D: HirDatabase> ExprValidator<'a, 'b, 'd, D> { pat: body_source_map .expr_syntax(expr) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()), }) } diff --git a/crates/mun_hir/src/ids.rs b/crates/mun_hir/src/ids.rs index b41cd201c..8069ebe6e 100644 --- a/crates/mun_hir/src/ids.rs +++ b/crates/mun_hir/src/ids.rs @@ -1,5 +1,6 @@ +use crate::in_file::InFile; use crate::source_id::{AstId, FileAstId}; -use crate::{code_model::src::Source, DefDatabase, FileId}; +use crate::{DefDatabase, FileId}; use mun_syntax::{ast, AstNode}; use std::hash::{Hash, Hasher}; @@ -78,13 +79,10 @@ pub(crate) trait AstItemDef: salsa::InternKey + Clone { Self::intern(ctx.db, loc) } - fn source(self, db: &impl DefDatabase) -> Source { + fn source(self, db: &impl DefDatabase) -> InFile { let loc = self.lookup_intern(db); let ast = loc.ast_id.to_node(db); - Source { - file_id: loc.ast_id.file_id(), - ast, - } + InFile::new(loc.ast_id.file_id(), ast) } fn file_id(self, db: &impl DefDatabase) -> FileId { diff --git a/crates/mun_hir/src/in_file.rs b/crates/mun_hir/src/in_file.rs new file mode 100644 index 000000000..115ac71e0 --- /dev/null +++ b/crates/mun_hir/src/in_file.rs @@ -0,0 +1,42 @@ +use crate::{FileId, SourceDatabase}; +use mun_syntax::SyntaxNode; + +/// `InFile` stores a value of `T` inside a particular file/syntax tree. +/// +/// Typical usages are: +/// +/// * `InFile` -- syntax node in a file +/// * `InFile` -- ast node in a file +/// * `InFile` -- offset in a file +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)] +pub struct InFile { + pub file_id: FileId, + pub value: T, +} + +impl InFile { + pub fn new(file_id: FileId, value: T) -> InFile { + InFile { file_id, value } + } + + // Similarly, naming here is stupid... + pub fn with_value(&self, value: U) -> InFile { + InFile::new(self.file_id, value) + } + + pub fn map U, U>(self, f: F) -> InFile { + InFile::new(self.file_id, f(self.value)) + } + pub fn as_ref(&self) -> InFile<&T> { + self.with_value(&self.value) + } + pub fn file_syntax(&self, db: &impl SourceDatabase) -> SyntaxNode { + db.parse(self.file_id).syntax_node() + } +} + +impl InFile<&T> { + pub fn cloned(&self) -> InFile { + self.with_value(self.value.clone()) + } +} diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index 59d505143..42560da7d 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -16,6 +16,7 @@ pub mod diagnostics; mod display; mod expr; mod ids; +mod in_file; mod input; pub mod line_index; mod model; diff --git a/crates/mun_hir/src/ty/infer.rs b/crates/mun_hir/src/ty/infer.rs index a1b02e650..2a22ed1c0 100644 --- a/crates/mun_hir/src/ty/infer.rs +++ b/crates/mun_hir/src/ty/infer.rs @@ -838,11 +838,11 @@ mod diagnostics { InferenceDiagnostic::UnresolvedValue { id } => { let expr = match id { ExprOrPatId::ExprId(id) => body.expr_syntax(*id).map(|ptr| { - ptr.ast + ptr.value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()) }), ExprOrPatId::PatId(id) => { - body.pat_syntax(*id).map(|ptr| ptr.ast.syntax_node_ptr()) + body.pat_syntax(*id).map(|ptr| ptr.value.syntax_node_ptr()) } } .unwrap(); @@ -861,7 +861,7 @@ mod diagnostics { let expr = body .expr_syntax(*id) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(ParameterCountMismatch { file, @@ -874,7 +874,7 @@ mod diagnostics { let expr = body .expr_syntax(*id) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(ExpectedFunction { file, @@ -890,7 +890,7 @@ mod diagnostics { let expr = body .expr_syntax(*id) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(MismatchedType { file, @@ -907,7 +907,7 @@ mod diagnostics { let expr = body .expr_syntax(*id) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(IncompatibleBranch { file, @@ -920,7 +920,7 @@ mod diagnostics { let expr = body .expr_syntax(*id) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(MissingElseBranch { file, @@ -932,7 +932,7 @@ mod diagnostics { let expr = body .expr_syntax(*id) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(CannotApplyBinaryOp { file, @@ -945,12 +945,12 @@ mod diagnostics { let id = body .expr_syntax(*id) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); let lhs = body .expr_syntax(*lhs) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(InvalidLHS { file, @@ -962,7 +962,7 @@ mod diagnostics { let id = body .expr_syntax(*id) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(ReturnMissingExpression { file, @@ -973,7 +973,7 @@ mod diagnostics { let id = body .expr_syntax(*id) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(BreakOutsideLoop { file, @@ -984,7 +984,7 @@ mod diagnostics { let id = body .expr_syntax(*id) .unwrap() - .ast + .value .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); sink.push(BreakWithValueOutsideLoop { file, diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index b44c18789..111daf80c 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -268,12 +268,14 @@ fn infer(content: &str) -> String { } // Sort ranges for consistency - types.sort_by_key(|(src_ptr, _)| (src_ptr.ast.range().start(), src_ptr.ast.range().end())); + types.sort_by_key(|(src_ptr, _)| { + (src_ptr.value.range().start(), src_ptr.value.range().end()) + }); for (src_ptr, ty) in &types { - let node = src_ptr.ast.to_node(&src_ptr.file_syntax(&db)); + let node = src_ptr.value.to_node(&src_ptr.file_syntax(&db)); let (range, text) = ( - src_ptr.ast.range(), + src_ptr.value.range(), node.text().to_string().replace("\n", " "), ); write!( From e5f1fe809ec3d116f8ac4e12f9217ab5cdf75813 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Fri, 10 Jan 2020 22:41:29 +0100 Subject: [PATCH 18/21] misc: better diagnostic for uninitialized access --- crates/mun/.gitignore | 1 + crates/mun/test/main.mun | 4 ---- crates/mun_compiler/src/diagnostics.rs | 16 +++++++++++++--- 3 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 crates/mun/.gitignore delete mode 100644 crates/mun/test/main.mun diff --git a/crates/mun/.gitignore b/crates/mun/.gitignore new file mode 100644 index 000000000..b59f7e3a9 --- /dev/null +++ b/crates/mun/.gitignore @@ -0,0 +1 @@ +test/ \ No newline at end of file diff --git a/crates/mun/test/main.mun b/crates/mun/test/main.mun deleted file mode 100644 index 30003d9bf..000000000 --- a/crates/mun/test/main.mun +++ /dev/null @@ -1,4 +0,0 @@ -fn foo():int { - let a:float = 3.0; - a -} diff --git a/crates/mun_compiler/src/diagnostics.rs b/crates/mun_compiler/src/diagnostics.rs index d100118de..b3934806c 100644 --- a/crates/mun_compiler/src/diagnostics.rs +++ b/crates/mun_compiler/src/diagnostics.rs @@ -28,7 +28,7 @@ pub fn diagnostics(db: &impl HirDatabase, file_id: FileId) -> Vec { }); }) .on::(|d| { - let text = d.expr.to_node(&parse.tree().syntax()).text().to_string(); + let text = d.expr.to_node(&parse.syntax_node()).text().to_string(); result.borrow_mut().push(Diagnostic { level: Level::Error, loc: d.highlight_range().into(), @@ -38,7 +38,7 @@ pub fn diagnostics(db: &impl HirDatabase, file_id: FileId) -> Vec { .on::(|d| { let text = d .type_ref - .to_node(&parse.tree().syntax()) + .to_node(&parse.syntax_node()) .syntax() .text() .to_string(); @@ -71,7 +71,7 @@ pub fn diagnostics(db: &impl HirDatabase, file_id: FileId) -> Vec { level: Level::Error, loc: match d.definition.kind() { SyntaxKind::FUNCTION_DEF => { - ast::FunctionDef::cast(d.definition.to_node(&parse.tree().syntax())) + ast::FunctionDef::cast(d.definition.to_node(&parse.syntax_node())) .map(|f| f.signature_range()) .unwrap_or_else(|| d.highlight_range()) .into() @@ -80,6 +80,16 @@ pub fn diagnostics(db: &impl HirDatabase, file_id: FileId) -> Vec { }, message: d.message(), }); + }) + .on::(|d| { + result.borrow_mut().push(Diagnostic { + level: Level::Error, + loc: d.highlight_range().into(), + message: format!( + "use of possibly-uninitialized variable: `{}`", + d.pat.to_node(&parse.syntax_node()).text().to_string() + ), + }) }); Module::from(file_id).diagnostics(db, &mut sink); From 0e7584257087422c531e52856f4297959151f414 Mon Sep 17 00:00:00 2001 From: Wodann Date: Sat, 11 Jan 2020 12:46:33 +0100 Subject: [PATCH 19/21] feat(struct): add validity checks and diagnostics for struct literals --- crates/mun_compiler/src/diagnostics.rs | 14 + crates/mun_hir/src/adt.rs | 14 +- crates/mun_hir/src/diagnostics.rs | 124 ++++++- crates/mun_hir/src/ty/infer.rs | 318 +++++++++++++++--- crates/mun_hir/src/ty/lower.rs | 22 +- .../snapshots/tests__struct_field_index.snap | 54 +-- .../src/ty/snapshots/tests__struct_lit.snap | 33 +- crates/mun_hir/src/ty/tests.rs | 18 +- crates/mun_syntax/src/ast/expr_extensions.rs | 20 +- crates/mun_syntax/src/parsing/grammar/adt.rs | 2 +- .../tests/snapshots/parser__struct_def.snap | 2 +- 11 files changed, 551 insertions(+), 70 deletions(-) diff --git a/crates/mun_compiler/src/diagnostics.rs b/crates/mun_compiler/src/diagnostics.rs index b3934806c..d9d770e42 100644 --- a/crates/mun_compiler/src/diagnostics.rs +++ b/crates/mun_compiler/src/diagnostics.rs @@ -90,6 +90,20 @@ pub fn diagnostics(db: &impl HirDatabase, file_id: FileId) -> Vec { d.pat.to_node(&parse.syntax_node()).text().to_string() ), }) + }) + .on::(|d| { + result.borrow_mut().push(Diagnostic { + level: Level::Error, + loc: ast::FieldExpr::cast(d.expr.to_node(&parse.syntax_node())) + .map(|f| f.field_range()) + .unwrap_or_else(|| d.highlight_range()) + .into(), + message: format!( + "no field `{}` on type `{}`", + d.name, + d.receiver_ty.display(db), + ), + }) }); Module::from(file_id).diagnostics(db, &mut sink); diff --git a/crates/mun_hir/src/adt.rs b/crates/mun_hir/src/adt.rs index c1401bbed..c59248b13 100644 --- a/crates/mun_hir/src/adt.rs +++ b/crates/mun_hir/src/adt.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{fmt, sync::Arc}; use crate::type_ref::{TypeRefBuilder, TypeRefId, TypeRefMap, TypeRefSourceMap}; use crate::{ @@ -34,13 +34,23 @@ pub struct StructFieldId(RawId); impl_arena_id!(StructFieldId); /// A struct's fields' data (record, tuple, or unit struct) -#[derive(Clone, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum StructKind { Record, Tuple, Unit, } +impl fmt::Display for StructKind { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + StructKind::Record => write!(f, "record"), + StructKind::Tuple => write!(f, "tuple"), + StructKind::Unit => write!(f, "unit struct"), + } + } +} + #[derive(Debug, PartialEq, Eq)] pub struct StructData { pub name: Name, diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs index 8bc6bc487..23337a3c9 100644 --- a/crates/mun_hir/src/diagnostics.rs +++ b/crates/mun_hir/src/diagnostics.rs @@ -1,5 +1,6 @@ +use crate::adt::StructKind; use crate::in_file::InFile; -use crate::{FileId, HirDatabase, Ty}; +use crate::{FileId, HirDatabase, Name, Ty}; use mun_syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr, TextRange}; use std::{any::Any, fmt}; @@ -357,6 +358,127 @@ impl Diagnostic for BreakWithValueOutsideLoop { } } +#[derive(Debug)] +pub struct AccessUnknownField { + pub file: FileId, + pub expr: SyntaxNodePtr, + pub receiver_ty: Ty, + pub name: Name, +} + +impl Diagnostic for AccessUnknownField { + fn message(&self) -> String { + "attempted to access a non-existent field in a struct.".to_string() + } + + fn source(&self) -> InFile { + InFile::new(self.file, self.expr) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +#[derive(Debug)] +pub struct FieldCountMismatch { + pub file: FileId, + pub expr: SyntaxNodePtr, + pub expected: usize, + pub found: usize, +} + +impl Diagnostic for FieldCountMismatch { + fn message(&self) -> String { + format!( + "this tuple struct literal has {} field{} but {} field{} supplied", + self.expected, + if self.expected == 1 { "" } else { "s" }, + self.found, + if self.found == 1 { " was" } else { "s were" }, + ) + } + + fn source(&self) -> InFile { + InFile::new(self.file, self.expr) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +#[derive(Debug)] +pub struct MissingFields { + pub file: FileId, + pub fields: SyntaxNodePtr, + pub field_names: Vec, +} + +impl Diagnostic for MissingFields { + fn message(&self) -> String { + use std::fmt::Write; + let mut message = "missing record fields:\n".to_string(); + for field in &self.field_names { + writeln!(message, "- {}", field).unwrap(); + } + message + } + + fn source(&self) -> InFile { + InFile::new(self.file, self.fields) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +#[derive(Debug)] +pub struct MismatchedStructLit { + pub file: FileId, + pub expr: SyntaxNodePtr, + pub expected: StructKind, + pub found: StructKind, +} + +impl Diagnostic for MismatchedStructLit { + fn message(&self) -> String { + format!( + "mismatched struct literal kind. expected `{}`, found `{}`", + self.expected, self.found + ) + } + + fn source(&self) -> InFile { + InFile::new(self.file, self.expr) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +#[derive(Debug)] +pub struct NoFields { + pub file: FileId, + pub receiver_expr: SyntaxNodePtr, + pub found: Ty, +} + +impl Diagnostic for NoFields { + fn message(&self) -> String { + "attempted to access a field on a primitive type.".to_string() + } + + fn source(&self) -> InFile { + InFile::new(self.file, self.receiver_expr) + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} #[derive(Debug)] pub struct NoSuchField { pub file: FileId, diff --git a/crates/mun_hir/src/ty/infer.rs b/crates/mun_hir/src/ty/infer.rs index 2a22ed1c0..822790426 100644 --- a/crates/mun_hir/src/ty/infer.rs +++ b/crates/mun_hir/src/ty/infer.rs @@ -1,9 +1,10 @@ use crate::{ + adt::StructKind, arena::map::ArenaMap, - code_model::{DefWithBody, DefWithStruct}, + code_model::{DefWithBody, DefWithStruct, Struct}, diagnostics::DiagnosticSink, expr, - expr::{Body, Expr, ExprId, Literal, Pat, PatId, Statement}, + expr::{Body, Expr, ExprId, Literal, Pat, PatId, RecordLitField, Statement}, name_resolution::Namespace, resolve::{Resolution, Resolver}, ty::infer::diagnostics::InferenceDiagnostic, @@ -12,8 +13,9 @@ use crate::{ ty::op, ty::{Ty, TypableDef}, type_ref::TypeRefId, - BinaryOp, Function, HirDatabase, Path, TypeCtor, + BinaryOp, Function, HirDatabase, Name, Path, TypeCtor, }; +use rustc_hash::FxHashSet; use std::mem; use std::ops::Index; use std::sync::Arc; @@ -231,7 +233,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { /// Infers the type of the `tgt_expr` fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { - let ty = self.infer_expr_inner(tgt_expr, expected); + let ty = self.infer_expr_inner(tgt_expr, expected, &CheckParams::default()); if !expected.is_none() && ty != expected.ty { self.diagnostics.push(InferenceDiagnostic::MismatchedTypes { expected: expected.ty.clone(), @@ -246,7 +248,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { /// Infer type of expression with possibly implicit coerce to the expected type. Return the type /// after possible coercion. Adds a diagnostic message if coercion failed. fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty { - let ty = self.infer_expr_inner(expr, expected); + let ty = self.infer_expr_inner(expr, expected, &CheckParams::default()); self.coerce_expr_ty(expr, ty, expected) } @@ -268,14 +270,19 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { } /// Infer the type of the given expression. Returns the type of the expression. - fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { + fn infer_expr_inner( + &mut self, + tgt_expr: ExprId, + expected: &Expectation, + check_params: &CheckParams, + ) -> Ty { let body = Arc::clone(&self.body); // avoid borrow checker problem let ty = match &body[tgt_expr] { Expr::Missing => Ty::Unknown, Expr::Path(p) => { // FIXME this could be more efficient... let resolver = expr::resolver_for_expr(self.body.clone(), self.db, tgt_expr); - self.infer_path_expr(&resolver, p, tgt_expr.into()) + self.infer_path_expr(&resolver, p, tgt_expr, check_params) .unwrap_or(Ty::Unknown) } Expr::If { @@ -348,7 +355,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { Some(field) => Some(field), None => { self.diagnostics.push(InferenceDiagnostic::NoSuchField { - expr: tgt_expr, + id: tgt_expr, field: idx, }); None @@ -360,6 +367,9 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { if let Some(expr) = spread { self.infer_expr(*expr, &Expectation::has_type(ty.clone())); } + if let Some(s) = ty.as_struct() { + self.check_record_lit(tgt_expr, s, &fields); + } ty } Expr::Field { expr, name } => { @@ -369,13 +379,22 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { match s.field(self.db, name).map(|field| field.ty(self.db)) { Some(field_ty) => field_ty, None => { - // TODO: Unknown struct field + self.diagnostics + .push(InferenceDiagnostic::AccessUnknownField { + id: tgt_expr, + receiver_ty, + name: name.clone(), + }); + Ty::Unknown } } } _ => { - // TODO: Expected receiver to be struct type + self.diagnostics.push(InferenceDiagnostic::NoFields { + id: *expr, + found: receiver_ty, + }); Ty::Unknown } } @@ -440,38 +459,140 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { args: &[ExprId], _expected: &Expectation, ) -> Ty { - let callee_ty = self.infer_expr(callee, &Expectation::none()); - let (param_tys, ret_ty) = match callee_ty.callable_sig(self.db) { - Some(sig) => (sig.params().to_vec(), sig.ret().clone()), - None => { + let callee_ty = self.infer_expr_inner( + callee, + &Expectation::none(), + &CheckParams { + is_unit_struct: false, + }, + ); + + match callee_ty { + ty_app!(TypeCtor::Struct(s)) => { + // Erroneously found either a unit struct or tuple struct literal + let struct_data = s.data(self.db); + self.diagnostics + .push(InferenceDiagnostic::MismatchedStructLit { + id: tgt_expr, + expected: struct_data.kind, + found: StructKind::Tuple, + }); + + // Still derive subtypes + for arg in args.iter() { + self.infer_expr(*arg, &Expectation::none()); + } + + callee_ty + } + ty_app!(TypeCtor::FnDef(def)) => { + // Found either a tuple struct literal or function + let sig = callee_ty.callable_sig(self.db).unwrap(); + let (param_tys, ret_ty) = (sig.params().to_vec(), sig.ret().clone()); + self.check_call_argument_count( + tgt_expr, + def.is_struct(), + args.len(), + param_tys.len(), + ); + for (&arg, param_ty) in args.iter().zip(param_tys.iter()) { + self.infer_expr_coerce(arg, &Expectation::has_type(param_ty.clone())); + } + + ret_ty + } + _ => { self.diagnostics .push(InferenceDiagnostic::ExpectedFunction { id: callee, found: callee_ty, }); - (Vec::new(), Ty::Unknown) + Ty::Unknown } - }; - self.check_call_arguments(tgt_expr, args, ¶m_tys); - ret_ty + } } - /// Checks whether the specified passed arguments match the parameters of a callable definition. - fn check_call_arguments(&mut self, tgt_expr: ExprId, args: &[ExprId], param_tys: &[Ty]) { - if args.len() != param_tys.len() { + /// Checks whether the specified struct type is a unit struct. + fn check_unit_struct_lit(&mut self, tgt_expr: ExprId, expected: Struct) { + let struct_data = expected.data(self.db); + if struct_data.kind != StructKind::Unit { self.diagnostics - .push(InferenceDiagnostic::ParameterCountMismatch { + .push(InferenceDiagnostic::MismatchedStructLit { id: tgt_expr, - found: args.len(), - expected: param_tys.len(), - }) + expected: struct_data.kind, + found: StructKind::Unit, + }); } - for (&arg, param_ty) in args.iter().zip(param_tys.iter()) { - self.infer_expr_coerce(arg, &Expectation::has_type(param_ty.clone())); + } + + /// Checks whether the number of passed arguments matches the number of parameters of a + /// callable definition. + fn check_call_argument_count( + &mut self, + tgt_expr: ExprId, + is_tuple_lit: bool, + num_args: usize, + num_params: usize, + ) { + if num_args != num_params { + self.diagnostics.push(if is_tuple_lit { + InferenceDiagnostic::FieldCountMismatch { + id: tgt_expr, + found: num_args, + expected: num_params, + } + } else { + InferenceDiagnostic::ParameterCountMismatch { + id: tgt_expr, + found: num_args, + expected: num_params, + } + }) } } - fn infer_path_expr(&mut self, resolver: &Resolver, path: &Path, id: ExprOrPatId) -> Option { + // Checks whether the passed fields match the fields of a struct definition. + fn check_record_lit(&mut self, tgt_expr: ExprId, expected: Struct, fields: &[RecordLitField]) { + let struct_data = expected.data(self.db); + if struct_data.kind != StructKind::Record { + self.diagnostics + .push(InferenceDiagnostic::MismatchedStructLit { + id: tgt_expr, + expected: struct_data.kind, + found: StructKind::Record, + }); + return; + } + + let lit_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); + let missed_fields: Vec = struct_data + .fields + .iter() + .filter_map(|(_f, d)| { + let name = d.name.clone(); + if lit_fields.contains(&name) { + None + } else { + Some(name) + } + }) + .collect(); + + if !missed_fields.is_empty() { + self.diagnostics.push(InferenceDiagnostic::MissingFields { + id: tgt_expr, + names: missed_fields, + }); + } + } + + fn infer_path_expr( + &mut self, + resolver: &Resolver, + path: &Path, + id: ExprId, + check_params: &CheckParams, + ) -> Option { let resolution = match resolver .resolve_path_without_assoc_items(self.db, path) .take_values() @@ -479,7 +600,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { Some(resolution) => resolution, None => { self.diagnostics - .push(InferenceDiagnostic::UnresolvedValue { id }); + .push(InferenceDiagnostic::UnresolvedValue { id: id.into() }); return None; } }; @@ -494,6 +615,11 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { let typable: Option = def.into(); let typable = typable?; let ty = self.db.type_for_def(typable, Namespace::Values); + if check_params.is_unit_struct { + if let Some(s) = ty.as_struct() { + self.check_unit_struct_lit(id, s); + } + } Some(ty) } } @@ -602,7 +728,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { let ty = if let Some(expr) = tail { // Perform coercion of the trailing expression unless the expression has a Never return // type because we want the block to get the Never type in that case. - let ty = self.infer_expr_inner(expr, expected); + let ty = self.infer_expr_inner(expr, expected, &CheckParams::default()); if let ty_app!(TypeCtor::Never) = ty { Ty::simple(TypeCtor::Never) } else { @@ -638,7 +764,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { // Infer the type of the break expression let ty = if let Some(expr) = expr { - self.infer_expr_inner(expr, &expected) + self.infer_expr_inner(expr, &expected, &CheckParams::default()) } else { Ty::Empty }; @@ -738,6 +864,20 @@ impl Expectation { } } +/// Parameters for toggling validation checks. +struct CheckParams { + /// Checks whether a `Expr::Path` of type struct, is actually a unit struct + is_unit_struct: bool, +} + +impl Default for CheckParams { + fn default() -> Self { + Self { + is_unit_struct: true, + } + } +} + #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] pub(crate) enum ExprOrPatId { ExprId(ExprId), @@ -758,16 +898,18 @@ impl From for ExprOrPatId { mod diagnostics { use crate::diagnostics::{ - BreakOutsideLoop, BreakWithValueOutsideLoop, CannotApplyBinaryOp, ExpectedFunction, - IncompatibleBranch, InvalidLHS, MismatchedType, MissingElseBranch, NoSuchField, + AccessUnknownField, BreakOutsideLoop, BreakWithValueOutsideLoop, CannotApplyBinaryOp, + ExpectedFunction, FieldCountMismatch, IncompatibleBranch, InvalidLHS, MismatchedStructLit, + MismatchedType, MissingElseBranch, MissingFields, NoFields, NoSuchField, ParameterCountMismatch, ReturnMissingExpression, }; use crate::{ + adt::StructKind, code_model::src::HasSource, diagnostics::{DiagnosticSink, UnresolvedType, UnresolvedValue}, ty::infer::ExprOrPatId, type_ref::TypeRefId, - ExprId, Function, HirDatabase, Ty, + ExprId, Function, HirDatabase, Name, Ty, }; #[derive(Debug, PartialEq, Eq, Clone)] @@ -819,8 +961,31 @@ mod diagnostics { BreakWithValueOutsideLoop { id: ExprId, }, + AccessUnknownField { + id: ExprId, + receiver_ty: Ty, + name: Name, + }, + FieldCountMismatch { + id: ExprId, + found: usize, + expected: usize, + }, + MissingFields { + id: ExprId, + names: Vec, + }, + MismatchedStructLit { + id: ExprId, + expected: StructKind, + found: StructKind, + }, + NoFields { + id: ExprId, + found: Ty, + }, NoSuchField { - expr: ExprId, + id: ExprId, field: usize, }, } @@ -991,10 +1156,85 @@ mod diagnostics { break_expr: id, }); } - InferenceDiagnostic::NoSuchField { expr, field } => { - let file = owner.source(db).file_id; - let field = owner.body_source_map(db).field_syntax(*expr, *field).into(); - sink.push(NoSuchField { file, field }) + InferenceDiagnostic::AccessUnknownField { + id, + receiver_ty, + name, + } => { + let expr = body + .expr_syntax(*id) + .unwrap() + .value + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); + sink.push(AccessUnknownField { + file, + expr, + receiver_ty: receiver_ty.clone(), + name: name.clone(), + }) + } + InferenceDiagnostic::FieldCountMismatch { + id, + expected, + found, + } => { + let expr = body + .expr_syntax(*id) + .unwrap() + .value + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); + sink.push(FieldCountMismatch { + file, + expr, + expected: *expected, + found: *found, + }) + } + InferenceDiagnostic::MissingFields { id, names } => { + let fields = body + .expr_syntax(*id) + .unwrap() + .value + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); + + sink.push(MissingFields { + file, + fields, + field_names: names.to_vec(), + }); + } + InferenceDiagnostic::MismatchedStructLit { + id, + expected, + found, + } => { + let expr = body + .expr_syntax(*id) + .unwrap() + .value + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); + sink.push(MismatchedStructLit { + file, + expr, + expected: *expected, + found: *found, + }); + } + InferenceDiagnostic::NoFields { id, found } => { + let expr = body + .expr_syntax(*id) + .unwrap() + .value + .either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr()); + sink.push(NoFields { + file, + receiver_expr: expr, + found: found.clone(), + }) + } + InferenceDiagnostic::NoSuchField { id, field } => { + let field = owner.body_source_map(db).field_syntax(*id, *field).into(); + sink.push(NoSuchField { file, field }); } } } diff --git a/crates/mun_hir/src/ty/lower.rs b/crates/mun_hir/src/ty/lower.rs index df8b71583..a96ca912d 100644 --- a/crates/mun_hir/src/ty/lower.rs +++ b/crates/mun_hir/src/ty/lower.rs @@ -171,6 +171,22 @@ pub enum CallableDef { } impl_froms!(CallableDef: Function, Struct); +impl CallableDef { + pub fn is_function(self) -> bool { + match self { + CallableDef::Function(_) => true, + _ => false, + } + } + + pub fn is_struct(self) -> bool { + match self { + CallableDef::Struct(_) => true, + _ => false, + } + } +} + /// Build the declared type of an item. This depends on the namespace; e.g. for /// `struct Foo(usize)`, we have two types: The type of the struct itself, and /// the constructor function `(usize) -> Foo` which lives in the values @@ -237,10 +253,10 @@ fn fn_sig_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> FnSig { /// Build the type of a struct constructor. fn type_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> Ty { let struct_data = db.struct_data(def.id); - if struct_data.kind == StructKind::Unit { - type_for_struct(db, def) - } else { + if struct_data.kind == StructKind::Tuple { Ty::simple(TypeCtor::FnDef(def.into())) + } else { + type_for_struct(db, def) } } diff --git a/crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap b/crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap index e3dcc68d2..0ca3292e0 100644 --- a/crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap +++ b/crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap @@ -1,22 +1,38 @@ --- source: crates/mun_hir/src/ty/tests.rs -expression: "struct Foo {\n a: float,\n b: int,\n}\nstruct Bar(float, int)\n\nfn main() {\n let foo = Foo { a: 1.23, b: 4 };\n foo.a\n foo.b\n let bar = Bar(1.23, 4);\n bar.0\n bar.1;\n}" +expression: "struct Foo {\n a: float,\n b: int,\n}\nstruct Bar(float, int)\nstruct Baz;\n\nfn main() {\n let foo = Foo { a: 1.23, b: 4 };\n foo.a\n foo.b\n foo.c // error: attempted to access a non-existent field in a struct.\n let bar = Bar(1.23, 4);\n bar.0\n bar.1\n bar.2 // error: attempted to access a non-existent field in a struct.\n let baz = Baz;\n baz.a // error: attempted to access a non-existent field in a struct.\n let f = 1.0\n f.0; // error: attempted to access a field on a primitive type.\n}" --- -[75; 184) '{ ...r.1; }': nothing -[85; 88) 'foo': Foo -[91; 112) 'Foo { ...b: 4 }': Foo -[100; 104) '1.23': float -[109; 110) '4': int -[118; 121) 'foo': Foo -[118; 123) 'foo.a': float -[128; 131) 'foo': Foo -[128; 133) 'foo.b': int -[142; 145) 'bar': Bar -[148; 151) 'Bar': Bar -[148; 160) 'Bar(1.23, 4)': Bar -[152; 156) '1.23': float -[158; 159) '4': int -[166; 169) 'bar': Bar -[166; 171) 'bar.0': float -[176; 179) 'bar': Bar -[176; 181) 'bar.1': int +[150; 155): attempted to access a non-existent field in a struct. +[272; 277): attempted to access a non-existent field in a struct. +[365; 370): attempted to access a non-existent field in a struct. +[455; 456): attempted to access a field on a primitive type. +[87; 520) '{ ...ype. }': nothing +[97; 100) 'foo': Foo +[103; 124) 'Foo { ...b: 4 }': Foo +[112; 116) '1.23': float +[121; 122) '4': int +[130; 133) 'foo': Foo +[130; 135) 'foo.a': float +[140; 143) 'foo': Foo +[140; 145) 'foo.b': int +[150; 153) 'foo': Foo +[150; 155) 'foo.c': {unknown} +[228; 231) 'bar': Bar +[234; 237) 'Bar': Bar +[234; 246) 'Bar(1.23, 4)': Bar +[238; 242) '1.23': float +[244; 245) '4': int +[252; 255) 'bar': Bar +[252; 257) 'bar.0': float +[262; 265) 'bar': Bar +[262; 267) 'bar.1': int +[272; 275) 'bar': Bar +[272; 277) 'bar.2': {unknown} +[350; 353) 'baz': Baz +[356; 359) 'Baz': Baz +[365; 368) 'baz': Baz +[365; 370) 'baz.a': {unknown} +[443; 444) 'f': float +[447; 450) '1.0': float +[455; 456) 'f': float +[455; 458) 'f.0': {unknown} diff --git a/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap b/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap index b419ff135..f4bc34a76 100644 --- a/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap +++ b/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap @@ -1,8 +1,18 @@ --- source: crates/mun_hir/src/ty/tests.rs -expression: "struct Foo;\nstruct Bar {\n a: float,\n}\nstruct Baz(float, int);\n\nfn main() {\n let a: Foo = Foo;\n let b: Bar = Bar { a: 1.23, };\n let c = Baz(1.23, 1);\n}" +expression: "struct Foo;\nstruct Bar {\n a: float,\n}\nstruct Baz(float, int);\n\nfn main() {\n let a: Foo = Foo;\n let b: Bar = Bar { a: 1.23, };\n let c = Baz(1.23, 1);\n\n let a = Foo{}; // error: mismatched struct literal kind. expected `unit struct`, found `record`\n let a = Foo(); // error: mismatched struct literal kind. expected `unit struct`, found `tuple`\n let b = Bar; // error: mismatched struct literal kind. expected `record`, found `unit struct`\n let b = Bar(); // error: mismatched struct literal kind. expected `record`, found `tuple`\n let b = Bar{}; // error: missing record fields: a\n let c = Baz; // error: mismatched struct literal kind. expected `tuple`, found `unit struct`\n let c = Baz{}; // error: mismatched struct literal kind. expected `tuple`, found `record`\n let c = Baz(); // error: this tuple struct literal has 2 fields but 0 fields were supplied\n}" --- -[76; 162) '{ ... 1); }': nothing +[174; 179): mismatched struct literal kind. expected `unit struct`, found `record` +[274; 279): mismatched struct literal kind. expected `unit struct`, found `tuple` +[373; 376): mismatched struct literal kind. expected `record`, found `unit struct` +[471; 476): mismatched struct literal kind. expected `record`, found `tuple` +[565; 570): missing record fields: +- a + +[619; 622): mismatched struct literal kind. expected `tuple`, found `unit struct` +[716; 721): mismatched struct literal kind. expected `tuple`, found `record` +[810; 815): this tuple struct literal has 2 fields but 0 fields were supplied +[76; 894) '{ ...lied }': nothing [86; 87) 'a': Foo [95; 98) 'Foo': Foo [108; 109) 'b': Bar @@ -13,3 +23,22 @@ expression: "struct Foo;\nstruct Bar {\n a: float,\n}\nstruct Baz(float, int) [147; 159) 'Baz(1.23, 1)': Baz [151; 155) '1.23': float [157; 158) '1': int +[170; 171) 'a': Foo +[174; 179) 'Foo{}': Foo +[270; 271) 'a': Foo +[274; 277) 'Foo': Foo +[274; 279) 'Foo()': Foo +[369; 370) 'b': Bar +[373; 376) 'Bar': Bar +[467; 468) 'b': Bar +[471; 474) 'Bar': Bar +[471; 476) 'Bar()': Bar +[561; 562) 'b': Bar +[565; 570) 'Bar{}': Bar +[615; 616) 'c': Baz +[619; 622) 'Baz': Baz +[712; 713) 'c': Baz +[716; 721) 'Baz{}': Baz +[806; 807) 'c': Baz +[810; 813) 'Baz': Baz +[810; 815) 'Baz()': Baz diff --git a/crates/mun_hir/src/ty/tests.rs b/crates/mun_hir/src/ty/tests.rs index 111daf80c..32e19c149 100644 --- a/crates/mun_hir/src/ty/tests.rs +++ b/crates/mun_hir/src/ty/tests.rs @@ -207,6 +207,15 @@ fn struct_lit() { let a: Foo = Foo; let b: Bar = Bar { a: 1.23, }; let c = Baz(1.23, 1); + + let a = Foo{}; // error: mismatched struct literal kind. expected `unit struct`, found `record` + let a = Foo(); // error: mismatched struct literal kind. expected `unit struct`, found `tuple` + let b = Bar; // error: mismatched struct literal kind. expected `record`, found `unit struct` + let b = Bar(); // error: mismatched struct literal kind. expected `record`, found `tuple` + let b = Bar{}; // error: missing record fields: a + let c = Baz; // error: mismatched struct literal kind. expected `tuple`, found `unit struct` + let c = Baz{}; // error: mismatched struct literal kind. expected `tuple`, found `record` + let c = Baz(); // error: this tuple struct literal has 2 fields but 0 fields were supplied } "#, ) @@ -221,14 +230,21 @@ fn struct_field_index() { b: int, } struct Bar(float, int) + struct Baz; fn main() { let foo = Foo { a: 1.23, b: 4 }; foo.a foo.b + foo.c // error: attempted to access a non-existent field in a struct. let bar = Bar(1.23, 4); bar.0 - bar.1; + bar.1 + bar.2 // error: attempted to access a non-existent field in a struct. + let baz = Baz; + baz.a // error: attempted to access a non-existent field in a struct. + let f = 1.0 + f.0; // error: attempted to access a field on a primitive type. } "#, ) diff --git a/crates/mun_syntax/src/ast/expr_extensions.rs b/crates/mun_syntax/src/ast/expr_extensions.rs index e20fb88cf..d4bf6abe8 100644 --- a/crates/mun_syntax/src/ast/expr_extensions.rs +++ b/crates/mun_syntax/src/ast/expr_extensions.rs @@ -3,7 +3,7 @@ use crate::ast::{child_opt, AstChildren, Literal}; use crate::{ ast, AstNode, SyntaxKind::{self, *}, - SyntaxToken, + SyntaxToken, TextRange, TextUnit, }; #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] @@ -128,6 +128,24 @@ impl ast::FieldExpr { None } } + + pub fn field_range(&self) -> TextRange { + let field_name = self.name_ref().map(|n| n.syntax().text_range()); + + let field_index = self.index_token().map(|i| i.text_range()); + + let start = field_name + .map(|f| f.start()) + .or_else(|| field_index.map(|i| TextUnit::from_usize(i.start().to_usize() + 1))) + .unwrap_or_else(|| self.syntax().text_range().start()); + + let end = field_name + .map(|f| f.end()) + .or_else(|| field_index.map(|f| f.end())) + .unwrap_or_else(|| self.syntax().text_range().end()); + + TextRange::from_to(start, end) + } } #[derive(Clone, Debug, PartialEq, Eq, Hash)] diff --git a/crates/mun_syntax/src/parsing/grammar/adt.rs b/crates/mun_syntax/src/parsing/grammar/adt.rs index 4423bf5a4..202ee8460 100644 --- a/crates/mun_syntax/src/parsing/grammar/adt.rs +++ b/crates/mun_syntax/src/parsing/grammar/adt.rs @@ -12,7 +12,7 @@ pub(super) fn struct_def(p: &mut Parser, m: Marker) { T!['{'] => record_field_def_list(p), T!['('] => tuple_field_def_list(p), _ => { - p.error("expected a ';', or '{'"); + p.error("expected a ';', '{', or '('"); } } m.complete(p, STRUCT_DEF); diff --git a/crates/mun_syntax/src/tests/snapshots/parser__struct_def.snap b/crates/mun_syntax/src/tests/snapshots/parser__struct_def.snap index cdb5aed26..0116f1b42 100644 --- a/crates/mun_syntax/src/tests/snapshots/parser__struct_def.snap +++ b/crates/mun_syntax/src/tests/snapshots/parser__struct_def.snap @@ -211,7 +211,7 @@ SOURCE_FILE@[0; 396) NAME_REF@[392; 395) IDENT@[392; 395) "int" R_PAREN@[395; 396) ")" -error Offset(10): expected a ';', or '{' +error Offset(10): expected a ';', '{', or '(' error Offset(74): expected a declaration error Offset(153): expected a field declaration error Offset(305): expected a type From 26621dffda16aa5f5d26c5c1b26538699755f660 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Sat, 11 Jan 2020 13:13:30 +0100 Subject: [PATCH 20/21] feat: initial very leaky implementation of heap allocation --- crates/mun_codegen/src/code_gen/symbols.rs | 36 ++++----- crates/mun_codegen/src/db.rs | 5 ++ crates/mun_codegen/src/ir/adt.rs | 47 +++++------- crates/mun_codegen/src/ir/body.rs | 76 ++++++++++++++++--- crates/mun_codegen/src/ir/ty.rs | 18 ++++- .../src/snapshots/test__gc_struct.snap | 31 ++++++++ crates/mun_codegen/src/test.rs | 27 +++++-- crates/mun_hir/src/lib.rs | 1 + crates/mun_hir/src/ty/infer/place_expr.rs | 2 +- crates/mun_runtime/src/test.rs | 17 +++++ crates/mun_syntax/src/ast/extensions.rs | 22 +++++- crates/mun_syntax/src/parsing/parser.rs | 2 +- crates/mun_syntax/src/tests/parser.rs | 4 +- .../parser__memory_type_specifier.snap | 44 +++++------ 14 files changed, 234 insertions(+), 98 deletions(-) create mode 100644 crates/mun_codegen/src/snapshots/test__gc_struct.snap diff --git a/crates/mun_codegen/src/code_gen/symbols.rs b/crates/mun_codegen/src/code_gen/symbols.rs index 3f73b3142..62593375c 100644 --- a/crates/mun_codegen/src/code_gen/symbols.rs +++ b/crates/mun_codegen/src/code_gen/symbols.rs @@ -7,7 +7,7 @@ use hir::{CallableDef, Ty, TypeCtor}; use inkwell::{ attributes::Attribute, module::{Linkage, Module}, - types::{AnyTypeEnum, StructType}, + types::StructType, values::{FunctionValue, IntValue, PointerValue, StructValue, UnnamedAddress}, AddressSpace, }; @@ -215,25 +215,21 @@ fn gen_struct_info_array<'a, D: IrDatabase>( ) -> GlobalValue { let struct_infos: Vec = structs .map(|(s, _)| { - if let AnyTypeEnum::StructType(_) = db.type_ir(s.ty(db)) { - let name_str = intern_string(&module, &s.name(db).to_string()); - - let fields = s.fields(db); - let field_types = fields.iter().map(|field| field.ty(db)); - let (fields, num_fields) = gen_type_info_array(db, module, types, field_types); - - types.struct_info_type.const_named_struct(&[ - name_str.into(), - fields.into(), - module - .get_context() - .i16_type() - .const_int(num_fields as u64, false) - .into(), - ]) - } else { - unreachable!(); - } + let name_str = intern_string(&module, &s.name(db).to_string()); + + let fields = s.fields(db); + let field_types = fields.iter().map(|field| field.ty(db)); + let (fields, num_fields) = gen_type_info_array(db, module, types, field_types); + + types.struct_info_type.const_named_struct(&[ + name_str.into(), + fields.into(), + module + .get_context() + .i16_type() + .const_int(num_fields as u64, false) + .into(), + ]) }) .collect(); diff --git a/crates/mun_codegen/src/db.rs b/crates/mun_codegen/src/db.rs index 17203db37..2c4977b50 100644 --- a/crates/mun_codegen/src/db.rs +++ b/crates/mun_codegen/src/db.rs @@ -3,6 +3,7 @@ use crate::{code_gen::symbols::TypeInfo, ir::module::ModuleIR, Context}; +use inkwell::types::StructType; use inkwell::{types::AnyTypeEnum, OptimizationLevel}; use mun_target::spec::Target; use std::sync::Arc; @@ -27,6 +28,10 @@ pub trait IrDatabase: hir::HirDatabase { #[salsa::invoke(crate::ir::ty::ir_query)] fn type_ir(&self, ty: hir::Ty) -> AnyTypeEnum; + /// Given a struct, return the corresponding IR type. + #[salsa::invoke(crate::ir::ty::struct_ty_query)] + fn struct_ty(&self, s: hir::Struct) -> StructType; + /// Given a `hir::FileId` generate code for the module. #[salsa::invoke(crate::ir::module::ir_query)] fn module_ir(&self, file: hir::FileId) -> Arc; diff --git a/crates/mun_codegen/src/ir/adt.rs b/crates/mun_codegen/src/ir/adt.rs index b477bb79c..cb26abff1 100644 --- a/crates/mun_codegen/src/ir/adt.rs +++ b/crates/mun_codegen/src/ir/adt.rs @@ -1,38 +1,33 @@ //use crate::ir::module::Types; use crate::ir::try_convert_any_to_basic; use crate::IrDatabase; -use inkwell::types::{AnyTypeEnum, BasicTypeEnum, StructType}; -use inkwell::values::BasicValueEnum; +use inkwell::types::{BasicTypeEnum, StructType}; +use inkwell::values::{BasicValueEnum, StructValue}; pub(super) fn gen_struct_decl(db: &impl IrDatabase, s: hir::Struct) -> StructType { - if let AnyTypeEnum::StructType(struct_type) = db.type_ir(s.ty(db)) { - if struct_type.is_opaque() { - let field_types: Vec = s - .fields(db) - .iter() - .map(|field| { - let field_type = field.ty(db); - try_convert_any_to_basic(db.type_ir(field_type)) - .expect("could not convert field type") - }) - .collect(); + let struct_type = db.struct_ty(s); + if struct_type.is_opaque() { + let field_types: Vec = s + .fields(db) + .iter() + .map(|field| { + let field_type = field.ty(db); + try_convert_any_to_basic(db.type_ir(field_type)) + .expect("could not convert field type") + }) + .collect(); - struct_type.set_body(&field_types, false); - } - struct_type - } else { - unreachable!() + struct_type.set_body(&field_types, false); } + struct_type } -pub(crate) fn gen_named_struct_lit( +/// Constructs a struct literal value of type `s`. +pub(super) fn gen_named_struct_lit( db: &impl IrDatabase, - ty: hir::Ty, + s: hir::Struct, values: &[BasicValueEnum], -) -> BasicValueEnum { - if let inkwell::types::AnyTypeEnum::StructType(struct_type) = db.type_ir(ty) { - struct_type.const_named_struct(&values).into() - } else { - unreachable!("at this stage there must be a struct type"); - } +) -> StructValue { + let struct_ty = db.struct_ty(s); + struct_ty.const_named_struct(values) } diff --git a/crates/mun_codegen/src/ir/body.rs b/crates/mun_codegen/src/ir/body.rs index c98f294c7..b5d38b460 100644 --- a/crates/mun_codegen/src/ir/body.rs +++ b/crates/mun_codegen/src/ir/body.rs @@ -1,7 +1,4 @@ -use crate::{ - ir::adt::gen_named_struct_lit, ir::dispatch_table::DispatchTable, ir::try_convert_any_to_basic, - IrDatabase, -}; +use crate::{ir::dispatch_table::DispatchTable, ir::try_convert_any_to_basic, IrDatabase}; use hir::{ ArenaId, ArithOp, BinaryOp, Body, CmpOp, Expr, ExprId, HirDisplay, InferenceResult, Literal, Name, Ordering, Pat, PatId, Path, Resolution, Resolver, Statement, TypeCtor, @@ -14,6 +11,7 @@ use inkwell::{ }; use std::{collections::HashMap, mem, sync::Arc}; +use crate::ir::adt::gen_named_struct_lit; use inkwell::basic_block::BasicBlock; use inkwell::values::PointerValue; @@ -174,7 +172,8 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { name, } => { let ptr = self.gen_field(expr, *receiver_expr, name); - Some(self.builder.build_load(ptr, &name.to_string())) + let value = self.builder.build_load(ptr, &name.to_string()); + Some(value) } _ => unimplemented!("unimplemented expr type {:?}", &body[expr]), } @@ -215,6 +214,28 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { self.module.get_context().const_struct(&[], false).into() } + /// Allocate a struct literal either on the stack or the heap based on the type of the struct. + fn gen_struct_alloc( + &mut self, + hir_struct: hir::Struct, + args: &[BasicValueEnum], + ) -> BasicValueEnum { + let struct_lit = gen_named_struct_lit(self.db, hir_struct, &args); + match hir_struct.data(self.db).memory_kind { + hir::StructMemoryKind::Value => struct_lit.into(), + hir::StructMemoryKind::GC => { + // TODO: Root memory in GC + let struct_ir_ty = self.db.struct_ty(hir_struct); + let struct_ptr: PointerValue = self + .builder + .build_malloc(struct_ir_ty, &hir_struct.name(self.db).to_string()); + let struct_value = gen_named_struct_lit(self.db, hir_struct, &args); + self.builder.build_store(struct_ptr, struct_value); + struct_ptr.into() + } + } + } + /// Generates IR for a record literal, e.g. `Foo { a: 1.23, b: 4 }` fn gen_record_lit( &mut self, @@ -222,29 +243,32 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { fields: &[hir::RecordLitField], ) -> BasicValueEnum { let struct_ty = self.infer[type_expr].clone(); + let hir_struct = struct_ty.as_struct().unwrap(); // Can only really get here if the type is a struct let fields: Vec = fields .iter() .map(|field| self.gen_expr(field.expr).expect("expected a field value")) .collect(); - gen_named_struct_lit(self.db, struct_ty, &fields) + self.gen_struct_alloc(hir_struct, &fields) } /// Generates IR for a named tuple literal, e.g. `Foo(1.23, 4)` fn gen_named_tuple_lit(&mut self, type_expr: ExprId, args: &[ExprId]) -> BasicValueEnum { let struct_ty = self.infer[type_expr].clone(); + let hir_struct = struct_ty.as_struct().unwrap(); // Can only really get here if the type is a struct let args: Vec = args .iter() .map(|expr| self.gen_expr(*expr).expect("expected a field value")) .collect(); - gen_named_struct_lit(self.db, struct_ty, &args) + self.gen_struct_alloc(hir_struct, &args) } /// Generates IR for a unit struct literal, e.g `Foo` - fn gen_unit_struct_lit(&self, type_expr: ExprId) -> BasicValueEnum { + fn gen_unit_struct_lit(&mut self, type_expr: ExprId) -> BasicValueEnum { let struct_ty = self.infer[type_expr].clone(); - gen_named_struct_lit(self.db, struct_ty, &[]) + let hir_struct = struct_ty.as_struct().unwrap(); // Can only really get here if the type is a struct + self.gen_struct_alloc(hir_struct, &[]) } /// Generates IR for the specified block expression. @@ -313,7 +337,7 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { /// Generates IR for looking up a certain path expression. fn gen_path_expr( - &self, + &mut self, path: &Path, expr: ExprId, resolver: &Resolver, @@ -339,6 +363,22 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { } } + /// Given an expression and the type of the expression, optionally dereference the value. + fn opt_deref_value(&mut self, expr: ExprId, value: BasicValueEnum) -> BasicValueEnum { + match &self.infer[expr] { + hir::Ty::Apply(hir::ApplicationTy { + ctor: hir::TypeCtor::Struct(s), + .. + }) => match s.data(self.db).memory_kind { + hir::StructMemoryKind::GC => { + self.builder.build_load(value.into_pointer_value(), "deref") + } + hir::StructMemoryKind::Value => value, + }, + _ => value, + } + } + /// Generates IR for looking up a certain path expression. fn gen_path_place_expr( &self, @@ -390,10 +430,12 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { ) -> Option { let lhs = self .gen_expr(lhs_expr) + .map(|value| self.opt_deref_value(lhs_expr, value)) .expect("no lhs value") .into_float_value(); let rhs = self .gen_expr(rhs_expr) + .map(|value| self.opt_deref_value(rhs_expr, value)) .expect("no rhs value") .into_float_value(); match op { @@ -447,10 +489,12 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { ) -> Option { let lhs = self .gen_expr(lhs_expr) + .map(|value| self.opt_deref_value(lhs_expr, value)) .expect("no lhs value") .into_int_value(); let rhs = self .gen_expr(rhs_expr) + .map(|value| self.opt_deref_value(lhs_expr, value)) .expect("no rhs value") .into_int_value(); match op { @@ -573,7 +617,10 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { else_branch: Option, ) -> Option { // Generate IR for the condition - let condition_ir = self.gen_expr(condition)?.into_int_value(); + let condition_ir = self + .gen_expr(condition) + .map(|value| self.opt_deref_value(condition, value))? + .into_int_value(); // Generate the code blocks to branch to let context = self.module.get_context(); @@ -708,7 +755,9 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { // Generate condition block self.builder.position_at_end(&cond_block); - let condition_ir = self.gen_expr(condition_expr); + let condition_ir = self + .gen_expr(condition_expr) + .map(|value| self.opt_deref_value(condition_expr, value)); if let Some(condition_ir) = condition_ir { self.builder.build_conditional_branch( condition_ir.into_int_value(), @@ -777,6 +826,9 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { .into(); let receiver_ptr = self.gen_place_expr(receiver_expr); + let receiver_ptr = self + .opt_deref_value(receiver_expr, receiver_ptr.into()) + .into_pointer_value(); unsafe { self.builder.build_struct_gep( receiver_ptr, diff --git a/crates/mun_codegen/src/ir/ty.rs b/crates/mun_codegen/src/ir/ty.rs index 2ab1cfb9e..dace7496e 100644 --- a/crates/mun_codegen/src/ir/ty.rs +++ b/crates/mun_codegen/src/ir/ty.rs @@ -1,7 +1,8 @@ use super::try_convert_any_to_basic; use crate::IrDatabase; use hir::{ApplicationTy, CallableDef, Ty, TypeCtor}; -use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum}; +use inkwell::types::{AnyTypeEnum, BasicType, BasicTypeEnum, StructType}; +use inkwell::AddressSpace; /// Given a mun type, construct an LLVM IR type pub(crate) fn ir_query(db: &impl IrDatabase, ty: Ty) -> AnyTypeEnum { @@ -29,12 +30,21 @@ pub(crate) fn ir_query(db: &impl IrDatabase, ty: Ty) -> AnyTypeEnum { AnyTypeEnum::FunctionType(fn_type) } - TypeCtor::FnDef(CallableDef::Struct(s)) | TypeCtor::Struct(s) => { - let name = s.name(db).to_string(); - context.opaque_struct_type(&name).into() + TypeCtor::Struct(s) => { + let struct_ty = db.struct_ty(s); + match s.data(db).memory_kind { + hir::StructMemoryKind::GC => struct_ty.ptr_type(AddressSpace::Generic).into(), + hir::StructMemoryKind::Value => struct_ty.into(), + } } _ => unreachable!(), }, _ => unreachable!("unknown type can not be converted"), } } + +/// Returns the LLVM IR type of the specified struct +pub fn struct_ty_query(db: &impl IrDatabase, s: hir::Struct) -> StructType { + let name = s.name(db).to_string(); + db.context().opaque_struct_type(&name) +} diff --git a/crates/mun_codegen/src/snapshots/test__gc_struct.snap b/crates/mun_codegen/src/snapshots/test__gc_struct.snap new file mode 100644 index 000000000..2441fedc5 --- /dev/null +++ b/crates/mun_codegen/src/snapshots/test__gc_struct.snap @@ -0,0 +1,31 @@ +--- +source: crates/mun_codegen/src/test.rs +expression: "struct(gc) Foo { a: int, b: int };\n\nfn foo() {\n let a = Foo { a: 3, b: 4 };\n a.b += 3;\n let b = a;\n}" +--- +; ModuleID = 'main.mun' +source_filename = "main.mun" + +%Foo = type { i64, i64 } + +define void @foo() { +body: + %b4 = alloca %Foo* + %a = alloca %Foo* + %malloccall = tail call i8* @malloc(i32 trunc (i64 mul nuw (i64 ptrtoint (i64* getelementptr (i64, i64* null, i32 1) to i64), i64 2) to i32)) + %Foo = bitcast i8* %malloccall to %Foo* + store %Foo { i64 3, i64 4 }, %Foo* %Foo + store %Foo* %Foo, %Foo** %a + %deref = load %Foo*, %Foo** %a + %Foo.b = getelementptr inbounds %Foo, %Foo* %deref, i32 0, i32 1 + %b = load i64, i64* %Foo.b + %add = add i64 %b, 3 + %deref1 = load %Foo*, %Foo** %a + %Foo.b2 = getelementptr inbounds %Foo, %Foo* %deref1, i32 0, i32 1 + store i64 %add, i64* %Foo.b2 + %a3 = load %Foo*, %Foo** %a + store %Foo* %a3, %Foo** %b4 + ret void +} + +declare noalias i8* @malloc(i32) + diff --git a/crates/mun_codegen/src/test.rs b/crates/mun_codegen/src/test.rs index b05490cdd..8579d725a 100644 --- a/crates/mun_codegen/src/test.rs +++ b/crates/mun_codegen/src/test.rs @@ -366,9 +366,9 @@ fn while_expr() { fn struct_test() { test_snapshot_unoptimized( r#" - struct Bar(float, int, bool, Foo); - struct Foo { a: int }; - struct Baz; + struct(value) Bar(float, int, bool, Foo); + struct(value) Foo { a: int }; + struct(value) Baz; fn foo() { let a: Foo = Foo { a: 5 }; let b: Bar = Bar(1.23, 4, true, a); @@ -382,13 +382,13 @@ fn struct_test() { fn field_expr() { test_snapshot( r#" - struct Bar(float, Foo); - struct Foo { a: int }; + struct(value) Bar(float, Foo); + struct(value) Foo { a: int }; fn bar_0(bar: Bar): float { bar.0 } - + fn bar_1(bar: Bar): Foo { bar.1 } @@ -416,6 +416,21 @@ fn field_expr() { ) } +#[test] +fn gc_struct() { + test_snapshot_unoptimized( + r#" + struct(gc) Foo { a: int, b: int }; + + fn foo() { + let a = Foo { a: 3, b: 4 }; + a.b += 3; + let b = a; + } + "#, + ) +} + fn test_snapshot(text: &str) { test_snapshot_with_optimization(text, OptimizationLevel::Default); } diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index 42560da7d..2fcf97665 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -65,4 +65,5 @@ use crate::{ source_id::{AstIdMap, FileAstId}, }; +pub use self::adt::StructMemoryKind; pub use self::code_model::{FnData, Function, Module, ModuleDef, Struct, Visibility}; diff --git a/crates/mun_hir/src/ty/infer/place_expr.rs b/crates/mun_hir/src/ty/infer/place_expr.rs index e8039f9ea..903506ca4 100644 --- a/crates/mun_hir/src/ty/infer/place_expr.rs +++ b/crates/mun_hir/src/ty/infer/place_expr.rs @@ -10,7 +10,7 @@ impl<'a, D: HirDatabase> InferenceResultBuilder<'a, D> { let body = Arc::clone(&self.body); // avoid borrow checker problem match &body[expr] { Expr::Path(p) => self.check_place_path(resolver, p), - Expr::Field { .. } => false, // TODO: add support for assigning structs + Expr::Field { .. } => true, _ => false, } } diff --git a/crates/mun_runtime/src/test.rs b/crates/mun_runtime/src/test.rs index c661448c5..e7cd53ff5 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -370,3 +370,20 @@ fn struct_info() { } } } + +#[test] +fn fields() { + let mut driver = TestDriver::new( + r#" + struct(gc) Foo { a:int, b:int }; + fn main(foo:int):bool { + let a = Foo { a: foo, b: foo }; + a.a += a.b; + let result = a; + result.a += a.b; + result.a == a.a + } + "#, + ); + assert_invoke_eq!(bool, true, driver, "main", 48); +} diff --git a/crates/mun_syntax/src/ast/extensions.rs b/crates/mun_syntax/src/ast/extensions.rs index 8e3649843..6e4670c76 100644 --- a/crates/mun_syntax/src/ast/extensions.rs +++ b/crates/mun_syntax/src/ast/extensions.rs @@ -136,12 +136,26 @@ impl Default for StructMemoryKind { impl ast::MemoryTypeSpecifier { pub fn kind(&self) -> StructMemoryKind { - match self.syntax.first_child_or_token().map(|s| s.kind()) { - Some(SyntaxKind::VALUE_KW) => StructMemoryKind::Value, - Some(SyntaxKind::GC_KW) => StructMemoryKind::GC, - _ => StructMemoryKind::default(), + if self.is_value() { + StructMemoryKind::Value + } else if self.is_gc() { + StructMemoryKind::GC + } else { + StructMemoryKind::default() } } + + fn is_gc(&self) -> bool { + self.syntax() + .children_with_tokens() + .any(|it| it.kind() == SyntaxKind::GC_KW) + } + + fn is_value(&self) -> bool { + self.syntax() + .children_with_tokens() + .any(|it| it.kind() == SyntaxKind::VALUE_KW) + } } impl ast::StructDef { diff --git a/crates/mun_syntax/src/parsing/parser.rs b/crates/mun_syntax/src/parsing/parser.rs index e3951c8ff..25ce3fb4c 100644 --- a/crates/mun_syntax/src/parsing/parser.rs +++ b/crates/mun_syntax/src/parsing/parser.rs @@ -132,7 +132,7 @@ impl<'t> Parser<'t> { /// Advances the parser by one token, remapping its kind. /// This is useful to create contextual keywords from - /// identifiers. For example, the lexer creates an `union` + /// identifiers. For example, the lexer creates a `union` /// *identifier* token, but the parser remaps it to the /// `union` keyword, and keyword is what ends up in the /// final tree. diff --git a/crates/mun_syntax/src/tests/parser.rs b/crates/mun_syntax/src/tests/parser.rs index a01414a01..082e9cd02 100644 --- a/crates/mun_syntax/src/tests/parser.rs +++ b/crates/mun_syntax/src/tests/parser.rs @@ -275,8 +275,8 @@ fn memory_type_specifier() { struct Foo {}; struct(gc) Baz {}; struct(value) Baz {}; - struct() Err1 {}; // expected memory type specifier - struct(foo) Err2 {}; // expected memory type specifier + struct() Err1 {}; // error: expected memory type specifier + struct(foo) Err2 {}; // error: expected memory type specifier "#, ) } diff --git a/crates/mun_syntax/src/tests/snapshots/parser__memory_type_specifier.snap b/crates/mun_syntax/src/tests/snapshots/parser__memory_type_specifier.snap index 595a4a8de..893f9a227 100644 --- a/crates/mun_syntax/src/tests/snapshots/parser__memory_type_specifier.snap +++ b/crates/mun_syntax/src/tests/snapshots/parser__memory_type_specifier.snap @@ -1,8 +1,8 @@ --- source: crates/mun_syntax/src/tests/parser.rs -expression: "struct Foo {};\nstruct(gc) Baz {};\nstruct(value) Baz {};\nstruct() Err1 {}; // expected memory type specifier\nstruct(foo) Err2 {}; // expected memory type specifier" +expression: "struct Foo {};\nstruct(gc) Baz {};\nstruct(value) Baz {};\nstruct() Err1 {}; // error: expected memory type specifier\nstruct(foo) Err2 {}; // error: expected memory type specifier" --- -SOURCE_FILE@[0; 165) +SOURCE_FILE@[0; 179) STRUCT_DEF@[0; 14) STRUCT_KW@[0; 6) "struct" WHITESPACE@[6; 7) " " @@ -58,25 +58,25 @@ SOURCE_FILE@[0; 165) R_CURLY@[71; 72) "}" SEMI@[72; 73) ";" WHITESPACE@[73; 77) " " - COMMENT@[77; 110) "// expected memory ty ..." - WHITESPACE@[110; 111) "\n" - STRUCT_DEF@[111; 131) - STRUCT_KW@[111; 117) "struct" - MEMORY_TYPE_SPECIFIER@[117; 122) - L_PAREN@[117; 118) "(" - ERROR@[118; 121) - IDENT@[118; 121) "foo" - R_PAREN@[121; 122) ")" - WHITESPACE@[122; 123) " " - NAME@[123; 127) - IDENT@[123; 127) "Err2" - WHITESPACE@[127; 128) " " - RECORD_FIELD_DEF_LIST@[128; 131) - L_CURLY@[128; 129) "{" - R_CURLY@[129; 130) "}" - SEMI@[130; 131) ";" - WHITESPACE@[131; 132) " " - COMMENT@[132; 165) "// expected memory ty ..." + COMMENT@[77; 117) "// error: expected me ..." + WHITESPACE@[117; 118) "\n" + STRUCT_DEF@[118; 138) + STRUCT_KW@[118; 124) "struct" + MEMORY_TYPE_SPECIFIER@[124; 129) + L_PAREN@[124; 125) "(" + ERROR@[125; 128) + IDENT@[125; 128) "foo" + R_PAREN@[128; 129) ")" + WHITESPACE@[129; 130) " " + NAME@[130; 134) + IDENT@[130; 134) "Err2" + WHITESPACE@[134; 135) " " + RECORD_FIELD_DEF_LIST@[135; 138) + L_CURLY@[135; 136) "{" + R_CURLY@[136; 137) "}" + SEMI@[137; 138) ";" + WHITESPACE@[138; 139) " " + COMMENT@[139; 179) "// error: expected me ..." error Offset(63): expected memory type specifier -error Offset(118): expected memory type specifier +error Offset(125): expected memory type specifier From a2c7ce7effaa2e522c94b68cb14bd38712f1587b Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Sat, 11 Jan 2020 13:49:04 +0100 Subject: [PATCH 21/21] misc: suggestions from review --- crates/mun_codegen/src/code_gen/symbols.rs | 6 ++---- crates/mun_hir/src/ty.rs | 13 ++++++++++--- crates/mun_hir/src/ty/lower.rs | 4 ++-- .../src/ty/snapshots/tests__struct_field_index.snap | 2 +- .../mun_hir/src/ty/snapshots/tests__struct_lit.snap | 8 ++++---- 5 files changed, 19 insertions(+), 14 deletions(-) diff --git a/crates/mun_codegen/src/code_gen/symbols.rs b/crates/mun_codegen/src/code_gen/symbols.rs index 62593375c..0a97f9a31 100644 --- a/crates/mun_codegen/src/code_gen/symbols.rs +++ b/crates/mun_codegen/src/code_gen/symbols.rs @@ -3,7 +3,7 @@ use crate::ir::dispatch_table::DispatchTable; use crate::ir::function; use crate::values::{BasicValue, GlobalValue}; use crate::IrDatabase; -use hir::{CallableDef, Ty, TypeCtor}; +use hir::{Ty, TypeCtor}; use inkwell::{ attributes::Attribute, module::{Linkage, Module}, @@ -49,9 +49,7 @@ pub fn type_info_query(db: &impl IrDatabase, ty: Ty) -> TypeInfo { TypeCtor::Float => TypeInfo::from_name("@core::float"), TypeCtor::Int => TypeInfo::from_name("@core::int"), TypeCtor::Bool => TypeInfo::from_name("@core::bool"), - TypeCtor::Struct(s) | TypeCtor::FnDef(CallableDef::Struct(s)) => { - TypeInfo::from_name(s.name(db).to_string()) - } + TypeCtor::Struct(s) => TypeInfo::from_name(s.name(db).to_string()), _ => unreachable!("{:?} unhandled", ctor), }, _ => unreachable!(), diff --git a/crates/mun_hir/src/ty.rs b/crates/mun_hir/src/ty.rs index af38b12be..6f3f84cf3 100644 --- a/crates/mun_hir/src/ty.rs +++ b/crates/mun_hir/src/ty.rs @@ -4,6 +4,7 @@ mod op; use crate::display::{HirDisplay, HirFormatter}; use crate::ty::infer::TypeVarId; +use crate::ty::lower::fn_sig_for_struct_constructor; use crate::{HirDatabase, Struct}; pub(crate) use infer::infer_query; pub use infer::InferenceResult; @@ -190,9 +191,7 @@ impl HirDisplay for ApplicationTy { TypeCtor::Float => write!(f, "float"), TypeCtor::Int => write!(f, "int"), TypeCtor::Bool => write!(f, "bool"), - TypeCtor::Struct(def) | TypeCtor::FnDef(CallableDef::Struct(def)) => { - write!(f, "{}", def.name(f.db)) - } + TypeCtor::Struct(def) => write!(f, "{}", def.name(f.db)), TypeCtor::Never => write!(f, "never"), TypeCtor::FnDef(CallableDef::Function(def)) => { let sig = fn_sig_for_fn(f.db, def); @@ -202,6 +201,14 @@ impl HirDisplay for ApplicationTy { f.write_joined(sig.params(), ", ")?; write!(f, ") -> {}", sig.ret().display(f.db)) } + TypeCtor::FnDef(CallableDef::Struct(def)) => { + let sig = fn_sig_for_struct_constructor(f.db, def); + let name = def.name(f.db); + write!(f, "ctor {}", name)?; + write!(f, "(")?; + f.write_joined(sig.params(), ", ")?; + write!(f, ") -> {}", sig.ret().display(f.db)) + } } } } diff --git a/crates/mun_hir/src/ty/lower.rs b/crates/mun_hir/src/ty/lower.rs index a96ca912d..5c8ca8b33 100644 --- a/crates/mun_hir/src/ty/lower.rs +++ b/crates/mun_hir/src/ty/lower.rs @@ -226,7 +226,7 @@ pub(crate) fn callable_item_sig(db: &impl HirDatabase, def: CallableDef) -> FnSi } } -pub fn fn_sig_for_fn(db: &impl HirDatabase, def: Function) -> FnSig { +pub(crate) fn fn_sig_for_fn(db: &impl HirDatabase, def: Function) -> FnSig { let data = def.data(db); let resolver = def.resolver(db); let params = data @@ -238,7 +238,7 @@ pub fn fn_sig_for_fn(db: &impl HirDatabase, def: Function) -> FnSig { FnSig::from_params_and_return(params, ret) } -fn fn_sig_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> FnSig { +pub(crate) fn fn_sig_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> FnSig { let data = def.data(db); let resolver = def.resolver(db); let params = data diff --git a/crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap b/crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap index 0ca3292e0..2673b3c23 100644 --- a/crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap +++ b/crates/mun_hir/src/ty/snapshots/tests__struct_field_index.snap @@ -18,7 +18,7 @@ expression: "struct Foo {\n a: float,\n b: int,\n}\nstruct Bar(float, int) [150; 153) 'foo': Foo [150; 155) 'foo.c': {unknown} [228; 231) 'bar': Bar -[234; 237) 'Bar': Bar +[234; 237) 'Bar': ctor Bar(float, int) -> Bar [234; 246) 'Bar(1.23, 4)': Bar [238; 242) '1.23': float [244; 245) '4': int diff --git a/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap b/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap index f4bc34a76..79d20d127 100644 --- a/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap +++ b/crates/mun_hir/src/ty/snapshots/tests__struct_lit.snap @@ -19,7 +19,7 @@ expression: "struct Foo;\nstruct Bar {\n a: float,\n}\nstruct Baz(float, int) [117; 133) 'Bar { ....23, }': Bar [126; 130) '1.23': float [143; 144) 'c': Baz -[147; 150) 'Baz': Baz +[147; 150) 'Baz': ctor Baz(float, int) -> Baz [147; 159) 'Baz(1.23, 1)': Baz [151; 155) '1.23': float [157; 158) '1': int @@ -35,10 +35,10 @@ expression: "struct Foo;\nstruct Bar {\n a: float,\n}\nstruct Baz(float, int) [471; 476) 'Bar()': Bar [561; 562) 'b': Bar [565; 570) 'Bar{}': Bar -[615; 616) 'c': Baz -[619; 622) 'Baz': Baz +[615; 616) 'c': ctor Baz(float, int) -> Baz +[619; 622) 'Baz': ctor Baz(float, int) -> Baz [712; 713) 'c': Baz [716; 721) 'Baz{}': Baz [806; 807) 'c': Baz -[810; 813) 'Baz': Baz +[810; 813) 'Baz': ctor Baz(float, int) -> Baz [810; 815) 'Baz()': Baz