diff --git a/Cargo.lock b/Cargo.lock index 25c2bd6..0b34ba8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -357,6 +357,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "concrete_check" +version = "0.1.0" +dependencies = [ + "ariadne", + "concrete_ast", + "concrete_session", + "itertools 0.12.0", + "thiserror", +] + [[package]] name = "concrete_codegen_mlir" version = "0.1.0" @@ -364,6 +375,7 @@ dependencies = [ "bumpalo", "cc", "concrete_ast", + "concrete_check", "concrete_session", "itertools 0.12.0", "llvm-sys", @@ -379,6 +391,7 @@ dependencies = [ "ariadne", "clap", "concrete_ast", + "concrete_check", "concrete_codegen_mlir", "concrete_parser", "concrete_session", @@ -409,10 +422,6 @@ dependencies = [ "ariadne", ] -[[package]] -name = "concrete_type_checker" -version = "0.1.0" - [[package]] name = "convert_case" version = "0.6.0" diff --git a/Cargo.toml b/Cargo.toml index d8e9029..27930ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [workspace] resolver = "2" -members = [ "crates/concrete","crates/concrete_ast", "crates/concrete_codegen_mlir", "crates/concrete_driver", "crates/concrete_parser", "crates/concrete_session", "crates/concrete_type_checker"] +members = [ "crates/concrete","crates/concrete_ast", "crates/concrete_codegen_mlir", "crates/concrete_driver", "crates/concrete_parser", "crates/concrete_session", "crates/concrete_check"] [profile.release] lto = true diff --git a/crates/concrete_ast/src/common.rs b/crates/concrete_ast/src/common.rs index c94d349..ef7a45f 100644 --- a/crates/concrete_ast/src/common.rs +++ b/crates/concrete_ast/src/common.rs @@ -1,3 +1,5 @@ +use std::ops::Range; + #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)] pub struct Span { pub from: usize, @@ -10,6 +12,12 @@ impl Span { } } +impl From for Range { + fn from(val: Span) -> Self { + val.from..val.to + } +} + #[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)] pub struct DocString { contents: String, diff --git a/crates/concrete_ast/src/functions.rs b/crates/concrete_ast/src/functions.rs index 3635665..c0e1539 100644 --- a/crates/concrete_ast/src/functions.rs +++ b/crates/concrete_ast/src/functions.rs @@ -1,5 +1,5 @@ use crate::{ - common::{DocString, GenericParam, Ident}, + common::{DocString, GenericParam, Ident, Span}, statements::Statement, types::TypeSpec, }; @@ -17,6 +17,7 @@ pub struct FunctionDecl { pub struct FunctionDef { pub decl: FunctionDecl, pub body: Vec, + pub span: Span, } #[derive(Clone, Debug, Eq, Hash, PartialEq)] diff --git a/crates/concrete_ast/src/imports.rs b/crates/concrete_ast/src/imports.rs index f158b9b..615230c 100644 --- a/crates/concrete_ast/src/imports.rs +++ b/crates/concrete_ast/src/imports.rs @@ -1,7 +1,8 @@ -use crate::common::Ident; +use crate::common::{Ident, Span}; #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub struct ImportStmt { pub module: Vec, pub symbols: Vec, + pub span: Span, } diff --git a/crates/concrete_ast/src/modules.rs b/crates/concrete_ast/src/modules.rs index d67b590..5ed22cf 100644 --- a/crates/concrete_ast/src/modules.rs +++ b/crates/concrete_ast/src/modules.rs @@ -1,5 +1,5 @@ use crate::{ - common::{DocString, Ident}, + common::{DocString, Ident, Span}, constants::ConstantDef, functions::FunctionDef, imports::ImportStmt, @@ -13,6 +13,7 @@ pub struct Module { pub imports: Vec, pub name: Ident, pub contents: Vec, + pub span: Span, } #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/crates/concrete_ast/src/types.rs b/crates/concrete_ast/src/types.rs index 93116a8..1b0d386 100644 --- a/crates/concrete_ast/src/types.rs +++ b/crates/concrete_ast/src/types.rs @@ -35,6 +35,14 @@ impl TypeSpec { TypeSpec::Array { is_ref, .. } => *is_ref, } } + + pub fn get_name(&self) -> String { + match self { + TypeSpec::Simple { name, .. } => name.name.clone(), + TypeSpec::Generic { name, .. } => name.name.clone(), + TypeSpec::Array { of_type, .. } => format!("[{}]", of_type.get_name()), + } + } } #[derive(Clone, Debug, Eq, Hash, PartialEq)] diff --git a/crates/concrete_check/Cargo.toml b/crates/concrete_check/Cargo.toml new file mode 100644 index 0000000..b326b82 --- /dev/null +++ b/crates/concrete_check/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "concrete_check" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ariadne = { version = "0.4.0", features = ["auto-color"] } +concrete_ast = { version = "0.1.0", path = "../concrete_ast" } +concrete_session = { version = "0.1.0", path = "../concrete_session" } +itertools = "0.12.0" +thiserror = "1.0.56" diff --git a/crates/concrete_codegen_mlir/src/ast_helper.rs b/crates/concrete_check/src/ast_helper.rs similarity index 100% rename from crates/concrete_codegen_mlir/src/ast_helper.rs rename to crates/concrete_check/src/ast_helper.rs diff --git a/crates/concrete_check/src/lib.rs b/crates/concrete_check/src/lib.rs new file mode 100644 index 0000000..a5f02c7 --- /dev/null +++ b/crates/concrete_check/src/lib.rs @@ -0,0 +1,345 @@ +use std::{collections::HashMap, ops::Range}; + +use ariadne::{ColorGenerator, Label, Report, ReportKind}; +use ast_helper::{AstHelper, ModuleInfo}; +use concrete_ast::{ + common::Ident, + constants::ConstantDef, + functions::FunctionDef, + imports::ImportStmt, + modules::{Module, ModuleDefItem}, + statements::Statement, + types::TypeSpec, + Program, +}; +use concrete_session::Session; +use itertools::Itertools; +use thiserror::Error; + +pub mod ast_helper; + +#[derive(Error, Debug, Clone)] +pub enum CheckError { + #[error("import not found {:?} in module {}", import, module.name.name)] + ImportModuleMissing { module: Module, import: ImportStmt }, + #[error("import symbol {:?} not found in module {}", symbol, module.name.name)] + ImportSymbolMissing { + module: Module, + import: ImportStmt, + symbol: Ident, + }, + #[error("type not found {:?}", type_spec)] + TypeNotFound { type_spec: TypeSpec }, +} + +impl CheckError { + pub fn to_report<'s>(&self, session: &'s Session) -> Report<'s, (String, Range)> { + let path = session.file_path.display().to_string(); + let mut colors = ColorGenerator::new(); + colors.next(); + + match self { + CheckError::ImportModuleMissing { module, import } => { + let contex_module_span = module.span; + let span = { + let mut span = import + .module + .first() + .expect("module path can't be empty") + .span; + span.to = import + .module + .last() + .expect("module path can't be empty") + .span + .to; + span + }; + let offset = import.module[0].span.from; + Report::build(ReportKind::Error, path.clone(), offset) + .with_code("E1") + .with_label( + Label::new((path.clone(), contex_module_span.into())) + .with_message(format!("In module {:?}.", module.name.name)), + ) + .with_label( + Label::new((path, span.into())) + .with_message(format!( + "Module {:?} not found.", + import.module.iter().map(|x| &x.name).join(".") + )) + .with_color(colors.next()), + ) + .with_message("Unresolved import.") + .finish() + } + CheckError::ImportSymbolMissing { + module, + import, + symbol, + } => { + let contex_module_span = module.span; + let import_span = import.span; + let offset = symbol.span.from; + Report::build(ReportKind::Error, path.clone(), offset) + .with_code("E2") + .with_label( + Label::new((path.clone(), contex_module_span.into())) + .with_message(format!("In module {:?}.", module.name.name)), + ) + .with_label( + Label::new((path.clone(), import_span.into())) + .with_message("In this import statement"), + ) + .with_label( + Label::new((path, symbol.span.into())) + .with_message(format!("Failed to find symbol {:?}", symbol.name)) + .with_color(colors.next()), + ) + .with_message("Unresolved import.") + .finish() + } + CheckError::TypeNotFound { type_spec } => { + let span = match type_spec { + TypeSpec::Simple { span, .. } => span, + TypeSpec::Generic { span, .. } => span, + TypeSpec::Array { span, .. } => span, + }; + Report::build(ReportKind::Error, path.clone(), span.from) + .with_code("E3") + .with_label( + Label::new((path, (*span).into())) + .with_message(format!("Failed to find type {:?}", type_spec.get_name())) + .with_color(colors.next()), + ) + .with_message(format!("Unresolved type {:?}.", type_spec.get_name())) + .finish() + } + } + } +} + +#[allow(unused)] +#[derive(Debug, Clone)] +struct ScopeContext<'parent> { + pub locals: HashMap, + pub function: Option, + pub imports: HashMap>, + pub module_info: &'parent ModuleInfo<'parent>, +} + +#[allow(unused)] +#[derive(Debug, Clone)] +struct LocalVar { + pub type_spec: TypeSpec, +} + +#[allow(unused)] +impl<'parent> ScopeContext<'parent> { + pub fn get_function(&self, local_name: &str) -> Option<&FunctionDef> { + if let Some(module) = self.imports.get(local_name) { + // a import + module.functions.get(local_name).copied() + } else { + self.module_info.functions.get(local_name).copied() + } + } + + fn resolve_type(&self, name: &str) -> Option { + Some(match name { + "u64" | "i64" => name.to_string(), + "u32" | "i32" => name.to_string(), + "u16" | "i16" => name.to_string(), + "u8" | "i8" => name.to_string(), + "f32" => name.to_string(), + "f64" => name.to_string(), + "bool" => name.to_string(), + name => { + if let Some(module) = self.imports.get(name) { + // a import + self.resolve_type_spec(&module.types.get(name)?.value)? + } else { + self.resolve_type_spec(&self.module_info.types.get(name)?.value)? + } + } + }) + } + + fn resolve_type_spec(&self, spec: &TypeSpec) -> Option { + match spec.is_ref() { + Some(_) => Some(format!("&{}", self.resolve_type_spec_ref(spec)?)), + None => self.resolve_type_spec_ref(spec), + } + } + + /// Resolves the type this ref points to. + fn resolve_type_spec_ref(&self, spec: &TypeSpec) -> Option { + match spec { + TypeSpec::Simple { name, .. } => self.resolve_type(&name.name), + TypeSpec::Generic { name, .. } => self.resolve_type(&name.name), + TypeSpec::Array { .. } => { + todo!("implement arrays") + } + } + } +} + +pub fn check_program(program: &Program) -> Result<(), Vec> { + let helper = AstHelper::new(program); + + let mut errors = Vec::new(); + + for module in &program.modules { + let module_info = helper.modules.get(&module.name.name).unwrap(); + if let Err(e) = check_module(module, &helper, module_info) { + errors.extend(e); + } + } + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } +} + +pub fn check_module<'p, 'x: 'p>( + module: &Module, + helper: &AstHelper<'p>, + module_info: &ModuleInfo<'p>, +) -> Result<(), Vec> { + let mut errors = Vec::new(); + + let mut scope_ctx = ScopeContext { + locals: HashMap::new(), + function: None, + imports: HashMap::new(), + module_info, + }; + + // check modules + for import in &module.imports { + let target_module = helper.get_module_from_import(&import.module); + + if target_module.is_none() { + errors.push(CheckError::ImportModuleMissing { + module: module.clone(), + import: import.clone(), + }); + continue; + } + + let target_module = target_module.unwrap(); + + // check if symbol exists + for symbol in &import.symbols { + let name = &symbol.name; + let exists = target_module.functions.get(name).is_some() + || target_module.constants.get(name).is_some() + || target_module.structs.get(name).is_some() + || target_module.types.get(name).is_some(); + + if !exists { + errors.push(CheckError::ImportSymbolMissing { + module: module.clone(), + import: import.clone(), + symbol: symbol.clone(), + }); + continue; + } + + scope_ctx.imports.insert(symbol.name.clone(), target_module); + } + } + + for stmt in &module.contents { + match stmt { + ModuleDefItem::Constant(info) => { + if let Err(e) = check_constant(info, &scope_ctx, helper, module_info) { + errors.extend(e); + } + } + ModuleDefItem::Function(info) => { + scope_ctx.function = Some(info.clone()); + if let Err(e) = check_function(info, &scope_ctx, helper, module_info) { + errors.extend(e); + } + } + ModuleDefItem::Struct(_) => {} + ModuleDefItem::Type(_) => {} + ModuleDefItem::Module(info) => { + let module_info = module_info.modules.get(&info.name.name).unwrap(); + if let Err(e) = check_module(info, helper, module_info) { + errors.extend(e); + } + } + } + } + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } +} + +fn check_function<'p>( + info: &FunctionDef, + scope_ctx: &ScopeContext<'p>, + _helper: &AstHelper<'p>, + _module_info: &ModuleInfo<'p>, +) -> Result<(), Vec> { + let mut errors = Vec::new(); + + for param in &info.decl.params { + if scope_ctx.resolve_type_spec(¶m.r#type).is_none() { + errors.push(CheckError::TypeNotFound { + type_spec: param.r#type.clone(), + }) + } + } + + if let Some(ret_type) = info.decl.ret_type.as_ref() { + if scope_ctx.resolve_type_spec(ret_type).is_none() { + errors.push(CheckError::TypeNotFound { + type_spec: ret_type.clone(), + }) + } + } + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } +} + +fn check_constant<'p>( + _info: &ConstantDef, + _scope_ctx: &ScopeContext<'p>, + _helper: &AstHelper<'p>, + _module_info: &ModuleInfo<'p>, +) -> Result<(), Vec> { + let errors = Vec::new(); + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } +} + +fn _check_statement<'p>( + _info: &Statement, + _scope_ctx: &mut ScopeContext<'p>, + _helper: &AstHelper<'p>, + _module_info: &ModuleInfo<'p>, +) -> Result<(), Vec> { + let errors = Vec::new(); + + if errors.is_empty() { + Ok(()) + } else { + Err(errors) + } +} diff --git a/crates/concrete_codegen_mlir/Cargo.toml b/crates/concrete_codegen_mlir/Cargo.toml index 9df9c6f..6013882 100644 --- a/crates/concrete_codegen_mlir/Cargo.toml +++ b/crates/concrete_codegen_mlir/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] bumpalo = { version = "3.14.0", features = ["std"] } concrete_ast = { path = "../concrete_ast"} +concrete_check = { version = "0.1.0", path = "../concrete_check" } concrete_session = { path = "../concrete_session"} itertools = "0.12.0" llvm-sys = "170.0.1" diff --git a/crates/concrete_codegen_mlir/src/codegen.rs b/crates/concrete_codegen_mlir/src/codegen.rs index 90b2151..8e29aba 100644 --- a/crates/concrete_codegen_mlir/src/codegen.rs +++ b/crates/concrete_codegen_mlir/src/codegen.rs @@ -12,6 +12,7 @@ use concrete_ast::{ types::{RefType, TypeSpec}, Program, }; +use concrete_check::ast_helper::{AstHelper, ModuleInfo}; use concrete_session::Session; use melior::{ dialect::{ @@ -32,10 +33,7 @@ use melior::{ Context as MeliorContext, }; -use crate::{ - ast_helper::{AstHelper, ModuleInfo}, - scope_context::ScopeContext, -}; +use crate::scope_context::ScopeContext; pub fn compile_program( session: &Session, @@ -106,8 +104,6 @@ fn compile_module( module_info: &ModuleInfo<'_>, module: &Module, ) -> Result<(), Box> { - // todo: handle imports - let body = mlir_module.body(); let mut imports = HashMap::new(); diff --git a/crates/concrete_codegen_mlir/src/lib.rs b/crates/concrete_codegen_mlir/src/lib.rs index 244da61..dfa9b1f 100644 --- a/crates/concrete_codegen_mlir/src/lib.rs +++ b/crates/concrete_codegen_mlir/src/lib.rs @@ -29,7 +29,6 @@ use llvm_sys::{ }; use module::MLIRModule; -mod ast_helper; mod codegen; mod context; mod error; diff --git a/crates/concrete_codegen_mlir/src/scope_context.rs b/crates/concrete_codegen_mlir/src/scope_context.rs index 5f97eb1..a016af9 100644 --- a/crates/concrete_codegen_mlir/src/scope_context.rs +++ b/crates/concrete_codegen_mlir/src/scope_context.rs @@ -1,6 +1,7 @@ use std::{collections::HashMap, error::Error}; use concrete_ast::{functions::FunctionDef, structs::StructDecl, types::TypeSpec}; +use concrete_check::ast_helper::ModuleInfo; use melior::{ dialect::llvm, ir::{ @@ -10,7 +11,7 @@ use melior::{ Context as MeliorContext, }; -use crate::{ast_helper::ModuleInfo, codegen::LocalVar}; +use crate::codegen::LocalVar; #[derive(Debug, Clone)] pub struct ScopeContext<'ctx, 'parent: 'ctx> { diff --git a/crates/concrete_driver/Cargo.toml b/crates/concrete_driver/Cargo.toml index 35b9084..865d496 100644 --- a/crates/concrete_driver/Cargo.toml +++ b/crates/concrete_driver/Cargo.toml @@ -15,6 +15,7 @@ concrete_session = { path = "../concrete_session"} concrete_codegen_mlir = { path = "../concrete_codegen_mlir"} salsa = { git = "https://github.com/salsa-rs/salsa.git", package = "salsa-2022" } ariadne = { version = "0.4.0", features = ["auto-color"] } +concrete_check = { version = "0.1.0", path = "../concrete_check" } [dev-dependencies] tempfile = "3.9.0" diff --git a/crates/concrete_driver/src/lib.rs b/crates/concrete_driver/src/lib.rs index e293af0..e959e44 100644 --- a/crates/concrete_driver/src/lib.rs +++ b/crates/concrete_driver/src/lib.rs @@ -88,8 +88,20 @@ pub fn main() -> Result<(), Box> { target_dir, output_file, }; + tracing::debug!("Compiling with session: {:#?}", session); + if let Err(errors) = concrete_check::check_program(&program) { + for error in &errors { + let path = session.file_path.display().to_string(); + error + .to_report(&session) + .eprint((path, session.source.clone()))?; + } + + std::process::exit(1); + } + let object_path = concrete_codegen_mlir::compile(&session, &program)?; if session.library { diff --git a/crates/concrete_parser/src/grammar.lalrpop b/crates/concrete_parser/src/grammar.lalrpop index e8777b9..c6f7851 100644 --- a/crates/concrete_parser/src/grammar.lalrpop +++ b/crates/concrete_parser/src/grammar.lalrpop @@ -188,12 +188,13 @@ pub Program: ast::Program = { // Modules pub(crate) Module: ast::modules::Module = { - "mod" "{" "}" => { + "mod" "{" "}" => { ast::modules::Module { doc_string: None, imports: imports.unwrap_or_else(Vec::new), name, - contents + contents: contents.unwrap_or_else(Vec::new), + span: Span::new(lo, hi), } } } @@ -208,10 +209,11 @@ pub(crate) ImportList: Vec = { pub(crate) ImportStmt: ast::imports::ImportStmt = { - "import" > "{" > "}" ";" => { + "import" > "{" > "}" ";" => { ast::imports::ImportStmt { module, symbols, + span: Span::new(lo, hi), } } } @@ -291,9 +293,9 @@ pub(crate) Param: ast::functions::Param = { } pub(crate) FunctionDef: ast::functions::FunctionDef = { - "fn" "(" > ")" "{" - - "}" => { + "fn" "(" > ")" "{" + + "}" => { ast::functions::FunctionDef { decl: ast::functions::FunctionDecl { doc_string: None, @@ -302,7 +304,8 @@ pub(crate) FunctionDef: ast::functions::FunctionDef = { params, ret_type, }, - body: statements + body: statements.unwrap_or_else(Vec::new), + span: Span::new(lo, hi), } } } diff --git a/crates/concrete_parser/src/lib.rs b/crates/concrete_parser/src/lib.rs index 61e1c89..2881a4a 100644 --- a/crates/concrete_parser/src/lib.rs +++ b/crates/concrete_parser/src/lib.rs @@ -91,7 +91,7 @@ mod ModuleName { "##; let lexer = Lexer::new(source); let parser = grammar::ProgramParser::new(); - dbg!(parser.parse(lexer).unwrap()); + parser.parse(lexer).unwrap(); } #[test] @@ -110,7 +110,7 @@ mod ModuleName { }"##; let lexer = Lexer::new(source); let parser = grammar::ProgramParser::new(); - dbg!(parser.parse(lexer).unwrap()); + parser.parse(lexer).unwrap(); } #[test] @@ -122,7 +122,7 @@ mod ModuleName { }"##; let lexer = Lexer::new(source); let parser = grammar::ProgramParser::new(); - dbg!(parser.parse(lexer).unwrap()); + parser.parse(lexer).unwrap(); } #[test] @@ -134,6 +134,25 @@ mod ModuleName { }"##; let lexer = Lexer::new(source); let parser = grammar::ProgramParser::new(); - dbg!(parser.parse(lexer).unwrap()); + parser.parse(lexer).unwrap(); + } + + #[test] + fn parse_empty_mod() { + let source = r##"mod MyMod { +}"##; + let lexer = Lexer::new(source); + let parser = grammar::ProgramParser::new(); + parser.parse(lexer).unwrap(); + } + + #[test] + fn parse_empty_fn() { + let source = r##"mod MyMod { + fn hello() {} +}"##; + let lexer = Lexer::new(source); + let parser = grammar::ProgramParser::new(); + parser.parse(lexer).unwrap(); } } diff --git a/crates/concrete_type_checker/Cargo.toml b/crates/concrete_type_checker/Cargo.toml deleted file mode 100644 index 44762dd..0000000 --- a/crates/concrete_type_checker/Cargo.toml +++ /dev/null @@ -1,8 +0,0 @@ -[package] -name = "concrete_type_checker" -version = "0.1.0" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] diff --git a/crates/concrete_type_checker/src/lib.rs b/crates/concrete_type_checker/src/lib.rs deleted file mode 100644 index 8b13789..0000000 --- a/crates/concrete_type_checker/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/examples/check_errors.con b/examples/check_errors.con new file mode 100644 index 0000000..47fb23f --- /dev/null +++ b/examples/check_errors.con @@ -0,0 +1,23 @@ +mod Simple { + import Other.{hello1}; + import Other.Deep.{hello2}; + import Hello.{world}; + + fn main() -> i64 { + return hello1(2) + hello2(2); + } + + fn lol(a: x) -> b { + + } +} + +mod Hello { + fn a() { + return 1; + } +} + +mod A { + +}