diff --git a/builtins/Defaults.roc b/builtins/Defaults.roc new file mode 100644 index 0000000000..a05c8f74ee --- /dev/null +++ b/builtins/Defaults.roc @@ -0,0 +1,3 @@ +interface Defaults + exposes [] + imports [ Map.{ Map }, Set.{ Set } ] diff --git a/builtins/Map.roc b/builtins/Map.roc new file mode 100644 index 0000000000..fa8e3afe2e --- /dev/null +++ b/builtins/Map.roc @@ -0,0 +1,5 @@ +interface Map + exposes [ isEmpty ] + imports [] + +isEmpty = 0 diff --git a/builtins/Set.roc b/builtins/Set.roc new file mode 100644 index 0000000000..3422d82ae2 --- /dev/null +++ b/builtins/Set.roc @@ -0,0 +1,7 @@ +interface Set + exposes [ isEmpty ] + imports [] + +## Check + +# isEmpty : List * -> Bool diff --git a/src/load/mod.rs b/src/load/mod.rs index d842173d3a..df1f02ae22 100644 --- a/src/load/mod.rs +++ b/src/load/mod.rs @@ -23,7 +23,6 @@ use tokio::sync::mpsc::{self, Receiver, Sender}; pub struct Loaded { pub requested_module: LoadedModule, pub vars_created: usize, - pub deps: Deps, } #[derive(Debug, Clone)] @@ -36,13 +35,29 @@ pub enum BuildProblem<'a> { FileNotFound(&'a Path), } -type Deps = SendSet>; +type LoadedDeps = Vec; +type DepNames = SendSet>; -#[derive(Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq)] pub enum LoadedModule { Valid(Module), - FileProblem(io::ErrorKind), - ParsingFailed(Fail), + FileProblem { + filename: PathBuf, + error: io::ErrorKind, + }, + ParsingFailed { + filename: PathBuf, + fail: Fail, + }, +} + +impl LoadedModule { + pub fn into_module(self) -> Option { + match self { + LoadedModule::Valid(module) => Some(module), + _ => None, + } + } } /// The loading process works like this, starting from the given filename (e.g. "main.roc"): @@ -54,21 +69,26 @@ pub enum LoadedModule { /// 4. Add everything we were able to import unqualified to the module's default scope. /// 5. Parse the module's defs. /// 6. Canonicalize the module. -pub async fn load<'a>(src_dir: PathBuf, filename: PathBuf) -> Loaded { +/// +/// The loaded_modules argument specifies which modules have already been loaded. +/// It typically contains the standard modules, but is empty when loading the +/// standard modules themselves. +pub async fn load<'a>(src_dir: PathBuf, filename: PathBuf, loaded_deps: &mut LoadedDeps) -> Loaded { let env = Env { src_dir: src_dir.clone(), }; - let (tx, mut rx): (Sender, Receiver) = mpsc::channel(1024); + let (tx, mut rx): (Sender, Receiver) = mpsc::channel(1024); let arc_var_store = Arc::new(VarStore::new()); let main_tx = tx.clone(); let var_store = Arc::clone(&arc_var_store); let handle = - tokio::spawn(async move { load_filename(&env, &filename, main_tx, &var_store).await }); + tokio::spawn(async move { load_filename(&env, filename, main_tx, &var_store).await }); - let requested_module = handle.await.expect("Unable to load requested module."); - let mut other_modules = Vec::new(); - let mut all_deps = SendSet::default(); + let requested_module = handle + .await + .unwrap_or_else(|err| panic!("Unable to load requested module: {:?}", err)); + let mut all_deps: SendSet> = SendSet::default(); // Get a fresh env, since the previous one has been consumed let env = Env { src_dir }; @@ -95,7 +115,7 @@ pub async fn load<'a>(src_dir: PathBuf, filename: PathBuf) -> Loaded { .await; for module in loaded_modules { - other_modules.push(module.expect("Unable to load dependent module")); + loaded_deps.push(module.expect("Unable to load dependent module")); } // Once we've run out of pending modules to process, we're done! @@ -110,7 +130,6 @@ pub async fn load<'a>(src_dir: PathBuf, filename: PathBuf) -> Loaded { Loaded { requested_module, - deps: all_deps, vars_created, } } @@ -118,7 +137,7 @@ pub async fn load<'a>(src_dir: PathBuf, filename: PathBuf) -> Loaded { async fn load_module( env: &Env, module_name: Box, - tx: Sender, + tx: Sender, var_store: &VarStore, ) -> LoadedModule { let mut filename = PathBuf::new(); @@ -133,16 +152,16 @@ async fn load_module( // End with .roc filename.set_extension("roc"); - load_filename(env, &filename, tx, var_store).await + load_filename(env, filename, tx, var_store).await } async fn load_filename( env: &Env, - filename: &Path, - tx: Sender, + filename: PathBuf, + tx: Sender, var_store: &VarStore, ) -> LoadedModule { - match read_to_string(filename).await { + match read_to_string(&filename).await { Ok(src) => { let arena = Bump::new(); // TODO instead of env.arena.alloc(src), we should create a new buffer @@ -231,12 +250,15 @@ async fn load_filename( LoadedModule::Valid(module) } - Err((fail, _)) => LoadedModule::ParsingFailed(fail), + Err((fail, _)) => LoadedModule::ParsingFailed { filename, fail }, }; answer } - Err(err) => LoadedModule::FileProblem(err.kind()), + Err(err) => LoadedModule::FileProblem { + filename, + error: err.kind(), + }, } } diff --git a/tests/helpers/mod.rs b/tests/helpers/mod.rs index bacc722977..a58a82e42b 100644 --- a/tests/helpers/mod.rs +++ b/tests/helpers/mod.rs @@ -191,3 +191,8 @@ where pub fn fixtures_dir<'a>() -> PathBuf { Path::new("tests").join("fixtures").join("build") } + +#[allow(dead_code)] +pub fn builtins_dir<'a>() -> PathBuf { + PathBuf::new().join("builtins") +} diff --git a/tests/test_load.rs b/tests/test_load.rs index 0014aeb0ef..f3a514387b 100644 --- a/tests/test_load.rs +++ b/tests/test_load.rs @@ -11,8 +11,9 @@ mod helpers; #[cfg(test)] mod test_load { - use crate::helpers::{fixtures_dir, send_set_from}; - use roc::load::{load, LoadedModule}; + use crate::helpers::{builtins_dir, fixtures_dir}; + use roc::can::module::Module; + use roc::load::{load, Loaded, LoadedModule}; fn test_async(future: F) -> F::Output { use tokio::runtime::Runtime; @@ -24,32 +25,104 @@ mod test_load { rt.block_on(future) } + fn expect_module(loaded: Loaded) -> Module { + match loaded.requested_module { + LoadedModule::Valid(module) => module, + LoadedModule::FileProblem { filename, error } => panic!( + "{:?} failed to load with FileProblem: {:?}", + filename, error + ), + LoadedModule::ParsingFailed { filename, fail } => panic!( + "{:?} failed to load with ParsingFailed: {:?}", + filename, fail + ), + } + } + + async fn load_builtins(deps: &mut Vec) { + let src_dir = builtins_dir(); + let filename = src_dir.join("Defaults.roc"); + + load(src_dir, filename, deps).await; + } + #[test] fn interface_with_deps() { + let mut deps = Vec::new(); let src_dir = fixtures_dir().join("interface_with_deps"); let filename = src_dir.join("Primary.roc"); test_async(async { - let loaded = load(src_dir, filename).await; - - let module = match loaded.requested_module { - LoadedModule::Valid(module) => module, - LoadedModule::FileProblem(err) => panic!( - "requested_module failed to load with FileProblem: {:?}", - err - ), - LoadedModule::ParsingFailed(fail) => panic!( - "requested_module failed to load with ParsingFailed: {:?}", - fail - ), - }; + let module = expect_module(load(src_dir, filename, &mut deps).await); assert_eq!(module.name, Some("Primary".into())); assert_eq!(module.defs.len(), 6); + let module_names: Vec>> = deps + .into_iter() + .map(|dep| dep.into_module().unwrap().name) + .collect(); + assert_eq!( - loaded.deps, - send_set_from(vec!["Dep1".into(), "Dep2".into(), "Dep3.Blah".into()]) + module_names, + vec![ + Some("Dep1".into()), + Some("Dep3.Blah".into()), + Some("Dep2".into()) + ] + ); + }); + } + + #[test] + fn builtins() { + let mut deps = Vec::new(); + let src_dir = builtins_dir(); + let filename = src_dir.join("Defaults.roc"); + + test_async(async { + let module = expect_module(load(src_dir, filename, &mut deps).await); + + assert_eq!(module.name, Some("Defaults".into())); + assert_eq!(module.defs.len(), 0); + + let module_names: Vec>> = deps + .into_iter() + .map(|dep| dep.into_module().unwrap().name) + .collect(); + + assert_eq!(module_names, vec![Some("Map".into()), Some("Set".into()),]); + }); + } + + #[test] + fn interface_with_builtins() { + let mut deps = Vec::new(); + + test_async(async { + load_builtins(&mut deps).await; + + let src_dir = fixtures_dir().join("interface_with_deps"); + let filename = src_dir.join("Primary.roc"); + let module = expect_module(load(src_dir, filename, &mut deps).await); + + assert_eq!(module.name, Some("Primary".into())); + assert_eq!(module.defs.len(), 6); + + let module_names: Vec>> = deps + .into_iter() + .map(|dep| dep.into_module().unwrap().name) + .collect(); + + assert_eq!( + module_names, + vec![ + Some("Map".into()), + Some("Set".into()), + Some("Dep1".into()), + Some("Dep3.Blah".into()), + Some("Dep2".into()) + ] ); }); }