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 (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..d100118de --- /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 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(); + + 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 100% rename from crates/mun_compiler/src/diagnostic.rs rename to crates/mun_compiler/src/diagnostics/emit.rs diff --git a/crates/mun_compiler/src/driver.rs b/crates/mun_compiler/src/driver.rs new file mode 100644 index 000000000..d464a9fd8 --- /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 { + /// Constructs 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 and a single file. + 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 were + /// 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..4056a26b4 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::{FileId, 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..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 diagnostic. +/// 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/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..4ba5e94a4 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 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/lib.rs b/crates/mun_runtime/src/lib.rs index 4979dcc7b..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(()) @@ -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 e2a4f285f..14e85064f 100644 --- a/crates/mun_runtime/src/test.rs +++ b/crates/mun_runtime/src/test.rs @@ -1,95 +1,132 @@ use crate::{Runtime, RuntimeBuilder}; -use mun_compiler::CompilerOptions; +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() +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() + }; + 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 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(); + 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)); + } + } + } + + /// Returns the `Runtime` used by this instance + fn runtime_mut(&mut self) -> &mut Runtime { + &mut self.runtime } } -/// 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 { - out_dir: Some(temp_dir.path().to_path_buf()), - ..CompilerOptions::with_file(text) - }; - let result = mun_compiler::main(&options).unwrap().unwrap(); - CompileResult { - _temp_dir: temp_dir, - result, +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); } } #[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(); + assert_invoke_eq!((), (), driver, "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(); - assert_eq!(result, 3); + assert_invoke_eq!(i64, 3, driver, "main"); } #[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(); - assert_eq!(result, a + b); + assert_invoke_eq!(i64, a + b, driver, "main", 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_invoke_eq!(i64, a + b, driver, "main", 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_invoke_eq!(i64, a + b, driver, "add", 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 } @@ -100,115 +137,40 @@ 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 } "#, ); - let mut runtime = compile_result.new_runtime(); - 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(), - true - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "equalf", 123.0, 234.0).unwrap(), - false - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "not_equal", 52, 764).unwrap(), - true - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "not_equal", 64, 64).unwrap(), - false - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "not_equalf", 123.0, 123.0).unwrap(), - 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(), - false - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "lessf", 123.0, 123.0).unwrap(), - false - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "lessf", 123.0, 234.0).unwrap(), - 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(), - true - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "lessf_equal", 123.0, 123.0).unwrap(), - true - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "lessf_equal", 123.0, 234.0).unwrap(), - true - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greater_equal", 52, 764).unwrap(), - false - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greater_equal", 64, 64).unwrap(), - true - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greaterf_equal", 123.0, 123.0) - .unwrap(), - true - ); - assert_eq!( - Runtime::invoke_fn2::(&mut runtime, "greaterf_equal", 123.0, 234.0) - .unwrap(), - false - ); + 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] fn fibonacci() { - let compile_result = compile( + let mut driver = TestDriver::new( r#" fn fibonacci(n:int):int { if n <= 1 { @@ -219,24 +181,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_invoke_eq!(i64, 5, driver, "fibonacci", 5i64); + assert_invoke_eq!(i64, 89, driver, "fibonacci", 11i64); + assert_invoke_eq!(i64, 987, driver, "fibonacci", 16i64); } #[test] fn true_is_true() { - let compile_result = compile( + let mut driver = TestDriver::new( r#" fn test_true():bool { true @@ -247,13 +200,22 @@ fn true_is_true() { } "#, ); - let mut runtime = compile_result.new_runtime(); - assert_eq!( - Runtime::invoke_fn0::(&mut runtime, "test_true").unwrap(), - true + assert_invoke_eq!(bool, true, driver, "test_true"); + assert_invoke_eq!(bool, false, driver, "test_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_invoke_eq!(i64, 5, driver, "main"); + driver.update( + r" + fn main():int { 10 } + ", ); + assert_invoke_eq!(i64, 10, driver, "main"); } 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) } }