From a9a5aa0c2f2f7d920eee4f2343675334f21013c5 Mon Sep 17 00:00:00 2001 From: German Nikolishin Date: Mon, 19 Feb 2024 19:07:19 +0000 Subject: [PATCH] map types --- crates/semantics/src/ast.rs | 27 ++++-------- crates/semantics/src/contract.rs | 49 ++++++++++++++++++--- crates/semantics/src/global_symbol.rs | 2 +- crates/semantics/src/lib.rs | 17 ++++++++ crates/semantics/src/tests.rs | 38 ++++++++++++++++ crates/semantics/src/types.rs | 62 +++++++++++++++++++++++++++ 6 files changed, 170 insertions(+), 25 deletions(-) create mode 100644 crates/semantics/src/tests.rs diff --git a/crates/semantics/src/ast.rs b/crates/semantics/src/ast.rs index 2629dcd..11d5260 100644 --- a/crates/semantics/src/ast.rs +++ b/crates/semantics/src/ast.rs @@ -1,10 +1,12 @@ use derive_node::Node; use folidity_parser::{ - ast::{BinaryExpression, Identifier, UnaryExpression}, + ast::{BinaryExpression, Identifier, MappingRelation, UnaryExpression}, Span, }; use indexmap::IndexMap; +use crate::global_symbol::SymbolInfo; + #[derive(Clone, Debug, PartialEq, Node)] pub struct Type { pub loc: Span, @@ -25,10 +27,11 @@ pub enum TypeVariant { Set(Set), List(List), Mapping(Mapping), - Function(Box), - Struct(usize), - Model(usize), - State(usize), + Function(SymbolInfo), + Struct(SymbolInfo), + Model(SymbolInfo), + Enum(SymbolInfo), + State(SymbolInfo), } #[derive(Clone, Debug, PartialEq)] @@ -47,20 +50,6 @@ pub struct List { pub ty: Box, } -#[derive(Clone, Debug, PartialEq, Node)] -pub struct MappingRelation { - pub loc: Span, - pub injective: bool, - pub partial: bool, - pub surjective: bool, -} - -impl MappingRelation { - pub fn is_bijective(&self) -> bool { - self.injective && self.surjective - } -} - #[derive(Clone, Debug, PartialEq, Node)] pub struct Mapping { pub from_ty: Box, diff --git a/crates/semantics/src/contract.rs b/crates/semantics/src/contract.rs index b112f3e..267cdc9 100644 --- a/crates/semantics/src/contract.rs +++ b/crates/semantics/src/contract.rs @@ -6,7 +6,8 @@ use folidity_parser::{ast::Source, Span}; use indexmap::IndexMap; use crate::ast::{ - EnumDeclaration, FunctionDeclaration, ModelDeclaration, StateDeclaration, StructDeclaration, + EnumDeclaration, FunctionDeclaration, ModelDeclaration, Param, StateDeclaration, + StructDeclaration, }; use crate::decls::DelayedFields; use crate::global_symbol::SymbolInfo; @@ -23,6 +24,7 @@ const RESERVED_TYPE_NAMES: &[&str] = &[ /// Semantically analysed contract definition. /// Ready for the the next stage of compilation. +#[derive(Debug, Clone, Default)] pub struct ContractDefinition { /// List of all enums in the contract. pub enums: Vec, @@ -69,6 +71,7 @@ impl ContractDefinition { delay } + /// Resolves fields during the second pass. pub fn resolve_fields(&mut self, delay: &DelayedDeclarations) {} fn analyze_enum(&mut self, item: &parsed_ast::EnumDeclaration) { @@ -113,7 +116,43 @@ impl ContractDefinition { ); } - pub fn analyze_struct( + fn analyze_fields(&mut self, fields: &[&parsed_ast::Param], ident: &Identifier) { + let analyzed_fields: Vec = Vec::new(); + if fields.is_empty() { + self.diagnostics.push(Report::semantic_error( + ident.loc.clone(), + format!("`{}` has no fields", &ident.name), + )); + return; + } + + for field in fields { + let duplicates: Vec<&parsed_ast::Param> = fields + .iter() + .filter(|f| f.name.name == field.name.name) + .copied() + .collect(); + if !duplicates.is_empty() { + let start = duplicates + .iter() + .min_by(|x, y| x.loc.start.cmp(&y.loc.start)) + .map(|p| p.loc.start) + .unwrap(); + let end = duplicates + .iter() + .max_by(|x, y| x.loc.end.cmp(&y.loc.end)) + .map(|p| p.loc.end) + .unwrap(); + + self.diagnostics.push(Report::semantic_error( + Span { start, end }, + format!("`{}` is duplicated", field.name.name), + )); + } + } + } + + fn analyze_struct( &mut self, item: &parsed_ast::StructDeclaration, delay: &mut DelayedDeclarations, @@ -140,7 +179,7 @@ impl ContractDefinition { } } - pub fn analyze_model( + fn analyze_model( &mut self, item: &parsed_ast::ModelDeclaration, delay: &mut DelayedDeclarations, @@ -169,7 +208,7 @@ impl ContractDefinition { } } - pub fn analyze_state( + fn analyze_state( &mut self, item: &parsed_ast::StateDeclaration, delay: &mut DelayedDeclarations, @@ -203,7 +242,7 @@ impl ContractDefinition { /// # Errors /// - The symbol table is already in use. /// - The symbol name is a reserved word. - fn add_global_symbol(&mut self, ident: &Identifier, symbol: GlobalSymbol) -> bool { + pub fn add_global_symbol(&mut self, ident: &Identifier, symbol: GlobalSymbol) -> bool { if RESERVED_TYPE_NAMES.contains(&ident.name.as_str()) { self.diagnostics.push(Report::semantic_error( ident.loc.clone(), diff --git a/crates/semantics/src/global_symbol.rs b/crates/semantics/src/global_symbol.rs index 84fd088..fe18aa0 100644 --- a/crates/semantics/src/global_symbol.rs +++ b/crates/semantics/src/global_symbol.rs @@ -10,7 +10,7 @@ pub enum GlobalSymbol { } /// Global user defined symbol info. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct SymbolInfo { /// Locations of the global symbol. pub loc: Span, diff --git a/crates/semantics/src/lib.rs b/crates/semantics/src/lib.rs index 3eecf80..a472313 100644 --- a/crates/semantics/src/lib.rs +++ b/crates/semantics/src/lib.rs @@ -1,6 +1,23 @@ +use contract::ContractDefinition; +use folidity_parser::ast::Source; + mod ast; mod contract; mod decls; mod global_symbol; mod symtable; mod types; + +#[cfg(test)] +mod tests; + +/// Resolves the contract's parsed tree into the semantically analysed and typed-checked definition. +/// +/// # Errors +/// [`ContractDefinition`] may contain errors stored in the `diagnostics` field. +pub fn resolve_semantics(source: &Source) -> ContractDefinition { + let mut definition = ContractDefinition::default(); + let delay = definition.resolve_declarations(source); + + definition +} diff --git a/crates/semantics/src/tests.rs b/crates/semantics/src/tests.rs new file mode 100644 index 0000000..7d0efd1 --- /dev/null +++ b/crates/semantics/src/tests.rs @@ -0,0 +1,38 @@ +use crate::resolve_semantics; +use folidity_parser::parse; + +const DECL_SRC: &str = r#" +struct MyStruct { + a: int, + b: address +} + +enum MyEnum { + A, + B +} + +model MyModel: ParentModel { + c: int, + b: string, +} + +state NoState(MyModel) +"#; + +#[test] +fn test_first_pass() { + let tree = parse(DECL_SRC).unwrap(); + + let def = resolve_semantics(&tree); + assert_eq!(def.structs.len(), 1); + assert_eq!(def.enums.len(), 1); + assert_eq!(def.models.len(), 1); + assert_eq!(def.states.len(), 1); + + let e = &def.enums[0]; + assert_eq!(e.variants.len(), 2); + + assert!(e.variants.contains_key("A")); + assert!(e.variants.contains_key("B")); +} diff --git a/crates/semantics/src/types.rs b/crates/semantics/src/types.rs index 8b13789..78f1b09 100644 --- a/crates/semantics/src/types.rs +++ b/crates/semantics/src/types.rs @@ -1 +1,63 @@ +use crate::ast::{List, Mapping, Set, Type, TypeVariant}; +use crate::contract::ContractDefinition; +use crate::global_symbol::GlobalSymbol; +use folidity_diagnostics::Report; +use folidity_parser::ast as parsed_ast; +use folidity_parser::ast::Identifier; +/// Maps type from parsed AST to semantically resolved type. +/// - Primitive types are simply mapped 1-1. +/// - User defined types (e.g. structs, enums) are looked up in the global symbol table. +/// - List types are recursively mapped. +pub fn map_type(contract: &mut ContractDefinition, ty: &parsed_ast::Type) -> Result { + let variant = match &ty.ty { + parsed_ast::TypeVariant::Int => TypeVariant::Int, + parsed_ast::TypeVariant::Uint => TypeVariant::Uint, + parsed_ast::TypeVariant::Float => TypeVariant::Float, + parsed_ast::TypeVariant::Char => TypeVariant::Char, + parsed_ast::TypeVariant::String => TypeVariant::String, + parsed_ast::TypeVariant::Hex => TypeVariant::Hex, + parsed_ast::TypeVariant::Address => TypeVariant::Address, + parsed_ast::TypeVariant::Unit => TypeVariant::Address, + parsed_ast::TypeVariant::Bool => TypeVariant::Bool, + parsed_ast::TypeVariant::Set(s) => { + let set_ty = map_type(contract, &s.ty)?; + TypeVariant::Set(Set::new(Box::new(set_ty))) + } + parsed_ast::TypeVariant::List(l) => { + let list_ty = map_type(contract, &l.ty)?; + TypeVariant::List(List::new(Box::new(list_ty))) + } + parsed_ast::TypeVariant::Mapping(m) => { + let m_from_ty = map_type(contract, &m.from_ty)?; + let m_to_ty = map_type(contract, &m.to_ty)?; + TypeVariant::Mapping(Mapping::new( + Box::new(m_from_ty), + m.relation.clone(), + Box::new(m_to_ty), + )) + } + parsed_ast::TypeVariant::Custom(user_ty) => { + if let Some(symbol) = contract.declaration_symbols.get(&user_ty.name) { + match symbol { + GlobalSymbol::Struct(info) => TypeVariant::Struct(info.clone()), + GlobalSymbol::Model(info) => TypeVariant::Model(info.clone()), + GlobalSymbol::Enum(info) => TypeVariant::Enum(info.clone()), + GlobalSymbol::State(info) => TypeVariant::State(info.clone()), + GlobalSymbol::Function(info) => TypeVariant::Function(info.clone()), + } + } else { + contract.diagnostics.push(Report::semantic_error( + user_ty.loc.clone(), + format!("`{}` is not defined", user_ty.name), + )); + return Err(()); + } + } + }; + + Ok(Type { + loc: ty.loc.clone(), + ty: variant, + }) +}