From 9074f03a74dcf8cc9cf24da7f062bc7b50adaf36 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 14 Nov 2019 16:12:49 +0100 Subject: [PATCH 1/5] feat: adds Driver to enable incremental compilation --- crates/mun/src/main.rs | 14 +- crates/mun_codegen/src/ir/body.rs | 2 +- crates/mun_codegen/src/mock.rs | 19 +- crates/mun_codegen/src/test.rs | 7 +- crates/mun_compiler/src/db.rs | 26 ++ crates/mun_compiler/src/diagnostics.rs | 89 ++++++ .../{diagnostic.rs => diagnostics/emit.rs} | 4 +- crates/mun_compiler/src/driver.rs | 157 +++++++++++ crates/mun_compiler/src/driver/config.rs | 31 +++ crates/mun_compiler/src/lib.rs | 263 +++--------------- crates/mun_compiler_daemon/src/lib.rs | 26 +- crates/mun_errors/src/lib.rs | 2 +- crates/mun_hir/src/code_model.rs | 13 +- crates/mun_hir/src/db.rs | 15 +- crates/mun_hir/src/diagnostics.rs | 4 +- crates/mun_hir/src/input.rs | 51 ++-- crates/mun_hir/src/lib.rs | 8 +- crates/mun_hir/src/mock.rs | 18 +- crates/mun_runtime/src/test.rs | 11 +- crates/mun_target/Cargo.toml | 1 + crates/mun_target/src/spec.rs | 17 +- 21 files changed, 453 insertions(+), 325 deletions(-) create mode 100644 crates/mun_compiler/src/db.rs create mode 100644 crates/mun_compiler/src/diagnostics.rs rename crates/mun_compiler/src/{diagnostic.rs => diagnostics/emit.rs} (97%) create mode 100644 crates/mun_compiler/src/driver.rs create mode 100644 crates/mun_compiler/src/driver/config.rs diff --git a/crates/mun/src/main.rs b/crates/mun/src/main.rs index cc26f8731..09a967044 100644 --- a/crates/mun/src/main.rs +++ b/crates/mun/src/main.rs @@ -5,7 +5,7 @@ use std::time::Duration; use clap::{App, AppSettings, Arg, ArgMatches, SubCommand}; use mun_abi::Reflection; -use mun_compiler::PathOrInline; +use mun_compiler::{host_triple, Config, PathOrInline, Target}; use mun_runtime::{invoke_fn, Runtime, RuntimeBuilder}; fn main() -> Result<(), failure::Error> { @@ -77,9 +77,9 @@ fn main() -> Result<(), failure::Error> { fn build(matches: &ArgMatches) -> Result<(), failure::Error> { let options = compiler_options(matches)?; if matches.is_present("watch") { - mun_compiler_daemon::main(&options) + mun_compiler_daemon::main(options) } else { - mun_compiler::main(&options).map(|_| {}) + mun_compiler::main(options).map(|_| {}) } } @@ -136,9 +136,11 @@ fn compiler_options(matches: &ArgMatches) -> Result BodyIrGenerator<'a, 'b, D> { // Wildcard patterns cannot be referenced from code. So nothing to do. } Pat::Path(_) => unreachable!( - "Path patterns are not supported as parameters, are we missing a diagnostic?" + "Path patterns are not supported as parameters, are we missing a diagnostics?" ), Pat::Missing => unreachable!( "found missing Pattern, should not be generating IR for incomplete code" diff --git a/crates/mun_codegen/src/mock.rs b/crates/mun_codegen/src/mock.rs index b3e36b083..555aec346 100644 --- a/crates/mun_codegen/src/mock.rs +++ b/crates/mun_codegen/src/mock.rs @@ -1,6 +1,6 @@ use crate::{IrDatabase, OptimizationLevel}; -use mun_hir::SourceDatabase; -use mun_hir::{FileId, PackageInput, RelativePathBuf}; +use mun_hir::{FileId, RelativePathBuf}; +use mun_hir::{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. @@ -25,12 +25,19 @@ impl MockDatabase { /// Creates a database from the given text. pub fn with_single_file(text: &str) -> (MockDatabase, FileId) { let mut db: MockDatabase = Default::default(); + + let mut source_root = SourceRoot::default(); + let source_root_id = SourceRootId(0); + + let text = Arc::new(text.to_owned()); + let rel_path = RelativePathBuf::from("main.mun"); let file_id = FileId(0); - db.set_file_relative_path(file_id, RelativePathBuf::from("main.mun")); + db.set_file_relative_path(file_id, rel_path.clone()); db.set_file_text(file_id, Arc::new(text.to_string())); - let mut package_input = PackageInput::default(); - package_input.add_module(file_id); - db.set_package_input(Arc::new(package_input)); + db.set_file_source_root(file_id, source_root_id); + 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); let context = crate::Context::create(); diff --git a/crates/mun_codegen/src/test.rs b/crates/mun_codegen/src/test.rs index 33461478b..ff00b19a8 100644 --- a/crates/mun_codegen/src/test.rs +++ b/crates/mun_codegen/src/test.rs @@ -310,12 +310,7 @@ fn test_snapshot(text: &str) { diag.message() )); }); - if let Some(module) = Module::package_modules(&db) - .iter() - .find(|m| m.file_id() == file_id) - { - module.diagnostics(&db, &mut sink) - } + Module::from(file_id).diagnostics(&db, &mut sink); drop(sink); let messages = messages.into_inner(); diff --git a/crates/mun_compiler/src/db.rs b/crates/mun_compiler/src/db.rs new file mode 100644 index 000000000..f5ba06abe --- /dev/null +++ b/crates/mun_compiler/src/db.rs @@ -0,0 +1,26 @@ +use mun_hir::salsa; + +#[salsa::database( + mun_hir::SourceDatabaseStorage, + mun_hir::DefDatabaseStorage, + mun_hir::HirDatabaseStorage, + mun_codegen::IrDatabaseStorage +)] +#[derive(Debug)] +pub(crate) struct CompilerDatabase { + runtime: salsa::Runtime, +} + +impl CompilerDatabase { + pub fn new() -> Self { + CompilerDatabase { + runtime: salsa::Runtime::default(), + } + } +} + +impl salsa::Database for CompilerDatabase { + fn salsa_runtime(&self) -> &salsa::Runtime { + &self.runtime + } +} diff --git a/crates/mun_compiler/src/diagnostics.rs b/crates/mun_compiler/src/diagnostics.rs new file mode 100644 index 000000000..899c681ca --- /dev/null +++ b/crates/mun_compiler/src/diagnostics.rs @@ -0,0 +1,89 @@ +use mun_hir::diagnostics::{Diagnostic as HirDiagnostic, DiagnosticSink}; +use mun_hir::{FileId, HirDatabase, HirDisplay, Module}; +use mun_syntax::{ast, AstNode, SyntaxKind}; +use std::cell::RefCell; + +mod emit; + +pub use emit::Emit; +use mun_errors::{Diagnostic, Level}; + +/// Constructs diagnostics message for the given file. +pub fn diagnostics(db: &impl HirDatabase, file_id: FileId) -> Vec { + let parse = db.parse(file_id); + let mut result = Vec::new(); + + result.extend(parse.errors().iter().map(|err| Diagnostic { + level: Level::Error, + loc: err.location(), + message: format!("Syntax Error: {}", err), + })); + + let result = RefCell::new(result); + let mut sink = DiagnosticSink::new(|d| { + result.borrow_mut().push(Diagnostic { + level: Level::Error, + loc: d.highlight_range().into(), + message: d.message(), + }); + }) + .on::(|d| { + let text = d.expr.to_node(&parse.tree().syntax()).text().to_string(); + result.borrow_mut().push(Diagnostic { + level: Level::Error, + loc: d.highlight_range().into(), + message: format!("could not find value `{}` in this scope", text), + }); + }) + .on::(|d| { + let text = d + .type_ref + .to_node(&parse.tree().syntax()) + .syntax() + .text() + .to_string(); + result.borrow_mut().push(Diagnostic { + level: Level::Error, + loc: d.highlight_range().into(), + message: format!("could not find type `{}` in this scope", text), + }); + }) + .on::(|d| { + result.borrow_mut().push(Diagnostic { + level: Level::Error, + loc: d.highlight_range().into(), + message: format!("expected function, found `{}`", d.found.display(db)), + }); + }) + .on::(|d| { + result.borrow_mut().push(Diagnostic { + level: Level::Error, + loc: d.highlight_range().into(), + message: format!( + "expected `{}`, found `{}`", + d.expected.display(db), + d.found.display(db) + ), + }); + }) + .on::(|d| { + result.borrow_mut().push(Diagnostic { + level: Level::Error, + loc: match d.definition.kind() { + SyntaxKind::FUNCTION_DEF => { + ast::FunctionDef::cast(d.definition.to_node(&parse.tree().syntax())) + .map(|f| f.signature_range()) + .unwrap_or_else(|| d.highlight_range()) + .into() + } + _ => d.highlight_range().into(), + }, + message: d.message(), + }); + }); + + Module::from(file_id).diagnostics(db, &mut sink); + + drop(sink); + result.into_inner() +} diff --git a/crates/mun_compiler/src/diagnostic.rs b/crates/mun_compiler/src/diagnostics/emit.rs similarity index 97% rename from crates/mun_compiler/src/diagnostic.rs rename to crates/mun_compiler/src/diagnostics/emit.rs index 20fb339c9..c71d727fb 100644 --- a/crates/mun_compiler/src/diagnostic.rs +++ b/crates/mun_compiler/src/diagnostics/emit.rs @@ -42,7 +42,7 @@ impl Emit for Diagnostic { writer.set_color(&error)?; write!(writer, "error")?; - // Write diagnostic message + // Write diagnostics message writer.set_color(&header)?; writeln!(writer, ": {}", self.message)?; @@ -74,7 +74,7 @@ impl Emit for Diagnostic { writer.set_color(&error)?; if line_col.line == line_col_end.line { - // single-line diagnostic + // single-line diagnostics writeln!( writer, " {}{}", diff --git a/crates/mun_compiler/src/driver.rs b/crates/mun_compiler/src/driver.rs new file mode 100644 index 000000000..f7327f87d --- /dev/null +++ b/crates/mun_compiler/src/driver.rs @@ -0,0 +1,157 @@ +//! `Driver` is a stateful compiler frontend that enables incremental compilation by retaining state +//! from previous compilation. + +use crate::{ + db::CompilerDatabase, + diagnostics::{diagnostics, Emit}, + PathOrInline, +}; +use mun_codegen::IrDatabase; +use mun_hir::{FileId, RelativePathBuf, SourceDatabase, SourceRoot, SourceRootId}; +use mun_target::spec::Target; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; + +mod config; + +pub use self::config::Config; +use mun_errors::{Diagnostic, Level}; +use termcolor::WriteColor; + +pub const WORKSPACE: SourceRootId = SourceRootId(0); + +#[derive(Debug)] +pub struct Driver { + db: CompilerDatabase, + out_dir: Option, +} + +impl Driver { + /// Construct a driver with a specific configuration. + pub fn with_config(config: Config) -> Self { + let mut driver = Driver { + db: CompilerDatabase::new(), + out_dir: None, + }; + + // Move relevant configuration into the database + driver.db.set_target(config.target); + driver + .db + .set_context(Arc::new(mun_codegen::Context::create())); + driver.db.set_optimization_lvl(config.optimization_lvl); + + driver.out_dir = config.out_dir; + + driver + } + + /// Constructs a driver with a configuration + pub fn with_file( + config: Config, + path: PathOrInline, + ) -> Result<(Driver, FileId), failure::Error> { + let mut driver = Driver::with_config(config); + + // Construct a SourceRoot + let mut source_root = SourceRoot::default(); + + // Get the path and contents of the path + let (rel_path, text) = match path { + PathOrInline::Path(p) => { + let filename = p.file_name().ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidInput, + "Input path is missing a filename.", + ) + })?; + ( + RelativePathBuf::from_path(filename).unwrap(), + std::fs::read_to_string(p)?, + ) + } + PathOrInline::Inline { rel_path, contents } => (rel_path, contents), + }; + + // Store the file information in the database together with the source root + let file_id = FileId(0); + driver.db.set_file_relative_path(file_id, rel_path.clone()); + driver.db.set_file_text(file_id, Arc::new(text)); + driver.db.set_file_source_root(file_id, WORKSPACE); + source_root.insert_file(rel_path, file_id); + driver.db.set_source_root(WORKSPACE, Arc::new(source_root)); + + Ok((driver, file_id)) + } +} + +impl Driver { + /// Sets the contents of a specific file. + pub fn set_file_text>(&mut self, file_id: FileId, text: T) { + self.db + .set_file_text(file_id, Arc::new(text.as_ref().to_owned())); + } +} + +impl Driver { + /// Returns a vector containing all the diagnostic messages for the project. + pub fn diagnostics(&self) -> Vec { + self.db + .source_root(WORKSPACE) + .files() + .map(|f| diagnostics(&self.db, f)) + .flatten() + .collect() + } + + /// Emits all diagnostic messages currently in the database; returns true if errors where + /// emitted. + pub fn emit_diagnostics(&self, writer: &mut impl WriteColor) -> Result { + let mut has_errors = false; + for file_id in self.db.source_root(WORKSPACE).files() { + let diags = diagnostics(&self.db, file_id); + for diagnostic in diags.iter() { + diagnostic.emit(writer, &self.db, file_id)?; + if diagnostic.level == Level::Error { + has_errors = true; + } + } + } + Ok(has_errors) + } +} + +impl Driver { + /// Computes the output path for the assembly of the specified file. + fn assembly_output_path(&self, file_id: FileId) -> PathBuf { + let target: Target = self.db.target(); + let relative_path: RelativePathBuf = self.db.file_relative_path(file_id); + let original_filename = Path::new(relative_path.file_name().unwrap()); + + // Get the dll suffix without the starting dot + let dll_extension = if target.options.dll_suffix.starts_with('.') { + &target.options.dll_suffix[1..] + } else { + &target.options.dll_suffix + }; + + // Add the dll suffix to the original filename + let output_file_name = original_filename.with_extension(dll_extension); + + // If there is an out dir specified, prepend the output directory + if let Some(ref out_dir) = self.out_dir { + out_dir.join(output_file_name) + } else { + output_file_name + } + } + + /// Generate an assembly for the given file + pub fn write_assembly(&self, file_id: FileId) -> Result, failure::Error> { + let output_path = self.assembly_output_path(file_id); + mun_codegen::write_module_shared_object(&self.db, file_id, &output_path)?; + Ok(Some(output_path)) + } +} diff --git a/crates/mun_compiler/src/driver/config.rs b/crates/mun_compiler/src/driver/config.rs new file mode 100644 index 000000000..0c5b55c6b --- /dev/null +++ b/crates/mun_compiler/src/driver/config.rs @@ -0,0 +1,31 @@ +use crate::host_triple; +use mun_codegen::OptimizationLevel; +use mun_target::spec::Target; +use std::path::PathBuf; + +/// Describes all the permanent settings that are used during compilations. +#[derive(Debug, Clone)] +pub struct Config { + /// The target triple to compile the code for. + pub target: Target, + + /// The optimization level to use for the IR generation. + pub optimization_lvl: OptimizationLevel, + + /// The optional output directory to store all outputs. If no directory is specified all output + /// is stored in a temporary directory. + pub out_dir: Option, +} + +impl Default for Config { + fn default() -> Self { + let target = Target::search(&host_triple()); + Config { + // This unwrap is safe because we only compile for targets that have an implemented host + // triple. + target: target.unwrap(), + optimization_lvl: OptimizationLevel::Default, + out_dir: None, + } + } +} diff --git a/crates/mun_compiler/src/lib.rs b/crates/mun_compiler/src/lib.rs index 42f828fe8..13664fd8e 100644 --- a/crates/mun_compiler/src/lib.rs +++ b/crates/mun_compiler/src/lib.rs @@ -1,28 +1,25 @@ #![allow(clippy::enum_variant_names)] // This is a HACK because we use salsa +mod db; ///! This library contains the code required to go from source code to binaries. -mod diagnostic; +mod diagnostics; +mod driver; -use crate::diagnostic::Emit; -use failure::Error; -use mun_codegen::IrDatabase; -use mun_errors::{Diagnostic, Level}; -use mun_hir::diagnostics::{Diagnostic as HirDiagnostic, DiagnosticSink}; -use mun_hir::{salsa, FileId, HirDisplay, Module, PackageInput, RelativePathBuf, SourceDatabase}; -use mun_syntax::ast::AstNode; -use std::cell::RefCell; +pub use mun_hir::{RelativePath, RelativePathBuf}; +pub use mun_target::spec::Target; use std::path::{Path, PathBuf}; -use std::sync::{Arc, Mutex}; -use termcolor::{ColorChoice, StandardStream}; +pub use termcolor::{ColorChoice, StandardStream}; +pub use crate::driver::{Config, Driver}; pub use mun_codegen::OptimizationLevel; -use mun_syntax::{ast, SyntaxKind}; -use mun_target::spec; #[derive(Debug, Clone)] pub enum PathOrInline { Path(PathBuf), - Inline(String), + Inline { + rel_path: RelativePathBuf, + contents: String, + }, } #[derive(Debug, Clone)] @@ -30,121 +27,33 @@ pub struct CompilerOptions { /// The input for the compiler pub input: PathOrInline, - /// The target triple to compile the code for - pub target: Option, - - /// The Optimization level to use for the IR generation - pub optimization_lvl: OptimizationLevel, - - /// An optional output directory to store all outputs - pub out_dir: Option, + /// The compiler configuration + pub config: Config, } impl CompilerOptions { pub fn with_path>(input: P) -> CompilerOptions { CompilerOptions { input: PathOrInline::Path(input.as_ref().to_path_buf()), - target: None, - optimization_lvl: OptimizationLevel::default(), - out_dir: None, + config: Config::default(), } } - pub fn with_file>(input: P) -> CompilerOptions { + pub fn with_file, T: AsRef>( + path: P, + input: T, + ) -> CompilerOptions { CompilerOptions { - input: PathOrInline::Inline(input.as_ref().to_string()), - target: None, - optimization_lvl: OptimizationLevel::default(), - out_dir: None, - } - } -} - -#[salsa::database( - mun_hir::SourceDatabaseStorage, - mun_hir::DefDatabaseStorage, - mun_hir::HirDatabaseStorage, - mun_codegen::IrDatabaseStorage -)] -#[derive(Debug)] -pub struct CompilerDatabase { - events: Mutex>>>, - runtime: salsa::Runtime, -} - -impl salsa::Database for CompilerDatabase { - fn salsa_runtime(&self) -> &salsa::Runtime { - &self.runtime - } - fn salsa_event(&self, event: impl Fn() -> salsa::Event) { - let mut events = self.events.lock().unwrap(); - if let Some(events) = &mut *events { - events.push(event()); + input: PathOrInline::Inline { + rel_path: path.into(), + contents: input.as_ref().to_string(), + }, + config: Config::default(), } } } -/// Implements the ability to retrieve query results in a closure. -impl CompilerDatabase { - pub fn log(&self, f: impl FnOnce()) -> Vec> { - *self.events.lock().unwrap() = Some(Vec::new()); - f(); - self.events.lock().unwrap().take().unwrap() - } - - pub fn log_executed(&self, f: impl FnOnce()) -> Vec { - let events = self.log(f); - events - .into_iter() - .filter_map(|e| match e.kind { - // This pretty horrible, but `Debug` is the only way to inspect - // QueryDescriptor at the moment. - salsa::EventKind::WillExecute { database_key } => { - Some(format!("{:#?}", database_key.kind)) - } - _ => None, - }) - .collect() - } -} - -impl CompilerDatabase { - fn from_file(path: &PathOrInline) -> Result<(CompilerDatabase, FileId), Error> { - let mut db = CompilerDatabase { - runtime: salsa::Runtime::default(), - events: Mutex::new(Some(Vec::new())), - }; - let file_id = FileId(0); - match path { - PathOrInline::Path(p) => { - let filename = p.file_name().ok_or_else(|| { - std::io::Error::new( - std::io::ErrorKind::InvalidInput, - "Input path is missing a filename.", - ) - })?; - db.set_file_relative_path(file_id, RelativePathBuf::from_path(filename).unwrap()); - db.set_file_text(file_id, Arc::new(std::fs::read_to_string(p)?)); - } - PathOrInline::Inline(text) => { - db.set_file_relative_path(file_id, RelativePathBuf::from_path("main.mun").unwrap()); - db.set_file_text(file_id, Arc::new(text.clone())); - } - }; - - let mut package_input = PackageInput::default(); - package_input.add_module(file_id); - db.set_package_input(Arc::new(package_input)); - db.set_optimization_lvl(OptimizationLevel::Default); - db.set_target(mun_target::spec::Target::search(host_triple()).unwrap()); - - let context = mun_codegen::Context::create(); - db.set_context(Arc::new(context)); - - Ok((db, file_id)) - } -} - +/// Returns the target triple of the host machine. This can be used as a default target. pub fn host_triple() -> &'static str { // Get the host triple out of the build environment. This ensures that our // idea of the host triple is the same as for the set of libraries we've @@ -157,123 +66,13 @@ pub fn host_triple() -> &'static str { (option_env!("CFG_COMPILER_HOST_TRIPLE")).expect("CFG_COMPILER_HOST_TRIPLE") } -fn diagnostics(db: &CompilerDatabase, file_id: FileId) -> Vec { - let parse = db.parse(file_id); - let mut result = Vec::new(); - - result.extend(parse.errors().iter().map(|err| Diagnostic { - level: Level::Error, - loc: err.location(), - message: format!("Syntax Error: {}", err), - })); - - let result = RefCell::new(result); - let mut sink = DiagnosticSink::new(|d| { - result.borrow_mut().push(Diagnostic { - level: Level::Error, - loc: d.highlight_range().into(), - message: d.message(), - }); - }) - .on::(|d| { - let text = d.expr.to_node(&parse.tree().syntax()).text().to_string(); - result.borrow_mut().push(Diagnostic { - level: Level::Error, - loc: d.highlight_range().into(), - message: format!("could not find value `{}` in this scope", text), - }); - }) - .on::(|d| { - let text = d - .type_ref - .to_node(&parse.tree().syntax()) - .syntax() - .text() - .to_string(); - result.borrow_mut().push(Diagnostic { - level: Level::Error, - loc: d.highlight_range().into(), - message: format!("could not find type `{}` in this scope", text), - }); - }) - .on::(|d| { - result.borrow_mut().push(Diagnostic { - level: Level::Error, - loc: d.highlight_range().into(), - message: format!("expected function, found `{}`", d.found.display(db)), - }); - }) - .on::(|d| { - result.borrow_mut().push(Diagnostic { - level: Level::Error, - loc: d.highlight_range().into(), - message: format!( - "expected `{}`, found `{}`", - d.expected.display(db), - d.found.display(db) - ), - }); - }) - .on::(|d| { - result.borrow_mut().push(Diagnostic { - level: Level::Error, - loc: match d.definition.kind() { - SyntaxKind::FUNCTION_DEF => { - ast::FunctionDef::cast(d.definition.to_node(&parse.tree().syntax())) - .map(|f| f.signature_range()) - .unwrap_or_else(|| d.highlight_range()) - .into() - } - _ => d.highlight_range().into(), - }, - message: d.message(), - }); - }); - - if let Some(module) = Module::package_modules(db) - .iter() - .find(|m| m.file_id() == file_id) - { - module.diagnostics(db, &mut sink) - } - - drop(sink); - result.into_inner() -} - -pub fn main(options: &CompilerOptions) -> Result, failure::Error> { - let (mut db, file_id) = CompilerDatabase::from_file(&options.input)?; - db.set_optimization_lvl(options.optimization_lvl); - if let Some(ref target) = options.target { - db.set_target(spec::Target::search(&target).unwrap()); - } - - let diagnostics = diagnostics(&db, file_id); - if !diagnostics.is_empty() { - let mut writer = StandardStream::stderr(ColorChoice::Auto); - for diagnostic in diagnostics { - diagnostic.emit(&mut writer, &db, file_id)?; - } - return Ok(None); - } +pub fn main(options: CompilerOptions) -> Result, failure::Error> { + let (driver, file_id) = Driver::with_file(options.config, options.input)?; - // Determine output file path - let target = db.target(); - let relative_path = db.file_relative_path(file_id); - let original_filename = Path::new(relative_path.file_name().unwrap()); - let dll_extension = if target.options.dll_suffix.starts_with('.') { - &target.options.dll_suffix[1..] + let mut writer = StandardStream::stderr(ColorChoice::Auto); + if driver.emit_diagnostics(&mut writer)? { + Ok(None) } else { - &target.options.dll_suffix - }; - let output_file_name = original_filename.with_extension(dll_extension); - let output_file_path = if let Some(ref out_dir) = options.out_dir { - out_dir.join(output_file_name) - } else { - output_file_name - }; - - mun_codegen::write_module_shared_object(&db, file_id, &output_file_path)?; - - Ok(Some(output_file_path)) + driver.write_assembly(file_id) + } } diff --git a/crates/mun_compiler_daemon/src/lib.rs b/crates/mun_compiler_daemon/src/lib.rs index 93d8cab78..bd1df75bf 100644 --- a/crates/mun_compiler_daemon/src/lib.rs +++ b/crates/mun_compiler_daemon/src/lib.rs @@ -2,14 +2,14 @@ use std::sync::mpsc::channel; use std::time::Duration; use failure::Error; -use mun_compiler::{CompilerOptions, PathOrInline}; +use mun_compiler::{ColorChoice, CompilerOptions, Driver, PathOrInline, StandardStream}; use notify::{RecommendedWatcher, RecursiveMode, Watcher}; -pub fn main(options: &CompilerOptions) -> Result<(), Error> { +pub fn main(options: CompilerOptions) -> Result<(), Error> { // Need to canonicalize path to do comparisons let input_path = match &options.input { PathOrInline::Path(path) => path.canonicalize()?, - PathOrInline::Inline(_) => panic!("cannot run compiler with inline path"), + PathOrInline::Inline { .. } => panic!("cannot run compiler with inline path"), }; let (tx, rx) = channel(); @@ -18,22 +18,26 @@ pub fn main(options: &CompilerOptions) -> Result<(), Error> { watcher.watch(&input_path, RecursiveMode::NonRecursive)?; println!("Watching: {}", input_path.display()); + let (mut driver, file_id) = Driver::with_file(options.config, options.input)?; + // Compile at least once - if let Err(e) = mun_compiler::main(&options) { - println!("Compilation failed with error: {}", e); + let mut writer = StandardStream::stderr(ColorChoice::Auto); + if !driver.emit_diagnostics(&mut writer)? { + driver.write_assembly(file_id)?; } loop { use notify::DebouncedEvent::*; match rx.recv() { - Ok(Write(ref path)) => { - // TODO: Check whether file contents changed (using sha hash?) - match mun_compiler::main(&options) { - Ok(_) => println!("Successfully compiled: {}", path.to_string_lossy()), - Err(e) => println!("Compilation failed with error: {}", e), + Ok(Write(ref path)) | Ok(Create(ref path)) if path == &input_path => { + let contents = std::fs::read_to_string(path)?; + driver.set_file_text(file_id, &contents); + if !driver.emit_diagnostics(&mut writer)? { + driver.write_assembly(file_id)?; } + println!("Successfully compiled: {}", path.display()) } - Ok(_) => (), + Ok(_) => {} Err(e) => eprintln!("Watcher error: {:?}", e), } } diff --git a/crates/mun_errors/src/lib.rs b/crates/mun_errors/src/lib.rs index 49e643874..41ffb9ea3 100644 --- a/crates/mun_errors/src/lib.rs +++ b/crates/mun_errors/src/lib.rs @@ -2,7 +2,7 @@ mod location; pub use crate::location::Location; -/// Defines the severity of a diagnostic. +/// Defines the severity of a diagnostics. /// TODO: Contains only Error, for now, maybe add some more? #[derive(Clone, Copy, Debug, PartialEq, Hash)] pub enum Level { diff --git a/crates/mun_hir/src/code_model.rs b/crates/mun_hir/src/code_model.rs index 48e9d847f..808e7b992 100644 --- a/crates/mun_hir/src/code_model.rs +++ b/crates/mun_hir/src/code_model.rs @@ -20,18 +20,17 @@ pub struct Module { pub(crate) file_id: FileId, } +impl From for Module { + fn from(file_id: FileId) -> Self { + Module { file_id } + } +} + impl Module { pub fn file_id(self) -> FileId { self.file_id } - pub fn package_modules(db: &impl DefDatabase) -> Vec { - db.package_input() - .modules() - .map(|m| Module { file_id: m }) - .collect() - } - /// Returns all the definitions declared in this module. pub fn declarations(self, db: &impl HirDatabase) -> Vec { db.module_data(self.file_id).definitions.clone() diff --git a/crates/mun_hir/src/db.rs b/crates/mun_hir/src/db.rs index ea74cca92..03fa9d65e 100644 --- a/crates/mun_hir/src/db.rs +++ b/crates/mun_hir/src/db.rs @@ -1,5 +1,6 @@ #![allow(clippy::type_repetition_in_bounds)] +use crate::input::{SourceRoot, SourceRootId}; use crate::name_resolution::Namespace; use crate::ty::{FnSig, Ty, TypableDef}; use crate::{ @@ -9,7 +10,7 @@ use crate::{ name_resolution::ModuleScope, source_id::ErasedFileAstId, ty::InferenceResult, - AstIdMap, ExprScopes, FileId, PackageInput, RawItems, + AstIdMap, ExprScopes, FileId, RawItems, }; use mun_syntax::{ast, Parse, SourceFile, SyntaxNode}; pub use relative_path::RelativePathBuf; @@ -31,13 +32,17 @@ pub trait SourceDatabase: std::fmt::Debug { #[salsa::invoke(parse_query)] fn parse(&self, file_id: FileId) -> Parse; + /// Source root of a file + #[salsa::input] + fn file_source_root(&self, file_id: FileId) -> SourceRootId; + + /// Contents of the source root + #[salsa::input] + fn source_root(&self, id: SourceRootId) -> Arc; + /// Returns the line index of a file #[salsa::invoke(line_index_query)] fn line_index(&self, file_id: FileId) -> Arc; - - /// The input to the package - #[salsa::input] - fn package_input(&self) -> Arc; } #[salsa::query_group(DefDatabaseStorage)] diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs index 3e0d3a71a..6e9b7ca18 100644 --- a/crates/mun_hir/src/diagnostics.rs +++ b/crates/mun_hir/src/diagnostics.rs @@ -4,11 +4,11 @@ use std::{any::Any, fmt}; /// Diagnostic defines hir API for errors and warnings. /// -/// It is used as a `dyn` object, which you can downcast to a concrete diagnostic. DiagnosticSink +/// It is used as a `dyn` object, which you can downcast to a concrete diagnostics. DiagnosticSink /// are structured, meaning that they include rich information which can be used by IDE to create /// fixes. /// -/// Internally, various subsystems of HIR produce diagnostic specific to a subsystem (typically, +/// Internally, various subsystems of HIR produce diagnostics specific to a subsystem (typically, /// an `enum`), which are safe to store in salsa but do not include source locations. Such internal /// diagnostics are transformed into an instance of `Diagnostic` on demand. pub trait Diagnostic: Any + Send + Sync + fmt::Debug + 'static { diff --git a/crates/mun_hir/src/input.rs b/crates/mun_hir/src/input.rs index 7dd1f42f1..df06681f1 100644 --- a/crates/mun_hir/src/input.rs +++ b/crates/mun_hir/src/input.rs @@ -1,3 +1,4 @@ +use relative_path::{RelativePath, RelativePathBuf}; use rustc_hash::FxHashMap; /// `FileId` is an integer which uniquely identifies a file. File paths are messy and @@ -7,34 +8,36 @@ use rustc_hash::FxHashMap; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct FileId(pub u32); -/// `PackageInput` contains which files belong to a specific package. -#[derive(Debug, Clone, Default, PartialEq, Eq)] -pub struct PackageInput { - arena: FxHashMap, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct ModuleId(pub u32); +/// Files are grouped into source roots. A source root is a directory on the file systems which is +/// watched for changes. Typically it corresponds to a single library. +/// +/// Paths to files are always relative to a source root, the compiler does not know the root path +/// of the source root at all. So, a file from one source root can't refer to a file in another +/// source root by path. +/// +/// Multiple source roots can be present if the language server is monitoring multiple directories. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct SourceRootId(pub u32); -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ModuleData { - file_id: FileId, +#[derive(Default, Clone, Debug, PartialEq, Eq)] +pub struct SourceRoot { + files: FxHashMap, } -impl ModuleData { - fn new(file_id: FileId) -> ModuleData { - ModuleData { file_id } +impl SourceRoot { + pub fn new() -> SourceRoot { + Default::default() } -} - -impl PackageInput { - pub fn add_module(&mut self, file_id: FileId) -> ModuleId { - let module_id = ModuleId(self.arena.len() as u32); - self.arena.insert(module_id, ModuleData::new(file_id)); - module_id + pub fn insert_file(&mut self, path: RelativePathBuf, file_id: FileId) { + self.files.insert(path, file_id); } - - pub fn modules<'a>(&'a self) -> impl Iterator + 'a { - self.arena.values().map(|module| module.file_id) + pub fn remove_file(&mut self, path: &RelativePath) { + self.files.remove(path); + } + pub fn files(&self) -> impl Iterator + '_ { + self.files.values().copied() + } + pub fn file_by_relative_path(&self, path: &RelativePath) -> Option { + self.files.get(path).copied() } } diff --git a/crates/mun_hir/src/lib.rs b/crates/mun_hir/src/lib.rs index a97128cfd..535ab19c8 100644 --- a/crates/mun_hir/src/lib.rs +++ b/crates/mun_hir/src/lib.rs @@ -32,10 +32,12 @@ mod tests; pub use salsa; +pub use relative_path::{RelativePath, RelativePathBuf}; + pub use crate::{ db::{ - DefDatabase, DefDatabaseStorage, HirDatabase, HirDatabaseStorage, RelativePathBuf, - SourceDatabase, SourceDatabaseStorage, + DefDatabase, DefDatabaseStorage, HirDatabase, HirDatabaseStorage, SourceDatabase, + SourceDatabaseStorage, }, display::HirDisplay, expr::{ @@ -43,7 +45,7 @@ pub use crate::{ LogicOp, Ordering, Pat, PatId, Statement, }, ids::ItemLoc, - input::{FileId, PackageInput}, + input::{FileId, SourceRoot, SourceRootId}, name::Name, name_resolution::PerNs, path::{Path, PathKind}, diff --git a/crates/mun_hir/src/mock.rs b/crates/mun_hir/src/mock.rs index 6df957986..3a8cf9a69 100644 --- a/crates/mun_hir/src/mock.rs +++ b/crates/mun_hir/src/mock.rs @@ -1,5 +1,6 @@ use crate::db::SourceDatabase; -use crate::{FileId, PackageInput, RelativePathBuf}; +use crate::input::{SourceRoot, SourceRootId}; +use crate::{FileId, RelativePathBuf}; use std::sync::{Arc, Mutex}; /// A mock implementation of the IR database. It can be used to set up a simple test case. @@ -31,12 +32,19 @@ impl MockDatabase { /// Creates a database from the given text. pub fn with_single_file(text: &str) -> (MockDatabase, FileId) { let mut db: MockDatabase = Default::default(); + + let mut source_root = SourceRoot::default(); + let source_root_id = SourceRootId(0); + + let text = Arc::new(text.to_owned()); + let rel_path = RelativePathBuf::from("main.mun"); let file_id = FileId(0); - db.set_file_relative_path(file_id, RelativePathBuf::from("main.mun")); + db.set_file_relative_path(file_id, rel_path.clone()); db.set_file_text(file_id, Arc::new(text.to_string())); - let mut package_input = PackageInput::default(); - package_input.add_module(file_id); - db.set_package_input(Arc::new(package_input)); + db.set_file_source_root(file_id, source_root_id); + source_root.insert_file(rel_path, file_id); + + db.set_source_root(source_root_id, Arc::new(source_root)); (db, file_id) } } diff --git a/crates/mun_runtime/src/test.rs b/crates/mun_runtime/src/test.rs index e2a4f285f..13bb65f65 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -1,5 +1,5 @@ use crate::{Runtime, RuntimeBuilder}; -use mun_compiler::CompilerOptions; +use mun_compiler::{CompilerOptions, Config, RelativePathBuf}; use std::path::PathBuf; struct CompileResult { @@ -19,10 +19,13 @@ impl CompileResult { fn compile(text: &str) -> CompileResult { let temp_dir = tempfile::TempDir::new().unwrap(); let options = CompilerOptions { - out_dir: Some(temp_dir.path().to_path_buf()), - ..CompilerOptions::with_file(text) + config: Config { + out_dir: Some(temp_dir.path().to_path_buf()), + ..Config::default() + }, + ..CompilerOptions::with_file(RelativePathBuf::from_path("main.mun").unwrap(), text) }; - let result = mun_compiler::main(&options).unwrap().unwrap(); + let result = mun_compiler::main(options).unwrap().unwrap(); CompileResult { _temp_dir: temp_dir, result, diff --git a/crates/mun_target/Cargo.toml b/crates/mun_target/Cargo.toml index 118a0cc70..b217833f6 100644 --- a/crates/mun_target/Cargo.toml +++ b/crates/mun_target/Cargo.toml @@ -8,3 +8,4 @@ edition = "2018" [dependencies] log = "0.4.8" +failure = "0.1.6" diff --git a/crates/mun_target/src/spec.rs b/crates/mun_target/src/spec.rs index c46fa1dd8..b38c20353 100644 --- a/crates/mun_target/src/spec.rs +++ b/crates/mun_target/src/spec.rs @@ -1,6 +1,7 @@ mod apple_base; mod linux_base; mod windows_msvc_base; +use failure::Fail; #[derive(Debug, Clone, Copy, Eq, Ord, PartialOrd, PartialEq, Hash)] pub enum LinkerFlavor { @@ -70,9 +71,12 @@ impl Default for TargetOptions { } } } - +#[derive(Fail, Debug)] pub enum LoadTargetError { + #[fail(display = "target not found: {}", 0)] BuiltinTargetNotFound(String), + + #[fail(display = "{}", 0)] Other(String), } @@ -119,14 +123,7 @@ supported_targets!( ); impl Target { - pub fn search(target_triple: &str) -> Result { - match load_specific(target_triple) { - Ok(t) => Ok(t), - Err(LoadTargetError::BuiltinTargetNotFound(_)) => Err(format!( - "Could not find specification for target {:?}", - target_triple - )), - Err(LoadTargetError::Other(e)) => Err(e), - } + pub fn search(target_triple: &str) -> Result { + load_specific(target_triple) } } From e7ff6447df19001f30c5d5a9c51cc62fa0c5f68d Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Mon, 18 Nov 2019 15:54:34 +0100 Subject: [PATCH 2/5] feat: tests for runtime hotreloading and incremental compilation --- crates/mun_codegen/src/ir/body.rs | 2 +- crates/mun_compiler/src/driver.rs | 2 +- crates/mun_compiler/src/lib.rs | 2 +- crates/mun_runtime/src/lib.rs | 23 +-- crates/mun_runtime/src/test.rs | 249 ++++++++++++++++-------------- 5 files changed, 148 insertions(+), 130 deletions(-) diff --git a/crates/mun_codegen/src/ir/body.rs b/crates/mun_codegen/src/ir/body.rs index 8e853218e..4dc2a97e5 100644 --- a/crates/mun_codegen/src/ir/body.rs +++ b/crates/mun_codegen/src/ir/body.rs @@ -82,7 +82,7 @@ impl<'a, 'b, D: IrDatabase> BodyIrGenerator<'a, 'b, D> { // Wildcard patterns cannot be referenced from code. So nothing to do. } Pat::Path(_) => unreachable!( - "Path patterns are not supported as parameters, are we missing a diagnostics?" + "Path patterns are not supported as parameters, are we missing a diagnostic?" ), Pat::Missing => unreachable!( "found missing Pattern, should not be generating IR for incomplete code" diff --git a/crates/mun_compiler/src/driver.rs b/crates/mun_compiler/src/driver.rs index f7327f87d..d231568ed 100644 --- a/crates/mun_compiler/src/driver.rs +++ b/crates/mun_compiler/src/driver.rs @@ -48,7 +48,7 @@ impl Driver { driver } - /// Constructs a driver with a configuration + /// Constructs a driver with a configuration and a single file. pub fn with_file( config: Config, path: PathOrInline, diff --git a/crates/mun_compiler/src/lib.rs b/crates/mun_compiler/src/lib.rs index 13664fd8e..4056a26b4 100644 --- a/crates/mun_compiler/src/lib.rs +++ b/crates/mun_compiler/src/lib.rs @@ -5,7 +5,7 @@ mod db; mod diagnostics; mod driver; -pub use mun_hir::{RelativePath, RelativePathBuf}; +pub use mun_hir::{FileId, RelativePath, RelativePathBuf}; pub use mun_target::spec::Target; use std::path::{Path, PathBuf}; pub use termcolor::{ColorChoice, StandardStream}; diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index 4979dcc7b..b8d9fd9e2 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -147,18 +147,21 @@ impl Runtime { pub fn update(&mut self) -> bool { while let Ok(event) = self.watcher_rx.try_recv() { use notify::DebouncedEvent::*; - if let Write(ref path) = event { - if let Some(assembly) = self.assemblies.get_mut(path) { - if let Err(e) = assembly.swap(path, &mut self.dispatch_table) { - println!( - "An error occured while reloading assembly '{}': {:?}", - path.to_string_lossy(), - e - ); - } else { - return true; + match event { + Write(ref path) | Rename(_, ref path) | Create(ref path) => { + if let Some(assembly) = self.assemblies.get_mut(path) { + if let Err(e) = assembly.swap(path, &mut self.dispatch_table) { + println!( + "An error occured while reloading assembly '{}': {:?}", + path.to_string_lossy(), + e + ); + } else { + return true; + } } } + _ => {} } } false diff --git a/crates/mun_runtime/src/test.rs b/crates/mun_runtime/src/test.rs index 13bb65f65..767d57a42 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -1,98 +1,143 @@ use crate::{Runtime, RuntimeBuilder}; -use mun_compiler::{CompilerOptions, Config, RelativePathBuf}; +use mun_abi::Reflection; +use mun_compiler::{ColorChoice, Config, Driver, FileId, PathOrInline, RelativePathBuf}; use std::path::PathBuf; +use std::thread::sleep; +use std::time::Duration; -struct CompileResult { +/// Implements a compiler and runtime in one that can invoke functions. Use of the TestDriver +/// enables quick testing of Mun constructs in the runtime with hot-reloading support. +struct TestDriver { _temp_dir: tempfile::TempDir, - result: PathBuf, + out_path: PathBuf, + file_id: FileId, + driver: Driver, + runtime: Runtime, } -impl CompileResult { - /// Construct a runtime from the compilation result that can be used to execute the compiled - /// files. - pub fn new_runtime(&self) -> Runtime { - RuntimeBuilder::new(&self.result).spawn().unwrap() - } -} - -/// Compiles the given mun and returns a `CompileResult` that can be used to execute it. -fn compile(text: &str) -> CompileResult { - let temp_dir = tempfile::TempDir::new().unwrap(); - let options = CompilerOptions { - config: Config { +impl TestDriver { + /// Construct a new TestDriver from a single Mun source + fn new(text: &str) -> Self { + let temp_dir = tempfile::TempDir::new().unwrap(); + let config = Config { out_dir: Some(temp_dir.path().to_path_buf()), ..Config::default() - }, - ..CompilerOptions::with_file(RelativePathBuf::from_path("main.mun").unwrap(), text) - }; - let result = mun_compiler::main(options).unwrap().unwrap(); - CompileResult { - _temp_dir: temp_dir, - result, + }; + let input = PathOrInline::Inline { + rel_path: RelativePathBuf::from("main.mun"), + contents: text.to_owned(), + }; + let (driver, file_id) = Driver::with_file(config, input).unwrap(); + let mut err_stream = mun_compiler::StandardStream::stderr(ColorChoice::Auto); + if driver.emit_diagnostics(&mut err_stream).unwrap() { + panic!("compiler errors..") + } + let out_path = driver.write_assembly(file_id).unwrap().unwrap(); + let runtime = RuntimeBuilder::new(&out_path).spawn().unwrap(); + TestDriver { + _temp_dir: temp_dir, + driver, + out_path, + file_id, + runtime, + } + } + + /// Updates the text of the Mun source and ensure the generated assembly has been reloaded. + fn update(&mut self, text: &str) { + self.driver.set_file_text(self.file_id, text); + let out_path = self.driver.write_assembly(self.file_id).unwrap().unwrap(); + assert_eq!( + &out_path, &self.out_path, + "recompiling did not result in the same assembly" + ); + let start_time = std::time::Instant::now(); + while !self.runtime.update() { + let now = std::time::Instant::now(); + if now - start_time > std::time::Duration::from_secs(10) { + panic!("runtime did not update after recompilation within 10secs"); + } else { + sleep(Duration::from_millis(1)); + } + } + } + + /// Calls the function with the specified name + fn invoke0(&mut self, fn_name: &str) -> T { + Runtime::invoke_fn0::(&mut self.runtime, fn_name).unwrap() + } + + /// Calls the function with the specified name passing a single argument + fn invoke1(&mut self, fn_name: &str, arg0: A) -> T { + Runtime::invoke_fn1::(&mut self.runtime, fn_name, arg0).unwrap() + } + + /// Calls the function with the specified name passing two arguments + fn invoke2( + &mut self, + fn_name: &str, + arg0: A, + arg1: B, + ) -> T { + Runtime::invoke_fn2::(&mut self.runtime, fn_name, arg0, arg1).unwrap() } } #[test] fn compile_and_run() { - let compile_result = compile( + let mut driver = TestDriver::new( r" fn main() {} ", ); - let mut runtime = compile_result.new_runtime(); - let _result: () = invoke_fn!(runtime, "main").unwrap(); + let _result: () = driver.invoke0("main"); } #[test] fn return_value() { - let compile_result = compile( + let mut driver = TestDriver::new( r" fn main():int { 3 } ", ); - let mut runtime = compile_result.new_runtime(); - let result: i64 = invoke_fn!(runtime, "main").unwrap(); + let result: i64 = driver.invoke0("main"); assert_eq!(result, 3); } #[test] fn arguments() { - let compile_result = compile( + let mut driver = TestDriver::new( r" fn main(a:int, b:int):int { a+b } ", ); - let mut runtime = compile_result.new_runtime(); let a: i64 = 52; let b: i64 = 746; - let result: i64 = invoke_fn!(runtime, "main", a, b).unwrap(); + let result: i64 = driver.invoke2("main", a, b); assert_eq!(result, a + b); } #[test] fn dispatch_table() { - let compile_result = compile( + let mut driver = TestDriver::new( r" fn add(a:int, b:int):int { a+b } fn main(a:int, b:int):int { add(a,b) } ", ); - let mut runtime = compile_result.new_runtime(); let a: i64 = 52; let b: i64 = 746; - let result: i64 = invoke_fn!(runtime, "main", a, b).unwrap(); - assert_eq!(result, a + b); + assert_eq!(driver.invoke2::("main", a, b), a + b); let a: i64 = 6274; let b: i64 = 72; - let result: i64 = invoke_fn!(runtime, "add", a, b).unwrap(); - assert_eq!(result, a + b); + assert_eq!(driver.invoke2::("add", a, b), a + b); } #[test] fn booleans() { - let compile_result = compile( + let mut driver = TestDriver::new( r#" fn equal(a:int, b:int):bool { a==b } fn equalf(a:float, b:float):bool { a==b } @@ -108,110 +153,80 @@ fn booleans() { fn greaterf_equal(a:float, b:float):bool { a>=b } "#, ); - let mut runtime = compile_result.new_runtime(); + assert_eq!(driver.invoke2::<_, _, bool>("equal", 52i64, 764i64), false); + assert_eq!(driver.invoke2::<_, _, bool>("equal", 64i64, 64i64), true); + assert_eq!(driver.invoke2::<_, _, bool>("equalf", 52f64, 764f64), false); + assert_eq!(driver.invoke2::<_, _, bool>("equalf", 64f64, 64f64), true); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "equal", 52, 764).unwrap(), - false - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "equal", 64, 64).unwrap(), - true - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "equalf", 123.0, 123.0).unwrap(), + driver.invoke2::<_, _, bool>("not_equal", 52i64, 764i64), true ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "equalf", 123.0, 234.0).unwrap(), + driver.invoke2::<_, _, bool>("not_equal", 64i64, 64i64), false ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "not_equal", 52, 764).unwrap(), + driver.invoke2::<_, _, bool>("not_equalf", 52f64, 764f64), true ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "not_equal", 64, 64).unwrap(), + driver.invoke2::<_, _, bool>("not_equalf", 64f64, 64f64), false ); + assert_eq!(driver.invoke2::<_, _, bool>("less", 52i64, 764i64), true); + assert_eq!(driver.invoke2::<_, _, bool>("less", 64i64, 64i64), false); + assert_eq!(driver.invoke2::<_, _, bool>("lessf", 52f64, 764f64), true); + assert_eq!(driver.invoke2::<_, _, bool>("lessf", 64f64, 64f64), false); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "not_equalf", 123.0, 123.0).unwrap(), + driver.invoke2::<_, _, bool>("greater", 52i64, 764i64), false ); + assert_eq!(driver.invoke2::<_, _, bool>("greater", 64i64, 64i64), false); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "not_equalf", 123.0, 234.0).unwrap(), - true - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "less", 52, 764).unwrap(), - true - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "less", 64, 64).unwrap(), + driver.invoke2::<_, _, bool>("greaterf", 52f64, 764f64), false ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "lessf", 123.0, 123.0).unwrap(), + driver.invoke2::<_, _, bool>("greaterf", 64f64, 64f64), false ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "lessf", 123.0, 234.0).unwrap(), + driver.invoke2::<_, _, bool>("less_equal", 52i64, 764i64), true ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greater", 52, 764).unwrap(), - false - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greater", 64, 64).unwrap(), - false - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greaterf", 123.0, 123.0).unwrap(), - false - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greaterf", 123.0, 234.0).unwrap(), - false - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "less_equal", 52, 764).unwrap(), - true - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "less_equal", 64, 64).unwrap(), + driver.invoke2::<_, _, bool>("less_equal", 64i64, 64i64), true ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "lessf_equal", 123.0, 123.0).unwrap(), + driver.invoke2::<_, _, bool>("lessf_equal", 52f64, 764f64), true ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "lessf_equal", 123.0, 234.0).unwrap(), + driver.invoke2::<_, _, bool>("lessf_equal", 64f64, 64f64), true ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greater_equal", 52, 764).unwrap(), + driver.invoke2::<_, _, bool>("greater_equal", 52i64, 764i64), false ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greater_equal", 64, 64).unwrap(), + driver.invoke2::<_, _, bool>("greater_equal", 64i64, 64i64), true ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greaterf_equal", 123.0, 123.0) - .unwrap(), - true + driver.invoke2::<_, _, bool>("greaterf_equal", 52f64, 764f64), + false ); assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greaterf_equal", 123.0, 234.0) - .unwrap(), - false + driver.invoke2::<_, _, bool>("greaterf_equal", 64f64, 64f64), + true ); } #[test] fn fibonacci() { - let compile_result = compile( + let mut driver = TestDriver::new( r#" fn fibonacci(n:int):int { if n <= 1 { @@ -222,24 +237,15 @@ fn fibonacci() { } "#, ); - let mut runtime = compile_result.new_runtime(); - assert_eq!( - Runtime::invoke_fn1::(&mut runtime, "fibonacci", 5).unwrap(), - 5 - ); - assert_eq!( - Runtime::invoke_fn1::(&mut runtime, "fibonacci", 11).unwrap(), - 89 - ); - assert_eq!( - Runtime::invoke_fn1::(&mut runtime, "fibonacci", 16).unwrap(), - 987 - ); + + assert_eq!(driver.invoke1::("fibonacci", 5i64), 5); + assert_eq!(driver.invoke1::("fibonacci", 11i64), 89); + assert_eq!(driver.invoke1::("fibonacci", 16i64), 987); } #[test] fn true_is_true() { - let compile_result = compile( + let mut driver = TestDriver::new( r#" fn test_true():bool { true @@ -250,13 +256,22 @@ fn true_is_true() { } "#, ); - let mut runtime = compile_result.new_runtime(); - assert_eq!( - Runtime::invoke_fn0::(&mut runtime, "test_true").unwrap(), - true + assert_eq!(driver.invoke0::("test_true"), true); + assert_eq!(driver.invoke0::("test_false"), false); +} + +#[test] +fn hotreloadable() { + let mut driver = TestDriver::new( + r" + fn main():int { 5 } + ", ); - assert_eq!( - Runtime::invoke_fn0::(&mut runtime, "test_false").unwrap(), - false + assert_eq!(driver.invoke0::("main"), 5); + driver.update( + r" + fn main():int { 10 } + ", ); + assert_eq!(driver.invoke0::("main"), 10); } From 01e16e6f2cbc15ce62b69265dbe745676b2cf052 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Tue, 19 Nov 2019 15:21:01 +0100 Subject: [PATCH 3/5] fix: linux file watching did not detect recreate The notify crate does not properly handle watching of recreated files on all platform. This commit instead watches the parent directory as proposed in the documentation of Notify. See: https://github.com/notify-rs/notify/pull/166 --- crates/mun_runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/mun_runtime/src/lib.rs b/crates/mun_runtime/src/lib.rs index b8d9fd9e2..10a3645ab 100644 --- a/crates/mun_runtime/src/lib.rs +++ b/crates/mun_runtime/src/lib.rs @@ -131,7 +131,7 @@ impl Runtime { assembly.link(&self.dispatch_table)?; self.watcher - .watch(library_path.clone(), RecursiveMode::NonRecursive)?; + .watch(library_path.parent().unwrap(), RecursiveMode::NonRecursive)?; self.assemblies.insert(library_path, assembly); Ok(()) From 77f6f6453c507beb04e3c5c6ec574c54c14a81eb Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Wed, 20 Nov 2019 20:10:06 +0100 Subject: [PATCH 4/5] Apply suggestions from code review Co-Authored-By: Wodann --- crates/mun_compiler/src/diagnostics.rs | 2 +- crates/mun_compiler/src/diagnostics/emit.rs | 4 ++-- crates/mun_compiler/src/driver.rs | 4 ++-- crates/mun_errors/src/lib.rs | 2 +- crates/mun_hir/src/diagnostics.rs | 2 +- crates/mun_runtime/src/test.rs | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/mun_compiler/src/diagnostics.rs b/crates/mun_compiler/src/diagnostics.rs index 899c681ca..d100118de 100644 --- a/crates/mun_compiler/src/diagnostics.rs +++ b/crates/mun_compiler/src/diagnostics.rs @@ -8,7 +8,7 @@ mod emit; pub use emit::Emit; use mun_errors::{Diagnostic, Level}; -/// Constructs diagnostics message for the given file. +/// Constructs diagnostic messages for the given file. pub fn diagnostics(db: &impl HirDatabase, file_id: FileId) -> Vec { let parse = db.parse(file_id); let mut result = Vec::new(); diff --git a/crates/mun_compiler/src/diagnostics/emit.rs b/crates/mun_compiler/src/diagnostics/emit.rs index c71d727fb..20fb339c9 100644 --- a/crates/mun_compiler/src/diagnostics/emit.rs +++ b/crates/mun_compiler/src/diagnostics/emit.rs @@ -42,7 +42,7 @@ impl Emit for Diagnostic { writer.set_color(&error)?; write!(writer, "error")?; - // Write diagnostics message + // Write diagnostic message writer.set_color(&header)?; writeln!(writer, ": {}", self.message)?; @@ -74,7 +74,7 @@ impl Emit for Diagnostic { writer.set_color(&error)?; if line_col.line == line_col_end.line { - // single-line diagnostics + // single-line diagnostic writeln!( writer, " {}{}", diff --git a/crates/mun_compiler/src/driver.rs b/crates/mun_compiler/src/driver.rs index d231568ed..d464a9fd8 100644 --- a/crates/mun_compiler/src/driver.rs +++ b/crates/mun_compiler/src/driver.rs @@ -29,7 +29,7 @@ pub struct Driver { } impl Driver { - /// Construct a driver with a specific configuration. + /// Constructs a driver with a specific configuration. pub fn with_config(config: Config) -> Self { let mut driver = Driver { db: CompilerDatabase::new(), @@ -106,7 +106,7 @@ impl Driver { .collect() } - /// Emits all diagnostic messages currently in the database; returns true if errors where + /// Emits all diagnostic messages currently in the database; returns true if errors were /// emitted. pub fn emit_diagnostics(&self, writer: &mut impl WriteColor) -> Result { let mut has_errors = false; diff --git a/crates/mun_errors/src/lib.rs b/crates/mun_errors/src/lib.rs index 41ffb9ea3..94227dc9b 100644 --- a/crates/mun_errors/src/lib.rs +++ b/crates/mun_errors/src/lib.rs @@ -2,7 +2,7 @@ mod location; pub use crate::location::Location; -/// Defines the severity of a diagnostics. +/// Defines the severity of diagnostics. /// TODO: Contains only Error, for now, maybe add some more? #[derive(Clone, Copy, Debug, PartialEq, Hash)] pub enum Level { diff --git a/crates/mun_hir/src/diagnostics.rs b/crates/mun_hir/src/diagnostics.rs index 6e9b7ca18..4ba5e94a4 100644 --- a/crates/mun_hir/src/diagnostics.rs +++ b/crates/mun_hir/src/diagnostics.rs @@ -4,7 +4,7 @@ use std::{any::Any, fmt}; /// Diagnostic defines hir API for errors and warnings. /// -/// It is used as a `dyn` object, which you can downcast to a concrete diagnostics. DiagnosticSink +/// It is used as a `dyn` object, which you can downcast to concrete diagnostics. DiagnosticSink /// are structured, meaning that they include rich information which can be used by IDE to create /// fixes. /// diff --git a/crates/mun_runtime/src/test.rs b/crates/mun_runtime/src/test.rs index 767d57a42..48df76be7 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -43,7 +43,7 @@ impl TestDriver { } } - /// Updates the text of the Mun source and ensure the generated assembly has been reloaded. + /// Updates the text of the Mun source and ensures that the generated assembly has been reloaded. fn update(&mut self, text: &str) { self.driver.set_file_text(self.file_id, text); let out_path = self.driver.write_assembly(self.file_id).unwrap().unwrap(); From 8b6c3bfdfa4151d335bb0947aed5160019e2a497 Mon Sep 17 00:00:00 2001 From: Bas Zalmstra Date: Thu, 21 Nov 2019 12:30:39 +0100 Subject: [PATCH 5/5] refactor(test): added assert_invoke_eq! for runtime tests --- crates/mun_runtime/src/test.rs | 148 ++++++++++----------------------- 1 file changed, 46 insertions(+), 102 deletions(-) diff --git a/crates/mun_runtime/src/test.rs b/crates/mun_runtime/src/test.rs index 48df76be7..14e85064f 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -1,5 +1,4 @@ use crate::{Runtime, RuntimeBuilder}; -use mun_abi::Reflection; use mun_compiler::{ColorChoice, Config, Driver, FileId, PathOrInline, RelativePathBuf}; use std::path::PathBuf; use std::thread::sleep; @@ -62,24 +61,16 @@ impl TestDriver { } } - /// Calls the function with the specified name - fn invoke0(&mut self, fn_name: &str) -> T { - Runtime::invoke_fn0::(&mut self.runtime, fn_name).unwrap() - } - - /// Calls the function with the specified name passing a single argument - fn invoke1(&mut self, fn_name: &str, arg0: A) -> T { - Runtime::invoke_fn1::(&mut self.runtime, fn_name, arg0).unwrap() + /// Returns the `Runtime` used by this instance + fn runtime_mut(&mut self) -> &mut Runtime { + &mut self.runtime } +} - /// Calls the function with the specified name passing two arguments - fn invoke2( - &mut self, - fn_name: &str, - arg0: A, - arg1: B, - ) -> T { - Runtime::invoke_fn2::(&mut self.runtime, fn_name, arg0, arg1).unwrap() +macro_rules! assert_invoke_eq { + ($ExpectedType:ty, $ExpectedResult:expr, $Driver:expr, $($Arg:tt)+) => { + let result: $ExpectedType = invoke_fn!($Driver.runtime_mut(), $($Arg)*).unwrap(); + assert_eq!(result, $ExpectedResult, "{} == {:?}", stringify!(invoke_fn!($Driver.runtime_mut(), $($Arg)*).unwrap()), $ExpectedResult); } } @@ -90,7 +81,7 @@ fn compile_and_run() { fn main() {} ", ); - let _result: () = driver.invoke0("main"); + assert_invoke_eq!((), (), driver, "main"); } #[test] @@ -100,8 +91,7 @@ fn return_value() { fn main():int { 3 } ", ); - let result: i64 = driver.invoke0("main"); - assert_eq!(result, 3); + assert_invoke_eq!(i64, 3, driver, "main"); } #[test] @@ -113,8 +103,7 @@ fn arguments() { ); let a: i64 = 52; let b: i64 = 746; - let result: i64 = driver.invoke2("main", a, b); - assert_eq!(result, a + b); + assert_invoke_eq!(i64, a + b, driver, "main", a, b); } #[test] @@ -128,11 +117,11 @@ fn dispatch_table() { let a: i64 = 52; let b: i64 = 746; - assert_eq!(driver.invoke2::("main", a, b), a + b); + assert_invoke_eq!(i64, a + b, driver, "main", a, b); let a: i64 = 6274; let b: i64 = 72; - assert_eq!(driver.invoke2::("add", a, b), a + b); + assert_invoke_eq!(i64, a + b, driver, "add", a, b); } #[test] @@ -148,80 +137,35 @@ fn booleans() { fn greater(a:int, b:int):bool { a>b } fn greaterf(a:float, b:float):bool { a>b } fn less_equal(a:int, b:int):bool { a<=b } - fn lessf_equal(a:float, b:float):bool { a<=b } + fn less_equalf(a:float, b:float):bool { a<=b } fn greater_equal(a:int, b:int):bool { a>=b } - fn greaterf_equal(a:float, b:float):bool { a>=b } + fn greater_equalf(a:float, b:float):bool { a>=b } "#, ); - assert_eq!(driver.invoke2::<_, _, bool>("equal", 52i64, 764i64), false); - assert_eq!(driver.invoke2::<_, _, bool>("equal", 64i64, 64i64), true); - assert_eq!(driver.invoke2::<_, _, bool>("equalf", 52f64, 764f64), false); - assert_eq!(driver.invoke2::<_, _, bool>("equalf", 64f64, 64f64), true); - assert_eq!( - driver.invoke2::<_, _, bool>("not_equal", 52i64, 764i64), - true - ); - assert_eq!( - driver.invoke2::<_, _, bool>("not_equal", 64i64, 64i64), - false - ); - assert_eq!( - driver.invoke2::<_, _, bool>("not_equalf", 52f64, 764f64), - true - ); - assert_eq!( - driver.invoke2::<_, _, bool>("not_equalf", 64f64, 64f64), - false - ); - assert_eq!(driver.invoke2::<_, _, bool>("less", 52i64, 764i64), true); - assert_eq!(driver.invoke2::<_, _, bool>("less", 64i64, 64i64), false); - assert_eq!(driver.invoke2::<_, _, bool>("lessf", 52f64, 764f64), true); - assert_eq!(driver.invoke2::<_, _, bool>("lessf", 64f64, 64f64), false); - assert_eq!( - driver.invoke2::<_, _, bool>("greater", 52i64, 764i64), - false - ); - assert_eq!(driver.invoke2::<_, _, bool>("greater", 64i64, 64i64), false); - assert_eq!( - driver.invoke2::<_, _, bool>("greaterf", 52f64, 764f64), - false - ); - assert_eq!( - driver.invoke2::<_, _, bool>("greaterf", 64f64, 64f64), - false - ); - assert_eq!( - driver.invoke2::<_, _, bool>("less_equal", 52i64, 764i64), - true - ); - assert_eq!( - driver.invoke2::<_, _, bool>("less_equal", 64i64, 64i64), - true - ); - assert_eq!( - driver.invoke2::<_, _, bool>("lessf_equal", 52f64, 764f64), - true - ); - assert_eq!( - driver.invoke2::<_, _, bool>("lessf_equal", 64f64, 64f64), - true - ); - assert_eq!( - driver.invoke2::<_, _, bool>("greater_equal", 52i64, 764i64), - false - ); - assert_eq!( - driver.invoke2::<_, _, bool>("greater_equal", 64i64, 64i64), - true - ); - assert_eq!( - driver.invoke2::<_, _, bool>("greaterf_equal", 52f64, 764f64), - false - ); - assert_eq!( - driver.invoke2::<_, _, bool>("greaterf_equal", 64f64, 64f64), - true - ); + assert_invoke_eq!(bool, false, driver, "equal", 52i64, 764i64); + assert_invoke_eq!(bool, true, driver, "equal", 64i64, 64i64); + assert_invoke_eq!(bool, false, driver, "equalf", 52f64, 764f64); + assert_invoke_eq!(bool, true, driver, "equalf", 64f64, 64f64); + assert_invoke_eq!(bool, true, driver, "not_equal", 52i64, 764i64); + assert_invoke_eq!(bool, false, driver, "not_equal", 64i64, 64i64); + assert_invoke_eq!(bool, true, driver, "not_equalf", 52f64, 764f64); + assert_invoke_eq!(bool, false, driver, "not_equalf", 64f64, 64f64); + assert_invoke_eq!(bool, true, driver, "less", 52i64, 764i64); + assert_invoke_eq!(bool, false, driver, "less", 64i64, 64i64); + assert_invoke_eq!(bool, true, driver, "lessf", 52f64, 764f64); + assert_invoke_eq!(bool, false, driver, "lessf", 64f64, 64f64); + assert_invoke_eq!(bool, false, driver, "greater", 52i64, 764i64); + assert_invoke_eq!(bool, false, driver, "greater", 64i64, 64i64); + assert_invoke_eq!(bool, false, driver, "greaterf", 52f64, 764f64); + assert_invoke_eq!(bool, false, driver, "greaterf", 64f64, 64f64); + assert_invoke_eq!(bool, true, driver, "less_equal", 52i64, 764i64); + assert_invoke_eq!(bool, true, driver, "less_equal", 64i64, 64i64); + assert_invoke_eq!(bool, true, driver, "less_equalf", 52f64, 764f64); + assert_invoke_eq!(bool, true, driver, "less_equalf", 64f64, 64f64); + assert_invoke_eq!(bool, false, driver, "greater_equal", 52i64, 764i64); + assert_invoke_eq!(bool, true, driver, "greater_equal", 64i64, 64i64); + assert_invoke_eq!(bool, false, driver, "greater_equalf", 52f64, 764f64); + assert_invoke_eq!(bool, true, driver, "greater_equalf", 64f64, 64f64); } #[test] @@ -238,9 +182,9 @@ fn fibonacci() { "#, ); - assert_eq!(driver.invoke1::("fibonacci", 5i64), 5); - assert_eq!(driver.invoke1::("fibonacci", 11i64), 89); - assert_eq!(driver.invoke1::("fibonacci", 16i64), 987); + assert_invoke_eq!(i64, 5, driver, "fibonacci", 5i64); + assert_invoke_eq!(i64, 89, driver, "fibonacci", 11i64); + assert_invoke_eq!(i64, 987, driver, "fibonacci", 16i64); } #[test] @@ -256,8 +200,8 @@ fn true_is_true() { } "#, ); - assert_eq!(driver.invoke0::("test_true"), true); - assert_eq!(driver.invoke0::("test_false"), false); + assert_invoke_eq!(bool, true, driver, "test_true"); + assert_invoke_eq!(bool, false, driver, "test_false"); } #[test] @@ -267,11 +211,11 @@ fn hotreloadable() { fn main():int { 5 } ", ); - assert_eq!(driver.invoke0::("main"), 5); + assert_invoke_eq!(i64, 5, driver, "main"); driver.update( r" fn main():int { 10 } ", ); - assert_eq!(driver.invoke0::("main"), 10); + assert_invoke_eq!(i64, 10, driver, "main"); }