use bumpalo::Bump; use roc_can::module::ExposedByModule; use roc_collections::all::MutMap; use roc_module::symbol::{ModuleId, Symbol}; use roc_reporting::report::RenderTarget; use roc_target::TargetInfo; use roc_types::subs::{Subs, Variable}; use std::path::PathBuf; const SKIP_SUBS_CACHE: bool = { match option_env!("ROC_SKIP_SUBS_CACHE") { Some(s) => s.len() == 1 && s.as_bytes()[0] == b'1', None => false, } }; pub use roc_load_internal::docs; pub use roc_load_internal::file::{ EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadResult, LoadStart, LoadedModule, LoadingProblem, MonomorphizedModule, Phase, Threading, }; #[allow(clippy::too_many_arguments)] fn load<'a>( arena: &'a Bump, load_start: LoadStart<'a>, exposed_types: ExposedByModule, load_config: LoadConfig, ) -> Result, LoadingProblem<'a>> { let cached_subs = read_cached_subs(); roc_load_internal::file::load(arena, load_start, exposed_types, cached_subs, load_config) } /// Load using only a single thread; used when compiling to webassembly pub fn load_single_threaded<'a>( arena: &'a Bump, load_start: LoadStart<'a>, exposed_types: ExposedByModule, target_info: TargetInfo, render: RenderTarget, exec_mode: ExecutionMode, ) -> Result, LoadingProblem<'a>> { let cached_subs = read_cached_subs(); roc_load_internal::file::load_single_threaded( arena, load_start, exposed_types, target_info, cached_subs, render, exec_mode, ) } #[derive(Debug)] #[allow(clippy::large_enum_variant)] pub enum LoadMonomorphizedError<'a> { LoadingProblem(LoadingProblem<'a>), /// Errors in the module that should be reported, without compiling the executable. /// Relevant in check-and-then-build mode. ErrorModule(LoadedModule), } impl<'a> From> for LoadMonomorphizedError<'a> { fn from(problem: LoadingProblem<'a>) -> Self { Self::LoadingProblem(problem) } } // HACK only relevant because of some uses of `map_err` that decay into this error, but call `todo` - // rustc seems to be unhappy with that. impl<'a> From<()> for LoadMonomorphizedError<'a> { fn from(_: ()) -> Self { todo!() } } #[allow(clippy::too_many_arguments)] pub fn load_and_monomorphize_from_str<'a>( arena: &'a Bump, filename: PathBuf, src: &'a str, src_dir: PathBuf, exposed_types: ExposedByModule, load_config: LoadConfig, ) -> Result, LoadingProblem<'a>> { use LoadResult::*; let load_start = LoadStart::from_str(arena, filename, src, src_dir)?; match load(arena, load_start, exposed_types, load_config)? { Monomorphized(module) => Ok(module), TypeChecked(_) => unreachable!(""), } } pub fn load_and_monomorphize( arena: &Bump, filename: PathBuf, exposed_types: ExposedByModule, load_config: LoadConfig, ) -> Result, LoadMonomorphizedError<'_>> { use LoadResult::*; let load_start = LoadStart::from_path(arena, filename, load_config.render)?; match load(arena, load_start, exposed_types, load_config)? { Monomorphized(module) => Ok(module), TypeChecked(module) => Err(LoadMonomorphizedError::ErrorModule(module)), } } pub fn load_and_typecheck( arena: &Bump, filename: PathBuf, exposed_types: ExposedByModule, load_config: LoadConfig, ) -> Result> { use LoadResult::*; let load_start = LoadStart::from_path(arena, filename, load_config.render)?; match load(arena, load_start, exposed_types, load_config)? { Monomorphized(_) => unreachable!(""), TypeChecked(module) => Ok(module), } } pub fn load_and_typecheck_str<'a>( arena: &'a Bump, filename: PathBuf, source: &'a str, src_dir: PathBuf, exposed_types: ExposedByModule, target_info: TargetInfo, render: RenderTarget, ) -> Result> { use LoadResult::*; let load_start = LoadStart::from_str(arena, filename, source, src_dir)?; // NOTE: this function is meant for tests, and so we use single-threaded // solving so we don't use too many threads per-test. That gives higher // throughput for the test run overall match load_single_threaded( arena, load_start, exposed_types, target_info, render, ExecutionMode::Check, )? { Monomorphized(_) => unreachable!(""), TypeChecked(module) => Ok(module), } } const BOOL: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Bool.dat")) as &[_]; const RESULT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Result.dat")) as &[_]; const LIST: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/List.dat")) as &[_]; const STR: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Str.dat")) as &[_]; const DICT: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Dict.dat")) as &[_]; const SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Set.dat")) as &[_]; const BOX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Box.dat")) as &[_]; const NUM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/Num.dat")) as &[_]; fn deserialize_help(bytes: &[u8]) -> (Subs, Vec<(Symbol, Variable)>) { let (subs, slice) = Subs::deserialize(bytes); (subs, slice.to_vec()) } fn read_cached_subs() -> MutMap)> { let mut output = MutMap::default(); // Wasm seems to re-order definitions between build time and runtime, but only in release mode. // That is very strange, but we can solve it separately if !cfg!(target_family = "wasm") && !cfg!(windows) && !SKIP_SUBS_CACHE { output.insert(ModuleId::BOOL, deserialize_help(BOOL)); output.insert(ModuleId::RESULT, deserialize_help(RESULT)); output.insert(ModuleId::NUM, deserialize_help(NUM)); output.insert(ModuleId::LIST, deserialize_help(LIST)); output.insert(ModuleId::STR, deserialize_help(STR)); output.insert(ModuleId::DICT, deserialize_help(DICT)); output.insert(ModuleId::SET, deserialize_help(SET)); output.insert(ModuleId::BOX, deserialize_help(BOX)); } output }