diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index ad547620b4..8decd7e317 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -1,9 +1,17 @@ # Building the Roc compiler from source -## Installing LLVM and libc++abi +## Installing LLVM, libunwind, and libc++abi -To build the compiler, you need both `libc++abi` and a particular version of LLVM installed on your system. Some systems may already have `libc++abi` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `apt-get install libc++abi-dev`.) +To build the compiler, you need these installed: + +* `libunwind` (macOS should already have this one installed) +* `libc++-dev` +* a particular version of LLVM + +Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.) macOS systems +should already have `libunwind`, but other systems will need to install it +(e.g. with `sudo apt-get install libunwind-dev`). To see which version of LLVM you need, take a look at `Cargo.toml`, in particular the `branch` section of the `inkwell` dependency. It should have something like `llvmX-Y` where X and Y are the major and minor revisions of LLVM you need. diff --git a/ci/install-llvm.sh b/ci/install-llvm.sh index 0c327914ee..8f64d6c869 100755 --- a/ci/install-llvm.sh +++ b/ci/install-llvm.sh @@ -59,4 +59,4 @@ esac wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add - add-apt-repository "${REPO_NAME}" apt-get update -apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION +apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc++abi-dev libunwind-dev diff --git a/cli/src/build.rs b/cli/src/build.rs new file mode 100644 index 0000000000..e82fbe6d03 --- /dev/null +++ b/cli/src/build.rs @@ -0,0 +1,112 @@ +use bumpalo::Bump; +use roc_build::{link::link, program}; +use roc_collections::all::MutMap; +use roc_gen::llvm::build::OptLevel; +use roc_load::file::LoadingProblem; +use std::fs; +use std::path::PathBuf; +use std::time::{Duration, SystemTime}; +use target_lexicon::Triple; + +fn report_timing(buf: &mut String, label: &str, duration: Duration) { + buf.push_str(&format!( + " {:.3} ms {}\n", + duration.as_secs_f64() * 1000.0, + label, + )); +} + +pub fn build_file( + target: &Triple, + src_dir: PathBuf, + filename: PathBuf, + opt_level: OptLevel, +) -> Result { + let compilation_start = SystemTime::now(); + let arena = Bump::new(); + + // Step 1: compile the app and generate the .o file + let subs_by_module = MutMap::default(); + + // Release builds use uniqueness optimizations + let stdlib = match opt_level { + OptLevel::Normal => roc_builtins::std::standard_stdlib(), + OptLevel::Optimize => roc_builtins::unique::uniq_stdlib(), + }; + let loaded = + roc_load::file::load(filename.clone(), &stdlib, src_dir.as_path(), subs_by_module)?; + let dest_filename = filename.with_file_name("roc_app.o"); + let buf = &mut String::with_capacity(1024); + + for (module_id, module_timing) in loaded.timings.iter() { + let module_name = loaded.interns.module_name(*module_id); + + buf.push_str(" "); + buf.push_str(module_name); + buf.push_str("\n"); + + report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file); + report_timing(buf, "Parse header", module_timing.parse_header); + report_timing(buf, "Parse body", module_timing.parse_body); + report_timing(buf, "Canonicalize", module_timing.canonicalize); + report_timing(buf, "Constrain", module_timing.constrain); + report_timing(buf, "Solve", module_timing.solve); + report_timing(buf, "Other", module_timing.other()); + buf.push('\n'); + report_timing(buf, "Total", module_timing.total()); + } + + println!( + "\n\nCompilation finished! Here's how long each module took to compile:\n\n{}", + buf + ); + + program::gen( + &arena, + loaded, + filename, + Triple::host(), + &dest_filename, + opt_level, + ); + + let compilation_end = compilation_start.elapsed().unwrap(); + + println!( + "Finished compilation and code gen in {} ms\n", + compilation_end.as_millis() + ); + + let cwd = dest_filename.parent().unwrap(); + + // Step 2: link the precompiled host and compiled app + let host_input_path = cwd.join("platform").join("host.o"); + let binary_path = cwd.join("app"); // TODO should be app.exe on Windows + + // TODO try to move as much of this linking as possible to the precompiled + // host, to minimize the amount of host-application linking required. + let cmd_result = // TODO use lld + link( + target, + binary_path.as_path(), + host_input_path.as_path(), + dest_filename.as_path(), + ) + .map_err(|_| { + todo!("gracefully handle `rustc` failing to spawn."); + })? + .wait() + .map_err(|_| { + todo!("gracefully handle error after `rustc` spawned"); + }); + + // Clean up the leftover .o file from the Roc, if possible. + // (If cleaning it up fails, that's fine. No need to take action.) + // TODO compile the dest_filename to a tmpdir, as an extra precaution. + let _ = fs::remove_file(dest_filename); + + // If the cmd errored out, return the Err. + cmd_result?; + + Ok(binary_path) +} diff --git a/cli/src/lib.rs b/cli/src/lib.rs index b84b6ed76a..e821a13017 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,20 +1,16 @@ #[macro_use] extern crate clap; -use bumpalo::Bump; use clap::ArgMatches; use clap::{App, Arg}; -use roc_build::program::gen; -use roc_collections::all::MutMap; use roc_gen::llvm::build::OptLevel; -use roc_load::file::LoadingProblem; -use std::io::{self, ErrorKind}; -use std::path::{Path, PathBuf}; +use std::io; +use std::path::Path; use std::process; use std::process::Command; -use std::time::{Duration, SystemTime}; use target_lexicon::Triple; +pub mod build; pub mod repl; pub static FLAG_OPTIMIZE: &str = "optimize"; @@ -66,7 +62,7 @@ pub fn build_app<'a>() -> App<'a> { ) } -pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { +pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { let filename = matches.value_of(FLAG_ROC_FILE).unwrap(); let opt_level = if matches.is_present(FLAG_OPTIMIZE) { OptLevel::Optimize @@ -78,7 +74,7 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { // Spawn the root task let path = path.canonicalize().unwrap_or_else(|err| { - use ErrorKind::*; + use io::ErrorKind::*; match err.kind() { NotFound => { @@ -95,8 +91,8 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { } }); - let binary_path = - build_file(src_dir, path, opt_level).expect("TODO gracefully handle build_file failing"); + let binary_path = build::build_file(target, src_dir, path, opt_level) + .expect("TODO gracefully handle build_file failing"); if run_after_build { // Run the compiled app @@ -109,123 +105,3 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { Ok(()) } - -fn report_timing(buf: &mut String, label: &str, duration: Duration) { - buf.push_str(&format!( - " {:.3} ms {}\n", - duration.as_secs_f64() * 1000.0, - label, - )); -} - -fn build_file( - src_dir: PathBuf, - filename: PathBuf, - opt_level: OptLevel, -) -> Result { - let compilation_start = SystemTime::now(); - let arena = Bump::new(); - - // Step 1: compile the app and generate the .o file - let subs_by_module = MutMap::default(); - - // Release builds use uniqueness optimizations - let stdlib = match opt_level { - OptLevel::Normal => roc_builtins::std::standard_stdlib(), - OptLevel::Optimize => roc_builtins::unique::uniq_stdlib(), - }; - let loaded = - roc_load::file::load(filename.clone(), &stdlib, src_dir.as_path(), subs_by_module)?; - let dest_filename = filename.with_extension("o"); - - let buf = &mut String::with_capacity(1024); - - for (module_id, module_timing) in loaded.timings.iter() { - let module_name = loaded.interns.module_name(*module_id); - - buf.push_str(" "); - buf.push_str(module_name); - buf.push_str("\n"); - - report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file); - report_timing(buf, "Parse header", module_timing.parse_header); - report_timing(buf, "Parse body", module_timing.parse_body); - report_timing(buf, "Canonicalize", module_timing.canonicalize); - report_timing(buf, "Constrain", module_timing.constrain); - report_timing(buf, "Solve", module_timing.solve); - report_timing(buf, "Other", module_timing.other()); - buf.push('\n'); - report_timing(buf, "Total", module_timing.total()); - } - - println!( - "\n\nCompilation finished! Here's how long each module took to compile:\n\n{}", - buf - ); - - gen( - &arena, - loaded, - filename, - Triple::host(), - &dest_filename, - opt_level, - ); - - let compilation_end = compilation_start.elapsed().unwrap(); - - println!( - "Finished compilation and code gen in {} ms\n", - compilation_end.as_millis() - ); - - let cwd = dest_filename.parent().unwrap(); - let lib_path = dest_filename.with_file_name("libroc_app.a"); - - // Step 2: turn the .o file into a .a static library - Command::new("ar") // TODO on Windows, use `link` - .args(&[ - "rcs", - lib_path.to_str().unwrap(), - dest_filename.to_str().unwrap(), - ]) - .spawn() - .map_err(|_| { - todo!("gracefully handle `ar` failing to spawn."); - })? - .wait() - .map_err(|_| { - todo!("gracefully handle error after `ar` spawned"); - })?; - - // Step 3: have rustc compile the host and link in the .a file - let binary_path = cwd.join("app"); - - Command::new("rustc") - .args(&[ - "-L", - ".", - "--crate-type", - "bin", - "host.rs", - "-o", - binary_path.as_path().to_str().unwrap(), - // ensure we don't make a position-independent executable - "-C", - "link-arg=-no-pie", - // explicitly link in the c++ stdlib, for exceptions - "-C", - "link-arg=-lc++", - ]) - .current_dir(cwd) - .spawn() - .map_err(|_| { - todo!("gracefully handle `rustc` failing to spawn."); - })? - .wait() - .map_err(|_| { - todo!("gracefully handle error after `rustc` spawned"); - })?; - - Ok(binary_path) -} diff --git a/cli/src/main.rs b/cli/src/main.rs index dd92325b72..3a07efd95a 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,14 +1,23 @@ use roc_cli::{build, build_app, repl, DIRECTORY_OR_FILES}; use std::io; use std::path::Path; +use target_lexicon::Triple; fn main() -> io::Result<()> { let matches = build_app().get_matches(); match matches.subcommand_name() { None => roc_editor::launch(&[]), - Some("build") => build(matches.subcommand_matches("build").unwrap(), false), - Some("run") => build(matches.subcommand_matches("run").unwrap(), true), + Some("build") => build( + &Triple::host(), + matches.subcommand_matches("build").unwrap(), + false, + ), + Some("run") => build( + &Triple::host(), + matches.subcommand_matches("run").unwrap(), + true, + ), Some("repl") => repl::main(), Some("edit") => { match matches diff --git a/compiler/build/src/lib.rs b/compiler/build/src/lib.rs index 15ced922ef..896bcc6c21 100644 --- a/compiler/build/src/lib.rs +++ b/compiler/build/src/lib.rs @@ -10,4 +10,6 @@ // and encouraging shortcuts here creates bad incentives. I would rather temporarily // re-enable this when working on performance optimizations than have it block PRs. #![allow(clippy::large_enum_variant)] +pub mod link; pub mod program; +pub mod target; diff --git a/compiler/build/src/link.rs b/compiler/build/src/link.rs new file mode 100644 index 0000000000..39084a1e2f --- /dev/null +++ b/compiler/build/src/link.rs @@ -0,0 +1,192 @@ +use crate::target::arch_str; +use std::io; +use std::path::Path; +use std::process::{Child, Command}; +use target_lexicon::{Architecture, OperatingSystem, Triple}; + +pub fn link( + target: &Triple, + binary_path: &Path, + host_input_path: &Path, + dest_filename: &Path, +) -> io::Result { + // TODO we should no longer need to do this once we have platforms on + // a package repository, as we can then get precompiled hosts from there. + rebuild_host(host_input_path); + + match target { + Triple { + architecture: Architecture::X86_64, + operating_system: OperatingSystem::Linux, + .. + } => link_linux(target, binary_path, host_input_path, dest_filename), + Triple { + architecture: Architecture::X86_64, + operating_system: OperatingSystem::Darwin, + .. + } => link_macos(target, binary_path, host_input_path, dest_filename), + _ => panic!("TODO gracefully handle unsupported target: {:?}", target), + } +} + +fn rebuild_host(host_input_path: &Path) { + let c_host_src = host_input_path.with_file_name("host.c"); + let c_host_dest = host_input_path.with_file_name("c_host.o"); + let rust_host_src = host_input_path.with_file_name("host.rs"); + let rust_host_dest = host_input_path.with_file_name("rust_host.o"); + let cargo_host_src = host_input_path.with_file_name("Cargo.toml"); + let host_dest = host_input_path.with_file_name("host.o"); + + // Compile host.c + Command::new("clang") + .env_clear() + .args(&[ + "-c", + c_host_src.to_str().unwrap(), + "-o", + c_host_dest.to_str().unwrap(), + ]) + .output() + .unwrap(); + + if cargo_host_src.exists() { + // Compile and link Cargo.toml, if it exists + let cargo_dir = host_input_path.parent().unwrap(); + let libhost_dir = cargo_dir.join("target").join("release"); + + Command::new("cargo") + .args(&["build", "--release"]) + .current_dir(cargo_dir) + .output() + .unwrap(); + + Command::new("ld") + .env_clear() + .args(&[ + "-r", + "-L", + libhost_dir.to_str().unwrap(), + c_host_dest.to_str().unwrap(), + "-lhost", + "-o", + host_dest.to_str().unwrap(), + ]) + .output() + .unwrap(); + } else if rust_host_src.exists() { + // Compile and link host.rs, if it exists + Command::new("rustc") + .args(&[ + rust_host_src.to_str().unwrap(), + "-o", + rust_host_dest.to_str().unwrap(), + ]) + .output() + .unwrap(); + + Command::new("ld") + .env_clear() + .args(&[ + "-r", + c_host_dest.to_str().unwrap(), + rust_host_dest.to_str().unwrap(), + "-o", + host_dest.to_str().unwrap(), + ]) + .output() + .unwrap(); + + // Clean up rust_host.o + Command::new("rm") + .env_clear() + .args(&[ + "-f", + rust_host_dest.to_str().unwrap(), + c_host_dest.to_str().unwrap(), + ]) + .output() + .unwrap(); + } else { + // Clean up rust_host.o + Command::new("mv") + .env_clear() + .args(&[c_host_dest, host_dest]) + .output() + .unwrap(); + } +} + +fn link_linux( + target: &Triple, + binary_path: &Path, + host_input_path: &Path, + dest_filename: &Path, +) -> io::Result { + // NOTE: order of arguments to `ld` matters here! + // The `-l` flags should go after the `.o` arguments + Command::new("ld") + // Don't allow LD_ env vars to affect this + .env_clear() + .args(&[ + "-arch", + arch_str(target), + "/usr/lib/x86_64-linux-gnu/crti.o", + "/usr/lib/x86_64-linux-gnu/crtn.o", + "/usr/lib/x86_64-linux-gnu/Scrt1.o", + "-dynamic-linker", + "/lib64/ld-linux-x86-64.so.2", + // Inputs + host_input_path.to_str().unwrap(), // host.o + dest_filename.to_str().unwrap(), // app.o + // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925 + // for discussion and further references + "-lc", + "-lm", + "-lpthread", + "-ldl", + "-lrt", + "-lutil", + "-lc_nonshared", + "-lc++", + "-lunwind", + // "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840 + // Output + "-o", + binary_path.to_str().unwrap(), // app + ]) + .spawn() +} + +fn link_macos( + target: &Triple, + binary_path: &Path, + host_input_path: &Path, + dest_filename: &Path, +) -> io::Result { + // NOTE: order of arguments to `ld` matters here! + // The `-l` flags should go after the `.o` arguments + Command::new("ld") + // Don't allow LD_ env vars to affect this + .env_clear() + .args(&[ + "-arch", + target.architecture.to_string().as_str(), + // Inputs + host_input_path.to_str().unwrap(), // host.o + dest_filename.to_str().unwrap(), // roc_app.o + // Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274 + // for discussion and further references + "-lSystem", + "-lresolv", + "-lpthread", + // "-lrt", // TODO shouldn't we need this? + // "-lc_nonshared", // TODO shouldn't we need this? + // "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840 + // "-lunwind", // TODO will eventually need this, see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840 + "-lc++", // TODO shouldn't we need this? + // Output + "-o", + binary_path.to_str().unwrap(), // app + ]) + .spawn() +} diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 2a4ba3dde9..c08e1df1f3 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -1,8 +1,7 @@ +use crate::target; use bumpalo::Bump; use inkwell::context::Context; -use inkwell::targets::{ - CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, -}; +use inkwell::targets::{CodeModel, FileType, RelocMode}; use inkwell::OptimizationLevel; use roc_collections::all::default_hasher; use roc_gen::layout_id::LayoutIds; @@ -12,7 +11,7 @@ use roc_mono::ir::{Env, PartialProc, Procs}; use roc_mono::layout::{Layout, LayoutCache}; use std::collections::HashSet; use std::path::{Path, PathBuf}; -use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor}; +use target_lexicon::Triple; // TODO how should imported modules factor into this? What if those use builtins too? // TODO this should probably use more helper functions @@ -105,11 +104,18 @@ pub fn gen( Declare(def) | Builtin(def) => match def.loc_pattern.value { Identifier(symbol) => { match def.loc_expr.value { - Closure(annotation, _, recursivity, loc_args, boxed_body) => { + Closure { + function_type: annotation, + return_type: ret_var, + recursive: recursivity, + arguments: loc_args, + loc_body: boxed_body, + .. + } => { let is_tail_recursive = matches!(recursivity, roc_can::expr::Recursive::TailRecursive); - let (loc_body, ret_var) = *boxed_body; + let loc_body = *boxed_body; // If this is an exposed symbol, we need to // register it as such. Otherwise, since it @@ -288,80 +294,10 @@ pub fn gen( // Emit the .o file - // NOTE: arch_str is *not* the same as the beginning of the magic target triple - // string! For example, if it's "x86-64" here, the magic target triple string - // will begin with "x86_64" (with an underscore) instead. - let arch_str = match target.architecture { - Architecture::X86_64 => { - Target::initialize_x86(&InitializationConfig::default()); - - "x86-64" - } - Architecture::Arm(_) if cfg!(feature = "target-arm") => { - // NOTE: why not enable arm and wasm by default? - // - // We had some trouble getting them to link properly. This may be resolved in the - // future, or maybe it was just some weird configuration on one machine. - Target::initialize_arm(&InitializationConfig::default()); - - "arm" - } - Architecture::Wasm32 if cfg!(feature = "target-webassembly") => { - Target::initialize_webassembly(&InitializationConfig::default()); - - "wasm32" - } - _ => panic!( - "TODO gracefully handle unsupported target architecture: {:?}", - target.architecture - ), - }; - let opt = OptimizationLevel::Aggressive; let reloc = RelocMode::Default; let model = CodeModel::Default; - - // Best guide I've found on how to determine these magic strings: - // - // https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures - let target_triple_str = match target { - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Unknown, - operating_system: OperatingSystem::Linux, - .. - } => "x86_64-unknown-linux-gnu", - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Pc, - operating_system: OperatingSystem::Linux, - .. - } => "x86_64-pc-linux-gnu", - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Unknown, - operating_system: OperatingSystem::Darwin, - .. - } => "x86_64-unknown-darwin10", - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Apple, - operating_system: OperatingSystem::Darwin, - .. - } => "x86_64-apple-darwin10", - _ => panic!("TODO gracefully handle unsupported target: {:?}", target), - }; - let target_machine = Target::from_name(arch_str) - .unwrap() - .create_target_machine( - &TargetTriple::create(target_triple_str), - arch_str, - "+avx2", // TODO this string was used uncritically from an example, and should be reexamined - opt, - reloc, - model, - ) - .unwrap(); + let target_machine = target::target_machine(&target, opt, reloc, model).unwrap(); target_machine .write_to_file(&env.module, FileType::Object, &dest_filename) diff --git a/compiler/build/src/target.rs b/compiler/build/src/target.rs new file mode 100644 index 0000000000..48aad60072 --- /dev/null +++ b/compiler/build/src/target.rs @@ -0,0 +1,76 @@ +use inkwell::targets::{ + CodeModel, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple, +}; +use inkwell::OptimizationLevel; +use target_lexicon::{Architecture, OperatingSystem, Triple}; + +pub fn target_triple_str(target: &Triple) -> &'static str { + // Best guide I've found on how to determine these magic strings: + // + // https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures + match target { + Triple { + architecture: Architecture::X86_64, + operating_system: OperatingSystem::Linux, + .. + } => "x86_64-unknown-linux-gnu", + Triple { + architecture: Architecture::X86_64, + operating_system: OperatingSystem::Darwin, + .. + } => "x86_64-unknown-darwin10", + _ => panic!("TODO gracefully handle unsupported target: {:?}", target), + } +} + +/// NOTE: arch_str is *not* the same as the beginning of the magic target triple +/// string! For example, if it's "x86-64" here, the magic target triple string +/// will begin with "x86_64" (with an underscore) instead. +pub fn arch_str(target: &Triple) -> &'static str { + // Best guide I've found on how to determine these magic strings: + // + // https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures + match target.architecture { + Architecture::X86_64 => { + Target::initialize_x86(&InitializationConfig::default()); + + "x86-64" + } + Architecture::Arm(_) if cfg!(feature = "target-arm") => { + // NOTE: why not enable arm and wasm by default? + // + // We had some trouble getting them to link properly. This may be resolved in the + // future, or maybe it was just some weird configuration on one machine. + Target::initialize_arm(&InitializationConfig::default()); + + "arm" + } + Architecture::Wasm32 if cfg!(feature = "target-webassembly") => { + Target::initialize_webassembly(&InitializationConfig::default()); + + "wasm32" + } + _ => panic!( + "TODO gracefully handle unsupported target architecture: {:?}", + target.architecture + ), + } +} + +pub fn target_machine( + target: &Triple, + opt: OptimizationLevel, + reloc: RelocMode, + model: CodeModel, +) -> Option { + let arch = arch_str(target); + + Target::from_name(arch).unwrap().create_target_machine( + &TargetTriple::create(target_triple_str(target)), + arch, + "+avx2", // TODO this string was used uncritically from an example, and should be reexamined + opt, + reloc, + model, + ) +} diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index bc9e833b31..95e580d013 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -42,6 +42,8 @@ const NUM_BUILTIN_IMPORTS: usize = 7; const TVAR1: VarId = VarId::from_u32(1); const TVAR2: VarId = VarId::from_u32(2); const TVAR3: VarId = VarId::from_u32(3); +const TVAR4: VarId = VarId::from_u32(4); +const TOP_LEVEL_CLOSURE_VAR: VarId = VarId::from_u32(5); pub fn aliases() -> MutMap { let mut aliases = HashMap::with_capacity_and_hasher(NUM_BUILTIN_IMPORTS, default_hasher()); @@ -181,7 +183,7 @@ pub fn types() -> MutMap { // add or (+) : Num a, Num a -> Num a add_type( Symbol::NUM_ADD, - SolvedType::Func( + top_level_function( vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], Box::new(num_type(flex(TVAR1))), ), @@ -195,7 +197,7 @@ pub fn types() -> MutMap { add_type( Symbol::NUM_ADD_CHECKED, - SolvedType::Func( + top_level_function( vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], Box::new(result_type(num_type(flex(TVAR1)), overflow)), ), @@ -204,13 +206,13 @@ pub fn types() -> MutMap { // addWrap : Int, Int -> Int add_type( Symbol::NUM_ADD_WRAP, - SolvedType::Func(vec![int_type(), int_type()], Box::new(int_type())), + top_level_function(vec![int_type(), int_type()], Box::new(int_type())), ); // sub or (-) : Num a, Num a -> Num a add_type( Symbol::NUM_SUB, - SolvedType::Func( + top_level_function( vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], Box::new(num_type(flex(TVAR1))), ), @@ -219,7 +221,7 @@ pub fn types() -> MutMap { // mul or (*) : Num a, Num a -> Num a add_type( Symbol::NUM_MUL, - SolvedType::Func( + top_level_function( vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], Box::new(num_type(flex(TVAR1))), ), @@ -228,31 +230,31 @@ pub fn types() -> MutMap { // abs : Num a -> Num a add_type( Symbol::NUM_ABS, - SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(num_type(flex(TVAR1)))), + top_level_function(vec![num_type(flex(TVAR1))], Box::new(num_type(flex(TVAR1)))), ); // neg : Num a -> Num a add_type( Symbol::NUM_NEG, - SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(num_type(flex(TVAR1)))), + top_level_function(vec![num_type(flex(TVAR1))], Box::new(num_type(flex(TVAR1)))), ); // isEq or (==) : a, a -> Bool add_type( Symbol::BOOL_EQ, - SolvedType::Func(vec![flex(TVAR1), flex(TVAR1)], Box::new(bool_type())), + top_level_function(vec![flex(TVAR1), flex(TVAR1)], Box::new(bool_type())), ); // isNeq or (!=) : a, a -> Bool add_type( Symbol::BOOL_NEQ, - SolvedType::Func(vec![flex(TVAR1), flex(TVAR1)], Box::new(bool_type())), + top_level_function(vec![flex(TVAR1), flex(TVAR1)], Box::new(bool_type())), ); // isLt or (<) : Num a, Num a -> Bool add_type( Symbol::NUM_LT, - SolvedType::Func( + top_level_function( vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], Box::new(bool_type()), ), @@ -261,7 +263,7 @@ pub fn types() -> MutMap { // isLte or (<=) : Num a, Num a -> Bool add_type( Symbol::NUM_LTE, - SolvedType::Func( + top_level_function( vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], Box::new(bool_type()), ), @@ -270,7 +272,7 @@ pub fn types() -> MutMap { // isGt or (>) : Num a, Num a -> Bool add_type( Symbol::NUM_GT, - SolvedType::Func( + top_level_function( vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], Box::new(bool_type()), ), @@ -279,7 +281,7 @@ pub fn types() -> MutMap { // isGte or (>=) : Num a, Num a -> Bool add_type( Symbol::NUM_GTE, - SolvedType::Func( + top_level_function( vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], Box::new(bool_type()), ), @@ -288,7 +290,7 @@ pub fn types() -> MutMap { // compare : Num a, Num a -> [ LT, EQ, GT ] add_type( Symbol::NUM_COMPARE, - SolvedType::Func( + top_level_function( vec![num_type(flex(TVAR1)), num_type(flex(TVAR1))], Box::new(ordering_type()), ), @@ -297,37 +299,37 @@ pub fn types() -> MutMap { // toFloat : Num a -> Float add_type( Symbol::NUM_TO_FLOAT, - SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(float_type())), + top_level_function(vec![num_type(flex(TVAR1))], Box::new(float_type())), ); // isNegative : Num a -> Bool add_type( Symbol::NUM_IS_NEGATIVE, - SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())), + top_level_function(vec![num_type(flex(TVAR1))], Box::new(bool_type())), ); // isPositive : Num a -> Bool add_type( Symbol::NUM_IS_POSITIVE, - SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())), + top_level_function(vec![num_type(flex(TVAR1))], Box::new(bool_type())), ); // isZero : Num a -> Bool add_type( Symbol::NUM_IS_ZERO, - SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())), + top_level_function(vec![num_type(flex(TVAR1))], Box::new(bool_type())), ); // isEven : Num a -> Bool add_type( Symbol::NUM_IS_EVEN, - SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())), + top_level_function(vec![num_type(flex(TVAR1))], Box::new(bool_type())), ); // isOdd : Num a -> Bool add_type( Symbol::NUM_IS_ODD, - SolvedType::Func(vec![num_type(flex(TVAR1))], Box::new(bool_type())), + top_level_function(vec![num_type(flex(TVAR1))], Box::new(bool_type())), ); // maxInt : Int @@ -344,7 +346,7 @@ pub fn types() -> MutMap { add_type( Symbol::NUM_DIV_INT, - SolvedType::Func( + top_level_function( vec![int_type(), int_type()], Box::new(result_type(int_type(), div_by_zero.clone())), ), @@ -353,7 +355,7 @@ pub fn types() -> MutMap { // rem : Int, Int -> Result Int [ DivByZero ]* add_type( Symbol::NUM_REM, - SolvedType::Func( + top_level_function( vec![int_type(), int_type()], Box::new(result_type(int_type(), div_by_zero.clone())), ), @@ -362,7 +364,7 @@ pub fn types() -> MutMap { // mod : Int, Int -> Result Int [ DivByZero ]* add_type( Symbol::NUM_MOD_INT, - SolvedType::Func( + top_level_function( vec![int_type(), int_type()], Box::new(result_type(int_type(), div_by_zero.clone())), ), @@ -373,7 +375,7 @@ pub fn types() -> MutMap { // div : Float, Float -> Float add_type( Symbol::NUM_DIV_FLOAT, - SolvedType::Func( + top_level_function( vec![float_type(), float_type()], Box::new(result_type(float_type(), div_by_zero.clone())), ), @@ -382,7 +384,7 @@ pub fn types() -> MutMap { // mod : Float, Float -> Result Int [ DivByZero ]* add_type( Symbol::NUM_MOD_FLOAT, - SolvedType::Func( + top_level_function( vec![float_type(), float_type()], Box::new(result_type(float_type(), div_by_zero)), ), @@ -396,7 +398,7 @@ pub fn types() -> MutMap { add_type( Symbol::NUM_SQRT, - SolvedType::Func( + top_level_function( vec![float_type()], Box::new(result_type(float_type(), sqrt_of_negative)), ), @@ -405,25 +407,25 @@ pub fn types() -> MutMap { // round : Float -> Int add_type( Symbol::NUM_ROUND, - SolvedType::Func(vec![float_type()], Box::new(int_type())), + top_level_function(vec![float_type()], Box::new(int_type())), ); // sin : Float -> Float add_type( Symbol::NUM_SIN, - SolvedType::Func(vec![float_type()], Box::new(float_type())), + top_level_function(vec![float_type()], Box::new(float_type())), ); // cos : Float -> Float add_type( Symbol::NUM_COS, - SolvedType::Func(vec![float_type()], Box::new(float_type())), + top_level_function(vec![float_type()], Box::new(float_type())), ); // tan : Float -> Float add_type( Symbol::NUM_TAN, - SolvedType::Func(vec![float_type()], Box::new(float_type())), + top_level_function(vec![float_type()], Box::new(float_type())), ); // maxFloat : Float @@ -435,31 +437,31 @@ pub fn types() -> MutMap { // pow : Float, Float -> Float add_type( Symbol::NUM_POW, - SolvedType::Func(vec![float_type(), float_type()], Box::new(float_type())), + top_level_function(vec![float_type(), float_type()], Box::new(float_type())), ); // ceiling : Float -> Int add_type( Symbol::NUM_CEILING, - SolvedType::Func(vec![float_type()], Box::new(int_type())), + top_level_function(vec![float_type()], Box::new(int_type())), ); // powInt : Int, Int -> Int add_type( Symbol::NUM_POW_INT, - SolvedType::Func(vec![int_type(), int_type()], Box::new(int_type())), + top_level_function(vec![int_type(), int_type()], Box::new(int_type())), ); // floor : Float -> Int add_type( Symbol::NUM_FLOOR, - SolvedType::Func(vec![float_type()], Box::new(int_type())), + top_level_function(vec![float_type()], Box::new(int_type())), ); // atan : Float -> Float add_type( Symbol::NUM_ATAN, - SolvedType::Func(vec![float_type()], Box::new(float_type())), + top_level_function(vec![float_type()], Box::new(float_type())), ); // Bool module @@ -467,25 +469,25 @@ pub fn types() -> MutMap { // and : Bool, Bool -> Bool add_type( Symbol::BOOL_AND, - SolvedType::Func(vec![bool_type(), bool_type()], Box::new(bool_type())), + top_level_function(vec![bool_type(), bool_type()], Box::new(bool_type())), ); // or : Bool, Bool -> Bool add_type( Symbol::BOOL_OR, - SolvedType::Func(vec![bool_type(), bool_type()], Box::new(bool_type())), + top_level_function(vec![bool_type(), bool_type()], Box::new(bool_type())), ); // xor : Bool, Bool -> Bool add_type( Symbol::BOOL_XOR, - SolvedType::Func(vec![bool_type(), bool_type()], Box::new(bool_type())), + top_level_function(vec![bool_type(), bool_type()], Box::new(bool_type())), ); // not : Bool -> Bool add_type( Symbol::BOOL_NOT, - SolvedType::Func(vec![bool_type()], Box::new(bool_type())), + top_level_function(vec![bool_type()], Box::new(bool_type())), ); // Str module @@ -493,13 +495,13 @@ pub fn types() -> MutMap { // Str.concat : Str, Str -> Str add_type( Symbol::STR_CONCAT, - SolvedType::Func(vec![str_type(), str_type()], Box::new(str_type())), + top_level_function(vec![str_type(), str_type()], Box::new(str_type())), ); // isEmpty : Str -> Bool add_type( Symbol::STR_IS_EMPTY, - SolvedType::Func(vec![str_type()], Box::new(bool_type())), + top_level_function(vec![str_type()], Box::new(bool_type())), ); // List module @@ -512,7 +514,7 @@ pub fn types() -> MutMap { add_type( Symbol::LIST_GET, - SolvedType::Func( + top_level_function( vec![list_type(flex(TVAR1)), int_type()], Box::new(result_type(flex(TVAR1), index_out_of_bounds)), ), @@ -526,7 +528,7 @@ pub fn types() -> MutMap { add_type( Symbol::LIST_FIRST, - SolvedType::Func( + top_level_function( vec![list_type(flex(TVAR1))], Box::new(result_type(flex(TVAR1), list_was_empty)), ), @@ -535,7 +537,7 @@ pub fn types() -> MutMap { // set : List elem, Int, elem -> List elem add_type( Symbol::LIST_SET, - SolvedType::Func( + top_level_function( vec![list_type(flex(TVAR1)), int_type(), flex(TVAR1)], Box::new(list_type(flex(TVAR1))), ), @@ -544,7 +546,7 @@ pub fn types() -> MutMap { // concat : List elem, List elem -> List elem add_type( Symbol::LIST_CONCAT, - SolvedType::Func( + top_level_function( vec![list_type(flex(TVAR1)), list_type(flex(TVAR1))], Box::new(list_type(flex(TVAR1))), ), @@ -553,10 +555,10 @@ pub fn types() -> MutMap { // walkRight : List elem, (elem -> accum -> accum), accum -> accum add_type( Symbol::LIST_WALK_RIGHT, - SolvedType::Func( + top_level_function( vec![ list_type(flex(TVAR1)), - SolvedType::Func(vec![flex(TVAR1), flex(TVAR2)], Box::new(flex(TVAR2))), + closure(vec![flex(TVAR1), flex(TVAR2)], TVAR3, Box::new(flex(TVAR2))), flex(TVAR2), ], Box::new(flex(TVAR2)), @@ -566,10 +568,10 @@ pub fn types() -> MutMap { // keepIf : List elem, (elem -> Bool) -> List elem add_type( Symbol::LIST_KEEP_IF, - SolvedType::Func( + top_level_function( vec![ list_type(flex(TVAR1)), - SolvedType::Func(vec![flex(TVAR1)], Box::new(bool_type())), + closure(vec![flex(TVAR1)], TVAR2, Box::new(bool_type())), ], Box::new(list_type(flex(TVAR1))), ), @@ -578,10 +580,10 @@ pub fn types() -> MutMap { // map : List before, (before -> after) -> List after add_type( Symbol::LIST_MAP, - SolvedType::Func( + top_level_function( vec![ list_type(flex(TVAR1)), - SolvedType::Func(vec![flex(TVAR1)], Box::new(flex(TVAR2))), + closure(vec![flex(TVAR1)], TVAR3, Box::new(flex(TVAR2))), ], Box::new(list_type(flex(TVAR2))), ), @@ -590,7 +592,7 @@ pub fn types() -> MutMap { // append : List elem, elem -> List elem add_type( Symbol::LIST_APPEND, - SolvedType::Func( + top_level_function( vec![list_type(flex(TVAR1)), flex(TVAR1)], Box::new(list_type(flex(TVAR1))), ), @@ -599,7 +601,7 @@ pub fn types() -> MutMap { // prepend : List elem, elem -> List elem add_type( Symbol::LIST_PREPEND, - SolvedType::Func( + top_level_function( vec![list_type(flex(TVAR1)), flex(TVAR1)], Box::new(list_type(flex(TVAR1))), ), @@ -608,7 +610,7 @@ pub fn types() -> MutMap { // join : List (List elem) -> List elem add_type( Symbol::LIST_JOIN, - SolvedType::Func( + top_level_function( vec![list_type(list_type(flex(TVAR1)))], Box::new(list_type(flex(TVAR1))), ), @@ -617,13 +619,13 @@ pub fn types() -> MutMap { // single : a -> List a add_type( Symbol::LIST_SINGLE, - SolvedType::Func(vec![flex(TVAR1)], Box::new(list_type(flex(TVAR1)))), + top_level_function(vec![flex(TVAR1)], Box::new(list_type(flex(TVAR1)))), ); // repeat : Int, elem -> List elem add_type( Symbol::LIST_REPEAT, - SolvedType::Func( + top_level_function( vec![int_type(), flex(TVAR1)], Box::new(list_type(flex(TVAR1))), ), @@ -632,7 +634,7 @@ pub fn types() -> MutMap { // reverse : List elem -> List elem add_type( Symbol::LIST_REVERSE, - SolvedType::Func( + top_level_function( vec![list_type(flex(TVAR1))], Box::new(list_type(flex(TVAR1))), ), @@ -641,13 +643,13 @@ pub fn types() -> MutMap { // len : List * -> Int add_type( Symbol::LIST_LEN, - SolvedType::Func(vec![list_type(flex(TVAR1))], Box::new(int_type())), + top_level_function(vec![list_type(flex(TVAR1))], Box::new(int_type())), ); // isEmpty : List * -> Bool add_type( Symbol::LIST_IS_EMPTY, - SolvedType::Func(vec![list_type(flex(TVAR1))], Box::new(bool_type())), + top_level_function(vec![list_type(flex(TVAR1))], Box::new(bool_type())), ); // Map module @@ -658,7 +660,7 @@ pub fn types() -> MutMap { // singleton : k, v -> Map k v add_type( Symbol::MAP_SINGLETON, - SolvedType::Func( + top_level_function( vec![flex(TVAR1), flex(TVAR2)], Box::new(map_type(flex(TVAR1), flex(TVAR2))), ), @@ -672,7 +674,7 @@ pub fn types() -> MutMap { add_type( Symbol::MAP_GET, - SolvedType::Func( + top_level_function( vec![map_type(flex(TVAR1), flex(TVAR2)), flex(TVAR1)], Box::new(result_type(flex(TVAR2), key_not_found)), ), @@ -680,7 +682,7 @@ pub fn types() -> MutMap { add_type( Symbol::MAP_INSERT, - SolvedType::Func( + top_level_function( vec![map_type(flex(TVAR1), flex(TVAR2)), flex(TVAR1), flex(TVAR2)], Box::new(map_type(flex(TVAR1), flex(TVAR2))), ), @@ -694,13 +696,13 @@ pub fn types() -> MutMap { // singleton : a -> Set a add_type( Symbol::SET_SINGLETON, - SolvedType::Func(vec![flex(TVAR1)], Box::new(set_type(flex(TVAR1)))), + top_level_function(vec![flex(TVAR1)], Box::new(set_type(flex(TVAR1)))), ); // union : Set a, Set a -> Set a add_type( Symbol::SET_UNION, - SolvedType::Func( + top_level_function( vec![set_type(flex(TVAR1)), set_type(flex(TVAR1))], Box::new(set_type(flex(TVAR1))), ), @@ -709,7 +711,7 @@ pub fn types() -> MutMap { // diff : Set a, Set a -> Set a add_type( Symbol::SET_DIFF, - SolvedType::Func( + top_level_function( vec![set_type(flex(TVAR1)), set_type(flex(TVAR1))], Box::new(set_type(flex(TVAR1))), ), @@ -718,10 +720,10 @@ pub fn types() -> MutMap { // foldl : Set a, (a -> b -> b), b -> b add_type( Symbol::SET_FOLDL, - SolvedType::Func( + top_level_function( vec![ set_type(flex(TVAR1)), - SolvedType::Func(vec![flex(TVAR1), flex(TVAR2)], Box::new(flex(TVAR2))), + closure(vec![flex(TVAR1), flex(TVAR2)], TVAR3, Box::new(flex(TVAR2))), flex(TVAR2), ], Box::new(flex(TVAR2)), @@ -730,7 +732,7 @@ pub fn types() -> MutMap { add_type( Symbol::SET_INSERT, - SolvedType::Func( + top_level_function( vec![set_type(flex(TVAR1)), flex(TVAR1)], Box::new(set_type(flex(TVAR1))), ), @@ -738,7 +740,7 @@ pub fn types() -> MutMap { add_type( Symbol::SET_REMOVE, - SolvedType::Func( + top_level_function( vec![set_type(flex(TVAR1)), flex(TVAR1)], Box::new(set_type(flex(TVAR1))), ), @@ -749,10 +751,10 @@ pub fn types() -> MutMap { // map : Result a err, (a -> b) -> Result b err add_type( Symbol::RESULT_MAP, - SolvedType::Func( + top_level_function( vec![ result_type(flex(TVAR1), flex(TVAR3)), - SolvedType::Func(vec![flex(TVAR1)], Box::new(flex(TVAR2))), + closure(vec![flex(TVAR1)], TVAR4, Box::new(flex(TVAR2))), ], Box::new(result_type(flex(TVAR2), flex(TVAR3))), ), @@ -766,6 +768,20 @@ fn flex(tvar: VarId) -> SolvedType { SolvedType::Flex(tvar) } +#[inline(always)] +fn top_level_function(arguments: Vec, ret: Box) -> SolvedType { + SolvedType::Func( + arguments, + Box::new(SolvedType::Flex(TOP_LEVEL_CLOSURE_VAR)), + ret, + ) +} + +#[inline(always)] +fn closure(arguments: Vec, closure_var: VarId, ret: Box) -> SolvedType { + SolvedType::Func(arguments, Box::new(SolvedType::Flex(closure_var)), ret) +} + #[inline(always)] fn float_type() -> SolvedType { SolvedType::Apply(Symbol::NUM_FLOAT, Vec::new()) diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index a7fd715fc5..17db447b05 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -37,6 +37,7 @@ const NUM_BUILTIN_IMPORTS: usize = 7; /// These can be shared between definitions, they will get instantiated when converted to Type const FUVAR: VarId = VarId::from_u32(1000); +const TOP_LEVEL_CLOSURE_VAR: VarId = VarId::from_u32(1001); fn shared(base: SolvedType) -> SolvedType { SolvedType::Apply( @@ -831,12 +832,16 @@ pub fn types() -> MutMap { // , Attr Shared (a -> b) // -> Attr * (List b) add_type(Symbol::LIST_MAP, { - let_tvars! { a, b, star1, star2 }; + let_tvars! { a, b, star1, star2, closure }; unique_function( vec![ list_type(star1, a), - shared(SolvedType::Func(vec![flex(a)], Box::new(flex(b)))), + shared(SolvedType::Func( + vec![flex(a)], + Box::new(flex(closure)), + Box::new(flex(b)), + )), ], list_type(star2, b), ) @@ -846,12 +851,16 @@ pub fn types() -> MutMap { // , Attr Shared (a -> Attr * Bool) // -> Attr * (List a) add_type(Symbol::LIST_KEEP_IF, { - let_tvars! { a, star1, star2, star3 }; + let_tvars! { a, star1, star2, star3, closure }; unique_function( vec![ list_type(star1, a), - shared(SolvedType::Func(vec![flex(a)], Box::new(bool_type(star2)))), + shared(SolvedType::Func( + vec![flex(a)], + Box::new(flex(closure)), + Box::new(bool_type(star2)), + )), ], list_type(star3, a), ) @@ -862,7 +871,7 @@ pub fn types() -> MutMap { // , b // -> b add_type(Symbol::LIST_WALK_RIGHT, { - let_tvars! { u, a, b, star1 }; + let_tvars! { u, a, b, star1, closure }; unique_function( vec![ @@ -875,6 +884,7 @@ pub fn types() -> MutMap { ), shared(SolvedType::Func( vec![attr_type(u, a), flex(b)], + Box::new(flex(closure)), Box::new(flex(b)), )), flex(b), @@ -1032,7 +1042,7 @@ pub fn types() -> MutMap { // , b // -> b add_type(Symbol::SET_FOLDL, { - let_tvars! { star, u, a, b }; + let_tvars! { star, u, a, b, closure }; unique_function( vec![ @@ -1045,6 +1055,7 @@ pub fn types() -> MutMap { ), shared(SolvedType::Func( vec![attr_type(u, a), flex(b)], + Box::new(flex(closure)), Box::new(flex(b)), )), flex(b), @@ -1129,7 +1140,7 @@ pub fn types() -> MutMap { // , Attr * (a -> b) // -> Attr * (Result b e) add_type(Symbol::RESULT_MAP, { - let_tvars! { star1, star2, star3, a, b, e }; + let_tvars! { star1, star2, star3, a, b, e, closure }; unique_function( vec![ SolvedType::Apply( @@ -1143,7 +1154,7 @@ pub fn types() -> MutMap { Symbol::ATTR_ATTR, vec![ flex(star2), - SolvedType::Func(vec![flex(a)], Box::new(flex(b))), + SolvedType::Func(vec![flex(a)], Box::new(flex(closure)), Box::new(flex(b))), ], ), ], @@ -1169,7 +1180,14 @@ fn flex(tvar: VarId) -> SolvedType { fn unique_function(args: Vec, ret: SolvedType) -> SolvedType { SolvedType::Apply( Symbol::ATTR_ATTR, - vec![flex(FUVAR), SolvedType::Func(args, Box::new(ret))], + vec![ + flex(FUVAR), + SolvedType::Func( + args, + Box::new(SolvedType::Flex(TOP_LEVEL_CLOSURE_VAR)), + Box::new(ret), + ), + ], ) } diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index bc64df7f7b..dd90f36b2f 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -130,7 +130,9 @@ fn can_annotation_help( references, ); - Type::Function(args, Box::new(ret)) + let closure = Type::Variable(var_store.fresh()); + + Type::Function(args, Box::new(closure), Box::new(ret)) } Apply(module_name, ident, type_arguments) => { let symbol = if module_name.is_empty() { diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index 4c68bf592e..9d65369895 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1590,13 +1590,15 @@ fn defn( .map(|(var, symbol)| (var, no_region(Identifier(symbol)))) .collect(); - let expr = Closure( - var_store.fresh(), - fn_name, - Recursive::NotRecursive, - closure_args, - Box::new((no_region(body), ret_var)), - ); + let expr = Closure { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + return_type: ret_var, + name: fn_name, + recursive: Recursive::NotRecursive, + arguments: closure_args, + loc_body: Box::new(no_region(body)), + }; Def { loc_pattern: Located { diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index e721c90b7f..40a5e6d747 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -650,12 +650,12 @@ fn group_to_declaration( let mut new_def = can_def.clone(); // Determine recursivity of closures that are not tail-recursive - if let Closure(fn_var, name, Recursive::NotRecursive, args, body) = - new_def.loc_expr.value + if let Closure { + recursive: recursive @ Recursive::NotRecursive, + .. + } = &mut new_def.loc_expr.value { - let recursion = closure_recursivity(*symbol, closures); - - new_def.loc_expr.value = Closure(fn_var, name, recursion, args, body); + *recursive = closure_recursivity(*symbol, closures); } let is_recursive = successors(&symbol).contains(&symbol); @@ -678,12 +678,12 @@ fn group_to_declaration( let mut new_def = can_def.clone(); // Determine recursivity of closures that are not tail-recursive - if let Closure(fn_var, name, Recursive::NotRecursive, args, body) = - new_def.loc_expr.value + if let Closure { + recursive: recursive @ Recursive::NotRecursive, + .. + } = &mut new_def.loc_expr.value { - let recursion = closure_recursivity(symbol, closures); - - new_def.loc_expr.value = Closure(fn_var, name, recursion, args, body); + *recursive = closure_recursivity(symbol, closures); } if !seen_pattern_regions.contains(&new_def.loc_pattern.region) { @@ -808,16 +808,16 @@ fn canonicalize_pending_def<'a>( region: loc_ann.region, }; - let body = Box::new((body_expr, var_store.fresh())); - Located { - value: Closure( - var_store.fresh(), - symbol, - Recursive::NotRecursive, - underscores, - body, - ), + value: Closure { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + return_type: var_store.fresh(), + name: symbol, + recursive: Recursive::NotRecursive, + arguments: underscores, + loc_body: Box::new(body_expr), + }, region: loc_ann.region, } }; @@ -962,7 +962,15 @@ fn canonicalize_pending_def<'a>( if let ( &ast::Pattern::Identifier(ref _name), &Pattern::Identifier(ref defined_symbol), - &Closure(fn_var, ref symbol, _, ref arguments, ref body), + &Closure { + function_type, + closure_type, + return_type, + name: ref symbol, + ref arguments, + loc_body: ref body, + .. + }, ) = ( &loc_pattern.value, &loc_can_pattern.value, @@ -1000,13 +1008,15 @@ fn canonicalize_pending_def<'a>( }); // renamed_closure_def = Some(&defined_symbol); - loc_can_expr.value = Closure( - fn_var, - *symbol, - is_recursive, - arguments.clone(), - body.clone(), - ); + loc_can_expr.value = Closure { + function_type, + closure_type, + return_type, + name: *symbol, + recursive: is_recursive, + arguments: arguments.clone(), + loc_body: body.clone(), + }; } // Store the referenced locals in the refs_by_symbol map, so we can later figure out @@ -1086,7 +1096,15 @@ fn canonicalize_pending_def<'a>( if let ( &ast::Pattern::Identifier(ref _name), &Pattern::Identifier(ref defined_symbol), - &Closure(fn_var, ref symbol, _, ref arguments, ref body), + &Closure { + function_type, + closure_type, + return_type, + name: ref symbol, + ref arguments, + loc_body: ref body, + .. + }, ) = ( &loc_pattern.value, &loc_can_pattern.value, @@ -1123,13 +1141,15 @@ fn canonicalize_pending_def<'a>( refs.lookups = refs.lookups.without(defined_symbol); }); - loc_can_expr.value = Closure( - fn_var, - *symbol, - is_recursive, - arguments.clone(), - body.clone(), - ); + loc_can_expr.value = Closure { + function_type, + closure_type, + return_type, + name: *symbol, + recursive: is_recursive, + arguments: arguments.clone(), + loc_body: body.clone(), + }; } // Store the referenced locals in the refs_by_symbol map, so we can later figure out diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 6daebc1d77..d915954456 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -87,7 +87,7 @@ pub enum Expr { /// This is *only* for calling functions, not for tag application. /// The Tag variant contains any applied values inside it. Call( - Box<(Variable, Located, Variable)>, + Box<(Variable, Located, Variable, Variable)>, Vec<(Variable, Located)>, CalledVia, ), @@ -97,13 +97,15 @@ pub enum Expr { ret_var: Variable, }, - Closure( - Variable, - Symbol, - Recursive, - Vec<(Variable, Located)>, - Box<(Located, Variable)>, - ), + Closure { + function_type: Variable, + closure_type: Variable, + return_type: Variable, + name: Symbol, + recursive: Recursive, + arguments: Vec<(Variable, Located)>, + loc_body: Box>, + }, // Product Types Record { @@ -124,7 +126,9 @@ pub enum Expr { }, /// field accessor as a function, e.g. (.foo) expr Accessor { + function_var: Variable, record_var: Variable, + closure_var: Variable, ext_var: Variable, field_var: Variable, field: Lowercase, @@ -323,7 +327,12 @@ pub fn canonicalize_expr<'a>( }; Call( - Box::new((var_store.fresh(), fn_expr, var_store.fresh())), + Box::new(( + var_store.fresh(), + fn_expr, + var_store.fresh(), + var_store.fresh(), + )), args, *application_style, ) @@ -346,7 +355,12 @@ pub fn canonicalize_expr<'a>( _ => { // This could be something like ((if True then fn1 else fn2) arg1 arg2). Call( - Box::new((var_store.fresh(), fn_expr, var_store.fresh())), + Box::new(( + var_store.fresh(), + fn_expr, + var_store.fresh(), + var_store.fresh(), + )), args, *application_style, ) @@ -471,13 +485,15 @@ pub fn canonicalize_expr<'a>( env.register_closure(symbol, output.references.clone()); ( - Closure( - var_store.fresh(), - symbol, - Recursive::NotRecursive, - can_args, - Box::new((loc_body_expr, var_store.fresh())), - ), + Closure { + function_type: var_store.fresh(), + closure_type: var_store.fresh(), + return_type: var_store.fresh(), + name: symbol, + recursive: Recursive::NotRecursive, + arguments: can_args, + loc_body: Box::new(loc_body_expr), + }, output, ) } @@ -535,8 +551,10 @@ pub fn canonicalize_expr<'a>( } ast::Expr::AccessorFunction(field) => ( Accessor { + function_var: var_store.fresh(), record_var: var_store.fresh(), ext_var: var_store.fresh(), + closure_var: var_store.fresh(), field_var: var_store.fresh(), field: (*field).into(), }, @@ -1193,20 +1211,30 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> LetNonRec(Box::new(def), Box::new(loc_expr), var, aliases) } - Closure(var, symbol, recursive, patterns, boxed_expr) => { - let (loc_expr, expr_var) = *boxed_expr; + Closure { + function_type, + closure_type, + return_type, + recursive, + name, + arguments, + loc_body, + } => { + let loc_expr = *loc_body; let loc_expr = Located { value: inline_calls(var_store, scope, loc_expr.value), region: loc_expr.region, }; - Closure( - var, - symbol, + Closure { + function_type, + closure_type, + return_type, recursive, - patterns, - Box::new((loc_expr, expr_var)), - ) + name, + arguments, + loc_body: Box::new(loc_expr), + } } Record { record_var, fields } => { @@ -1243,14 +1271,20 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> } Call(boxed_tuple, args, called_via) => { - let (fn_var, loc_expr, expr_var) = *boxed_tuple; + let (fn_var, loc_expr, closure_var, expr_var) = *boxed_tuple; match loc_expr.value { Var(symbol) if symbol.is_builtin() => match builtin_defs(var_store).get(&symbol) { Some(Def { loc_expr: Located { - value: Closure(_var, _, recursive, params, boxed_body), + value: + Closure { + recursive, + arguments: params, + loc_body: boxed_body, + .. + }, .. }, .. @@ -1263,7 +1297,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> debug_assert_eq!(params.len(), args.len()); // Start with the function's body as the answer. - let (mut loc_answer, _body_var) = *boxed_body.clone(); + let mut loc_answer = *boxed_body.clone(); // Wrap the body in one LetNonRec for each argument, // such that at the end we have all the arguments in @@ -1311,7 +1345,11 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> }, _ => { // For now, we only inline calls to builtins. Leave this alone! - Call(Box::new((fn_var, loc_expr, expr_var)), args, called_via) + Call( + Box::new((fn_var, loc_expr, closure_var, expr_var)), + args, + called_via, + ) } } } @@ -1458,7 +1496,12 @@ fn desugar_str_segments(var_store: &mut VarStore, segments: Vec) -> let fn_expr = Located::new(0, 0, 0, 0, Expr::Var(Symbol::STR_CONCAT)); let expr = Expr::Call( - Box::new((var_store.fresh(), fn_expr, var_store.fresh())), + Box::new(( + var_store.fresh(), + fn_expr, + var_store.fresh(), + var_store.fresh(), + )), vec![ (var_store.fresh(), loc_new_expr), (var_store.fresh(), loc_expr), diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 9c40ee38ea..e4443495e3 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -309,7 +309,10 @@ mod test_can { match expr { LetRec(assignments, body, _, _) => { match &assignments.get(i).map(|def| &def.loc_expr.value) { - Some(Closure(_, _, recursion, _, _)) => recursion.clone(), + Some(Closure { + recursive: recursion, + .. + }) => recursion.clone(), Some(other @ _) => { panic!("assignment at {} is not a closure, but a {:?}", i, other) } @@ -328,7 +331,10 @@ mod test_can { get_closure(&body.value, i - 1) } else { match &def.loc_expr.value { - Closure(_, _, recursion, _, _) => recursion.clone(), + Closure { + recursive: recursion, + .. + } => recursion.clone(), other @ _ => { panic!("assignment at {} is not a closure, but a {:?}", i, other) } diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index b4148ac1d9..57e79ce785 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -239,7 +239,7 @@ pub fn constrain_expr( } } Call(boxed, loc_args, _application_style) => { - let (fn_var, loc_fn, ret_var) = &**boxed; + let (fn_var, loc_fn, closure_var, ret_var) = &**boxed; // The expression that evaluates to the function being called, e.g. `foo` in // (foo) bar baz let opt_symbol = if let Var(symbol) = loc_fn.value { @@ -262,11 +262,15 @@ pub fn constrain_expr( // The function's return type let ret_type = Variable(*ret_var); + // type of values captured in the closure + let closure_type = Variable(*closure_var); + // This will be used in the occurs check let mut vars = Vec::with_capacity(2 + loc_args.len()); vars.push(*fn_var); vars.push(*ret_var); + vars.push(*closure_var); let mut arg_types = Vec::with_capacity(loc_args.len()); let mut arg_cons = Vec::with_capacity(loc_args.len()); @@ -289,7 +293,11 @@ pub fn constrain_expr( let expected_fn_type = ForReason( fn_reason, - Function(arg_types, Box::new(ret_type.clone())), + Function( + arg_types, + Box::new(closure_type), + Box::new(ret_type.clone()), + ), region, ); @@ -306,21 +314,31 @@ pub fn constrain_expr( ) } Var(symbol) => Lookup(*symbol, expected, region), - Closure(fn_var, _symbol, _recursive, args, boxed) => { - let (loc_body_expr, ret_var) = boxed.as_ref(); + Closure { + function_type: fn_var, + closure_type: closure_var, + return_type: ret_var, + arguments, + loc_body: boxed, + .. + } => { + let loc_body_expr = &**boxed; let mut state = PatternState { headers: SendMap::default(), - vars: Vec::with_capacity(args.len()), + vars: Vec::with_capacity(arguments.len()), constraints: Vec::with_capacity(1), }; let mut vars = Vec::with_capacity(state.vars.capacity() + 1); let mut pattern_types = Vec::with_capacity(state.vars.capacity()); let ret_var = *ret_var; + let closure_var = *closure_var; let ret_type = Type::Variable(ret_var); + let closure_type = Type::Variable(closure_var); vars.push(ret_var); + vars.push(closure_var); - for (pattern_var, loc_pattern) in args { + for (pattern_var, loc_pattern) in arguments { let pattern_type = Type::Variable(*pattern_var); let pattern_expected = PExpected::NoExpectation(pattern_type.clone()); @@ -337,7 +355,11 @@ pub fn constrain_expr( vars.push(*pattern_var); } - let fn_type = Type::Function(pattern_types, Box::new(ret_type.clone())); + let fn_type = Type::Function( + pattern_types, + Box::new(closure_type), + Box::new(ret_type.clone()), + ); let body_type = NoExpectation(ret_type); let ret_constraint = constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type); @@ -653,8 +675,10 @@ pub fn constrain_expr( ) } Accessor { + function_var, field, record_var, + closure_var, ext_var, field_var, } => { @@ -678,12 +702,19 @@ pub fn constrain_expr( region, ); + let function_type = Type::Function( + vec![record_type], + Box::new(Type::Variable(*closure_var)), + Box::new(field_type), + ); + exists( - vec![*record_var, field_var, ext_var], + vec![*record_var, *function_var, *closure_var, field_var, ext_var], And(vec![ + Eq(function_type.clone(), expected, category.clone(), region), Eq( - Type::Function(vec![record_type], Box::new(field_type)), - expected, + function_type, + NoExpectation(Variable(*function_var)), category, region, ), @@ -1032,26 +1063,36 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { // instead of the more generic "something is wrong with the body of `f`" match (&def.loc_expr.value, &signature) { ( - Closure(fn_var, _symbol, _recursive, args, boxed), - Type::Function(arg_types, _), + Closure { + function_type: fn_var, + closure_type: closure_var, + return_type: ret_var, + arguments, + loc_body, + .. + }, + Type::Function(arg_types, _, _), ) => { let expected = annotation_expected; let region = def.loc_expr.region; - let (loc_body_expr, ret_var) = boxed.as_ref(); + let loc_body_expr = &**loc_body; let mut state = PatternState { headers: SendMap::default(), - vars: Vec::with_capacity(args.len()), + vars: Vec::with_capacity(arguments.len()), constraints: Vec::with_capacity(1), }; let mut vars = Vec::with_capacity(state.vars.capacity() + 1); let mut pattern_types = Vec::with_capacity(state.vars.capacity()); let ret_var = *ret_var; + let closure_var = *closure_var; let ret_type = Type::Variable(ret_var); + let closure_type = Type::Variable(closure_var); vars.push(ret_var); + vars.push(closure_var); - let it = args.iter().zip(arg_types.iter()).enumerate(); + let it = arguments.iter().zip(arg_types.iter()).enumerate(); for (index, ((pattern_var, loc_pattern), loc_ann)) in it { { // ensure type matches the one in the annotation @@ -1098,7 +1139,11 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { } } - let fn_type = Type::Function(pattern_types, Box::new(ret_type.clone())); + let fn_type = Type::Function( + pattern_types, + Box::new(closure_type), + Box::new(ret_type.clone()), + ); let body_type = NoExpectation(ret_type); let ret_constraint = constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type); diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 9a54c44551..c51100c9fe 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -178,7 +178,7 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V use roc_types::solved_types::SolvedType::*; match solved_type { - Func(args, ret) => { + Func(args, closure, ret) => { let mut new_args = Vec::with_capacity(args.len()); for arg in args { @@ -186,8 +186,9 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V } let new_ret = to_type(&ret, free_vars, var_store); + let new_closure = to_type(&closure, free_vars, var_store); - Type::Function(new_args, Box::new(new_ret)) + Type::Function(new_args, Box::new(new_closure), Box::new(new_ret)) } Apply(symbol, args) => { let mut new_args = Vec::with_capacity(args.len()); diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index bf1d9ef88e..942cf8c650 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -707,24 +707,35 @@ pub fn constrain_expr( expected, ) } - Closure(fn_var, _symbol, recursion, args, boxed) => { + Closure { + function_type: fn_var, + return_type: ret_var, + closure_type: closure_var, + recursive: recursion, + arguments, + loc_body: boxed, + .. + } => { use roc_can::expr::Recursive; - let (loc_body_expr, ret_var) = &**boxed; + let loc_body_expr = &**boxed; let mut state = PatternState { headers: SendMap::default(), - vars: Vec::with_capacity(args.len()), + vars: Vec::with_capacity(arguments.len()), constraints: Vec::with_capacity(1), }; let mut vars = Vec::with_capacity(state.vars.capacity() + 1); let mut pattern_types = Vec::with_capacity(state.vars.capacity()); let ret_var = *ret_var; let ret_type = Type::Variable(ret_var); + let closure_var = *closure_var; + let closure_type = Type::Variable(closure_var); vars.push(ret_var); + vars.push(closure_var); vars.push(*fn_var); - for (pattern_var, loc_pattern) in args { + for (pattern_var, loc_pattern) in arguments { let pattern_type = Type::Variable(*pattern_var); let pattern_expected = PExpected::NoExpectation(pattern_type.clone()); @@ -755,7 +766,11 @@ pub fn constrain_expr( let fn_type = attr_type( fn_uniq_type, - Type::Function(pattern_types, Box::new(ret_type.clone())), + Type::Function( + pattern_types, + Box::new(closure_type), + Box::new(ret_type.clone()), + ), ); let body_type = Expected::NoExpectation(ret_type); let ret_constraint = constrain_expr( @@ -795,9 +810,10 @@ pub fn constrain_expr( } Call(boxed, loc_args, _) => { - let (fn_var, fn_expr, ret_var) = &**boxed; + let (fn_var, fn_expr, closure_var, ret_var) = &**boxed; let fn_type = Variable(*fn_var); let ret_type = Variable(*ret_var); + let closure_type = Variable(*closure_var); let fn_expected = Expected::NoExpectation(fn_type.clone()); let fn_region = fn_expr.region; @@ -811,6 +827,7 @@ pub fn constrain_expr( vars.push(*fn_var); vars.push(*ret_var); + vars.push(*closure_var); // Canonicalize the function expression and its arguments let fn_con = constrain_expr( @@ -862,7 +879,11 @@ pub fn constrain_expr( fn_reason, attr_type( Bool::variable(expected_uniq_type), - Function(arg_types, Box::new(ret_type.clone())), + Function( + arg_types, + Box::new(closure_type), + Box::new(ret_type.clone()), + ), ), region, ); @@ -1424,8 +1445,10 @@ pub fn constrain_expr( } Accessor { + function_var, field, record_var, + closure_var, field_var, ext_var, } => { @@ -1455,21 +1478,37 @@ pub fn constrain_expr( ); let fn_uniq_var = var_store.fresh(); + let closure_type = Type::Variable(*closure_var); let fn_type = attr_type( Bool::variable(fn_uniq_var), - Type::Function(vec![record_type], Box::new(field_type)), + Type::Function( + vec![record_type], + Box::new(closure_type), + Box::new(field_type), + ), ); exists( vec![ *record_var, + *function_var, + *closure_var, *field_var, *ext_var, fn_uniq_var, field_uniq_var, record_uniq_var, ], - And(vec![Eq(fn_type, expected, category, region), record_con]), + And(vec![ + Eq(fn_type.clone(), expected, category.clone(), region), + Eq( + fn_type, + Expected::NoExpectation(Variable(*function_var)), + category, + region, + ), + record_con, + ]), ) } RuntimeError(_) => True, @@ -1873,21 +1912,33 @@ fn annotation_to_attr_type( ) } - Function(arguments, result) => { + Function(arguments, closure, result) => { let uniq_var = var_store.fresh(); let (mut arg_vars, args_lifted) = annotation_to_attr_type_many(var_store, arguments, rigids, change_var_kind); + let (closure_vars, closure_lifted) = + annotation_to_attr_type(var_store, closure, rigids, change_var_kind); let (result_vars, result_lifted) = annotation_to_attr_type(var_store, result, rigids, change_var_kind); arg_vars.extend(result_vars); + arg_vars.extend(closure_vars); arg_vars.push(uniq_var); + match **closure { + Type::Variable(c) => arg_vars.push(c), + _ => unreachable!("closure must contain a type variable"), + } + ( arg_vars, attr_type( Bool::variable(uniq_var), - Type::Function(args_lifted, Box::new(result_lifted)), + Type::Function( + args_lifted, + Box::new(closure_lifted), + Box::new(result_lifted), + ), ), ) } @@ -2542,8 +2593,9 @@ fn fix_mutual_recursive_alias_help_help(rec_var: Variable, attribute: &Type, int use Type::*; match into_type { - Function(args, ret) => { + Function(args, closure, ret) => { fix_mutual_recursive_alias_help(rec_var, attribute, ret); + fix_mutual_recursive_alias_help(rec_var, attribute, closure); args.iter_mut() .for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg)); } diff --git a/compiler/gen/src/layout_id.rs b/compiler/gen/src/layout_id.rs index 06133558b4..263f011087 100644 --- a/compiler/gen/src/layout_id.rs +++ b/compiler/gen/src/layout_id.rs @@ -10,7 +10,7 @@ impl LayoutId { // Returns something like "foo#1" when given a symbol that interns to "foo" // and a LayoutId of 1. pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String { - format!("{}#{}", symbol.ident_string(interns), self.0) + format!("{}_{}", symbol.ident_string(interns), self.0) } } diff --git a/compiler/gen/tests/gen_records.rs b/compiler/gen/tests/gen_records.rs index ffcb65cca3..d9e5dcf9cd 100644 --- a/compiler/gen/tests/gen_records.rs +++ b/compiler/gen/tests/gen_records.rs @@ -410,9 +410,9 @@ mod gen_records { { x: Blue, y ? 3 } -> y { x: Red, y ? 5 } -> y - a = f { x: Blue, y: 7 } + a = f { x: Blue, y: 7 } b = f { x: Blue } - c = f { x: Red, y: 11 } + c = f { x: Red, y: 11 } d = f { x: Red } a * b * c * d @@ -617,7 +617,7 @@ mod gen_records { assert_evals_to!( indoc!( r#" - { a: 3.14, b: 0x1 } + { a: 3.14, b: 0x1 } "# ), (3.14, 0x1), @@ -678,15 +678,58 @@ mod gen_records { } #[test] - fn just_to_be_sure() { + fn accessor() { assert_evals_to!( indoc!( r#" - { a: 1, b : 2, c : 3 } + .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 } "# ), - [1, 2, 3], - [i64; 3] + 7, + i64 + ); + } + + #[test] + fn accessor_single_element_record() { + assert_evals_to!( + indoc!( + r#" + .foo { foo: 4 } + "# + ), + 4, + i64 + ); + } + + #[test] + fn update_record() { + assert_evals_to!( + indoc!( + r#" + rec = { foo: 42, bar: 6.28 } + + { rec & foo: rec.foo + 1 } + "# + ), + (6.28, 43), + (f64, i64) + ); + } + + #[test] + fn update_single_element_record() { + assert_evals_to!( + indoc!( + r#" + rec = { foo: 42} + + { rec & foo: rec.foo + 1 } + "# + ), + 43, + i64 ); } } diff --git a/compiler/mono/src/borrow.rs b/compiler/mono/src/borrow.rs index 4f209d9dd3..9d917abe89 100644 --- a/compiler/mono/src/borrow.rs +++ b/compiler/mono/src/borrow.rs @@ -337,6 +337,7 @@ impl<'a> BorrowInfState<'a> { self.own_var(*x); } } + FunctionCall { call_type, args, diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index eef3308a5c..b165490c00 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -438,7 +438,9 @@ impl<'a> Procs<'a> { fn get_args_ret_var(subs: &Subs, var: Variable) -> Option<(std::vec::Vec, Variable)> { match subs.get_without_compacting(var).content { - Content::Structure(FlatType::Func(pattern_vars, ret_var)) => Some((pattern_vars, ret_var)), + Content::Structure(FlatType::Func(pattern_vars, _closure_var, ret_var)) => { + Some((pattern_vars, ret_var)) + } Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, args)) => { get_args_ret_var(subs, args[1]) } @@ -678,6 +680,7 @@ pub enum Expr<'a> { arguments: &'a [Symbol], }, Struct(&'a [Symbol]), + AccessAtIndex { index: u64, field_layouts: &'a [Layout<'a>], @@ -1334,24 +1337,32 @@ pub fn with_hole<'a>( }, LetNonRec(def, cont, _, _) => { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { - if let Closure(ann, _, recursivity, loc_args, boxed_body) = def.loc_expr.value { + if let Closure { + function_type, + return_type, + recursive, + arguments, + loc_body: boxed_body, + .. + } = def.loc_expr.value + { // Extract Procs, but discard the resulting Expr::Load. // That Load looks up the pointer, which we won't use here! - let (loc_body, ret_var) = *boxed_body; + let loc_body = *boxed_body; let is_self_recursive = - !matches!(recursivity, roc_can::expr::Recursive::NotRecursive); + !matches!(recursive, roc_can::expr::Recursive::NotRecursive); procs.insert_named( env, layout_cache, *symbol, - ann, - loc_args, + function_type, + arguments, loc_body, is_self_recursive, - ret_var, + return_type, ); return with_hole(env, cont.value, procs, layout_cache, assigned, hole); @@ -1418,24 +1429,32 @@ pub fn with_hole<'a>( // because Roc is strict, only functions can be recursive! for def in defs.into_iter() { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { - if let Closure(ann, _, recursivity, loc_args, boxed_body) = def.loc_expr.value { + if let Closure { + function_type, + return_type, + recursive, + arguments, + loc_body: boxed_body, + .. + } = def.loc_expr.value + { // Extract Procs, but discard the resulting Expr::Load. // That Load looks up the pointer, which we won't use here! - let (loc_body, ret_var) = *boxed_body; + let loc_body = *boxed_body; let is_self_recursive = - !matches!(recursivity, roc_can::expr::Recursive::NotRecursive); + !matches!(recursive, roc_can::expr::Recursive::NotRecursive); procs.insert_named( env, layout_cache, *symbol, - ann, - loc_args, + function_type, + arguments, loc_body, is_self_recursive, - ret_var, + return_type, ); continue; @@ -1941,13 +1960,188 @@ pub fn with_hole<'a>( stmt } - Accessor { .. } | Update { .. } => todo!("record access/accessor/update"), + Accessor { + function_var, + record_var, + closure_var: _, + ext_var, + field_var, + field, + } => { + // IDEA: convert accessor fromt + // + // .foo + // + // into + // + // (\r -> r.foo) + let record_symbol = env.unique_symbol(); + let body = roc_can::expr::Expr::Access { + record_var, + ext_var, + field_var, + loc_expr: Box::new(Located::at_zero(roc_can::expr::Expr::Var(record_symbol))), + field, + }; - Closure(ann, name, _, loc_args, boxed_body) => { - let (loc_body, ret_var) = *boxed_body; + let loc_body = Located::at_zero(body); - match procs.insert_anonymous(env, name, ann, loc_args, loc_body, ret_var, layout_cache) - { + let name = env.unique_symbol(); + + let arguments = vec![( + record_var, + Located::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)), + )]; + + match procs.insert_anonymous( + env, + name, + function_var, + arguments, + loc_body, + field_var, + layout_cache, + ) { + Ok(layout) => { + // TODO should the let have layout Pointer? + Stmt::Let( + assigned, + Expr::FunctionPointer(name, layout.clone()), + layout, + hole, + ) + } + + Err(_error) => Stmt::RuntimeError( + "TODO convert anonymous function error to a RuntimeError string", + ), + } + } + + Update { + record_var, + symbol: structure, + updates, + .. + } => { + use FieldType::*; + + enum FieldType<'a> { + CopyExisting(u64), + UpdateExisting(&'a roc_can::expr::Field), + }; + + // Strategy: turn a record update into the creation of a new record. + // This has the benefit that we don't need to do anything special for reference + // counting + + let sorted_fields = crate::layout::sort_record_fields(env.arena, record_var, env.subs); + + let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena); + + let mut symbols = Vec::with_capacity_in(sorted_fields.len(), env.arena); + let mut fields = Vec::with_capacity_in(sorted_fields.len(), env.arena); + + let mut current = 0; + for (label, opt_field_layout) in sorted_fields.into_iter() { + match opt_field_layout { + Err(_) => { + debug_assert!(!updates.contains_key(&label)); + // this was an optional field, and now does not exist! + // do not increment `current`! + } + Ok(field_layout) => { + field_layouts.push(field_layout); + + if let Some(field) = updates.get(&label) { + // TODO + let field_symbol = + possible_reuse_symbol(env, procs, &field.loc_expr.value); + + fields.push(UpdateExisting(field)); + symbols.push(field_symbol); + } else { + fields.push(CopyExisting(current)); + symbols.push(env.unique_symbol()); + } + + current += 1; + } + } + } + let symbols = symbols.into_bump_slice(); + + let record_layout = layout_cache + .from_var(env.arena, record_var, env.subs) + .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); + + let field_layouts = match &record_layout { + Layout::Struct(layouts) => *layouts, + other => arena.alloc([other.clone()]), + }; + + let wrapped = if field_layouts.len() == 1 { + Wrapped::SingleElementRecord + } else { + Wrapped::RecordOrSingleTagUnion + }; + + let expr = Expr::Struct(symbols); + let mut stmt = Stmt::Let(assigned, expr, record_layout, hole); + + let it = field_layouts.iter().zip(symbols.iter()).zip(fields); + for ((field_layout, symbol), what_to_do) in it { + match what_to_do { + UpdateExisting(field) => { + stmt = assign_to_symbol( + env, + procs, + layout_cache, + field.var, + *field.loc_expr.clone(), + *symbol, + stmt, + ); + } + CopyExisting(index) => { + let access_expr = Expr::AccessAtIndex { + structure, + index, + field_layouts, + wrapped, + }; + stmt = Stmt::Let( + *symbol, + access_expr, + field_layout.clone(), + arena.alloc(stmt), + ); + } + } + } + + stmt + } + + Closure { + function_type, + return_type, + name, + arguments, + loc_body: boxed_body, + .. + } => { + let loc_body = *boxed_body; + + match procs.insert_anonymous( + env, + name, + function_type, + arguments, + loc_body, + return_type, + layout_cache, + ) { Ok(layout) => { // TODO should the let have layout Pointer? Stmt::Let( @@ -1965,7 +2159,7 @@ pub fn with_hole<'a>( } Call(boxed, loc_args, _) => { - let (fn_var, loc_expr, ret_var) = *boxed; + let (fn_var, loc_expr, _closure_var, ret_var) = *boxed; // even if a call looks like it's by name, it may in fact be by-pointer. // E.g. in `(\f, x -> f x)` the call is in fact by pointer. @@ -2197,24 +2391,31 @@ pub fn from_can<'a>( // Now that we know for sure it's a closure, get an owned // version of these variant args so we can use them properly. match def.loc_expr.value { - Closure(ann, _, recursivity, loc_args, boxed_body) => { + Closure { + function_type, + return_type, + recursive, + arguments, + loc_body: boxed_body, + .. + } => { // Extract Procs, but discard the resulting Expr::Load. // That Load looks up the pointer, which we won't use here! - let (loc_body, ret_var) = *boxed_body; + let loc_body = *boxed_body; let is_self_recursive = - !matches!(recursivity, roc_can::expr::Recursive::NotRecursive); + !matches!(recursive, roc_can::expr::Recursive::NotRecursive); procs.insert_named( env, layout_cache, *symbol, - ann, - loc_args, + function_type, + arguments, loc_body, is_self_recursive, - ret_var, + return_type, ); continue; @@ -2229,28 +2430,35 @@ pub fn from_can<'a>( } LetNonRec(def, cont, _, _) => { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { - if let Closure(_, _, _, _, _) = &def.loc_expr.value { + if let Closure { .. } = &def.loc_expr.value { // Now that we know for sure it's a closure, get an owned // version of these variant args so we can use them properly. match def.loc_expr.value { - Closure(ann, _, recursivity, loc_args, boxed_body) => { + Closure { + function_type, + return_type, + recursive, + arguments, + loc_body: boxed_body, + .. + } => { // Extract Procs, but discard the resulting Expr::Load. // That Load looks up the pointer, which we won't use here! - let (loc_body, ret_var) = *boxed_body; + let loc_body = *boxed_body; let is_self_recursive = - !matches!(recursivity, roc_can::expr::Recursive::NotRecursive); + !matches!(recursive, roc_can::expr::Recursive::NotRecursive); procs.insert_named( env, layout_cache, *symbol, - ann, - loc_args, + function_type, + arguments, loc_body, is_self_recursive, - ret_var, + return_type, ); return from_can(env, cont.value, procs, layout_cache); diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 781d22ad0a..ad9f4d16fd 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -391,7 +391,7 @@ fn layout_from_flat_type<'a>( } } } - Func(args, ret_var) => { + Func(args, _, ret_var) => { let mut fn_args = Vec::with_capacity_in(args.len(), arena); for arg_var in args { @@ -406,15 +406,19 @@ fn layout_from_flat_type<'a>( )) } Record(fields, ext_var) => { - debug_assert!(ext_var_is_empty_record(subs, ext_var)); - // Sort the fields by label let mut sorted_fields = Vec::with_capacity_in(fields.len(), arena); + sorted_fields.extend(fields.into_iter()); - for tuple in fields { - sorted_fields.push(tuple); + // extract any values from the ext_var + let mut fields_map = MutMap::default(); + match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut fields_map) { + Ok(()) | Err((_, Content::FlexVar(_))) => {} + Err(_) => unreachable!("this would have been a type error"), } + sorted_fields.extend(fields_map.into_iter()); + sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2)); // Determine the layouts of the fields, maintaining sort order @@ -781,16 +785,6 @@ fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool { unreachable!(); } -#[cfg(debug_assertions)] -fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool { - // the ext_var is empty - let mut ext_fields = MutMap::default(); - match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut ext_fields) { - Ok(()) | Err((_, Content::FlexVar(_))) => ext_fields.is_empty(), - Err((_, content)) => panic!("invalid content in ext_var: {:?}", content), - } -} - #[cfg(not(debug_assertions))] fn ext_var_is_empty_record(_: &Subs, _: Variable) -> bool { // This should only ever be used in debug_assert! macros diff --git a/compiler/reporting/src/error/type.rs b/compiler/reporting/src/error/type.rs index 9f6eee3184..798cb38e2c 100644 --- a/compiler/reporting/src/error/type.rs +++ b/compiler/reporting/src/error/type.rs @@ -796,7 +796,7 @@ fn count_arguments(tipe: &ErrorType) -> usize { use ErrorType::*; match tipe { - Function(args, _) => args.len(), + Function(args, _, _) => args.len(), Type(Symbol::ATTR_ATTR, args) => count_arguments(&args[1]), Alias(_, _, actual) => count_arguments(actual), _ => 0, @@ -1334,7 +1334,7 @@ pub fn to_doc<'b>( use ErrorType::*; match tipe { - Function(args, ret) => report_text::function( + Function(args, _, ret) => report_text::function( alloc, parens, args.into_iter() @@ -1474,7 +1474,7 @@ fn to_diff<'b>( (FlexVar(x), FlexVar(y)) if x == y => same(alloc, parens, type1), (RigidVar(x), RigidVar(y)) if x == y => same(alloc, parens, type1), - (Function(args1, ret1), Function(args2, ret2)) => { + (Function(args1, _, ret1), Function(args2, _, ret2)) => { if args1.len() == args2.len() { let mut status = Status::Similar; let arg_diff = traverse(alloc, Parens::InFn, args1, args2); @@ -2406,7 +2406,7 @@ fn type_problem_to_pretty<'b>( match tipe { Infinite | Error | FlexVar(_) => alloc.nil(), RigidVar(y) => bad_double_rigid(x, y), - Function(_, _) => bad_rigid_var(x, alloc.reflow("a function value")), + Function(_, _, _) => bad_rigid_var(x, alloc.reflow("a function value")), Record(_, _) => bad_rigid_var(x, alloc.reflow("a record value")), TagUnion(_, _) | RecursiveTagUnion(_, _, _) => { bad_rigid_var(x, alloc.reflow("a tag value")) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 3fff70340d..506fd6bee4 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -599,7 +599,7 @@ fn type_to_variable( register(subs, rank, pools, content) } - Function(args, ret_type) => { + Function(args, closure_type, ret_type) => { let mut arg_vars = Vec::with_capacity(args.len()); for arg in args { @@ -607,7 +607,8 @@ fn type_to_variable( } let ret_var = type_to_variable(subs, rank, pools, cached, ret_type); - let content = Content::Structure(FlatType::Func(arg_vars, ret_var)); + let closure_var = type_to_variable(subs, rank, pools, cached, closure_type); + let content = Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)); register(subs, rank, pools, content) } @@ -1075,9 +1076,17 @@ fn adjust_rank_content( rank } - Func(arg_vars, ret_var) => { + Func(arg_vars, closure_var, ret_var) => { let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, ret_var); + rank = rank.max(adjust_rank( + subs, + young_mark, + visit_mark, + group_rank, + closure_var, + )); + for var in arg_vars { rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); } @@ -1239,14 +1248,15 @@ fn deep_copy_var_help( Apply(symbol, args) } - Func(arg_vars, ret_var) => { + Func(arg_vars, closure_var, ret_var) => { let new_ret_var = deep_copy_var_help(subs, max_rank, pools, ret_var); + let new_closure_var = deep_copy_var_help(subs, max_rank, pools, closure_var); let arg_vars = arg_vars .into_iter() .map(|var| deep_copy_var_help(subs, max_rank, pools, var)) .collect(); - Func(arg_vars, new_ret_var) + Func(arg_vars, new_closure_var, new_ret_var) } same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same, diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 053248f67e..9685dbc9a0 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -150,7 +150,7 @@ fn find_names_needed( find_names_needed(var, subs, roots, root_appearances, names_taken); } } - Structure(Func(arg_vars, ret_var)) => { + Structure(Func(arg_vars, _closure_var, ret_var)) => { for var in arg_vars { find_names_needed(var, subs, roots, root_appearances, names_taken); } @@ -376,7 +376,7 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String Apply(symbol, args) => write_apply(env, symbol, args, subs, buf, parens), EmptyRecord => buf.push_str(EMPTY_RECORD), EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION), - Func(args, ret) => write_fn(env, args, ret, subs, buf, parens), + Func(args, _closure, ret) => write_fn(env, args, ret, subs, buf, parens), Record(fields, ext_var) => { use crate::types::{gather_fields, RecordStructure}; diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index f48f6e482a..4957a53596 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -24,7 +24,7 @@ impl Solved { #[derive(Debug, Clone, PartialEq)] pub enum SolvedType { /// A function. The types of its arguments, then the type of its return value. - Func(Vec, Box), + Func(Vec, Box, Box), /// Applying a type to some arguments (e.g. Map.Map String Int) Apply(Symbol, Vec), /// A bound type variable, e.g. `a` in `(a -> a)` @@ -107,8 +107,9 @@ impl SolvedType { SolvedType::Apply(symbol, solved_types) } - Function(args, box_ret) => { + Function(args, box_closure, box_ret) => { let solved_ret = Self::from_type(solved_subs, *box_ret); + let solved_closure = Self::from_type(solved_subs, *box_closure); let mut solved_args = Vec::with_capacity(args.len()); for arg in args.into_iter() { @@ -117,7 +118,7 @@ impl SolvedType { solved_args.push(solved_arg); } - SolvedType::Func(solved_args, Box::new(solved_ret)) + SolvedType::Func(solved_args, Box::new(solved_closure), Box::new(solved_ret)) } Record(fields, box_ext) => { let solved_ext = Self::from_type(solved_subs, *box_ext); @@ -227,7 +228,7 @@ impl SolvedType { SolvedType::Apply(symbol, new_args) } - Func(args, ret) => { + Func(args, closure, ret) => { let mut new_args = Vec::with_capacity(args.len()); for var in args { @@ -235,8 +236,9 @@ impl SolvedType { } let ret = Self::from_var(subs, ret); + let closure = Self::from_var(subs, closure); - SolvedType::Func(new_args, Box::new(ret)) + SolvedType::Func(new_args, Box::new(closure), Box::new(ret)) } Record(fields, ext_var) => { let mut new_fields = Vec::with_capacity(fields.len()); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 135de82b09..e57fe32f9e 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -565,7 +565,7 @@ impl Content { #[derive(Clone, Debug, PartialEq, Eq)] pub enum FlatType { Apply(Symbol, Vec), - Func(Vec, Variable), + Func(Vec, Variable, Variable), Record(MutMap>, Variable), TagUnion(MutMap>, Variable), RecursiveTagUnion(Variable, MutMap>, Variable), @@ -606,8 +606,10 @@ fn occurs( match flat_type { Apply(_, args) => short_circuit(subs, root_var, &new_seen, args.iter()), - Func(arg_vars, ret_var) => { - let it = once(&ret_var).chain(arg_vars.iter()); + Func(arg_vars, closure_var, ret_var) => { + let it = once(&ret_var) + .chain(once(&closure_var)) + .chain(arg_vars.iter()); short_circuit(subs, root_var, &new_seen, it) } Record(vars_by_field, ext_var) => { @@ -699,14 +701,19 @@ fn explicit_substitute( subs.set_content(in_var, Structure(Apply(symbol, new_args))); } - Func(arg_vars, ret_var) => { + Func(arg_vars, closure_var, ret_var) => { let new_arg_vars = arg_vars .iter() .map(|var| explicit_substitute(subs, from, to, *var, seen)) .collect(); let new_ret_var = explicit_substitute(subs, from, to, ret_var, seen); + let new_closure_var = + explicit_substitute(subs, from, to, closure_var, seen); - subs.set_content(in_var, Structure(Func(new_arg_vars, new_ret_var))); + subs.set_content( + in_var, + Structure(Func(new_arg_vars, new_closure_var, new_ret_var)), + ); } TagUnion(mut tags, ext_var) => { let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen); @@ -804,8 +811,9 @@ fn get_var_names( }) } - FlatType::Func(arg_vars, ret_var) => { + FlatType::Func(arg_vars, closure_var, ret_var) => { let taken_names = get_var_names(subs, ret_var, taken_names); + let taken_names = get_var_names(subs, closure_var, taken_names); arg_vars.into_iter().fold(taken_names, |answer, arg_var| { get_var_names(subs, arg_var, answer) @@ -998,14 +1006,15 @@ fn flat_type_to_err_type( ErrorType::Type(symbol, arg_types) } - Func(arg_vars, ret_var) => { + Func(arg_vars, closure_var, ret_var) => { let args = arg_vars .into_iter() .map(|arg_var| var_to_err_type(subs, state, arg_var)) .collect(); let ret = var_to_err_type(subs, state, ret_var); + let closure = var_to_err_type(subs, state, closure_var); - ErrorType::Function(args, Box::new(ret)) + ErrorType::Function(args, Box::new(closure), Box::new(ret)) } EmptyRecord => ErrorType::Record(SendMap::default(), TypeExt::Closed), @@ -1145,12 +1154,13 @@ fn restore_content(subs: &mut Subs, content: &Content) { } } - Func(arg_vars, ret_var) => { + Func(arg_vars, closure_var, ret_var) => { for &var in arg_vars { subs.restore(var); } subs.restore(*ret_var); + subs.restore(*closure_var); } EmptyRecord => (), diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 5ca267f31c..5fb19fe21b 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -138,8 +138,8 @@ impl RecordField { pub enum Type { EmptyRec, EmptyTagUnion, - /// A function. The types of its arguments, then the type of its return value. - Function(Vec, Box), + /// A function. The types of its arguments, size of its closure, then the type of its return value. + Function(Vec, Box, Box), Record(SendMap>, Box), TagUnion(Vec<(TagName, Vec)>, Box), Alias(Symbol, Vec<(Lowercase, Type)>, Box), @@ -158,7 +158,7 @@ impl fmt::Debug for Type { match self { Type::EmptyRec => write!(f, "{{}}"), Type::EmptyTagUnion => write!(f, "[]"), - Type::Function(args, ret) => { + Type::Function(args, closure, ret) => { write!(f, "Fn(")?; for (index, arg) in args.iter().enumerate() { @@ -169,7 +169,9 @@ impl fmt::Debug for Type { arg.fmt(f)?; } - write!(f, " -> ")?; + write!(f, " -")?; + closure.fmt(f)?; + write!(f, "-> ")?; ret.fmt(f)?; @@ -339,7 +341,7 @@ impl fmt::Debug for Type { impl Type { pub fn arity(&self) -> usize { - if let Type::Function(args, _) = self { + if let Type::Function(args, _, _) = self { args.len() } else { 0 @@ -369,10 +371,11 @@ impl Type { *self = replacement.clone(); } } - Function(args, ret) => { + Function(args, closure, ret) => { for arg in args { arg.substitute(substitutions); } + closure.substitute(substitutions); ret.substitute(substitutions); } TagUnion(tags, ext) => { @@ -427,10 +430,11 @@ impl Type { use Type::*; match self { - Function(args, ret) => { + Function(args, closure, ret) => { for arg in args { arg.substitute_alias(rep_symbol, actual); } + closure.substitute_alias(rep_symbol, actual); ret.substitute_alias(rep_symbol, actual); } RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { @@ -472,8 +476,9 @@ impl Type { use Type::*; match self { - Function(args, ret) => { + Function(args, closure, ret) => { ret.contains_symbol(rep_symbol) + || closure.contains_symbol(rep_symbol) || args.iter().any(|arg| arg.contains_symbol(rep_symbol)) } RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { @@ -503,8 +508,9 @@ impl Type { match self { Variable(v) => *v == rep_variable, - Function(args, ret) => { + Function(args, closure, ret) => { ret.contains_variable(rep_variable) + || closure.contains_variable(rep_variable) || args.iter().any(|arg| arg.contains_variable(rep_variable)) } RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { @@ -553,10 +559,11 @@ impl Type { use Type::*; match self { - Function(args, ret) => { + Function(args, closure, ret) => { for arg in args { arg.instantiate_aliases(region, aliases, var_store, introduced); } + closure.instantiate_aliases(region, aliases, var_store, introduced); ret.instantiate_aliases(region, aliases, var_store, introduced); } RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { @@ -726,8 +733,9 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { use Type::*; match tipe { - Function(args, ret) => { + Function(args, closure, ret) => { symbols_help(&ret, accum); + symbols_help(&closure, accum); args.iter().for_each(|arg| symbols_help(arg, accum)); } RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { @@ -776,10 +784,11 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { accum.insert(*v); } - Function(args, ret) => { + Function(args, closure, ret) => { for arg in args { variables_help(arg, accum); } + variables_help(closure, accum); variables_help(ret, accum); } Record(fields, ext) => { @@ -1014,7 +1023,7 @@ pub enum ErrorType { Record(SendMap>, TypeExt), TagUnion(SendMap>, TypeExt), RecursiveTagUnion(Box, SendMap>, TypeExt), - Function(Vec, Box), + Function(Vec, Box, Box), Alias(Symbol, Vec<(Lowercase, ErrorType)>, Box), Boolean(boolean_algebra::Bool), Error, @@ -1102,7 +1111,7 @@ fn write_error_type_help( } } } - Function(arguments, result) => { + Function(arguments, _closure, result) => { let write_parens = parens != Parens::Unnecessary; if write_parens { @@ -1246,7 +1255,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens: buf.push(')'); } } - Function(arguments, result) => { + Function(arguments, _closure, result) => { let write_parens = parens != Parens::Unnecessary; if write_parens { diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 84b6c42130..fc7d90718f 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -140,6 +140,13 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { println!("\n --- \n"); dbg!(ctx.second, type2); println!("\n --------------- \n"); + println!( + "{:?} {:?} ~ {:?} {:?}", + ctx.first, + subs.get(ctx.first).content, + ctx.second, + subs.get(ctx.second).content + ); } match &ctx.first_desc.content { FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, &ctx.second_desc.content), @@ -849,15 +856,23 @@ fn unify_flat_type( problems } } - (Func(l_args, l_ret), Func(r_args, r_ret)) if l_args.len() == r_args.len() => { + (Func(l_args, l_closure, l_ret), Func(r_args, r_closure, r_ret)) + if l_args.len() == r_args.len() => + { let arg_problems = unify_zip(subs, pool, l_args.iter(), r_args.iter()); let ret_problems = unify_pool(subs, pool, *l_ret, *r_ret); + let closure_problems = unify_pool(subs, pool, *l_closure, *r_closure); - if arg_problems.is_empty() && ret_problems.is_empty() { - merge(subs, ctx, Structure(Func((*r_args).clone(), *r_ret))) + if arg_problems.is_empty() && closure_problems.is_empty() && ret_problems.is_empty() { + merge( + subs, + ctx, + Structure(Func((*r_args).clone(), *r_closure, *r_ret)), + ) } else { let mut problems = ret_problems; + problems.extend(closure_problems); problems.extend(arg_problems); problems diff --git a/compiler/uniq/src/sharing.rs b/compiler/uniq/src/sharing.rs index 61a7569453..1beffd24d8 100644 --- a/compiler/uniq/src/sharing.rs +++ b/compiler/uniq/src/sharing.rs @@ -923,13 +923,17 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) { annotate_usage(&arg.value, usage); } } - Closure(_, _, _, args, body) => { + Closure { + arguments, + loc_body, + .. + } => { // annotate defaults of optional record fields - for (_, loc_pattern) in args { + for (_, loc_pattern) in arguments { annotate_usage_pattern(&loc_pattern.value, usage); } - annotate_usage(&body.0.value, usage); + annotate_usage(&loc_body.value, usage); } Tag { arguments, .. } => { diff --git a/examples/.gitignore b/examples/.gitignore index 0bd05ae96a..789188bb46 100644 --- a/examples/.gitignore +++ b/examples/.gitignore @@ -1,3 +1,2 @@ app -*.o -*.a +host.o diff --git a/examples/hello-world/host.rs b/examples/hello-world/host.rs deleted file mode 100644 index f774532676..0000000000 --- a/examples/hello-world/host.rs +++ /dev/null @@ -1,14 +0,0 @@ -use std::ffi::CStr; -use std::os::raw::c_char; - -#[link(name = "roc_app", kind = "static")] -extern "C" { - #[link_name = "main#1"] - fn str_from_roc() -> *const c_char; -} - -pub fn main() { - let c_str = unsafe { CStr::from_ptr(str_from_roc()) }; - - println!("Roc says: {}", c_str.to_str().unwrap()); -} diff --git a/examples/hello-world/platform/README.md b/examples/hello-world/platform/README.md new file mode 100644 index 0000000000..eefff1996c --- /dev/null +++ b/examples/hello-world/platform/README.md @@ -0,0 +1,8 @@ +# Rebuilding the host from source + +Run `build.sh` to manually rebuild this platform's host. + +Note that the compiler currently has its own logic for rebuilding these hosts +(in `link.rs`). It's hardcoded for now, but the long-term goal is that +hosts will be precompiled by platform authors and distributed in packages, +at which point only package authors will need to think about rebuilding hosts. diff --git a/examples/hello-world/platform/build.sh b/examples/hello-world/platform/build.sh new file mode 100755 index 0000000000..16aa197589 --- /dev/null +++ b/examples/hello-world/platform/build.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +# We have a C host, so this is only one step. +clang -c host.c -o host.o diff --git a/examples/hello-world/platform/host.c b/examples/hello-world/platform/host.c new file mode 100644 index 0000000000..39adb8321c --- /dev/null +++ b/examples/hello-world/platform/host.c @@ -0,0 +1,13 @@ +#include + +// TODO: use an actual RocStr here instead of char* - which is not the correct +// type here, and which only works out to be equivalent in this particular +// example by pure coincidence. This should be easy to segfault by returning +// (for example) small or empty strings from Hello.roc. We should fix this! +extern char* main_1(); + +int main() { + printf("Roc says: %s\n", main_1()); + + return 0; +} diff --git a/examples/quicksort/README.md b/examples/quicksort/README.md index 6437a5e24e..c900f17b24 100644 --- a/examples/quicksort/README.md +++ b/examples/quicksort/README.md @@ -14,4 +14,4 @@ $ cargo run --release run Quicksort.roc ## Troubleshooting -If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`. \ No newline at end of file +If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`. diff --git a/examples/quicksort/platform/Cargo.lock b/examples/quicksort/platform/Cargo.lock new file mode 100644 index 0000000000..c386bb6c4a --- /dev/null +++ b/examples/quicksort/platform/Cargo.lock @@ -0,0 +1,23 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "host" +version = "0.1.0" +dependencies = [ + "roc_std 0.1.0", +] + +[[package]] +name = "libc" +version = "0.2.79" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "roc_std" +version = "0.1.0" +dependencies = [ + "libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" diff --git a/examples/quicksort/platform/Cargo.toml b/examples/quicksort/platform/Cargo.toml new file mode 100644 index 0000000000..70f3c1f86c --- /dev/null +++ b/examples/quicksort/platform/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "host" +version = "0.1.0" +authors = ["Richard Feldman "] +edition = "2018" + +[lib] +crate-type = ["staticlib"] + +[dependencies] +roc_std = { path = "../../../roc_std" } + +[workspace] diff --git a/examples/quicksort/platform/README.md b/examples/quicksort/platform/README.md new file mode 100644 index 0000000000..eefff1996c --- /dev/null +++ b/examples/quicksort/platform/README.md @@ -0,0 +1,8 @@ +# Rebuilding the host from source + +Run `build.sh` to manually rebuild this platform's host. + +Note that the compiler currently has its own logic for rebuilding these hosts +(in `link.rs`). It's hardcoded for now, but the long-term goal is that +hosts will be precompiled by platform authors and distributed in packages, +at which point only package authors will need to think about rebuilding hosts. diff --git a/examples/quicksort/platform/build.sh b/examples/quicksort/platform/build.sh new file mode 100755 index 0000000000..010c502222 --- /dev/null +++ b/examples/quicksort/platform/build.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# compile c_host.o and rust_host.o +clang -c host.c -o c_host.o +rustc host.rs -o rust_host.o + +# link them together into host.o +ld -r c_host.o rust_host.o -o host.o + +# clean up +rm -f c_host.o +rm -f rust_host.o diff --git a/examples/quicksort/platform/host.c b/examples/quicksort/platform/host.c new file mode 100644 index 0000000000..0378c69589 --- /dev/null +++ b/examples/quicksort/platform/host.c @@ -0,0 +1,7 @@ +#include + +extern int rust_main(); + +int main() { + return rust_main(); +} diff --git a/examples/quicksort/platform/src/lib.rs b/examples/quicksort/platform/src/lib.rs new file mode 100644 index 0000000000..3af3cee432 --- /dev/null +++ b/examples/quicksort/platform/src/lib.rs @@ -0,0 +1,40 @@ +use roc_std::RocList; +use std::time::SystemTime; + +extern "C" { + #[link_name = "quicksort_1"] + fn quicksort(list: RocList) -> RocList; +} + +const NUM_NUMS: usize = 1_000_000; + +#[no_mangle] +pub fn rust_main() -> isize { + let nums: RocList = { + let mut nums = Vec::with_capacity(NUM_NUMS); + + for index in 0..nums.capacity() { + let num = index as i64 % 12345; + + nums.push(num); + } + + RocList::from_slice(&nums) + }; + + println!("Running Roc quicksort on {} numbers...", nums.len()); + let start_time = SystemTime::now(); + let answer = unsafe { quicksort(nums) }; + let end_time = SystemTime::now(); + let duration = end_time.duration_since(start_time).unwrap(); + + println!( + "Roc quicksort took {:.4} ms to compute this answer: {:?}", + duration.as_secs_f64() * 1000.0, + // truncate the answer, so stdout is not swamped + &answer.as_slice()[0..20] + ); + + // Exit code + 0 +} diff --git a/examples/shared-quicksort/host.rs b/examples/shared-quicksort/host.rs deleted file mode 100644 index 08eaf722e4..0000000000 --- a/examples/shared-quicksort/host.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::time::SystemTime; - -#[link(name = "roc_app", kind = "static")] -extern "C" { - #[allow(improper_ctypes)] - #[link_name = "quicksort#1"] - fn quicksort(list: &[i64]) -> Box<[i64]>; -} - -const NUM_NUMS: usize = 1_000_000; - -pub fn main() { - let nums = { - let mut nums = Vec::with_capacity(NUM_NUMS + 1); - - // give this list refcount 1 - nums.push((std::usize::MAX - 1) as i64); - - for index in 1..nums.capacity() { - let num = index as i64 % 12345; - - nums.push(num); - } - - nums - }; - - println!("Running Roc shared quicksort"); - let start_time = SystemTime::now(); - let answer = unsafe { quicksort(&nums[1..]) }; - let end_time = SystemTime::now(); - let duration = end_time.duration_since(start_time).unwrap(); - - println!( - "Roc quicksort took {:.4} ms to compute this answer: {:?}", - duration.as_secs_f64() * 1000.0, - // truncate the answer, so stdout is not swamped - // NOTE index 0 is the refcount! - &answer[1..20] - ); - - // the pointer is to the first _element_ of the list, - // but the refcount precedes it. Thus calling free() on - // this pointer would segfault/cause badness. Therefore, we - // leak it for now - Box::leak(answer); -} diff --git a/examples/shared-quicksort/platform/README.md b/examples/shared-quicksort/platform/README.md new file mode 100644 index 0000000000..f51d79714d --- /dev/null +++ b/examples/shared-quicksort/platform/README.md @@ -0,0 +1,49 @@ +# Rebuilding the host from source + +Here are the current steps to rebuild this host. These +steps can likely be moved into a `build.rs` script after +turning `host.rs` into a `cargo` project, but that hasn't +been attempted yet. + +## Compile the Rust and C sources + +Currently this host has both a `host.rs` and a `host.c`. +This is only because we haven't figured out a way to convince +Rust to emit a `.o` file that doesn't define a `main` entrypoint, +but which is capable of being linked into one later. + +As a workaround, we have `host.rs` expose a function called +`rust_main` instead of `main`, and all `host.c` does is provide +an actual `main` which imports and then calls `rust_main` from +the compiled `host.rs`. It's not the most elegant workaround, +but [asking on `users.rust-lang.org`](https://users.rust-lang.org/t/error-when-compiling-linking-with-o-files/49635/4) +didn't turn up any nicer approaches. Maybe they're out there though! + +To make this workaround happen, we need to compile both `host.rs` +and `host.c`. First, `cd` into `platform/host/src/` and then run: + +``` +$ clang -c host.c -o c_host.o +$ rustc host.rs -o rust_host.o +``` + +Now we should have `c_host.o` and `rust_host.o` in the curent directory. + +## Link together the `.o` files + +Next, combine `c_host.o` and `rust_host.o` into `host.o` using `ld -r` like so: + +``` +$ ld -r c_host.o rust_host.o -o host.o +``` + +Move `host.o` into the appropriate `platform/` subdirectory +based on your architecture and operating system. For example, +on macOS, you'd move `host.o` into the `platform/host/x86_64-unknown-darwin10/` directory. + +## All done! + +Congratulations! You now have an updated host. + +It's now fine to delete `c_host.o` and `rust_host.o`, +since they were only needed to produce `host.o`. diff --git a/examples/shared-quicksort/platform/build.sh b/examples/shared-quicksort/platform/build.sh new file mode 100755 index 0000000000..010c502222 --- /dev/null +++ b/examples/shared-quicksort/platform/build.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# compile c_host.o and rust_host.o +clang -c host.c -o c_host.o +rustc host.rs -o rust_host.o + +# link them together into host.o +ld -r c_host.o rust_host.o -o host.o + +# clean up +rm -f c_host.o +rm -f rust_host.o diff --git a/examples/shared-quicksort/platform/host.c b/examples/shared-quicksort/platform/host.c new file mode 100644 index 0000000000..0378c69589 --- /dev/null +++ b/examples/shared-quicksort/platform/host.c @@ -0,0 +1,7 @@ +#include + +extern int rust_main(); + +int main() { + return rust_main(); +} diff --git a/examples/quicksort/host.rs b/examples/shared-quicksort/platform/host.rs similarity index 89% rename from examples/quicksort/host.rs rename to examples/shared-quicksort/platform/host.rs index d285d349de..53b46f036d 100644 --- a/examples/quicksort/host.rs +++ b/examples/shared-quicksort/platform/host.rs @@ -1,15 +1,17 @@ +#![crate_type = "staticlib"] + use std::time::SystemTime; -#[link(name = "roc_app", kind = "static")] extern "C" { #[allow(improper_ctypes)] - #[link_name = "quicksort#1"] + #[link_name = "quicksort_1"] fn quicksort(list: Box<[i64]>) -> Box<[i64]>; } const NUM_NUMS: usize = 10_000; -pub fn main() { +#[no_mangle] +pub fn rust_main() -> isize { let nums: Box<[i64]> = { let mut nums = Vec::with_capacity(NUM_NUMS); @@ -39,4 +41,7 @@ pub fn main() { // this pointer would segfault/cause badness. Therefore, we // leak it for now Box::leak(answer); + + // Exit code + 0 } diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index 53d3127af3..7b50cd2a0b 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -80,17 +80,9 @@ impl RocList { } fn get_storage_ptr(&self) -> *const usize { - let elem_alignment = core::mem::align_of::(); + let ptr = self.elements as *const usize; - unsafe { - if elem_alignment <= core::mem::align_of::() { - let ptr = self.elements as *const usize; - ptr.offset(-1) - } else { - let ptr = self.elements as *const (usize, usize); - (ptr.offset(-1)) as *const usize - } - } + unsafe { ptr.offset(-1) } } fn get_storage_ptr_mut(&mut self) -> *mut usize { @@ -99,14 +91,17 @@ impl RocList { fn get_element_ptr(elements: *const Q) -> *const usize { let elem_alignment = core::mem::align_of::(); + let ptr = elements as *const usize; unsafe { if elem_alignment <= core::mem::align_of::() { - let ptr = elements as *const usize; ptr.offset(1) } else { - let ptr = elements as *const (usize, usize); - (ptr.offset(1)) as *const usize + // If elements have an alignment bigger than usize (e.g. an i128), + // we will have necessarily allocated two usize slots worth of + // space for the storage value (with the first usize slot being + // padding for alignment's sake), and we need to skip past both. + ptr.offset(2) } } }