mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-03 19:58:18 +00:00
Merge remote-tracking branch 'origin/trunk' into hostgen
This commit is contained in:
commit
36f64d8496
45 changed files with 1801 additions and 1368 deletions
72
Cargo.lock
generated
72
Cargo.lock
generated
|
@ -468,9 +468,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.1.15"
|
||||
version = "3.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
|
||||
checksum = "47582c09be7c8b32c0ab3a6181825ababb713fde6fff20fc573a3870dd45c6a0"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
|
@ -516,6 +516,7 @@ name = "cli_utils"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"const_format",
|
||||
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
|
||||
"rlimit",
|
||||
"roc_cli",
|
||||
|
@ -1674,9 +1675,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glyph_brush"
|
||||
version = "0.7.3"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21932fbf719272848eec4583740d978203c6e7da4c4e203358f5b95946c97409"
|
||||
checksum = "a69c65dd1f1fbb6209aa00f78636e436ad0a55b7d8e5de886d00720dcad9c6e2"
|
||||
dependencies = [
|
||||
"glyph_brush_draw_cache",
|
||||
"glyph_brush_layout",
|
||||
|
@ -1865,12 +1866,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "1.0.4"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7906a9fababaeacb774f72410e497a1d18de916322e33797bb2cd29baa23c9e"
|
||||
dependencies = [
|
||||
"unindent",
|
||||
]
|
||||
checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e"
|
||||
|
||||
[[package]]
|
||||
name = "inkwell"
|
||||
|
@ -2688,9 +2686,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
|||
|
||||
[[package]]
|
||||
name = "ordered-float"
|
||||
version = "2.10.0"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87"
|
||||
checksum = "96bcbab4bfea7a59c2c0fe47211a1ac4e3e96bea6eb446d704f310bc5c732ae2"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
@ -3085,9 +3083,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
|
|||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.37"
|
||||
version = "1.0.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1"
|
||||
checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa"
|
||||
dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
@ -3435,7 +3433,7 @@ name = "roc-bindgen"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"clap 3.1.15",
|
||||
"clap 3.1.17",
|
||||
"indoc",
|
||||
"pretty_assertions",
|
||||
"roc_builtins",
|
||||
|
@ -3546,7 +3544,6 @@ dependencies = [
|
|||
"bumpalo",
|
||||
"indoc",
|
||||
"pretty_assertions",
|
||||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_exhaustive",
|
||||
|
@ -3556,7 +3553,6 @@ dependencies = [
|
|||
"roc_region",
|
||||
"roc_types",
|
||||
"static_assertions 1.1.0",
|
||||
"ven_graph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3564,7 +3560,7 @@ name = "roc_cli"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"clap 3.1.15",
|
||||
"clap 3.1.17",
|
||||
"cli_utils",
|
||||
"const_format",
|
||||
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
|
||||
|
@ -3590,6 +3586,8 @@ dependencies = [
|
|||
"roc_target",
|
||||
"roc_test_utils",
|
||||
"serial_test",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
"target-lexicon",
|
||||
"tempfile",
|
||||
"wasmer",
|
||||
|
@ -3645,7 +3643,6 @@ name = "roc_docs"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
"peg",
|
||||
"pretty_assertions",
|
||||
"pulldown-cmark",
|
||||
|
@ -3663,8 +3660,6 @@ dependencies = [
|
|||
"roc_target",
|
||||
"roc_types",
|
||||
"snafu",
|
||||
"tempfile",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3813,9 +3808,6 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "roc_ident"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_linker"
|
||||
|
@ -3823,7 +3815,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"bincode",
|
||||
"bumpalo",
|
||||
"clap 3.1.15",
|
||||
"clap 3.1.17",
|
||||
"iced-x86",
|
||||
"memmap2 0.5.3",
|
||||
"object 0.26.2",
|
||||
|
@ -3858,8 +3850,6 @@ dependencies = [
|
|||
"crossbeam",
|
||||
"indoc",
|
||||
"maplit",
|
||||
"morphic_lib",
|
||||
"num_cpus",
|
||||
"parking_lot 0.12.0",
|
||||
"pretty_assertions",
|
||||
"roc_builtins",
|
||||
|
@ -3879,7 +3869,6 @@ dependencies = [
|
|||
"roc_test_utils",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"tempfile",
|
||||
"ven_pretty",
|
||||
]
|
||||
|
||||
|
@ -3887,7 +3876,6 @@ dependencies = [
|
|||
name = "roc_module"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"arrayvec 0.7.2",
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"roc_collections",
|
||||
|
@ -3904,7 +3892,6 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"bumpalo",
|
||||
"hashbrown 0.11.2",
|
||||
"morphic_lib",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
|
@ -4047,7 +4034,6 @@ dependencies = [
|
|||
"roc_target",
|
||||
"roc_test_utils",
|
||||
"roc_types",
|
||||
"tempfile",
|
||||
"ven_pretty",
|
||||
]
|
||||
|
||||
|
@ -4690,6 +4676,25 @@ version = "0.10.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8"
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.24.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.92"
|
||||
|
@ -4778,7 +4783,6 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"bumpalo",
|
||||
"indoc",
|
||||
"pretty_assertions",
|
||||
"roc_builtins",
|
||||
"roc_can",
|
||||
"roc_collections",
|
||||
|
@ -5016,12 +5020,6 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
|
||||
|
||||
[[package]]
|
||||
name = "unindent"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.0"
|
||||
|
|
|
@ -56,7 +56,7 @@ pub fn main() {
|
|||
}
|
||||
};
|
||||
|
||||
match load_types(input_path.clone(), &cwd, Threading::Multi) {
|
||||
match load_types(input_path.clone(), &cwd, Threading::AllAvailable) {
|
||||
Ok(types) => {
|
||||
let mut buf;
|
||||
|
||||
|
|
|
@ -85,6 +85,8 @@ indoc = "1.0.3"
|
|||
serial_test = "0.5.1"
|
||||
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
|
||||
cli_utils = { path = "../cli_utils" }
|
||||
strum = "0.24.0"
|
||||
strum_macros = "0.24"
|
||||
|
||||
# Wasmer singlepass compiler only works on x86_64.
|
||||
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
|
||||
|
|
|
@ -350,6 +350,7 @@ pub fn check_file(
|
|||
src_dir: PathBuf,
|
||||
roc_file_path: PathBuf,
|
||||
emit_timings: bool,
|
||||
threading: Threading,
|
||||
) -> Result<(program::Problems, Duration), LoadingProblem> {
|
||||
let compilation_start = SystemTime::now();
|
||||
|
||||
|
@ -368,7 +369,7 @@ pub fn check_file(
|
|||
target_info,
|
||||
// TODO: expose this from CLI?
|
||||
RenderTarget::ColorTerminal,
|
||||
Threading::Multi,
|
||||
threading,
|
||||
)?;
|
||||
|
||||
let buf = &mut String::with_capacity(1024);
|
||||
|
|
|
@ -2,29 +2,17 @@ use std::ffi::OsStr;
|
|||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::FormatMode;
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_error_macros::{internal_error, user_error};
|
||||
use roc_fmt::def::fmt_def;
|
||||
use roc_fmt::module::fmt_module;
|
||||
use roc_fmt::Buf;
|
||||
use roc_module::called_via::{BinOp, UnaryOp};
|
||||
use roc_parse::ast::{
|
||||
AbilityMember, AssignedField, Collection, Expr, Has, HasClause, Pattern, Spaced, StrLiteral,
|
||||
StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
|
||||
};
|
||||
use roc_parse::header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
|
||||
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
|
||||
};
|
||||
use roc_fmt::spaces::RemoveSpaces;
|
||||
use roc_fmt::{Ast, Buf};
|
||||
use roc_parse::{
|
||||
ast::{Def, Module},
|
||||
ident::UppercaseIdent,
|
||||
module::{self, module_defs},
|
||||
parser::{Parser, SyntaxError},
|
||||
state::State,
|
||||
};
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
||||
fn flatten_directories(files: std::vec::Vec<PathBuf>) -> std::vec::Vec<PathBuf> {
|
||||
let mut to_flatten = files;
|
||||
|
@ -166,12 +154,6 @@ pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), Str
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Ast<'a> {
|
||||
module: Module<'a>,
|
||||
defs: Vec<'a, Loc<Def<'a>>>,
|
||||
}
|
||||
|
||||
fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result<Ast<'a>, SyntaxError<'a>> {
|
||||
let (module, state) = module::parse_header(arena, State::new(src.as_bytes()))
|
||||
.map_err(|e| SyntaxError::Header(e.problem))?;
|
||||
|
@ -189,575 +171,3 @@ fn fmt_all<'a>(arena: &'a Bump, buf: &mut Buf<'a>, ast: &'a Ast) {
|
|||
|
||||
buf.fmt_end_of_file();
|
||||
}
|
||||
|
||||
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
|
||||
///
|
||||
/// Currently this consists of:
|
||||
/// * Removing newlines
|
||||
/// * Removing comments
|
||||
/// * Removing parens in Exprs
|
||||
///
|
||||
/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting)
|
||||
/// - but there are currently several bugs where they're _not_ preserved.
|
||||
/// TODO: ensure formatting retains comments
|
||||
trait RemoveSpaces<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self;
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Ast<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
Ast {
|
||||
module: self.module.remove_spaces(arena),
|
||||
defs: {
|
||||
let mut defs = Vec::with_capacity_in(self.defs.len(), arena);
|
||||
for d in &self.defs {
|
||||
defs.push(d.remove_spaces(arena))
|
||||
}
|
||||
defs
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Module<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match self {
|
||||
Module::Interface { header } => Module::Interface {
|
||||
header: InterfaceHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_interface_keyword: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
},
|
||||
},
|
||||
Module::App { header } => Module::App {
|
||||
header: AppHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
provides: header.provides.remove_spaces(arena),
|
||||
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
|
||||
to: header.to.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_app_keyword: &[],
|
||||
before_packages: &[],
|
||||
after_packages: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_provides: &[],
|
||||
after_provides: &[],
|
||||
before_to: &[],
|
||||
after_to: &[],
|
||||
},
|
||||
},
|
||||
Module::Platform { header } => Module::Platform {
|
||||
header: PlatformHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
requires: header.requires.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
provides: header.provides.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_platform_keyword: &[],
|
||||
before_requires: &[],
|
||||
after_requires: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_packages: &[],
|
||||
after_packages: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_provides: &[],
|
||||
after_provides: &[],
|
||||
},
|
||||
},
|
||||
Module::Hosted { header } => Module::Hosted {
|
||||
header: HostedHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
generates: header.generates.remove_spaces(arena),
|
||||
generates_with: header.generates_with.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_hosted_keyword: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_generates: &[],
|
||||
after_generates: &[],
|
||||
before_with: &[],
|
||||
after_with: &[],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for &'a str {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
|
||||
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for To<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
To::ExistingPackage(a) => To::ExistingPackage(a),
|
||||
To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
TypedIdent {
|
||||
ident: self.ident.remove_spaces(arena),
|
||||
spaces_before_colon: &[],
|
||||
ann: self.ann.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
PlatformRequires {
|
||||
rigids: self.rigids.remove_spaces(arena),
|
||||
signature: self.signature.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
PackageEntry {
|
||||
shorthand: self.shorthand,
|
||||
spaces_after_shorthand: &[],
|
||||
package_name: self.package_name.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
|
||||
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
self.as_ref().map(|a| a.remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let res = self.value.remove_spaces(arena);
|
||||
Loc::at(Region::zero(), res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
(self.0.remove_spaces(arena), self.1.remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let mut items = Vec::with_capacity_in(self.items.len(), arena);
|
||||
for item in self.items {
|
||||
items.push(item.remove_spaces(arena));
|
||||
}
|
||||
Collection::with_items(items.into_bump_slice())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let mut items = Vec::with_capacity_in(self.len(), arena);
|
||||
for item in *self {
|
||||
let res = item.remove_spaces(arena);
|
||||
items.push(res);
|
||||
}
|
||||
items.into_bump_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for UnaryOp {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for BinOp {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
arena.alloc((*self).remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
use TypeDef::*;
|
||||
|
||||
match *self {
|
||||
Alias {
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
} => Alias {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
ann: ann.remove_spaces(arena),
|
||||
},
|
||||
Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
typ,
|
||||
} => Opaque {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
typ: typ.remove_spaces(arena),
|
||||
},
|
||||
Ability {
|
||||
header: TypeHeader { name, vars },
|
||||
loc_has,
|
||||
members,
|
||||
} => Ability {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
loc_has: loc_has.remove_spaces(arena),
|
||||
members: members.remove_spaces(arena),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
use ValueDef::*;
|
||||
|
||||
match *self {
|
||||
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
|
||||
Body(a, b) => Body(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment: _,
|
||||
body_pattern,
|
||||
body_expr,
|
||||
} => AnnotatedBody {
|
||||
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
|
||||
ann_type: arena.alloc(ann_type.remove_spaces(arena)),
|
||||
comment: None,
|
||||
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
|
||||
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
|
||||
},
|
||||
Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Def<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Def::Type(def) => Def::Type(def.remove_spaces(arena)),
|
||||
Def::Value(def) => Def::Value(def.remove_spaces(arena)),
|
||||
Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
|
||||
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Has<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
Has::Has
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
AbilityMember {
|
||||
name: self.name.remove_spaces(arena),
|
||||
typ: self.typ.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
WhenBranch {
|
||||
patterns: self.patterns.remove_spaces(arena),
|
||||
value: self.value.remove_spaces(arena),
|
||||
guard: self.guard.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue(
|
||||
a.remove_spaces(arena),
|
||||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue(
|
||||
a.remove_spaces(arena),
|
||||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
|
||||
AssignedField::Malformed(a) => AssignedField::Malformed(a),
|
||||
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
|
||||
StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)),
|
||||
StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
StrSegment::Plaintext(t) => StrSegment::Plaintext(t),
|
||||
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
|
||||
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
|
||||
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Expr::Float(a) => Expr::Float(a),
|
||||
Expr::Num(a) => Expr::Num(a),
|
||||
Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
},
|
||||
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
|
||||
Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b),
|
||||
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
|
||||
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
|
||||
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
|
||||
update: arena.alloc(update.remove_spaces(arena)),
|
||||
fields: fields.remove_spaces(arena),
|
||||
},
|
||||
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
|
||||
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
|
||||
Expr::Underscore(a) => Expr::Underscore(a),
|
||||
Expr::Tag(a) => Expr::Tag(a),
|
||||
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
|
||||
Expr::Closure(a, b) => Expr::Closure(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Defs(a, b) => {
|
||||
Expr::Defs(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Expr::Backpassing(a, b, c) => Expr::Backpassing(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Expect(a, b) => Expr::Expect(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Apply(a, b, c) => Expr::Apply(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
b.remove_spaces(arena),
|
||||
c,
|
||||
),
|
||||
Expr::BinOps(a, b) => {
|
||||
Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Expr::UnaryOp(a, b) => {
|
||||
Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
|
||||
}
|
||||
Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))),
|
||||
Expr::When(a, b) => {
|
||||
Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
|
||||
}
|
||||
Expr::ParensAround(a) => {
|
||||
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
|
||||
a.remove_spaces(arena)
|
||||
}
|
||||
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b),
|
||||
Expr::MalformedClosure => Expr::MalformedClosure,
|
||||
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
|
||||
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Expr::SingleQuote(a) => Expr::Num(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Pattern::Identifier(a) => Pattern::Identifier(a),
|
||||
Pattern::Tag(a) => Pattern::Tag(a),
|
||||
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
|
||||
Pattern::Apply(a, b) => Pattern::Apply(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)),
|
||||
Pattern::RequiredField(a, b) => {
|
||||
Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Pattern::OptionalField(a, b) => {
|
||||
Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Pattern::NumLiteral(a) => Pattern::NumLiteral(a),
|
||||
Pattern::NonBase10Literal {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => Pattern::NonBase10Literal {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
},
|
||||
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
|
||||
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
|
||||
Pattern::Underscore(a) => Pattern::Underscore(a),
|
||||
Pattern::Malformed(a) => Pattern::Malformed(a),
|
||||
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b),
|
||||
Pattern::QualifiedIdentifier { module_name, ident } => {
|
||||
Pattern::QualifiedIdentifier { module_name, ident }
|
||||
}
|
||||
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
TypeAnnotation::Function(a, b) => TypeAnnotation::Function(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
|
||||
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
|
||||
TypeAnnotation::As(a, _, c) => {
|
||||
TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c)
|
||||
}
|
||||
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
|
||||
fields: fields.remove_spaces(arena),
|
||||
ext: ext.remove_spaces(arena),
|
||||
},
|
||||
TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion {
|
||||
ext: ext.remove_spaces(arena),
|
||||
tags: tags.remove_spaces(arena),
|
||||
},
|
||||
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
|
||||
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
|
||||
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
|
||||
arena.alloc(annot.remove_spaces(arena)),
|
||||
arena.alloc(has_clauses.remove_spaces(arena)),
|
||||
),
|
||||
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for HasClause<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
HasClause {
|
||||
var: self.var.remove_spaces(arena),
|
||||
ability: self.ability.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Tag<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Tag::Apply { name, args } => Tag::Apply {
|
||||
name: name.remove_spaces(arena),
|
||||
args: args.remove_spaces(arena),
|
||||
},
|
||||
Tag::Malformed(a) => Tag::Malformed(a),
|
||||
Tag::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Tag::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
205
cli/src/lib.rs
205
cli/src/lib.rs
|
@ -3,16 +3,15 @@ extern crate const_format;
|
|||
|
||||
use build::BuiltFile;
|
||||
use bumpalo::Bump;
|
||||
use clap::Command;
|
||||
use clap::{Arg, ArgMatches};
|
||||
use clap::{Arg, ArgMatches, Command};
|
||||
use roc_build::link::LinkType;
|
||||
use roc_error_macros::user_error;
|
||||
use roc_load::{LoadingProblem, Threading};
|
||||
use roc_mono::ir::OptLevel;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
use target_lexicon::BinaryFormat;
|
||||
use target_lexicon::{
|
||||
|
@ -35,6 +34,7 @@ pub const CMD_FORMAT: &str = "format";
|
|||
pub const FLAG_DEBUG: &str = "debug";
|
||||
pub const FLAG_DEV: &str = "dev";
|
||||
pub const FLAG_OPTIMIZE: &str = "optimize";
|
||||
pub const FLAG_MAX_THREADS: &str = "max-threads";
|
||||
pub const FLAG_OPT_SIZE: &str = "opt-size";
|
||||
pub const FLAG_LIB: &str = "lib";
|
||||
pub const FLAG_NO_LINK: &str = "no-link";
|
||||
|
@ -58,6 +58,14 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.requires(ROC_FILE)
|
||||
.required(false);
|
||||
|
||||
let flag_max_threads = Arg::new(FLAG_MAX_THREADS)
|
||||
.long(FLAG_MAX_THREADS)
|
||||
.help("Limit the number of threads (and hence cores) used during compilation.")
|
||||
.requires(ROC_FILE)
|
||||
.takes_value(true)
|
||||
.validator(|s| s.parse::<usize>())
|
||||
.required(false);
|
||||
|
||||
let flag_opt_size = Arg::new(FLAG_OPT_SIZE)
|
||||
.long(FLAG_OPT_SIZE)
|
||||
.help("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)")
|
||||
|
@ -96,12 +104,23 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.possible_values(["true", "false"])
|
||||
.required(false);
|
||||
|
||||
let roc_file_to_run = Arg::new(ROC_FILE)
|
||||
.help("The .roc file of an app to run")
|
||||
.allow_invalid_utf8(true);
|
||||
|
||||
let args_for_app = Arg::new(ARGS_FOR_APP)
|
||||
.help("Arguments to pass into the app being run")
|
||||
.requires(ROC_FILE)
|
||||
.allow_invalid_utf8(true)
|
||||
.multiple_values(true);
|
||||
|
||||
let app = Command::new("roc")
|
||||
.version(concatcp!(VERSION, "\n"))
|
||||
.about("Runs the given .roc file, if there are no compilation errors.\nUse one of the SUBCOMMANDS below to do something else!")
|
||||
.subcommand(Command::new(CMD_BUILD)
|
||||
.about("Build a binary from the given .roc file, but don't run it")
|
||||
.arg(flag_optimize.clone())
|
||||
.arg(flag_max_threads.clone())
|
||||
.arg(flag_opt_size.clone())
|
||||
.arg(flag_dev.clone())
|
||||
.arg(flag_debug.clone())
|
||||
|
@ -132,6 +151,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.arg(
|
||||
Arg::new(ROC_FILE)
|
||||
.help("The .roc file to build")
|
||||
.allow_invalid_utf8(true)
|
||||
.required(true),
|
||||
)
|
||||
)
|
||||
|
@ -141,6 +161,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.subcommand(Command::new(CMD_RUN)
|
||||
.about("Run a .roc file even if it has build errors")
|
||||
.arg(flag_optimize.clone())
|
||||
.arg(flag_max_threads.clone())
|
||||
.arg(flag_opt_size.clone())
|
||||
.arg(flag_dev.clone())
|
||||
.arg(flag_debug.clone())
|
||||
|
@ -148,11 +169,8 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.arg(flag_linker.clone())
|
||||
.arg(flag_precompiled.clone())
|
||||
.arg(flag_valgrind.clone())
|
||||
.arg(
|
||||
Arg::new(ROC_FILE)
|
||||
.help("The .roc file of an app to run")
|
||||
.required(true),
|
||||
)
|
||||
.arg(roc_file_to_run.clone().required(true))
|
||||
.arg(args_for_app.clone())
|
||||
)
|
||||
.subcommand(Command::new(CMD_FORMAT)
|
||||
.about("Format a .roc file using standard Roc formatting")
|
||||
|
@ -174,9 +192,11 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.subcommand(Command::new(CMD_CHECK)
|
||||
.about("Check the code for problems, but doesn’t build or run it")
|
||||
.arg(flag_time.clone())
|
||||
.arg(flag_max_threads.clone())
|
||||
.arg(
|
||||
Arg::new(ROC_FILE)
|
||||
.help("The .roc file of an app to check")
|
||||
.allow_invalid_utf8(true)
|
||||
.required(true),
|
||||
)
|
||||
)
|
||||
|
@ -193,6 +213,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
)
|
||||
.trailing_var_arg(true)
|
||||
.arg(flag_optimize)
|
||||
.arg(flag_max_threads.clone())
|
||||
.arg(flag_opt_size)
|
||||
.arg(flag_dev)
|
||||
.arg(flag_debug)
|
||||
|
@ -200,17 +221,8 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.arg(flag_linker)
|
||||
.arg(flag_precompiled)
|
||||
.arg(flag_valgrind)
|
||||
.arg(
|
||||
Arg::new(ROC_FILE)
|
||||
.help("The .roc file of an app to build and run")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(ARGS_FOR_APP)
|
||||
.help("Arguments to pass into the app being run")
|
||||
.requires(ROC_FILE)
|
||||
.multiple_values(true),
|
||||
);
|
||||
.arg(roc_file_to_run.required(false))
|
||||
.arg(args_for_app);
|
||||
|
||||
if cfg!(feature = "editor") {
|
||||
app.subcommand(
|
||||
|
@ -236,8 +248,8 @@ pub fn docs(files: Vec<PathBuf>) {
|
|||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum BuildConfig {
|
||||
BuildOnly,
|
||||
BuildAndRun { roc_file_arg_index: usize },
|
||||
BuildAndRunIfNoErrors { roc_file_arg_index: usize },
|
||||
BuildAndRun,
|
||||
BuildAndRunIfNoErrors,
|
||||
}
|
||||
|
||||
pub enum FormatMode {
|
||||
|
@ -255,7 +267,7 @@ pub fn build(
|
|||
use BuildConfig::*;
|
||||
|
||||
let arena = Bump::new();
|
||||
let filename = matches.value_of(ROC_FILE).unwrap();
|
||||
let filename = matches.value_of_os(ROC_FILE).unwrap();
|
||||
|
||||
let original_cwd = std::env::current_dir()?;
|
||||
let opt_level = match (
|
||||
|
@ -272,6 +284,16 @@ pub fn build(
|
|||
let emit_debug_info = matches.is_present(FLAG_DEBUG);
|
||||
let emit_timings = matches.is_present(FLAG_TIME);
|
||||
|
||||
let threading = match matches
|
||||
.value_of(FLAG_MAX_THREADS)
|
||||
.and_then(|s| s.parse::<usize>().ok())
|
||||
{
|
||||
None => Threading::AllAvailable,
|
||||
Some(0) => user_error!("cannot build with at most 0 threads"),
|
||||
Some(1) => Threading::Single,
|
||||
Some(n) => Threading::AtMost(n),
|
||||
};
|
||||
|
||||
// Use surgical linking when supported, or when explicitly requested with --linker surgical
|
||||
let surgically_link = if matches.is_present(FLAG_LINKER) {
|
||||
matches.value_of(FLAG_LINKER) == Some("surgical")
|
||||
|
@ -321,7 +343,7 @@ pub fn build(
|
|||
surgically_link,
|
||||
precompiled,
|
||||
target_valgrind,
|
||||
Threading::Multi,
|
||||
threading,
|
||||
);
|
||||
|
||||
match res_binary_path {
|
||||
|
@ -372,7 +394,7 @@ pub fn build(
|
|||
// Return a nonzero exit code if there were problems
|
||||
Ok(problems.exit_code())
|
||||
}
|
||||
BuildAndRun { roc_file_arg_index } => {
|
||||
BuildAndRun => {
|
||||
if problems.errors > 0 || problems.warnings > 0 {
|
||||
println!(
|
||||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m",
|
||||
|
@ -403,15 +425,11 @@ pub fn build(
|
|||
);
|
||||
}
|
||||
|
||||
roc_run(
|
||||
arena,
|
||||
&original_cwd,
|
||||
triple,
|
||||
roc_file_arg_index,
|
||||
&binary_path,
|
||||
)
|
||||
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
|
||||
|
||||
roc_run(arena, &original_cwd, triple, args, &binary_path)
|
||||
}
|
||||
BuildAndRunIfNoErrors { roc_file_arg_index } => {
|
||||
BuildAndRunIfNoErrors => {
|
||||
if problems.errors == 0 {
|
||||
if problems.warnings > 0 {
|
||||
println!(
|
||||
|
@ -427,13 +445,9 @@ pub fn build(
|
|||
);
|
||||
}
|
||||
|
||||
roc_run(
|
||||
arena,
|
||||
&original_cwd,
|
||||
triple,
|
||||
roc_file_arg_index,
|
||||
&binary_path,
|
||||
)
|
||||
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
|
||||
|
||||
roc_run(arena, &original_cwd, triple, args, &binary_path)
|
||||
} else {
|
||||
println!(
|
||||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with: \x1B[32mroc run {}\x1B[39m",
|
||||
|
@ -460,7 +474,7 @@ pub fn build(
|
|||
"warnings"
|
||||
},
|
||||
total_time.as_millis(),
|
||||
filename
|
||||
filename.to_string_lossy()
|
||||
);
|
||||
|
||||
Ok(problems.exit_code())
|
||||
|
@ -479,17 +493,14 @@ pub fn build(
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(target_family = "unix")]
|
||||
fn roc_run(
|
||||
fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
||||
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
|
||||
cwd: &Path,
|
||||
triple: Triple,
|
||||
roc_file_arg_index: usize,
|
||||
args: I,
|
||||
binary_path: &Path,
|
||||
) -> io::Result<i32> {
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
let mut cmd = match triple.architecture {
|
||||
match triple.architecture {
|
||||
Architecture::Wasm32 => {
|
||||
// If possible, report the generated executable name relative to the current dir.
|
||||
let generated_filename = binary_path
|
||||
|
@ -500,19 +511,44 @@ fn roc_run(
|
|||
// since the process is about to exit anyway.
|
||||
std::mem::forget(arena);
|
||||
|
||||
let args = std::env::args()
|
||||
.skip(roc_file_arg_index)
|
||||
.collect::<Vec<_>>();
|
||||
if cfg!(target_family = "unix") {
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
|
||||
run_with_wasmer(generated_filename, &args);
|
||||
return Ok(0);
|
||||
run_with_wasmer(
|
||||
generated_filename,
|
||||
args.into_iter().map(|os_str| os_str.as_bytes()),
|
||||
);
|
||||
} else {
|
||||
run_with_wasmer(
|
||||
generated_filename,
|
||||
args.into_iter().map(|os_str| {
|
||||
os_str.to_str().expect(
|
||||
"Roc does not currently support passing non-UTF8 arguments to Wasmer.",
|
||||
)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
_ => {
|
||||
if cfg!(target_family = "unix") {
|
||||
roc_run_unix(cwd, args, binary_path)
|
||||
} else {
|
||||
roc_run_non_unix(arena, cwd, args, binary_path)
|
||||
}
|
||||
}
|
||||
_ => std::process::Command::new(&binary_path),
|
||||
};
|
||||
|
||||
if let Architecture::Wasm32 = triple.architecture {
|
||||
cmd.arg(binary_path);
|
||||
}
|
||||
}
|
||||
|
||||
fn roc_run_unix<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
||||
cwd: &Path,
|
||||
args: I,
|
||||
binary_path: &Path,
|
||||
) -> io::Result<i32> {
|
||||
use std::os::unix::process::CommandExt;
|
||||
|
||||
let mut cmd = std::process::Command::new(&binary_path);
|
||||
|
||||
// Forward all the arguments after the .roc file argument
|
||||
// to the new process. This way, you can do things like:
|
||||
|
@ -521,10 +557,8 @@ fn roc_run(
|
|||
//
|
||||
// ...and have it so that app.roc will receive only `foo`,
|
||||
// `bar`, and `baz` as its arguments.
|
||||
for (index, arg) in std::env::args().enumerate() {
|
||||
if index > roc_file_arg_index {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
for arg in args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
// This is much faster than spawning a subprocess if we're on a UNIX system!
|
||||
|
@ -536,29 +570,36 @@ fn roc_run(
|
|||
Err(err)
|
||||
}
|
||||
|
||||
#[cfg(not(target_family = "unix"))]
|
||||
fn roc_run(cmd: &mut Command) -> io::Result<i32> {
|
||||
// Run the compiled app
|
||||
let exit_status = cmd
|
||||
.spawn()
|
||||
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
|
||||
.wait()
|
||||
.expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app");
|
||||
fn roc_run_non_unix<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
||||
_arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
|
||||
_cwd: &Path,
|
||||
_args: I,
|
||||
_binary_path: &Path,
|
||||
) -> io::Result<i32> {
|
||||
todo!("TODO support running roc programs on non-UNIX targets");
|
||||
// let mut cmd = std::process::Command::new(&binary_path);
|
||||
|
||||
// `roc [FILE]` exits with the same status code as the app it ran.
|
||||
//
|
||||
// If you want to know whether there were compilation problems
|
||||
// via status code, use either `roc build` or `roc check` instead!
|
||||
match exit_status.code() {
|
||||
Some(code) => Ok(code),
|
||||
None => {
|
||||
todo!("TODO gracefully handle the `roc [FILE]` subprocess terminating with a signal.");
|
||||
}
|
||||
}
|
||||
// // Run the compiled app
|
||||
// let exit_status = cmd
|
||||
// .spawn()
|
||||
// .unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
|
||||
// .wait()
|
||||
// .expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app");
|
||||
|
||||
// // `roc [FILE]` exits with the same status code as the app it ran.
|
||||
// //
|
||||
// // If you want to know whether there were compilation problems
|
||||
// // via status code, use either `roc build` or `roc check` instead!
|
||||
// match exit_status.code() {
|
||||
// Some(code) => Ok(code),
|
||||
// None => {
|
||||
// todo!("TODO gracefully handle the `roc [FILE]` subprocess terminating with a signal.");
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
#[cfg(feature = "run-wasm32")]
|
||||
fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
|
||||
fn run_with_wasmer<I: Iterator<Item = S>, S: AsRef<[u8]>>(wasm_path: &std::path::Path, args: I) {
|
||||
use wasmer::{Instance, Module, Store};
|
||||
|
||||
let store = Store::default();
|
||||
|
@ -589,8 +630,8 @@ fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
|
|||
}
|
||||
|
||||
#[cfg(not(feature = "run-wasm32"))]
|
||||
fn run_with_wasmer(_wasm_path: &std::path::Path, _args: &[String]) {
|
||||
println!("Running wasm files not support");
|
||||
fn run_with_wasmer<I: Iterator<Item = S>, S: AsRef<[u8]>>(_wasm_path: &std::path::Path, _args: I) {
|
||||
println!("Running wasm files is not supported on this target.");
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
|
|
|
@ -6,7 +6,7 @@ use roc_cli::{
|
|||
FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, ROC_FILE,
|
||||
};
|
||||
use roc_error_macros::user_error;
|
||||
use roc_load::LoadingProblem;
|
||||
use roc_load::{LoadingProblem, Threading};
|
||||
use std::fs::{self, FileType};
|
||||
use std::io;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -27,43 +27,31 @@ fn main() -> io::Result<()> {
|
|||
|
||||
let exit_code = match matches.subcommand() {
|
||||
None => {
|
||||
match matches.index_of(ROC_FILE) {
|
||||
Some(arg_index) => {
|
||||
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
|
||||
if matches.is_present(ROC_FILE) {
|
||||
build(
|
||||
&matches,
|
||||
BuildConfig::BuildAndRunIfNoErrors,
|
||||
Triple::host(),
|
||||
LinkType::Executable,
|
||||
)
|
||||
} else {
|
||||
launch_editor(None)?;
|
||||
|
||||
build(
|
||||
&matches,
|
||||
BuildConfig::BuildAndRunIfNoErrors { roc_file_arg_index },
|
||||
Triple::host(),
|
||||
LinkType::Executable,
|
||||
)
|
||||
}
|
||||
|
||||
None => {
|
||||
launch_editor(None)?;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
Some((CMD_RUN, matches)) => {
|
||||
match matches.index_of(ROC_FILE) {
|
||||
Some(arg_index) => {
|
||||
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
|
||||
if matches.is_present(ROC_FILE) {
|
||||
build(
|
||||
matches,
|
||||
BuildConfig::BuildAndRun,
|
||||
Triple::host(),
|
||||
LinkType::Executable,
|
||||
)
|
||||
} else {
|
||||
eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command.");
|
||||
|
||||
build(
|
||||
matches,
|
||||
BuildConfig::BuildAndRun { roc_file_arg_index },
|
||||
Triple::host(),
|
||||
LinkType::Executable,
|
||||
)
|
||||
}
|
||||
|
||||
None => {
|
||||
eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command.");
|
||||
|
||||
Ok(1)
|
||||
}
|
||||
Ok(1)
|
||||
}
|
||||
}
|
||||
Some((CMD_BUILD, matches)) => {
|
||||
|
@ -90,11 +78,21 @@ fn main() -> io::Result<()> {
|
|||
let arena = bumpalo::Bump::new();
|
||||
|
||||
let emit_timings = matches.is_present(FLAG_TIME);
|
||||
let filename = matches.value_of(ROC_FILE).unwrap();
|
||||
let filename = matches.value_of_os(ROC_FILE).unwrap();
|
||||
let roc_file_path = PathBuf::from(filename);
|
||||
let src_dir = roc_file_path.parent().unwrap().to_owned();
|
||||
|
||||
match check_file(&arena, src_dir, roc_file_path, emit_timings) {
|
||||
let threading = match matches
|
||||
.value_of(roc_cli::FLAG_MAX_THREADS)
|
||||
.and_then(|s| s.parse::<usize>().ok())
|
||||
{
|
||||
None => Threading::AllAvailable,
|
||||
Some(0) => user_error!("cannot build with at most 0 threads"),
|
||||
Some(1) => Threading::Single,
|
||||
Some(n) => Threading::AtMost(n),
|
||||
};
|
||||
|
||||
match check_file(&arena, src_dir, roc_file_path, emit_timings, threading) {
|
||||
Ok((problems, total_time)) => {
|
||||
println!(
|
||||
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.",
|
||||
|
|
|
@ -14,10 +14,29 @@ mod cli_run {
|
|||
known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError,
|
||||
ValgrindErrorXWhat,
|
||||
};
|
||||
use const_format::concatcp;
|
||||
use indoc::indoc;
|
||||
use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_FORMAT, CMD_RUN};
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
use serial_test::serial;
|
||||
use std::iter;
|
||||
use std::path::{Path, PathBuf};
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE);
|
||||
const VALGRIND_FLAG: &str = concatcp!("--", roc_cli::FLAG_VALGRIND);
|
||||
const LINKER_FLAG: &str = concatcp!("--", roc_cli::FLAG_LINKER);
|
||||
const CHECK_FLAG: &str = concatcp!("--", roc_cli::FLAG_CHECK);
|
||||
#[allow(dead_code)]
|
||||
const TARGET_FLAG: &str = concatcp!("--", roc_cli::FLAG_TARGET);
|
||||
|
||||
#[derive(Debug, EnumIter)]
|
||||
enum CliMode {
|
||||
RocBuild,
|
||||
RocRun,
|
||||
Roc,
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
use roc_collections::all::MutMap;
|
||||
|
@ -65,7 +84,7 @@ mod cli_run {
|
|||
}
|
||||
|
||||
fn check_compile_error(file: &Path, flags: &[&str], expected: &str) {
|
||||
let compile_out = run_roc(&[&["check", file.to_str().unwrap()], flags].concat());
|
||||
let compile_out = run_roc([CMD_CHECK, file.to_str().unwrap()].iter().chain(flags), &[]);
|
||||
let err = compile_out.stdout.trim();
|
||||
let err = strip_colors(err);
|
||||
|
||||
|
@ -77,19 +96,41 @@ mod cli_run {
|
|||
}
|
||||
|
||||
fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
|
||||
let flags = &["--check"];
|
||||
let out = run_roc(&[&["format", file.to_str().unwrap()], &flags[..]].concat());
|
||||
if expects_success_exit_code {
|
||||
assert!(out.status.success());
|
||||
} else {
|
||||
assert!(!out.status.success());
|
||||
}
|
||||
let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[]);
|
||||
|
||||
assert_eq!(out.status.success(), expects_success_exit_code);
|
||||
}
|
||||
|
||||
fn build_example(file: &Path, flags: &[&str]) -> Out {
|
||||
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
|
||||
if !compile_out.stderr.is_empty() {
|
||||
panic!("roc build had stderr: {}", compile_out.stderr);
|
||||
fn run_roc_on<'a, I: IntoIterator<Item = &'a str>>(
|
||||
file: &'a Path,
|
||||
args: I,
|
||||
stdin: &[&str],
|
||||
input_file: Option<PathBuf>,
|
||||
) -> Out {
|
||||
let compile_out = match input_file {
|
||||
Some(input_file) => run_roc(
|
||||
// converting these all to String avoids lifetime issues
|
||||
args.into_iter().map(|arg| arg.to_string()).chain([
|
||||
file.to_str().unwrap().to_string(),
|
||||
input_file.to_str().unwrap().to_string(),
|
||||
]),
|
||||
stdin,
|
||||
),
|
||||
None => run_roc(
|
||||
args.into_iter().chain(iter::once(file.to_str().unwrap())),
|
||||
stdin,
|
||||
),
|
||||
};
|
||||
|
||||
if !compile_out.stderr.is_empty() &&
|
||||
// If there is any stderr, it should be reporting the runtime and that's it!
|
||||
!(compile_out.stderr.starts_with("runtime: ")
|
||||
&& compile_out.stderr.ends_with("ms\n"))
|
||||
{
|
||||
panic!(
|
||||
"`roc` command had unexpected stderr: {}",
|
||||
compile_out.stderr
|
||||
);
|
||||
}
|
||||
|
||||
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
|
||||
|
@ -106,86 +147,104 @@ mod cli_run {
|
|||
expected_ending: &str,
|
||||
use_valgrind: bool,
|
||||
) {
|
||||
let mut all_flags = vec![];
|
||||
all_flags.extend_from_slice(flags);
|
||||
for cli_mode in CliMode::iter() {
|
||||
let flags = {
|
||||
let mut vec = flags.to_vec();
|
||||
|
||||
if use_valgrind {
|
||||
all_flags.extend_from_slice(&["--valgrind"]);
|
||||
}
|
||||
if use_valgrind {
|
||||
vec.push(VALGRIND_FLAG);
|
||||
}
|
||||
|
||||
build_example(file, &all_flags[..]);
|
||||
|
||||
let out = if use_valgrind && ALLOW_VALGRIND {
|
||||
let (valgrind_out, raw_xml) = if let Some(input_file) = input_file {
|
||||
run_with_valgrind(
|
||||
stdin,
|
||||
&[
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
input_file.to_str().unwrap(),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
run_with_valgrind(
|
||||
stdin,
|
||||
&[file.with_file_name(executable_filename).to_str().unwrap()],
|
||||
)
|
||||
vec.into_iter()
|
||||
};
|
||||
|
||||
if valgrind_out.status.success() {
|
||||
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
|
||||
panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr);
|
||||
});
|
||||
let out = match cli_mode {
|
||||
CliMode::RocBuild => {
|
||||
run_roc_on(file, iter::once(CMD_BUILD).chain(flags.clone()), &[], None);
|
||||
|
||||
if !memory_errors.is_empty() {
|
||||
for error in memory_errors {
|
||||
let ValgrindError {
|
||||
kind,
|
||||
what: _,
|
||||
xwhat,
|
||||
} = error;
|
||||
println!("Valgrind Error: {}\n", kind);
|
||||
if use_valgrind && ALLOW_VALGRIND {
|
||||
let (valgrind_out, raw_xml) = if let Some(ref input_file) = input_file {
|
||||
run_with_valgrind(
|
||||
stdin.clone().iter().copied(),
|
||||
&[
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
input_file.clone().to_str().unwrap(),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
run_with_valgrind(
|
||||
stdin.clone().iter().copied(),
|
||||
&[file.with_file_name(executable_filename).to_str().unwrap()],
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(ValgrindErrorXWhat {
|
||||
text,
|
||||
leakedbytes: _,
|
||||
leakedblocks: _,
|
||||
}) = xwhat
|
||||
{
|
||||
println!(" {}", text);
|
||||
if valgrind_out.status.success() {
|
||||
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
|
||||
panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr);
|
||||
});
|
||||
|
||||
if !memory_errors.is_empty() {
|
||||
for error in memory_errors {
|
||||
let ValgrindError {
|
||||
kind,
|
||||
what: _,
|
||||
xwhat,
|
||||
} = error;
|
||||
println!("Valgrind Error: {}\n", kind);
|
||||
|
||||
if let Some(ValgrindErrorXWhat {
|
||||
text,
|
||||
leakedbytes: _,
|
||||
leakedblocks: _,
|
||||
}) = xwhat
|
||||
{
|
||||
println!(" {}", text);
|
||||
}
|
||||
}
|
||||
panic!("Valgrind reported memory errors");
|
||||
}
|
||||
} else {
|
||||
let exit_code = match valgrind_out.status.code() {
|
||||
Some(code) => format!("exit code {}", code),
|
||||
None => "no exit code".to_string(),
|
||||
};
|
||||
|
||||
panic!("`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", exit_code, valgrind_out.stdout, valgrind_out.stderr);
|
||||
}
|
||||
}
|
||||
panic!("Valgrind reported memory errors");
|
||||
}
|
||||
} else {
|
||||
let exit_code = match valgrind_out.status.code() {
|
||||
Some(code) => format!("exit code {}", code),
|
||||
None => "no exit code".to_string(),
|
||||
};
|
||||
|
||||
panic!("`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", exit_code, valgrind_out.stdout, valgrind_out.stderr);
|
||||
valgrind_out
|
||||
} else if let Some(ref input_file) = input_file {
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
stdin.iter().copied(),
|
||||
&[input_file.to_str().unwrap()],
|
||||
)
|
||||
} else {
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
stdin.iter().copied(),
|
||||
&[],
|
||||
)
|
||||
}
|
||||
}
|
||||
CliMode::Roc => run_roc_on(file, flags.clone(), stdin, input_file.clone()),
|
||||
CliMode::RocRun => run_roc_on(
|
||||
file,
|
||||
iter::once(CMD_RUN).chain(flags.clone()),
|
||||
stdin,
|
||||
input_file.clone(),
|
||||
),
|
||||
};
|
||||
|
||||
if !&out.stdout.ends_with(expected_ending) {
|
||||
panic!(
|
||||
"expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}",
|
||||
expected_ending, out.stdout, out.stderr
|
||||
);
|
||||
}
|
||||
|
||||
valgrind_out
|
||||
} else if let Some(input_file) = input_file {
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
stdin,
|
||||
&[input_file.to_str().unwrap()],
|
||||
)
|
||||
} else {
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
stdin,
|
||||
&[],
|
||||
)
|
||||
};
|
||||
if !&out.stdout.ends_with(expected_ending) {
|
||||
panic!(
|
||||
"expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}",
|
||||
expected_ending, out.stdout, out.stderr
|
||||
);
|
||||
assert!(out.status.success());
|
||||
}
|
||||
assert!(out.status.success());
|
||||
}
|
||||
|
||||
#[cfg(feature = "wasm32-cli-run")]
|
||||
|
@ -199,9 +258,13 @@ mod cli_run {
|
|||
) {
|
||||
assert_eq!(input_file, None, "Wasm does not support input files");
|
||||
let mut flags = flags.to_vec();
|
||||
flags.push("--target=wasm32");
|
||||
flags.push(concatcp!(TARGET_FLAG, "=wasm32"));
|
||||
|
||||
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags.as_slice()].concat());
|
||||
let compile_out = run_roc(
|
||||
[CMD_BUILD, file.to_str().unwrap()]
|
||||
.iter()
|
||||
.chain(flags.as_slice()),
|
||||
);
|
||||
if !compile_out.stderr.is_empty() {
|
||||
panic!("{}", compile_out.stderr);
|
||||
}
|
||||
|
@ -258,7 +321,7 @@ mod cli_run {
|
|||
}
|
||||
"hello-gui" | "breakout" => {
|
||||
// Since these require opening a window, we do `roc build` on them but don't run them.
|
||||
build_example(&file_name, &["--optimize"]);
|
||||
run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], None);
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -283,7 +346,7 @@ mod cli_run {
|
|||
&file_name,
|
||||
example.stdin,
|
||||
example.executable_filename,
|
||||
&["--optimize"],
|
||||
&[OPTIMIZE_FLAG],
|
||||
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
|
||||
example.expected_ending,
|
||||
example.use_valgrind,
|
||||
|
@ -296,7 +359,7 @@ mod cli_run {
|
|||
&file_name,
|
||||
example.stdin,
|
||||
example.executable_filename,
|
||||
&["--linker", "legacy"],
|
||||
&[LINKER_FLAG, "legacy"],
|
||||
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
|
||||
example.expected_ending,
|
||||
example.use_valgrind,
|
||||
|
@ -513,7 +576,7 @@ mod cli_run {
|
|||
&file_name,
|
||||
benchmark.stdin,
|
||||
benchmark.executable_filename,
|
||||
&["--optimize"],
|
||||
&[OPTIMIZE_FLAG],
|
||||
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
benchmark.expected_ending,
|
||||
benchmark.use_valgrind,
|
||||
|
@ -555,7 +618,7 @@ mod cli_run {
|
|||
&file_name,
|
||||
benchmark.stdin,
|
||||
benchmark.executable_filename,
|
||||
&["--optimize"],
|
||||
&[OPTIMIZE_FLAG],
|
||||
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
benchmark.expected_ending,
|
||||
);
|
||||
|
@ -587,7 +650,7 @@ mod cli_run {
|
|||
&file_name,
|
||||
benchmark.stdin,
|
||||
benchmark.executable_filename,
|
||||
&["--target=x86_32"],
|
||||
[concatcp!(TARGET_FLAG, "=x86_32")],
|
||||
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
benchmark.expected_ending,
|
||||
benchmark.use_valgrind,
|
||||
|
@ -597,7 +660,7 @@ mod cli_run {
|
|||
&file_name,
|
||||
benchmark.stdin,
|
||||
benchmark.executable_filename,
|
||||
&["--target=x86_32", "--optimize"],
|
||||
[concatcp!(TARGET_FLAG, "=x86_32"), OPTIMIZE_FLAG],
|
||||
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
benchmark.expected_ending,
|
||||
benchmark.use_valgrind,
|
||||
|
@ -816,7 +879,7 @@ mod cli_run {
|
|||
&fixture_file("multi-dep-str", "Main.roc"),
|
||||
&[],
|
||||
"multi-dep-str",
|
||||
&["--optimize"],
|
||||
&[OPTIMIZE_FLAG],
|
||||
None,
|
||||
"I am Dep2.str2\n",
|
||||
true,
|
||||
|
@ -844,7 +907,7 @@ mod cli_run {
|
|||
&fixture_file("multi-dep-thunk", "Main.roc"),
|
||||
&[],
|
||||
"multi-dep-thunk",
|
||||
&["--optimize"],
|
||||
&[OPTIMIZE_FLAG],
|
||||
None,
|
||||
"I am Dep2.value2\n",
|
||||
true,
|
||||
|
|
|
@ -20,6 +20,7 @@ serde = { version = "1.0.130", features = ["derive"] }
|
|||
serde-xml-rs = "0.5.1"
|
||||
strip-ansi-escapes = "0.1.1"
|
||||
tempfile = "3.2.0"
|
||||
const_format = "0.2.22"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
rlimit = "0.6.2"
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
use crate::helpers::{example_file, run_cmd, run_roc};
|
||||
use const_format::concatcp;
|
||||
use criterion::{black_box, measurement::Measurement, BenchmarkGroup};
|
||||
use roc_cli::CMD_BUILD;
|
||||
use std::{path::Path, thread};
|
||||
|
||||
const CFOLD_STACK_SIZE: usize = 8192 * 100000;
|
||||
|
||||
const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE);
|
||||
|
||||
fn exec_bench_w_input<T: Measurement>(
|
||||
file: &Path,
|
||||
stdin_str: &'static str,
|
||||
|
@ -11,9 +15,10 @@ fn exec_bench_w_input<T: Measurement>(
|
|||
expected_ending: &str,
|
||||
bench_group_opt: Option<&mut BenchmarkGroup<T>>,
|
||||
) {
|
||||
let flags: &[&str] = &["--optimize"];
|
||||
|
||||
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
|
||||
let compile_out = run_roc(
|
||||
[CMD_BUILD, OPTIMIZE_FLAG, file.to_str().unwrap()],
|
||||
&[stdin_str],
|
||||
);
|
||||
|
||||
if !compile_out.stderr.is_empty() {
|
||||
panic!("{}", compile_out.stderr);
|
||||
|
@ -45,12 +50,12 @@ fn check_cmd_output(
|
|||
let out = if cmd_str.contains("cfold") {
|
||||
let child = thread::Builder::new()
|
||||
.stack_size(CFOLD_STACK_SIZE)
|
||||
.spawn(move || run_cmd(&cmd_str, &[stdin_str], &[]))
|
||||
.spawn(move || run_cmd(&cmd_str, [stdin_str], &[]))
|
||||
.unwrap();
|
||||
|
||||
child.join().unwrap()
|
||||
} else {
|
||||
run_cmd(&cmd_str, &[stdin_str], &[])
|
||||
run_cmd(&cmd_str, [stdin_str], &[])
|
||||
};
|
||||
|
||||
if !&out.stdout.ends_with(expected_ending) {
|
||||
|
@ -93,12 +98,12 @@ fn bench_cmd<T: Measurement>(
|
|||
|
||||
if let Some(bench_group) = bench_group_opt {
|
||||
bench_group.bench_function(&format!("Benchmarking {:?}", executable_filename), |b| {
|
||||
b.iter(|| run_cmd(black_box(&cmd_str), black_box(&[stdin_str]), &[]))
|
||||
b.iter(|| run_cmd(black_box(&cmd_str), black_box([stdin_str]), &[]))
|
||||
});
|
||||
} else {
|
||||
run_cmd(
|
||||
black_box(file.with_file_name(executable_filename).to_str().unwrap()),
|
||||
black_box(&[stdin_str]),
|
||||
black_box([stdin_str]),
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ extern crate tempfile;
|
|||
use serde::Deserialize;
|
||||
use serde_xml_rs::from_str;
|
||||
use std::env;
|
||||
use std::ffi::OsStr;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
@ -44,18 +45,34 @@ pub fn path_to_roc_binary() -> PathBuf {
|
|||
path
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn run_roc(args: &[&str]) -> Out {
|
||||
pub fn run_roc<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(args: I, stdin_vals: &[&str]) -> Out {
|
||||
let mut cmd = Command::new(path_to_roc_binary());
|
||||
|
||||
for arg in args {
|
||||
cmd.arg(arg);
|
||||
}
|
||||
|
||||
let output = cmd
|
||||
.output()
|
||||
let mut child = cmd
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.expect("failed to execute compiled `roc` binary in CLI test");
|
||||
|
||||
{
|
||||
let stdin = child.stdin.as_mut().expect("Failed to open stdin");
|
||||
|
||||
for stdin_str in stdin_vals.iter() {
|
||||
stdin
|
||||
.write_all(stdin_str.as_bytes())
|
||||
.expect("Failed to write to stdin");
|
||||
}
|
||||
}
|
||||
|
||||
let output = child
|
||||
.wait_with_output()
|
||||
.expect("failed to get output for compiled `roc` binary in CLI test");
|
||||
|
||||
Out {
|
||||
stdout: String::from_utf8(output.stdout).unwrap(),
|
||||
stderr: String::from_utf8(output.stderr).unwrap(),
|
||||
|
@ -63,8 +80,11 @@ pub fn run_roc(args: &[&str]) -> Out {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn run_cmd(cmd_name: &str, stdin_vals: &[&str], args: &[&str]) -> Out {
|
||||
pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>>(
|
||||
cmd_name: &str,
|
||||
stdin_vals: I,
|
||||
args: &[&str],
|
||||
) -> Out {
|
||||
let mut cmd = Command::new(cmd_name);
|
||||
|
||||
for arg in args {
|
||||
|
@ -99,8 +119,10 @@ pub fn run_cmd(cmd_name: &str, stdin_vals: &[&str], args: &[&str]) -> Out {
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn run_with_valgrind(stdin_vals: &[&str], args: &[&str]) -> (Out, String) {
|
||||
pub fn run_with_valgrind<'a, I: IntoIterator<Item = &'a str>>(
|
||||
stdin_vals: I,
|
||||
args: &[&str],
|
||||
) -> (Out, String) {
|
||||
//TODO: figure out if there is a better way to get the valgrind executable.
|
||||
let mut cmd = Command::new("valgrind");
|
||||
let named_tempfile =
|
||||
|
|
|
@ -106,6 +106,9 @@ comptime {
|
|||
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max.");
|
||||
num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min.");
|
||||
}
|
||||
|
||||
num.exportRoundF32(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f32.");
|
||||
num.exportRoundF64(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f64.");
|
||||
}
|
||||
|
||||
inline for (FLOATS) |T| {
|
||||
|
@ -114,7 +117,6 @@ comptime {
|
|||
num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan.");
|
||||
|
||||
num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite.");
|
||||
num.exportRound(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round.");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -90,10 +90,19 @@ pub fn exportAtan(comptime T: type, comptime name: []const u8) void {
|
|||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportRound(comptime T: type, comptime name: []const u8) void {
|
||||
pub fn exportRoundF32(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(input: T) callconv(.C) i64 {
|
||||
return @floatToInt(i64, (@round(input)));
|
||||
fn func(input: f32) callconv(.C) T {
|
||||
return @floatToInt(T, (@round(input)));
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
}
|
||||
|
||||
pub fn exportRoundF64(comptime T: type, comptime name: []const u8) void {
|
||||
comptime var f = struct {
|
||||
fn func(input: f64) callconv(.C) T {
|
||||
return @floatToInt(T, (@round(input)));
|
||||
}
|
||||
}.func;
|
||||
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
|
||||
|
|
|
@ -279,7 +279,9 @@ pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan");
|
|||
pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite");
|
||||
pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int");
|
||||
pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil");
|
||||
pub const NUM_ROUND: IntrinsicName = float_intrinsic!("roc_builtins.num.round");
|
||||
|
||||
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
|
||||
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");
|
||||
|
||||
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
|
||||
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";
|
||||
|
|
|
@ -14,8 +14,6 @@ roc_module = { path = "../module" }
|
|||
roc_parse = { path = "../parse" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_types = { path = "../types" }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
ven_graph = { path = "../../vendor/pathfinding" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
static_assertions = "1.1.0"
|
||||
bitvec = "1"
|
||||
|
|
|
@ -192,17 +192,12 @@ fn sort_type_defs_before_introduction(
|
|||
}
|
||||
|
||||
// find the strongly connected components and their relations
|
||||
let nodes: Vec<_> = (0..capacity as u32).collect();
|
||||
|
||||
let mut output = Vec::with_capacity(capacity);
|
||||
|
||||
for group in matrix.strongly_connected_components(&nodes).groups() {
|
||||
for index in group.iter_ones() {
|
||||
output.push(symbols[index])
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
matrix
|
||||
.strongly_connected_components_all()
|
||||
.groups()
|
||||
.flat_map(|group| group.iter_ones())
|
||||
.map(|index| symbols[index])
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -790,14 +785,10 @@ pub(crate) fn sort_can_defs(
|
|||
};
|
||||
}
|
||||
|
||||
let nodes: Vec<_> = (0..defs.len() as u32).collect();
|
||||
|
||||
// We first perform SCC based on any reference, both variable usage and calls
|
||||
// considering both value definitions and function bodies. This will spot any
|
||||
// recursive relations between any 2 definitions.
|
||||
let sccs = def_ordering
|
||||
.references
|
||||
.strongly_connected_components(&nodes);
|
||||
let sccs = def_ordering.references.strongly_connected_components_all();
|
||||
|
||||
let mut declarations = Vec::new();
|
||||
|
||||
|
@ -838,10 +829,9 @@ pub(crate) fn sort_can_defs(
|
|||
// boom = \{} -> boom {}
|
||||
//
|
||||
// In general we cannot spot faulty recursion (halting problem) so this is our best attempt
|
||||
let nodes: Vec<_> = group.iter_ones().map(|v| v as u32).collect();
|
||||
let direct_sccs = def_ordering
|
||||
.direct_references
|
||||
.strongly_connected_components(&nodes);
|
||||
.strongly_connected_components_subset(group);
|
||||
|
||||
let declaration = if direct_sccs.groups().count() == 1 {
|
||||
// all defs are part of the same direct cycle, that is invalid!
|
||||
|
@ -1571,8 +1561,7 @@ fn correct_mutual_recursive_type_alias<'a>(
|
|||
|
||||
let mut solved_aliases = bitvec::vec::BitVec::<usize>::repeat(false, capacity);
|
||||
|
||||
let group: Vec<_> = (0u32..capacity as u32).collect();
|
||||
let sccs = matrix.strongly_connected_components(&group);
|
||||
let sccs = matrix.strongly_connected_components_all();
|
||||
|
||||
// scratchpad to store aliases that are modified in the current iteration.
|
||||
// Only used when there is are more than one alias in a group. See below why
|
||||
|
|
|
@ -129,8 +129,14 @@ impl ReferenceMatrix {
|
|||
TopologicalSort::Groups { groups }
|
||||
}
|
||||
|
||||
/// Get the strongly-connected components of the set of input nodes.
|
||||
pub fn strongly_connected_components(&self, nodes: &[u32]) -> Sccs {
|
||||
/// Get the strongly-connected components all nodes in the matrix
|
||||
pub fn strongly_connected_components_all(&self) -> Sccs {
|
||||
let bitvec = BitVec::repeat(true, self.length);
|
||||
self.strongly_connected_components_subset(&bitvec)
|
||||
}
|
||||
|
||||
/// Get the strongly-connected components of a set of input nodes.
|
||||
pub fn strongly_connected_components_subset(&self, nodes: &BitSlice) -> Sccs {
|
||||
let mut params = Params::new(self.length, nodes);
|
||||
|
||||
'outer: loop {
|
||||
|
@ -176,15 +182,15 @@ struct Params {
|
|||
p: Vec<u32>,
|
||||
s: Vec<u32>,
|
||||
scc: Sccs,
|
||||
scca: Vec<u32>,
|
||||
scca: BitVec,
|
||||
}
|
||||
|
||||
impl Params {
|
||||
fn new(length: usize, group: &[u32]) -> Self {
|
||||
fn new(length: usize, group: &BitSlice) -> Self {
|
||||
let mut preorders = vec![Preorder::Removed; length];
|
||||
|
||||
for value in group {
|
||||
preorders[*value as usize] = Preorder::Empty;
|
||||
for index in group.iter_ones() {
|
||||
preorders[index] = Preorder::Empty;
|
||||
}
|
||||
|
||||
Self {
|
||||
|
@ -196,7 +202,7 @@ impl Params {
|
|||
matrix: ReferenceMatrix::new(length),
|
||||
components: 0,
|
||||
},
|
||||
scca: Vec::new(),
|
||||
scca: BitVec::repeat(false, length),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,7 +216,7 @@ fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) {
|
|||
params.p.push(v as u32);
|
||||
|
||||
for w in bitvec[v * length..][..length].iter_ones() {
|
||||
if !params.scca.contains(&(w as u32)) {
|
||||
if !params.scca[w] {
|
||||
match params.preorders[w] {
|
||||
Preorder::Filled(pw) => loop {
|
||||
let index = *params.p.last().unwrap();
|
||||
|
@ -241,7 +247,7 @@ fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) {
|
|||
.scc
|
||||
.matrix
|
||||
.set_row_col(params.scc.components, node as usize, true);
|
||||
params.scca.push(node);
|
||||
params.scca.set(node as usize, true);
|
||||
params.preorders[node as usize] = Preorder::Removed;
|
||||
if node as usize == v {
|
||||
break;
|
||||
|
|
|
@ -13,6 +13,13 @@ impl<K, V> Default for VecMap<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<K, V> VecMap<K, V> {
|
||||
pub fn len(&self) -> usize {
|
||||
debug_assert_eq!(self.keys.len(), self.values.len());
|
||||
self.keys.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: PartialEq, V> VecMap<K, V> {
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
|
@ -21,11 +28,6 @@ impl<K: PartialEq, V> VecMap<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
debug_assert_eq!(self.keys.len(), self.values.len());
|
||||
self.keys.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
debug_assert_eq!(self.keys.len(), self.values.len());
|
||||
self.keys.is_empty()
|
||||
|
@ -58,15 +60,9 @@ impl<K: PartialEq, V> VecMap<K, V> {
|
|||
self.keys.contains(key)
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, key: &K) {
|
||||
match self.keys.iter().position(|x| x == key) {
|
||||
None => {
|
||||
// just do nothing
|
||||
}
|
||||
Some(index) => {
|
||||
self.swap_remove(index);
|
||||
}
|
||||
}
|
||||
pub fn remove(&mut self, key: &K) -> Option<(K, V)> {
|
||||
let index = self.keys.iter().position(|x| x == key)?;
|
||||
Some(self.swap_remove(index))
|
||||
}
|
||||
|
||||
pub fn get(&self, key: &K) -> Option<&V> {
|
||||
|
@ -83,7 +79,7 @@ impl<K: PartialEq, V> VecMap<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_or_insert(&mut self, key: K, default_value: impl Fn() -> V) -> &mut V {
|
||||
pub fn get_or_insert(&mut self, key: K, default_value: impl FnOnce() -> V) -> &mut V {
|
||||
match self.keys.iter().position(|x| x == &key) {
|
||||
Some(index) => &mut self.values[index],
|
||||
None => {
|
||||
|
@ -97,15 +93,15 @@ impl<K: PartialEq, V> VecMap<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
|
||||
pub fn iter(&self) -> impl ExactSizeIterator<Item = (&K, &V)> {
|
||||
self.keys.iter().zip(self.values.iter())
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> impl Iterator<Item = &K> {
|
||||
pub fn keys(&self) -> impl ExactSizeIterator<Item = &K> {
|
||||
self.keys.iter()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = &V> {
|
||||
pub fn values(&self) -> impl ExactSizeIterator<Item = &V> {
|
||||
self.values.iter()
|
||||
}
|
||||
|
||||
|
@ -159,6 +155,7 @@ impl<K, V> IntoIterator for VecMap<K, V> {
|
|||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
IntoIter {
|
||||
len: self.len(),
|
||||
keys: self.keys.into_iter(),
|
||||
values: self.values.into_iter(),
|
||||
}
|
||||
|
@ -166,6 +163,7 @@ impl<K, V> IntoIterator for VecMap<K, V> {
|
|||
}
|
||||
|
||||
pub struct IntoIter<K, V> {
|
||||
len: usize,
|
||||
keys: std::vec::IntoIter<K>,
|
||||
values: std::vec::IntoIter<V>,
|
||||
}
|
||||
|
@ -180,3 +178,9 @@ impl<K, V> Iterator for IntoIter<K, V> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> ExactSizeIterator for IntoIter<K, V> {
|
||||
fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,14 @@ pub mod pattern;
|
|||
pub mod spaces;
|
||||
|
||||
use bumpalo::{collections::String, Bump};
|
||||
use roc_parse::ast::{Def, Module};
|
||||
use roc_region::all::Loc;
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct Ast<'a> {
|
||||
pub module: Module<'a>,
|
||||
pub defs: bumpalo::collections::vec::Vec<'a, Loc<Def<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Buf<'a> {
|
||||
|
|
|
@ -1,6 +1,21 @@
|
|||
use roc_parse::ast::CommentOrNewline;
|
||||
use bumpalo::collections::vec::Vec;
|
||||
use bumpalo::Bump;
|
||||
use roc_module::called_via::{BinOp, UnaryOp};
|
||||
use roc_parse::{
|
||||
ast::{
|
||||
AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Expr, Has, HasClause,
|
||||
Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader,
|
||||
ValueDef, WhenBranch,
|
||||
},
|
||||
header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName,
|
||||
PackageEntry, PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
|
||||
},
|
||||
ident::UppercaseIdent,
|
||||
};
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
||||
use crate::Buf;
|
||||
use crate::{Ast, Buf};
|
||||
|
||||
/// The number of spaces to indent.
|
||||
pub const INDENT: u16 = 4;
|
||||
|
@ -149,3 +164,575 @@ fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
|
|||
}
|
||||
buf.push_str(docs);
|
||||
}
|
||||
|
||||
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
|
||||
///
|
||||
/// Currently this consists of:
|
||||
/// * Removing newlines
|
||||
/// * Removing comments
|
||||
/// * Removing parens in Exprs
|
||||
///
|
||||
/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting)
|
||||
/// - but there are currently several bugs where they're _not_ preserved.
|
||||
/// TODO: ensure formatting retains comments
|
||||
pub trait RemoveSpaces<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self;
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Ast<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
Ast {
|
||||
module: self.module.remove_spaces(arena),
|
||||
defs: {
|
||||
let mut defs = Vec::with_capacity_in(self.defs.len(), arena);
|
||||
for d in &self.defs {
|
||||
defs.push(d.remove_spaces(arena))
|
||||
}
|
||||
defs
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Module<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match self {
|
||||
Module::Interface { header } => Module::Interface {
|
||||
header: InterfaceHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_interface_keyword: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
},
|
||||
},
|
||||
Module::App { header } => Module::App {
|
||||
header: AppHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
provides: header.provides.remove_spaces(arena),
|
||||
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
|
||||
to: header.to.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_app_keyword: &[],
|
||||
before_packages: &[],
|
||||
after_packages: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_provides: &[],
|
||||
after_provides: &[],
|
||||
before_to: &[],
|
||||
after_to: &[],
|
||||
},
|
||||
},
|
||||
Module::Platform { header } => Module::Platform {
|
||||
header: PlatformHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
requires: header.requires.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
packages: header.packages.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
provides: header.provides.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_platform_keyword: &[],
|
||||
before_requires: &[],
|
||||
after_requires: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_packages: &[],
|
||||
after_packages: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_provides: &[],
|
||||
after_provides: &[],
|
||||
},
|
||||
},
|
||||
Module::Hosted { header } => Module::Hosted {
|
||||
header: HostedHeader {
|
||||
name: header.name.remove_spaces(arena),
|
||||
exposes: header.exposes.remove_spaces(arena),
|
||||
imports: header.imports.remove_spaces(arena),
|
||||
generates: header.generates.remove_spaces(arena),
|
||||
generates_with: header.generates_with.remove_spaces(arena),
|
||||
before_header: &[],
|
||||
after_hosted_keyword: &[],
|
||||
before_exposes: &[],
|
||||
after_exposes: &[],
|
||||
before_imports: &[],
|
||||
after_imports: &[],
|
||||
before_generates: &[],
|
||||
after_generates: &[],
|
||||
before_with: &[],
|
||||
after_with: &[],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for &'a str {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
|
||||
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for To<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
To::ExistingPackage(a) => To::ExistingPackage(a),
|
||||
To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
TypedIdent {
|
||||
ident: self.ident.remove_spaces(arena),
|
||||
spaces_before_colon: &[],
|
||||
ann: self.ann.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
PlatformRequires {
|
||||
rigids: self.rigids.remove_spaces(arena),
|
||||
signature: self.signature.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
PackageEntry {
|
||||
shorthand: self.shorthand,
|
||||
spaces_after_shorthand: &[],
|
||||
package_name: self.package_name.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
|
||||
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
self.as_ref().map(|a| a.remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let res = self.value.remove_spaces(arena);
|
||||
Loc::at(Region::zero(), res)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
(self.0.remove_spaces(arena), self.1.remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let mut items = Vec::with_capacity_in(self.items.len(), arena);
|
||||
for item in self.items {
|
||||
items.push(item.remove_spaces(arena));
|
||||
}
|
||||
Collection::with_items(items.into_bump_slice())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
let mut items = Vec::with_capacity_in(self.len(), arena);
|
||||
for item in *self {
|
||||
let res = item.remove_spaces(arena);
|
||||
items.push(res);
|
||||
}
|
||||
items.into_bump_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for UnaryOp {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for BinOp {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
arena.alloc((*self).remove_spaces(arena))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
use TypeDef::*;
|
||||
|
||||
match *self {
|
||||
Alias {
|
||||
header: TypeHeader { name, vars },
|
||||
ann,
|
||||
} => Alias {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
ann: ann.remove_spaces(arena),
|
||||
},
|
||||
Opaque {
|
||||
header: TypeHeader { name, vars },
|
||||
typ,
|
||||
} => Opaque {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
typ: typ.remove_spaces(arena),
|
||||
},
|
||||
Ability {
|
||||
header: TypeHeader { name, vars },
|
||||
loc_has,
|
||||
members,
|
||||
} => Ability {
|
||||
header: TypeHeader {
|
||||
name: name.remove_spaces(arena),
|
||||
vars: vars.remove_spaces(arena),
|
||||
},
|
||||
loc_has: loc_has.remove_spaces(arena),
|
||||
members: members.remove_spaces(arena),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
use ValueDef::*;
|
||||
|
||||
match *self {
|
||||
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
|
||||
Body(a, b) => Body(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
AnnotatedBody {
|
||||
ann_pattern,
|
||||
ann_type,
|
||||
comment: _,
|
||||
body_pattern,
|
||||
body_expr,
|
||||
} => AnnotatedBody {
|
||||
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
|
||||
ann_type: arena.alloc(ann_type.remove_spaces(arena)),
|
||||
comment: None,
|
||||
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
|
||||
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
|
||||
},
|
||||
Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Def<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Def::Type(def) => Def::Type(def.remove_spaces(arena)),
|
||||
Def::Value(def) => Def::Value(def.remove_spaces(arena)),
|
||||
Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
|
||||
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Has<'a> {
|
||||
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
|
||||
Has::Has
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
AbilityMember {
|
||||
name: self.name.remove_spaces(arena),
|
||||
typ: self.typ.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
WhenBranch {
|
||||
patterns: self.patterns.remove_spaces(arena),
|
||||
value: self.value.remove_spaces(arena),
|
||||
guard: self.guard.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue(
|
||||
a.remove_spaces(arena),
|
||||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue(
|
||||
a.remove_spaces(arena),
|
||||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
|
||||
AssignedField::Malformed(a) => AssignedField::Malformed(a),
|
||||
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
|
||||
StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)),
|
||||
StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
StrSegment::Plaintext(t) => StrSegment::Plaintext(t),
|
||||
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
|
||||
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
|
||||
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Expr::Float(a) => Expr::Float(a),
|
||||
Expr::Num(a) => Expr::Num(a),
|
||||
Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => Expr::NonBase10Int {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
},
|
||||
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
|
||||
Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b),
|
||||
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
|
||||
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
|
||||
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
|
||||
update: arena.alloc(update.remove_spaces(arena)),
|
||||
fields: fields.remove_spaces(arena),
|
||||
},
|
||||
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
|
||||
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
|
||||
Expr::Underscore(a) => Expr::Underscore(a),
|
||||
Expr::Tag(a) => Expr::Tag(a),
|
||||
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
|
||||
Expr::Closure(a, b) => Expr::Closure(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Defs(a, b) => {
|
||||
Expr::Defs(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Expr::Backpassing(a, b, c) => Expr::Backpassing(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Expect(a, b) => Expr::Expect(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Expr::Apply(a, b, c) => Expr::Apply(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
b.remove_spaces(arena),
|
||||
c,
|
||||
),
|
||||
Expr::BinOps(a, b) => {
|
||||
Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Expr::UnaryOp(a, b) => {
|
||||
Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
|
||||
}
|
||||
Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))),
|
||||
Expr::When(a, b) => {
|
||||
Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
|
||||
}
|
||||
Expr::ParensAround(a) => {
|
||||
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
|
||||
a.remove_spaces(arena)
|
||||
}
|
||||
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b),
|
||||
Expr::MalformedClosure => Expr::MalformedClosure,
|
||||
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
|
||||
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Expr::SingleQuote(a) => Expr::Num(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Pattern::Identifier(a) => Pattern::Identifier(a),
|
||||
Pattern::Tag(a) => Pattern::Tag(a),
|
||||
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
|
||||
Pattern::Apply(a, b) => Pattern::Apply(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)),
|
||||
Pattern::RequiredField(a, b) => {
|
||||
Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Pattern::OptionalField(a, b) => {
|
||||
Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena)))
|
||||
}
|
||||
Pattern::NumLiteral(a) => Pattern::NumLiteral(a),
|
||||
Pattern::NonBase10Literal {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
} => Pattern::NonBase10Literal {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
},
|
||||
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
|
||||
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
|
||||
Pattern::Underscore(a) => Pattern::Underscore(a),
|
||||
Pattern::Malformed(a) => Pattern::Malformed(a),
|
||||
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b),
|
||||
Pattern::QualifiedIdentifier { module_name, ident } => {
|
||||
Pattern::QualifiedIdentifier { module_name, ident }
|
||||
}
|
||||
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
TypeAnnotation::Function(a, b) => TypeAnnotation::Function(
|
||||
arena.alloc(a.remove_spaces(arena)),
|
||||
arena.alloc(b.remove_spaces(arena)),
|
||||
),
|
||||
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
|
||||
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
|
||||
TypeAnnotation::As(a, _, c) => {
|
||||
TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c)
|
||||
}
|
||||
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
|
||||
fields: fields.remove_spaces(arena),
|
||||
ext: ext.remove_spaces(arena),
|
||||
},
|
||||
TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion {
|
||||
ext: ext.remove_spaces(arena),
|
||||
tags: tags.remove_spaces(arena),
|
||||
},
|
||||
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
|
||||
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
|
||||
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
|
||||
arena.alloc(annot.remove_spaces(arena)),
|
||||
arena.alloc(has_clauses.remove_spaces(arena)),
|
||||
),
|
||||
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for HasClause<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
HasClause {
|
||||
var: self.var.remove_spaces(arena),
|
||||
ability: self.ability.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for Tag<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
Tag::Apply { name, args } => Tag::Apply {
|
||||
name: name.remove_spaces(arena),
|
||||
args: args.remove_spaces(arena),
|
||||
},
|
||||
Tag::Malformed(a) => Tag::Malformed(a),
|
||||
Tag::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
Tag::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,60 +10,153 @@ mod test_fmt {
|
|||
use roc_fmt::def::fmt_def;
|
||||
use roc_fmt::module::fmt_module;
|
||||
use roc_fmt::Buf;
|
||||
use roc_parse::ast::Module;
|
||||
use roc_parse::module::{self, module_defs};
|
||||
use roc_parse::parser::Parser;
|
||||
use roc_parse::state::State;
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
|
||||
// Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same
|
||||
fn expect_format_expr_helper(input: &str, expected: &str) {
|
||||
fn expr_formats_to(input: &str, expected: &str) {
|
||||
let arena = Bump::new();
|
||||
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
|
||||
let input = input.trim();
|
||||
let expected = expected.trim();
|
||||
|
||||
match roc_parse::test_helpers::parse_expr_with(&arena, input) {
|
||||
Ok(actual) => {
|
||||
use roc_fmt::spaces::RemoveSpaces;
|
||||
|
||||
let mut buf = Buf::new_in(&arena);
|
||||
|
||||
actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0);
|
||||
|
||||
assert_multiline_str_eq!(expected, buf.as_str());
|
||||
let output = buf.as_str();
|
||||
|
||||
assert_multiline_str_eq!(expected, output);
|
||||
|
||||
let reparsed_ast = roc_parse::test_helpers::parse_expr_with(&arena, output).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"After formatting, the source code no longer parsed!\n\nParse error was: {:?}\n\nThe code that failed to parse:\n\n{}\n\n",
|
||||
err, output
|
||||
);
|
||||
});
|
||||
|
||||
let ast_normalized = actual.remove_spaces(&arena);
|
||||
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
|
||||
|
||||
// HACK!
|
||||
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
|
||||
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
|
||||
// I don't have the patience to debug this right now, so let's leave it for another day...
|
||||
// TODO: fix PartialEq impl on ast types
|
||||
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
|
||||
panic!(
|
||||
"Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\
|
||||
* * * Source code before formatting:\n{}\n\n\
|
||||
* * * Source code after formatting:\n{}\n\n",
|
||||
input,
|
||||
output
|
||||
);
|
||||
}
|
||||
|
||||
// Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted
|
||||
let mut reformatted_buf = Buf::new_in(&arena);
|
||||
reparsed_ast.format_with_options(&mut reformatted_buf, Parens::NotNeeded, Newlines::Yes, 0);
|
||||
|
||||
if output != reformatted_buf.as_str() {
|
||||
eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n");
|
||||
|
||||
assert_multiline_str_eq!(output, reformatted_buf.as_str());
|
||||
}
|
||||
}
|
||||
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error)
|
||||
};
|
||||
}
|
||||
|
||||
fn expr_formats_to(input: &str, expected: &str) {
|
||||
let input = input.trim_end();
|
||||
let expected = expected.trim_end();
|
||||
|
||||
// First check that input formats to the expected version
|
||||
expect_format_expr_helper(input, expected);
|
||||
|
||||
// Parse the expected result format it, asserting that it doesn't change
|
||||
// It's important that formatting be stable / idempotent
|
||||
expect_format_expr_helper(expected, expected);
|
||||
}
|
||||
|
||||
fn expr_formats_same(input: &str) {
|
||||
expr_formats_to(input, input);
|
||||
}
|
||||
|
||||
fn fmt_module_and_defs<'a>(
|
||||
arena: &Bump,
|
||||
src: &str,
|
||||
module: &Module<'a>,
|
||||
state: State<'a>,
|
||||
buf: &mut Buf<'_>,
|
||||
) {
|
||||
fmt_module(buf, module);
|
||||
|
||||
match module_defs().parse(&arena, state) {
|
||||
Ok((_, loc_defs, _)) => {
|
||||
for loc_def in loc_defs {
|
||||
fmt_def(buf, arena.alloc(loc_def.value), 0);
|
||||
}
|
||||
}
|
||||
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Not intended to be used directly in tests; please use module_formats_to or module_formats_same
|
||||
fn expect_format_module_helper(src: &str, expected: &str) {
|
||||
let arena = Bump::new();
|
||||
let src = src.trim();
|
||||
let expected = expected.trim();
|
||||
|
||||
match module::parse_header(&arena, State::new(src.as_bytes())) {
|
||||
Ok((actual, state)) => {
|
||||
use roc_fmt::spaces::RemoveSpaces;
|
||||
|
||||
let mut buf = Buf::new_in(&arena);
|
||||
|
||||
fmt_module(&mut buf, &actual);
|
||||
fmt_module_and_defs(&arena, src, &actual, state, &mut buf);
|
||||
|
||||
match module_defs().parse(&arena, state) {
|
||||
Ok((_, loc_defs, _)) => {
|
||||
for loc_def in loc_defs {
|
||||
fmt_def(&mut buf, arena.alloc(loc_def.value), 0);
|
||||
}
|
||||
}
|
||||
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
|
||||
let output = buf.as_str().trim();
|
||||
|
||||
let (reparsed_ast, state) = module::parse_header(&arena, State::new(output.as_bytes())).unwrap_or_else(|err| {
|
||||
panic!(
|
||||
"After formatting, the source code no longer parsed!\n\nParse error was: {:?}\n\nThe code that failed to parse:\n\n{}\n\n",
|
||||
err, output
|
||||
);
|
||||
});
|
||||
|
||||
let ast_normalized = actual.remove_spaces(&arena);
|
||||
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
|
||||
|
||||
// HACK!
|
||||
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
|
||||
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
|
||||
// I don't have the patience to debug this right now, so let's leave it for another day...
|
||||
// TODO: fix PartialEq impl on ast types
|
||||
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
|
||||
panic!(
|
||||
"Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\
|
||||
* * * Source code before formatting:\n{}\n\n\
|
||||
* * * Source code after formatting:\n{}\n\n",
|
||||
src,
|
||||
output
|
||||
);
|
||||
}
|
||||
assert_multiline_str_eq!(expected, buf.as_str())
|
||||
|
||||
// Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted
|
||||
let mut reformatted_buf = Buf::new_in(&arena);
|
||||
|
||||
fmt_module_and_defs(&arena, output, &reparsed_ast, state, &mut reformatted_buf);
|
||||
|
||||
let reformatted = reformatted_buf.as_str().trim();
|
||||
|
||||
if output != reformatted {
|
||||
eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n");
|
||||
|
||||
assert_multiline_str_eq!(output, reformatted);
|
||||
}
|
||||
|
||||
// If everything was idempotent re-parsing worked, finally assert
|
||||
// that the formatted code was what we expected it to be.
|
||||
//
|
||||
// Do this last because if there were any serious problems with the
|
||||
// formatter (e.g. it wasn't idempotent), we want to know about
|
||||
// those more than we want to know that the expectation failed!
|
||||
assert_multiline_str_eq!(expected, output);
|
||||
}
|
||||
Err(error) => panic!("Unexpected parse failure when parsing this for module header formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
|
||||
};
|
||||
|
@ -680,7 +773,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -693,7 +786,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -706,7 +799,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -720,7 +813,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -737,7 +830,7 @@ mod test_fmt {
|
|||
# comment 3
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -753,7 +846,7 @@ mod test_fmt {
|
|||
# comment 3
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
));
|
||||
expr_formats_to(
|
||||
|
@ -767,7 +860,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
|
@ -779,7 +872,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -795,7 +888,7 @@ mod test_fmt {
|
|||
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
|
@ -807,7 +900,7 @@ mod test_fmt {
|
|||
# comment
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -823,7 +916,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
|
@ -835,7 +928,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -851,7 +944,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
|
@ -863,7 +956,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -881,7 +974,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
|
@ -894,7 +987,7 @@ mod test_fmt {
|
|||
1,
|
||||
]
|
||||
|
||||
list
|
||||
list
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
@ -1444,7 +1537,7 @@ mod test_fmt {
|
|||
3,
|
||||
]
|
||||
|
||||
toList
|
||||
toList
|
||||
"#
|
||||
));
|
||||
|
||||
|
@ -3939,6 +4032,39 @@ mod test_fmt {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn format_tui_package_config() {
|
||||
// At one point this failed to reformat.
|
||||
module_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
platform "tui"
|
||||
requires { Model } { main : { init : ({} -> Model), update : (Model, Str -> Model), view : (Model -> Str) } }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
|
||||
mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View }
|
||||
mainForHost = main
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
platform "tui"
|
||||
requires { Model } { main : { init : {} -> Model, update : Model, Str -> Model, view : Model -> Str } }
|
||||
exposes []
|
||||
packages {}
|
||||
imports []
|
||||
provides [ mainForHost ]
|
||||
|
||||
mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View }
|
||||
mainForHost = main
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_hosted() {
|
||||
module_formats_same(indoc!(
|
||||
|
|
|
@ -554,7 +554,7 @@ trait Backend<'a> {
|
|||
}
|
||||
LowLevel::NumRound => self.build_fn_call(
|
||||
sym,
|
||||
bitcode::NUM_ROUND[FloatWidth::F64].to_string(),
|
||||
bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(),
|
||||
args,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
|
|
|
@ -602,6 +602,7 @@ static LLVM_SIN: IntrinsicName = float_intrinsic!("llvm.sin");
|
|||
static LLVM_COS: IntrinsicName = float_intrinsic!("llvm.cos");
|
||||
static LLVM_CEILING: IntrinsicName = float_intrinsic!("llvm.ceil");
|
||||
static LLVM_FLOOR: IntrinsicName = float_intrinsic!("llvm.floor");
|
||||
static LLVM_ROUND: IntrinsicName = float_intrinsic!("llvm.round");
|
||||
|
||||
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
|
||||
static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32";
|
||||
|
@ -7403,20 +7404,67 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
}
|
||||
NumCeiling => env.builder.build_cast(
|
||||
InstructionOpcode::FPToSI,
|
||||
env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]),
|
||||
env.context.i64_type(),
|
||||
"num_ceiling",
|
||||
),
|
||||
NumFloor => env.builder.build_cast(
|
||||
InstructionOpcode::FPToSI,
|
||||
env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]),
|
||||
env.context.i64_type(),
|
||||
"num_floor",
|
||||
),
|
||||
NumCeiling => {
|
||||
let (return_signed, return_type) = match layout {
|
||||
Layout::Builtin(Builtin::Int(int_width)) => (
|
||||
int_width.is_signed(),
|
||||
convert::int_type_from_int_width(env, *int_width),
|
||||
),
|
||||
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
|
||||
};
|
||||
let opcode = if return_signed {
|
||||
InstructionOpcode::FPToSI
|
||||
} else {
|
||||
InstructionOpcode::FPToUI
|
||||
};
|
||||
env.builder.build_cast(
|
||||
opcode,
|
||||
env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]),
|
||||
return_type,
|
||||
"num_ceiling",
|
||||
)
|
||||
}
|
||||
NumFloor => {
|
||||
let (return_signed, return_type) = match layout {
|
||||
Layout::Builtin(Builtin::Int(int_width)) => (
|
||||
int_width.is_signed(),
|
||||
convert::int_type_from_int_width(env, *int_width),
|
||||
),
|
||||
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
|
||||
};
|
||||
let opcode = if return_signed {
|
||||
InstructionOpcode::FPToSI
|
||||
} else {
|
||||
InstructionOpcode::FPToUI
|
||||
};
|
||||
env.builder.build_cast(
|
||||
opcode,
|
||||
env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]),
|
||||
return_type,
|
||||
"num_floor",
|
||||
)
|
||||
}
|
||||
NumRound => {
|
||||
let (return_signed, return_type) = match layout {
|
||||
Layout::Builtin(Builtin::Int(int_width)) => (
|
||||
int_width.is_signed(),
|
||||
convert::int_type_from_int_width(env, *int_width),
|
||||
),
|
||||
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
|
||||
};
|
||||
let opcode = if return_signed {
|
||||
InstructionOpcode::FPToSI
|
||||
} else {
|
||||
InstructionOpcode::FPToUI
|
||||
};
|
||||
env.builder.build_cast(
|
||||
opcode,
|
||||
env.call_intrinsic(&LLVM_ROUND[float_width], &[arg.into()]),
|
||||
return_type,
|
||||
"num_round",
|
||||
)
|
||||
}
|
||||
NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE[float_width]),
|
||||
NumRound => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND[float_width]),
|
||||
|
||||
// trigonometry
|
||||
NumSin => env.call_intrinsic(&LLVM_SIN[float_width], &[arg.into()]),
|
||||
|
|
|
@ -561,18 +561,6 @@ impl<'a> LowLevelCall<'a> {
|
|||
NumCos => todo!("{:?}", self.lowlevel),
|
||||
NumSqrtUnchecked => todo!("{:?}", self.lowlevel),
|
||||
NumLogUnchecked => todo!("{:?}", self.lowlevel),
|
||||
NumRound => {
|
||||
self.load_args(backend);
|
||||
match CodeGenNumType::for_symbol(backend, self.arguments[0]) {
|
||||
F32 => {
|
||||
self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F32])
|
||||
}
|
||||
F64 => {
|
||||
self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F64])
|
||||
}
|
||||
_ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout),
|
||||
}
|
||||
}
|
||||
NumToFloat => {
|
||||
self.load_args(backend);
|
||||
let ret_type = CodeGenNumType::from(self.ret_layout);
|
||||
|
@ -592,35 +580,54 @@ impl<'a> LowLevelCall<'a> {
|
|||
}
|
||||
}
|
||||
NumPow => todo!("{:?}", self.lowlevel),
|
||||
NumCeiling => {
|
||||
NumRound => {
|
||||
self.load_args(backend);
|
||||
match CodeGenNumType::from(self.ret_layout) {
|
||||
I32 => {
|
||||
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
|
||||
let ret_type = CodeGenNumType::from(self.ret_layout);
|
||||
|
||||
let width = match ret_type {
|
||||
CodeGenNumType::I32 => IntWidth::I32,
|
||||
CodeGenNumType::I64 => IntWidth::I64,
|
||||
CodeGenNumType::I128 => todo!("{:?} for I128", self.lowlevel),
|
||||
_ => internal_error!("Invalid return type for round: {:?}", ret_type),
|
||||
};
|
||||
|
||||
match arg_type {
|
||||
F32 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F32[width]),
|
||||
F64 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F64[width]),
|
||||
_ => internal_error!("Invalid argument type for round: {:?}", arg_type),
|
||||
}
|
||||
}
|
||||
NumCeiling | NumFloor => {
|
||||
self.load_args(backend);
|
||||
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
|
||||
let ret_type = CodeGenNumType::from(self.ret_layout);
|
||||
match (arg_type, self.lowlevel) {
|
||||
(F32, NumCeiling) => {
|
||||
backend.code_builder.f32_ceil();
|
||||
backend.code_builder.i32_trunc_s_f32()
|
||||
}
|
||||
I64 => {
|
||||
(F64, NumCeiling) => {
|
||||
backend.code_builder.f64_ceil();
|
||||
backend.code_builder.i64_trunc_s_f64()
|
||||
}
|
||||
(F32, NumFloor) => {
|
||||
backend.code_builder.f32_floor();
|
||||
}
|
||||
(F64, NumFloor) => {
|
||||
backend.code_builder.f64_floor();
|
||||
}
|
||||
_ => internal_error!("Invalid argument type for ceiling: {:?}", arg_type),
|
||||
}
|
||||
match (ret_type, arg_type) {
|
||||
// TODO: unsigned truncation
|
||||
(I32, F32) => backend.code_builder.i32_trunc_s_f32(),
|
||||
(I32, F64) => backend.code_builder.i32_trunc_s_f64(),
|
||||
(I64, F32) => backend.code_builder.i64_trunc_s_f32(),
|
||||
(I64, F64) => backend.code_builder.i64_trunc_s_f64(),
|
||||
(I128, _) => todo!("{:?} for I128", self.lowlevel),
|
||||
_ => panic_ret_type(),
|
||||
}
|
||||
}
|
||||
NumPowInt => todo!("{:?}", self.lowlevel),
|
||||
NumFloor => {
|
||||
self.load_args(backend);
|
||||
match CodeGenNumType::from(self.ret_layout) {
|
||||
I32 => {
|
||||
backend.code_builder.f32_floor();
|
||||
backend.code_builder.i32_trunc_s_f32()
|
||||
}
|
||||
I64 => {
|
||||
backend.code_builder.f64_floor();
|
||||
backend.code_builder.i64_trunc_s_f64()
|
||||
}
|
||||
_ => panic_ret_type(),
|
||||
}
|
||||
}
|
||||
NumIsFinite => num_is_finite(backend, self.arguments[0]),
|
||||
|
||||
NumAtan => match self.ret_layout {
|
||||
|
|
|
@ -4,6 +4,3 @@ version = "0.1.0"
|
|||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
arrayvec = "0.7.2"
|
||||
|
|
|
@ -38,7 +38,7 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) {
|
|||
Default::default(),
|
||||
target_info,
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::Multi,
|
||||
Threading::AllAvailable,
|
||||
);
|
||||
|
||||
let module = res_module.unwrap();
|
||||
|
|
|
@ -22,15 +22,12 @@ roc_mono = { path = "../mono" }
|
|||
roc_target = { path = "../roc_target" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
parking_lot = "0.12"
|
||||
crossbeam = "0.8.1"
|
||||
num_cpus = "1.13.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.2.0"
|
||||
pretty_assertions = "1.0.0"
|
||||
maplit = "1.0.2"
|
||||
indoc = "1.0.3"
|
||||
|
|
|
@ -747,6 +747,7 @@ impl<'a> State<'a> {
|
|||
ident_ids_by_module: SharedIdentIdsByModule,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
number_of_workers: usize,
|
||||
) -> Self {
|
||||
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
|
||||
|
||||
|
@ -770,7 +771,7 @@ impl<'a> State<'a> {
|
|||
declarations_by_id: MutMap::default(),
|
||||
exposed_symbols_by_module: MutMap::default(),
|
||||
timings: MutMap::default(),
|
||||
layout_caches: std::vec::Vec::with_capacity(num_cpus::get()),
|
||||
layout_caches: std::vec::Vec::with_capacity(number_of_workers),
|
||||
cached_subs: Arc::new(Mutex::new(cached_subs)),
|
||||
render,
|
||||
}
|
||||
|
@ -1099,7 +1100,8 @@ pub enum LoadResult<'a> {
|
|||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Threading {
|
||||
Single,
|
||||
Multi,
|
||||
AllAvailable,
|
||||
AtMost(usize),
|
||||
}
|
||||
|
||||
/// The loading process works like this, starting from the given filename (e.g. "main.roc"):
|
||||
|
@ -1157,10 +1159,32 @@ pub fn load<'a>(
|
|||
render: RenderTarget,
|
||||
threading: Threading,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
// When compiling to wasm, we cannot spawn extra threads
|
||||
// so we have a single-threaded implementation
|
||||
if threading == Threading::Single || cfg!(target_family = "wasm") {
|
||||
load_single_threaded(
|
||||
enum Threads {
|
||||
Single,
|
||||
Many(usize),
|
||||
}
|
||||
|
||||
let threads = {
|
||||
if cfg!(target_family = "wasm") {
|
||||
// When compiling to wasm, we cannot spawn extra threads
|
||||
// so we have a single-threaded implementation
|
||||
Threads::Single
|
||||
} else {
|
||||
match std::thread::available_parallelism().map(|v| v.get()) {
|
||||
Err(_) => Threads::Single,
|
||||
Ok(0) => unreachable!("NonZeroUsize"),
|
||||
Ok(1) => Threads::Single,
|
||||
Ok(reported) => match threading {
|
||||
Threading::Single => Threads::Single,
|
||||
Threading::AllAvailable => Threads::Many(reported),
|
||||
Threading::AtMost(at_most) => Threads::Many(Ord::min(reported, at_most)),
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
match threads {
|
||||
Threads::Single => load_single_threaded(
|
||||
arena,
|
||||
load_start,
|
||||
src_dir,
|
||||
|
@ -1169,9 +1193,8 @@ pub fn load<'a>(
|
|||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
)
|
||||
} else {
|
||||
load_multi_threaded(
|
||||
),
|
||||
Threads::Many(threads) => load_multi_threaded(
|
||||
arena,
|
||||
load_start,
|
||||
src_dir,
|
||||
|
@ -1180,7 +1203,8 @@ pub fn load<'a>(
|
|||
target_info,
|
||||
cached_subs,
|
||||
render,
|
||||
)
|
||||
threads,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1210,6 +1234,7 @@ pub fn load_single_threaded<'a>(
|
|||
.send(root_msg)
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
|
||||
let number_of_workers = 1;
|
||||
let mut state = State::new(
|
||||
root_id,
|
||||
target_info,
|
||||
|
@ -1219,6 +1244,7 @@ pub fn load_single_threaded<'a>(
|
|||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
render,
|
||||
number_of_workers,
|
||||
);
|
||||
|
||||
// We'll add tasks to this, and then worker threads will take tasks from it.
|
||||
|
@ -1390,6 +1416,7 @@ fn load_multi_threaded<'a>(
|
|||
target_info: TargetInfo,
|
||||
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
|
||||
render: RenderTarget,
|
||||
available_threads: usize,
|
||||
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
|
||||
let LoadStart {
|
||||
arc_modules,
|
||||
|
@ -1399,6 +1426,28 @@ fn load_multi_threaded<'a>(
|
|||
..
|
||||
} = load_start;
|
||||
|
||||
let (msg_tx, msg_rx) = bounded(1024);
|
||||
msg_tx
|
||||
.send(root_msg)
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
|
||||
// Reserve one CPU for the main thread, and let all the others be eligible
|
||||
// to spawn workers.
|
||||
let available_workers = available_threads - 1;
|
||||
|
||||
let num_workers = match env::var("ROC_NUM_WORKERS") {
|
||||
Ok(env_str) => env_str
|
||||
.parse::<usize>()
|
||||
.unwrap_or(available_workers)
|
||||
.min(available_workers),
|
||||
Err(_) => available_workers,
|
||||
};
|
||||
|
||||
assert!(
|
||||
num_workers >= 1,
|
||||
"`load_multi_threaded` needs at least one worker"
|
||||
);
|
||||
|
||||
let mut state = State::new(
|
||||
root_id,
|
||||
target_info,
|
||||
|
@ -1408,28 +1457,9 @@ fn load_multi_threaded<'a>(
|
|||
ident_ids_by_module,
|
||||
cached_subs,
|
||||
render,
|
||||
num_workers,
|
||||
);
|
||||
|
||||
let (msg_tx, msg_rx) = bounded(1024);
|
||||
msg_tx
|
||||
.send(root_msg)
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
|
||||
// Reserve one CPU for the main thread, and let all the others be eligible
|
||||
// to spawn workers. We use .max(2) to enforce that we always
|
||||
// end up with at least 1 worker - since (.max(2) - 1) will
|
||||
// always return a number that's at least 1. Using
|
||||
// .max(2) on the initial number of CPUs instead of
|
||||
// doing .max(1) on the entire expression guards against
|
||||
// num_cpus returning 0, while also avoiding wrapping
|
||||
// unsigned subtraction overflow.
|
||||
let default_num_workers = num_cpus::get().max(2) - 1;
|
||||
|
||||
let num_workers = match env::var("ROC_NUM_WORKERS") {
|
||||
Ok(env_str) => env_str.parse::<usize>().unwrap_or(default_num_workers),
|
||||
Err(_) => default_num_workers,
|
||||
};
|
||||
|
||||
// an arena for every worker, stored in an arena-allocated bumpalo vec to make the lifetimes work
|
||||
let arenas = std::iter::repeat_with(Bump::new).take(num_workers);
|
||||
let worker_arenas = arena.alloc(bumpalo::collections::Vec::from_iter_in(arenas, arena));
|
||||
|
|
|
@ -14,4 +14,3 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
|
|||
lazy_static = "1.4.0"
|
||||
static_assertions = "1.1.0"
|
||||
snafu = { version = "0.6.10", features = ["backtraces"] }
|
||||
arrayvec = "0.7.2"
|
||||
|
|
|
@ -21,7 +21,6 @@ roc_target = { path = "../roc_target" }
|
|||
roc_error_macros = {path="../../error_macros"}
|
||||
roc_debug_flags = {path="../debug_flags"}
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
|
||||
ven_graph = { path = "../../vendor/pathfinding" }
|
||||
|
|
|
@ -1210,15 +1210,8 @@ pub fn optimize_when<'a>(
|
|||
// bind the fields referenced in the pattern. For guards this happens separately, so
|
||||
// the pattern variables are defined when evaluating the guard.
|
||||
if !has_guard {
|
||||
branch = crate::ir::store_pattern(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
&pattern,
|
||||
cond_layout,
|
||||
cond_symbol,
|
||||
branch,
|
||||
);
|
||||
branch =
|
||||
crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch);
|
||||
}
|
||||
|
||||
let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch);
|
||||
|
@ -1730,15 +1723,7 @@ fn decide_to_branching<'a>(
|
|||
body: arena.alloc(decide),
|
||||
};
|
||||
|
||||
crate::ir::store_pattern(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
&pattern,
|
||||
cond_layout,
|
||||
cond_symbol,
|
||||
join,
|
||||
)
|
||||
crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, join)
|
||||
}
|
||||
Chain {
|
||||
test_chain,
|
||||
|
|
|
@ -10,10 +10,12 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
|||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::expr::{AnnotatedMark, ClosureData, IntValue};
|
||||
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
|
||||
use roc_collections::VecMap;
|
||||
use roc_debug_flags::{
|
||||
dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE,
|
||||
ROC_PRINT_IR_AFTER_SPECIALIZATION,
|
||||
};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId};
|
||||
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
|
||||
use roc_module::low_level::LowLevel;
|
||||
|
@ -743,6 +745,157 @@ impl<'a> Specialized<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Uniquely determines the specialization of a polymorphic (non-proc) value symbol.
|
||||
/// Two specializations are equivalent if their [`SpecializationMark`]s are equal.
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
|
||||
struct SpecializationMark<'a> {
|
||||
/// The layout of the symbol itself.
|
||||
layout: Layout<'a>,
|
||||
|
||||
/// If this symbol is a closure def, we must also keep track of what function it specializes,
|
||||
/// because the [`layout`] field will only keep track of its closure and lambda set - which can
|
||||
/// be the same for two different function specializations. For example,
|
||||
///
|
||||
/// id = if True then \x -> x else \y -> y
|
||||
/// { a: id "", b: id 1u8 }
|
||||
///
|
||||
/// The lambda set and captures of `id` is the same in both usages inside the record, but the
|
||||
/// reified specializations of `\x -> x` and `\y -> y` must be for Str and U8.
|
||||
///
|
||||
/// Note that this field is not relevant for anything that is not a function.
|
||||
function_mark: Option<RawFunctionLayout<'a>>,
|
||||
}
|
||||
|
||||
/// When walking a function body, we may encounter specialized usages of polymorphic symbols. For
|
||||
/// example
|
||||
///
|
||||
/// myTag = A
|
||||
/// use1 : [A, B]
|
||||
/// use1 = myTag
|
||||
/// use2 : [A, B, C]
|
||||
/// use2 = myTag
|
||||
///
|
||||
/// We keep track of the specializations of `myTag` and create fresh symbols when there is more
|
||||
/// than one, so that a unique def can be created for each.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
struct SymbolSpecializations<'a>(
|
||||
// THEORY:
|
||||
// 1. the number of symbols in a def is very small
|
||||
// 2. the number of specializations of a symbol in a def is even smaller (almost always only one)
|
||||
// So, a linear VecMap is preferrable. Use a two-layered one to make (1) extraction of defs easy
|
||||
// and (2) reads of a certain symbol be determined by its first occurrence, not its last.
|
||||
VecMap<Symbol, VecMap<SpecializationMark<'a>, (Variable, Symbol)>>,
|
||||
);
|
||||
|
||||
impl<'a> SymbolSpecializations<'a> {
|
||||
/// Gets a specialization for a symbol, or creates a new one.
|
||||
#[inline(always)]
|
||||
fn get_or_insert(
|
||||
&mut self,
|
||||
env: &mut Env<'a, '_>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
symbol: Symbol,
|
||||
specialization_var: Variable,
|
||||
) -> Symbol {
|
||||
let arena = env.arena;
|
||||
let subs: &Subs = env.subs;
|
||||
|
||||
let layout = match layout_cache.from_var(arena, specialization_var, subs) {
|
||||
Ok(layout) => layout,
|
||||
// This can happen when the def symbol has a type error. In such cases just use the
|
||||
// def symbol, which is erroring.
|
||||
Err(_) => return symbol,
|
||||
};
|
||||
|
||||
let is_closure = matches!(
|
||||
subs.get_content_without_compacting(specialization_var),
|
||||
Content::Structure(FlatType::Func(..))
|
||||
);
|
||||
let function_mark = if is_closure {
|
||||
let fn_layout = match layout_cache.raw_from_var(arena, specialization_var, subs) {
|
||||
Ok(layout) => layout,
|
||||
// This can happen when the def symbol has a type error. In such cases just use the
|
||||
// def symbol, which is erroring.
|
||||
Err(_) => return symbol,
|
||||
};
|
||||
Some(fn_layout)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let specialization_mark = SpecializationMark {
|
||||
layout,
|
||||
function_mark,
|
||||
};
|
||||
|
||||
let symbol_specializations = self.0.get_or_insert(symbol, Default::default);
|
||||
|
||||
// For the first specialization, always reuse the current symbol. The vast majority of defs
|
||||
// only have one instance type, so this preserves readability of the IR.
|
||||
// TODO: turn me off and see what breaks.
|
||||
let needs_fresh_symbol = !symbol_specializations.is_empty();
|
||||
|
||||
let mut make_specialized_symbol = || {
|
||||
if needs_fresh_symbol {
|
||||
env.unique_symbol()
|
||||
} else {
|
||||
symbol
|
||||
}
|
||||
};
|
||||
|
||||
let (_var, specialized_symbol) = symbol_specializations
|
||||
.get_or_insert(specialization_mark, || {
|
||||
(specialization_var, make_specialized_symbol())
|
||||
});
|
||||
|
||||
*specialized_symbol
|
||||
}
|
||||
|
||||
/// Inserts a known specialization for a symbol. Returns the overwritten specialization, if any.
|
||||
pub fn get_or_insert_known(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
mark: SpecializationMark<'a>,
|
||||
specialization_var: Variable,
|
||||
specialization_symbol: Symbol,
|
||||
) -> Option<(Variable, Symbol)> {
|
||||
self.0
|
||||
.get_or_insert(symbol, Default::default)
|
||||
.insert(mark, (specialization_var, specialization_symbol))
|
||||
}
|
||||
|
||||
/// Removes all specializations for a symbol, returning the type and symbol of each specialization.
|
||||
pub fn remove(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
) -> impl ExactSizeIterator<Item = (SpecializationMark<'a>, (Variable, Symbol))> {
|
||||
self.0
|
||||
.remove(&symbol)
|
||||
.map(|(_, specializations)| specializations)
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
}
|
||||
|
||||
/// Expects and removes at most a single specialization symbol for the given requested symbol.
|
||||
/// A symbol may have no specializations if it is never referenced in a body, so it is possible
|
||||
/// for this to return None.
|
||||
pub fn remove_single(&mut self, symbol: Symbol) -> Option<Symbol> {
|
||||
let mut specializations = self.remove(symbol);
|
||||
|
||||
debug_assert!(
|
||||
specializations.len() < 2,
|
||||
"Symbol {:?} has multiple specializations",
|
||||
symbol
|
||||
);
|
||||
|
||||
specializations.next().map(|(_, (_, symbol))| symbol)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Procs<'a> {
|
||||
pub partial_procs: PartialProcs<'a>,
|
||||
|
@ -753,7 +906,7 @@ pub struct Procs<'a> {
|
|||
specialized: Specialized<'a>,
|
||||
pub runtime_errors: BumpMap<Symbol, &'a str>,
|
||||
pub externals_we_need: BumpMap<ModuleId, ExternalSpecializations>,
|
||||
pub needed_symbol_specializations: BumpMap<(Symbol, Layout<'a>), (Variable, Symbol)>,
|
||||
symbol_specializations: SymbolSpecializations<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Procs<'a> {
|
||||
|
@ -767,38 +920,9 @@ impl<'a> Procs<'a> {
|
|||
specialized: Specialized::default(),
|
||||
runtime_errors: BumpMap::new_in(arena),
|
||||
externals_we_need: BumpMap::new_in(arena),
|
||||
needed_symbol_specializations: BumpMap::new_in(arena),
|
||||
symbol_specializations: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Expects and removes a single specialization symbol for the given requested symbol.
|
||||
/// In debug builds, we assert that the layout of the specialization is the layout expected by
|
||||
/// the requested symbol.
|
||||
fn remove_single_symbol_specialization(
|
||||
&mut self,
|
||||
symbol: Symbol,
|
||||
layout: Layout,
|
||||
) -> Option<Symbol> {
|
||||
let mut specialized_symbols = self
|
||||
.needed_symbol_specializations
|
||||
.drain_filter(|(sym, _), _| sym == &symbol);
|
||||
|
||||
let specialization_symbol = specialized_symbols
|
||||
.next()
|
||||
.map(|((_, specialized_layout), (_, specialized_symbol))| {
|
||||
debug_assert_eq!(specialized_layout, layout, "Requested the single specialization of {:?}, but the specialization layout ({:?}) doesn't match the expected layout ({:?})", symbol, specialized_layout, layout);
|
||||
specialized_symbol
|
||||
});
|
||||
|
||||
debug_assert_eq!(
|
||||
specialized_symbols.count(),
|
||||
0,
|
||||
"Symbol {:?} has multiple specializations",
|
||||
symbol
|
||||
);
|
||||
|
||||
specialization_symbol
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -2166,9 +2290,9 @@ pub fn specialize_all<'a>(
|
|||
specialize_host_specializations(env, &mut procs, layout_cache, specializations_for_host);
|
||||
|
||||
debug_assert!(
|
||||
procs.needed_symbol_specializations.is_empty(),
|
||||
procs.symbol_specializations.is_empty(),
|
||||
"{:?}",
|
||||
&procs.needed_symbol_specializations
|
||||
&procs.symbol_specializations
|
||||
);
|
||||
|
||||
procs
|
||||
|
@ -2503,11 +2627,10 @@ fn specialize_external<'a>(
|
|||
// An argument from the closure list may have taken on a specialized symbol
|
||||
// name during the evaluation of the def body. If this is the case, load the
|
||||
// specialized name rather than the original captured name!
|
||||
let mut get_specialized_name = |symbol, layout| {
|
||||
let mut get_specialized_name = |symbol| {
|
||||
procs
|
||||
.needed_symbol_specializations
|
||||
.remove(&(symbol, layout))
|
||||
.map(|(_, specialized)| specialized)
|
||||
.symbol_specializations
|
||||
.remove_single(symbol)
|
||||
.unwrap_or(symbol)
|
||||
};
|
||||
|
||||
|
@ -2545,7 +2668,7 @@ fn specialize_external<'a>(
|
|||
union_layout,
|
||||
};
|
||||
|
||||
let symbol = get_specialized_name(**symbol, **layout);
|
||||
let symbol = get_specialized_name(**symbol);
|
||||
|
||||
specialized_body = Stmt::Let(
|
||||
symbol,
|
||||
|
@ -2588,7 +2711,7 @@ fn specialize_external<'a>(
|
|||
structure: Symbol::ARG_CLOSURE,
|
||||
};
|
||||
|
||||
let symbol = get_specialized_name(**symbol, **layout);
|
||||
let symbol = get_specialized_name(**symbol);
|
||||
|
||||
specialized_body = Stmt::Let(
|
||||
symbol,
|
||||
|
@ -2633,11 +2756,10 @@ fn specialize_external<'a>(
|
|||
let proc_args: Vec<_> = proc_args
|
||||
.iter()
|
||||
.map(|&(layout, symbol)| {
|
||||
// Grab the specialization symbol, if it exists.
|
||||
let symbol = procs
|
||||
.needed_symbol_specializations
|
||||
// We can remove the specialization since this is the definition site.
|
||||
.remove(&(symbol, layout))
|
||||
.map(|(_, specialized_symbol)| specialized_symbol)
|
||||
.symbol_specializations
|
||||
.remove_single(symbol)
|
||||
.unwrap_or(symbol);
|
||||
|
||||
(layout, symbol)
|
||||
|
@ -3351,18 +3473,7 @@ pub fn with_hole<'a>(
|
|||
);
|
||||
|
||||
let outer_symbol = env.unique_symbol();
|
||||
let pattern_layout = layout_cache
|
||||
.from_var(env.arena, def.expr_var, env.subs)
|
||||
.expect("Pattern has no layout");
|
||||
stmt = store_pattern(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
&mono_pattern,
|
||||
pattern_layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
);
|
||||
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
|
||||
|
||||
// convert the def body, store in outer_symbol
|
||||
with_hole(
|
||||
|
@ -3405,7 +3516,9 @@ pub fn with_hole<'a>(
|
|||
can_reuse_symbol(env, procs, &roc_can::expr::Expr::Var(symbol))
|
||||
{
|
||||
let real_symbol =
|
||||
reuse_symbol_or_specialize(env, procs, layout_cache, symbol, variable);
|
||||
procs
|
||||
.symbol_specializations
|
||||
.get_or_insert(env, layout_cache, symbol, variable);
|
||||
symbol = real_symbol;
|
||||
}
|
||||
|
||||
|
@ -3484,8 +3597,12 @@ pub fn with_hole<'a>(
|
|||
match can_reuse_symbol(env, procs, &loc_arg_expr.value) {
|
||||
// Opaques decay to their argument.
|
||||
ReuseSymbol::Value(symbol) => {
|
||||
let real_name =
|
||||
reuse_symbol_or_specialize(env, procs, layout_cache, symbol, arg_var);
|
||||
let real_name = procs.symbol_specializations.get_or_insert(
|
||||
env,
|
||||
layout_cache,
|
||||
symbol,
|
||||
arg_var,
|
||||
);
|
||||
let mut result = hole.clone();
|
||||
substitute_in_exprs(arena, &mut result, assigned, real_name);
|
||||
result
|
||||
|
@ -3538,9 +3655,8 @@ pub fn with_hole<'a>(
|
|||
can_fields.push(Field::Function(symbol, variable));
|
||||
}
|
||||
Value(symbol) => {
|
||||
let reusable = reuse_symbol_or_specialize(
|
||||
let reusable = procs.symbol_specializations.get_or_insert(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
symbol,
|
||||
field.var,
|
||||
|
@ -4353,25 +4469,38 @@ pub fn with_hole<'a>(
|
|||
}
|
||||
}
|
||||
}
|
||||
Value(function_symbol) => match full_layout {
|
||||
RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => {
|
||||
let closure_data_symbol = function_symbol;
|
||||
Value(function_symbol) => {
|
||||
let function_symbol = procs.symbol_specializations.get_or_insert(
|
||||
env,
|
||||
layout_cache,
|
||||
function_symbol,
|
||||
fn_var,
|
||||
);
|
||||
|
||||
result = match_on_lambda_set(
|
||||
env,
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
arg_symbols,
|
||||
match full_layout {
|
||||
RawFunctionLayout::Function(
|
||||
arg_layouts,
|
||||
lambda_set,
|
||||
ret_layout,
|
||||
assigned,
|
||||
hole,
|
||||
);
|
||||
) => {
|
||||
let closure_data_symbol = function_symbol;
|
||||
|
||||
result = match_on_lambda_set(
|
||||
env,
|
||||
lambda_set,
|
||||
closure_data_symbol,
|
||||
arg_symbols,
|
||||
arg_layouts,
|
||||
ret_layout,
|
||||
assigned,
|
||||
hole,
|
||||
);
|
||||
}
|
||||
RawFunctionLayout::ZeroArgumentThunk(_) => {
|
||||
unreachable!("calling a non-closure layout")
|
||||
}
|
||||
}
|
||||
RawFunctionLayout::ZeroArgumentThunk(_) => {
|
||||
unreachable!("calling a non-closure layout")
|
||||
}
|
||||
},
|
||||
}
|
||||
UnspecializedExpr(symbol) => {
|
||||
match procs.ability_member_aliases.get(symbol).unwrap() {
|
||||
&AbilityMember(member) => {
|
||||
|
@ -5521,7 +5650,6 @@ pub fn from_can<'a>(
|
|||
}
|
||||
LetNonRec(def, cont, outer_annotation) => {
|
||||
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
|
||||
// dbg!(symbol, &def.loc_expr.value);
|
||||
match def.loc_expr.value {
|
||||
roc_can::expr::Expr::Closure(closure_data) => {
|
||||
register_capturing_closure(env, procs, layout_cache, *symbol, closure_data);
|
||||
|
@ -5653,12 +5781,14 @@ pub fn from_can<'a>(
|
|||
_ => {
|
||||
let rest = from_can(env, variable, cont.value, procs, layout_cache);
|
||||
|
||||
let needs_def_specializations = procs
|
||||
.needed_symbol_specializations
|
||||
.keys()
|
||||
.any(|(s, _)| s == symbol);
|
||||
// Remove all the requested symbol specializations now, since this is the
|
||||
// def site and hence we won't need them any higher up.
|
||||
let mut needed_specializations =
|
||||
procs.symbol_specializations.remove(*symbol);
|
||||
|
||||
if !needs_def_specializations {
|
||||
if needed_specializations.len() == 0 {
|
||||
// We don't need any specializations, that means this symbol is never
|
||||
// referenced.
|
||||
return with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
|
@ -5674,16 +5804,9 @@ pub fn from_can<'a>(
|
|||
|
||||
let mut stmt = rest;
|
||||
|
||||
// Remove all the requested symbol specializations now, since this is the
|
||||
// def site and hence we won't need them any higher up.
|
||||
let mut needed_specializations = procs
|
||||
.needed_symbol_specializations
|
||||
.drain_filter(|(s, _), _| s == symbol)
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
|
||||
if needed_specializations.len() == 1 {
|
||||
let ((_, _wanted_layout), (var, specialized_symbol)) =
|
||||
needed_specializations.pop().unwrap();
|
||||
let (_specialization_mark, (var, specialized_symbol)) =
|
||||
needed_specializations.next().unwrap();
|
||||
|
||||
// Unify the expr_var with the requested specialization once.
|
||||
let _res =
|
||||
|
@ -5700,7 +5823,7 @@ pub fn from_can<'a>(
|
|||
);
|
||||
} else {
|
||||
// Need to eat the cost and create a specialized version of the body for each specialization.
|
||||
for ((_original_symbol, _wanted_layout), (var, specialized_symbol)) in
|
||||
for (_specialization_mark, (var, specialized_symbol)) in
|
||||
needed_specializations
|
||||
{
|
||||
use crate::copy::deep_copy_type_vars_into_expr;
|
||||
|
@ -5744,89 +5867,50 @@ pub fn from_can<'a>(
|
|||
Err(_) => todo!(),
|
||||
};
|
||||
|
||||
if let Pattern::Identifier(symbol) = mono_pattern {
|
||||
let mut hole =
|
||||
env.arena
|
||||
.alloc(from_can(env, variable, cont.value, procs, layout_cache));
|
||||
if let Pattern::Identifier(_symbol) = mono_pattern {
|
||||
internal_error!("Identifier patterns should be handled in a higher code pass!")
|
||||
}
|
||||
|
||||
for (symbol, variable, expr) in assignments {
|
||||
let stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole);
|
||||
// convert the continuation
|
||||
let mut stmt = from_can(env, variable, cont.value, procs, layout_cache);
|
||||
|
||||
hole = env.arena.alloc(stmt);
|
||||
}
|
||||
// layer on any default record fields
|
||||
for (symbol, variable, expr) in assignments {
|
||||
let specialization_symbol = procs
|
||||
.symbol_specializations
|
||||
.remove_single(symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(symbol);
|
||||
|
||||
let hole = env.arena.alloc(stmt);
|
||||
stmt = with_hole(
|
||||
env,
|
||||
expr,
|
||||
variable,
|
||||
procs,
|
||||
layout_cache,
|
||||
specialization_symbol,
|
||||
hole,
|
||||
);
|
||||
}
|
||||
|
||||
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
|
||||
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
|
||||
} else {
|
||||
let outer_symbol = env.unique_symbol();
|
||||
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
|
||||
|
||||
// convert the def body, store in outer_symbol
|
||||
with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
symbol,
|
||||
hole,
|
||||
outer_symbol,
|
||||
env.arena.alloc(stmt),
|
||||
)
|
||||
} else {
|
||||
// convert the continuation
|
||||
let mut stmt = from_can(env, variable, cont.value, procs, layout_cache);
|
||||
|
||||
// layer on any default record fields
|
||||
for (symbol, variable, expr) in assignments {
|
||||
let layout = layout_cache
|
||||
.from_var(env.arena, variable, env.subs)
|
||||
.expect("Default field has no layout");
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(symbol, layout)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(symbol);
|
||||
|
||||
let hole = env.arena.alloc(stmt);
|
||||
stmt = with_hole(
|
||||
env,
|
||||
expr,
|
||||
variable,
|
||||
procs,
|
||||
layout_cache,
|
||||
specialization_symbol,
|
||||
hole,
|
||||
);
|
||||
}
|
||||
|
||||
let pattern_layout = layout_cache
|
||||
.from_var(env.arena, def.expr_var, env.subs)
|
||||
.expect("Pattern has no layout");
|
||||
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
|
||||
store_pattern(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
&mono_pattern,
|
||||
pattern_layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
)
|
||||
} else {
|
||||
let outer_symbol = env.unique_symbol();
|
||||
stmt = store_pattern(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
&mono_pattern,
|
||||
pattern_layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
);
|
||||
|
||||
// convert the def body, store in outer_symbol
|
||||
with_hole(
|
||||
env,
|
||||
def.loc_expr.value,
|
||||
def.expr_var,
|
||||
procs,
|
||||
layout_cache,
|
||||
outer_symbol,
|
||||
env.arena.alloc(stmt),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6380,19 +6464,10 @@ pub fn store_pattern<'a>(
|
|||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
can_pat: &Pattern<'a>,
|
||||
pattern_layout: Layout,
|
||||
outer_symbol: Symbol,
|
||||
stmt: Stmt<'a>,
|
||||
) -> Stmt<'a> {
|
||||
match store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
can_pat,
|
||||
pattern_layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
) {
|
||||
match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) {
|
||||
StorePattern::Productive(new) => new,
|
||||
StorePattern::NotProductive(new) => new,
|
||||
}
|
||||
|
@ -6412,7 +6487,6 @@ fn store_pattern_help<'a>(
|
|||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
can_pat: &Pattern<'a>,
|
||||
pattern_layout: Layout,
|
||||
outer_symbol: Symbol,
|
||||
mut stmt: Stmt<'a>,
|
||||
) -> StorePattern<'a> {
|
||||
|
@ -6423,7 +6497,8 @@ fn store_pattern_help<'a>(
|
|||
// An identifier in a pattern can define at most one specialization!
|
||||
// Remove any requested specializations for this name now, since this is the definition site.
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, pattern_layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(*symbol);
|
||||
|
@ -6444,16 +6519,8 @@ fn store_pattern_help<'a>(
|
|||
return StorePattern::NotProductive(stmt);
|
||||
}
|
||||
NewtypeDestructure { arguments, .. } => match arguments.as_slice() {
|
||||
[(pattern, layout)] => {
|
||||
return store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
pattern,
|
||||
*layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
);
|
||||
[(pattern, _layout)] => {
|
||||
return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
|
||||
}
|
||||
_ => {
|
||||
let mut fields = Vec::with_capacity_in(arguments.len(), env.arena);
|
||||
|
@ -6490,16 +6557,8 @@ fn store_pattern_help<'a>(
|
|||
);
|
||||
}
|
||||
OpaqueUnwrap { argument, .. } => {
|
||||
let (pattern, layout) = &**argument;
|
||||
return store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
pattern,
|
||||
*layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
);
|
||||
let (pattern, _layout) = &**argument;
|
||||
return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
|
||||
}
|
||||
|
||||
RecordDestructure(destructs, [_single_field]) => {
|
||||
|
@ -6507,7 +6566,8 @@ fn store_pattern_help<'a>(
|
|||
match &destruct.typ {
|
||||
DestructType::Required(symbol) => {
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, destruct.layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(*symbol);
|
||||
|
@ -6525,7 +6585,6 @@ fn store_pattern_help<'a>(
|
|||
procs,
|
||||
layout_cache,
|
||||
guard_pattern,
|
||||
destruct.layout,
|
||||
outer_symbol,
|
||||
stmt,
|
||||
);
|
||||
|
@ -6598,7 +6657,8 @@ fn store_tag_pattern<'a>(
|
|||
Identifier(symbol) => {
|
||||
// Pattern can define only one specialization
|
||||
let symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, arg_layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
.unwrap_or(*symbol);
|
||||
|
||||
// store immediately in the given symbol
|
||||
|
@ -6619,15 +6679,7 @@ fn store_tag_pattern<'a>(
|
|||
let symbol = env.unique_symbol();
|
||||
|
||||
// first recurse, continuing to unpack symbol
|
||||
match store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
argument,
|
||||
arg_layout,
|
||||
symbol,
|
||||
stmt,
|
||||
) {
|
||||
match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
|
||||
StorePattern::Productive(new) => {
|
||||
is_productive = true;
|
||||
stmt = new;
|
||||
|
@ -6687,7 +6739,8 @@ fn store_newtype_pattern<'a>(
|
|||
Identifier(symbol) => {
|
||||
// store immediately in the given symbol, removing it specialization if it had any
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, arg_layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(*symbol);
|
||||
|
@ -6714,15 +6767,7 @@ fn store_newtype_pattern<'a>(
|
|||
let symbol = env.unique_symbol();
|
||||
|
||||
// first recurse, continuing to unpack symbol
|
||||
match store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
argument,
|
||||
arg_layout,
|
||||
symbol,
|
||||
stmt,
|
||||
) {
|
||||
match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
|
||||
StorePattern::Productive(new) => {
|
||||
is_productive = true;
|
||||
stmt = new;
|
||||
|
@ -6770,7 +6815,8 @@ fn store_record_destruct<'a>(
|
|||
// A destructure can define at most one specialization!
|
||||
// Remove any requested specializations for this name now, since this is the definition site.
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, destruct.layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(*symbol);
|
||||
|
@ -6785,7 +6831,8 @@ fn store_record_destruct<'a>(
|
|||
DestructType::Guard(guard_pattern) => match &guard_pattern {
|
||||
Identifier(symbol) => {
|
||||
let specialization_symbol = procs
|
||||
.remove_single_symbol_specialization(*symbol, destruct.layout)
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(*symbol);
|
||||
|
@ -6823,15 +6870,7 @@ fn store_record_destruct<'a>(
|
|||
_ => {
|
||||
let symbol = env.unique_symbol();
|
||||
|
||||
match store_pattern_help(
|
||||
env,
|
||||
procs,
|
||||
layout_cache,
|
||||
guard_pattern,
|
||||
destruct.layout,
|
||||
symbol,
|
||||
stmt,
|
||||
) {
|
||||
match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) {
|
||||
StorePattern::Productive(new) => {
|
||||
stmt = new;
|
||||
stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt));
|
||||
|
@ -6894,45 +6933,6 @@ fn can_reuse_symbol<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
/// Reuses the specialized symbol for a given symbol and instance type. If no specialization symbol
|
||||
/// yet exists, one is created.
|
||||
fn reuse_symbol_or_specialize<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
layout_cache: &mut LayoutCache<'a>,
|
||||
symbol: Symbol,
|
||||
var: Variable,
|
||||
) -> Symbol {
|
||||
let wanted_layout = match layout_cache.from_var(env.arena, var, env.subs) {
|
||||
Ok(layout) => layout,
|
||||
// This can happen when the def symbol has a type error. In such cases just use the
|
||||
// def symbol, which is erroring.
|
||||
Err(_) => return symbol,
|
||||
};
|
||||
|
||||
// For the first specialization, always reuse the current symbol. The vast majority of defs
|
||||
// only have one instance type, so this preserves readability of the IR.
|
||||
let needs_fresh_symbol = procs
|
||||
.needed_symbol_specializations
|
||||
.keys()
|
||||
.any(|(s, _)| *s == symbol);
|
||||
|
||||
let mut make_specialized_symbol = || {
|
||||
if needs_fresh_symbol {
|
||||
env.unique_symbol()
|
||||
} else {
|
||||
symbol
|
||||
}
|
||||
};
|
||||
|
||||
let (_, specialized_symbol) = procs
|
||||
.needed_symbol_specializations
|
||||
.entry((symbol, wanted_layout))
|
||||
.or_insert_with(|| (var, make_specialized_symbol()));
|
||||
|
||||
*specialized_symbol
|
||||
}
|
||||
|
||||
fn possible_reuse_symbol_or_specialize<'a>(
|
||||
env: &mut Env<'a, '_>,
|
||||
procs: &mut Procs<'a>,
|
||||
|
@ -6942,7 +6942,9 @@ fn possible_reuse_symbol_or_specialize<'a>(
|
|||
) -> Symbol {
|
||||
match can_reuse_symbol(env, procs, expr) {
|
||||
ReuseSymbol::Value(symbol) => {
|
||||
reuse_symbol_or_specialize(env, procs, layout_cache, symbol, var)
|
||||
procs
|
||||
.symbol_specializations
|
||||
.get_or_insert(env, layout_cache, symbol, var)
|
||||
}
|
||||
_ => env.unique_symbol(),
|
||||
}
|
||||
|
@ -6987,16 +6989,13 @@ where
|
|||
let result = build_rest(env, procs, layout_cache);
|
||||
|
||||
// The specializations we wanted of the symbol on the LHS of this alias.
|
||||
let needed_specializations_of_left = procs
|
||||
.needed_symbol_specializations
|
||||
.drain_filter(|(s, _), _| s == &left)
|
||||
.collect::<std::vec::Vec<_>>();
|
||||
let needed_specializations_of_left = procs.symbol_specializations.remove(left);
|
||||
|
||||
if procs.is_imported_module_thunk(right) {
|
||||
// if this is an imported symbol, then we must make sure it is
|
||||
// specialized, and wrap the original in a function pointer.
|
||||
let mut result = result;
|
||||
for (_, (variable, left)) in needed_specializations_of_left.into_iter() {
|
||||
for (_, (variable, left)) in needed_specializations_of_left {
|
||||
add_needed_external(procs, env, variable, right);
|
||||
|
||||
let res_layout = layout_cache.from_var(env.arena, variable, env.subs);
|
||||
|
@ -7016,14 +7015,17 @@ where
|
|||
// We need to lift all specializations of "left" to be specializations of "right".
|
||||
let mut scratchpad_update_specializations = std::vec::Vec::new();
|
||||
|
||||
let left_had_specialization_symbols = !needed_specializations_of_left.is_empty();
|
||||
let left_had_specialization_symbols = needed_specializations_of_left.len() > 0;
|
||||
|
||||
for ((_, layout), (specialized_var, specialized_sym)) in
|
||||
needed_specializations_of_left.into_iter()
|
||||
for (specialization_mark, (specialized_var, specialized_sym)) in
|
||||
needed_specializations_of_left
|
||||
{
|
||||
let old_specialized_sym = procs
|
||||
.needed_symbol_specializations
|
||||
.insert((right, layout), (specialized_var, specialized_sym));
|
||||
let old_specialized_sym = procs.symbol_specializations.get_or_insert_known(
|
||||
right,
|
||||
specialization_mark,
|
||||
specialized_var,
|
||||
specialized_sym,
|
||||
);
|
||||
|
||||
if let Some((_, old_specialized_sym)) = old_specialized_sym {
|
||||
scratchpad_update_specializations.push((old_specialized_sym, specialized_sym));
|
||||
|
|
|
@ -476,10 +476,13 @@ impl Pools {
|
|||
self.0.iter()
|
||||
}
|
||||
|
||||
pub fn split_last(&self) -> (&Vec<Variable>, &[Vec<Variable>]) {
|
||||
self.0
|
||||
.split_last()
|
||||
.unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools"))
|
||||
pub fn split_last(mut self) -> (Vec<Variable>, Vec<Vec<Variable>>) {
|
||||
let last = self
|
||||
.0
|
||||
.pop()
|
||||
.unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools"));
|
||||
|
||||
(last, self.0)
|
||||
}
|
||||
|
||||
pub fn extend_to(&mut self, n: usize) {
|
||||
|
@ -737,8 +740,7 @@ fn solve(
|
|||
|
||||
// pop pool
|
||||
generalize(subs, young_mark, visit_mark, next_rank, pools);
|
||||
|
||||
pools.get_mut(next_rank).clear();
|
||||
debug_assert!(pools.get(next_rank).is_empty());
|
||||
|
||||
// check that things went well
|
||||
dbg_do!(ROC_VERIFY_RIGID_LET_GENERALIZED, {
|
||||
|
@ -2426,7 +2428,7 @@ fn generalize(
|
|||
young_rank: Rank,
|
||||
pools: &mut Pools,
|
||||
) {
|
||||
let young_vars = pools.get(young_rank);
|
||||
let young_vars = std::mem::take(pools.get_mut(young_rank));
|
||||
let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars);
|
||||
|
||||
// Get the ranks right for each entry.
|
||||
|
@ -2437,12 +2439,12 @@ fn generalize(
|
|||
}
|
||||
}
|
||||
|
||||
let (last_pool, all_but_last_pool) = rank_table.split_last();
|
||||
let (mut last_pool, all_but_last_pool) = rank_table.split_last();
|
||||
|
||||
// For variables that have rank lowerer than young_rank, register them in
|
||||
// the appropriate old pool if they are not redundant.
|
||||
for vars in all_but_last_pool {
|
||||
for &var in vars {
|
||||
for var in vars {
|
||||
if !subs.redundant(var) {
|
||||
let rank = subs.get_rank(var);
|
||||
|
||||
|
@ -2453,7 +2455,7 @@ fn generalize(
|
|||
|
||||
// For variables with rank young_rank, if rank < young_rank: register in old pool,
|
||||
// otherwise generalize
|
||||
for &var in last_pool {
|
||||
for var in last_pool.drain(..) {
|
||||
if !subs.redundant(var) {
|
||||
let desc_rank = subs.get_rank(var);
|
||||
|
||||
|
@ -2464,32 +2466,38 @@ fn generalize(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// re-use the last_vector (which likely has a good capacity for future runs
|
||||
*pools.get_mut(young_rank) = last_pool;
|
||||
}
|
||||
|
||||
/// Sort the variables into buckets by rank.
|
||||
#[inline]
|
||||
fn pool_to_rank_table(
|
||||
subs: &mut Subs,
|
||||
young_mark: Mark,
|
||||
young_rank: Rank,
|
||||
young_vars: &[Variable],
|
||||
mut young_vars: Vec<Variable>,
|
||||
) -> Pools {
|
||||
let mut pools = Pools::new(young_rank.into_usize() + 1);
|
||||
|
||||
// the vast majority of young variables have young_rank
|
||||
// using `retain` here prevents many `pools.get_mut(young_rank)` lookups
|
||||
let mut young_vars = young_vars.to_vec();
|
||||
young_vars.retain(|var| {
|
||||
let rank = subs.get_rank_set_mark(*var, young_mark);
|
||||
let mut i = 0;
|
||||
while i < young_vars.len() {
|
||||
let var = young_vars[i];
|
||||
let rank = subs.get_rank_set_mark(var, young_mark);
|
||||
|
||||
if rank != young_rank {
|
||||
debug_assert!(rank.into_usize() < young_rank.into_usize() + 1);
|
||||
|
||||
pools.get_mut(rank).push(*var);
|
||||
false
|
||||
pools.get_mut(rank).push(var);
|
||||
|
||||
// swap an element in; don't increment i
|
||||
young_vars.swap_remove(i);
|
||||
} else {
|
||||
true
|
||||
i += 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
std::mem::swap(pools.get_mut(young_rank), &mut young_vars);
|
||||
|
||||
|
|
|
@ -6234,4 +6234,22 @@ mod solve_expr {
|
|||
"F b -> b",
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alias_in_opaque() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ foo ] to "./platform"
|
||||
|
||||
MyError : [ Error ]
|
||||
|
||||
MyResult := Result U8 MyError
|
||||
|
||||
foo = @MyResult (Err Error)
|
||||
"#
|
||||
),
|
||||
"MyResult",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3270,3 +3270,51 @@ fn dec_float_suffix() {
|
|||
i128
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn ceiling_to_u32() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
n : U32
|
||||
n = Num.ceiling 124.5
|
||||
n
|
||||
"#
|
||||
),
|
||||
125,
|
||||
u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn floor_to_u32() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
n : U32
|
||||
n = Num.floor 124.5
|
||||
n
|
||||
"#
|
||||
),
|
||||
124,
|
||||
u32
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn round_to_u32() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
n : U32
|
||||
n = Num.round 124.49
|
||||
n
|
||||
"#
|
||||
),
|
||||
124,
|
||||
u32
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3267,7 +3267,6 @@ fn polymophic_expression_captured_inside_closure() {
|
|||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[ignore = "Compile polymorphic functions"]
|
||||
fn issue_2322() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -3421,7 +3420,6 @@ fn polymorphic_def_used_in_closure() {
|
|||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
#[ignore = "This still doesn't work... yet"]
|
||||
fn polymorphic_lambda_set_usage() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
|
@ -3429,6 +3427,7 @@ fn polymorphic_lambda_set_usage() {
|
|||
id1 = \x -> x
|
||||
id2 = \y -> y
|
||||
id = if True then id1 else id2
|
||||
|
||||
id 9u8
|
||||
"#
|
||||
),
|
||||
|
@ -3436,3 +3435,21 @@ fn polymorphic_lambda_set_usage() {
|
|||
u8
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm"))]
|
||||
fn polymorphic_lambda_set_multiple_specializations() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
id1 = \x -> x
|
||||
id2 = \y -> y
|
||||
id = if True then id1 else id2
|
||||
|
||||
(id 9u8) + Num.toU8 (id 16u16)
|
||||
"#
|
||||
),
|
||||
25,
|
||||
u8
|
||||
)
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ fn create_llvm_module<'a>(
|
|||
Default::default(),
|
||||
target_info,
|
||||
RenderTarget::ColorTerminal,
|
||||
Threading::Multi,
|
||||
Threading::AllAvailable,
|
||||
);
|
||||
|
||||
let mut loaded = match loaded {
|
||||
|
|
|
@ -19,6 +19,5 @@ roc_mono = { path = "../mono" }
|
|||
roc_target = { path = "../roc_target" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
test_mono_macros = { path = "../test_mono_macros" }
|
||||
pretty_assertions = "1.0.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
indoc = "1.0.3"
|
||||
|
|
|
@ -1123,8 +1123,17 @@ impl Type {
|
|||
ext.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables);
|
||||
}
|
||||
}
|
||||
DelayedAlias(AliasCommon { .. }) => {
|
||||
// do nothing, yay
|
||||
DelayedAlias(AliasCommon {
|
||||
type_arguments,
|
||||
lambda_set_variables,
|
||||
symbol: _,
|
||||
}) => {
|
||||
debug_assert!(lambda_set_variables
|
||||
.iter()
|
||||
.all(|lambda_set| matches!(lambda_set.0, Type::Variable(..))));
|
||||
type_arguments.iter_mut().for_each(|t| {
|
||||
t.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables)
|
||||
});
|
||||
}
|
||||
HostExposedAlias {
|
||||
type_arguments: type_args,
|
||||
|
|
|
@ -28,6 +28,3 @@ peg = "0.8.0"
|
|||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.0.0"
|
||||
tempfile = "3.2.0"
|
||||
uuid = { version = "0.8.2", features = ["v4"] }
|
||||
indoc = "1.0.3"
|
||||
|
|
|
@ -435,7 +435,7 @@ pub fn load_modules_for_files(filenames: Vec<PathBuf>) -> Vec<LoadedModule> {
|
|||
Default::default(),
|
||||
roc_target::TargetInfo::default_x86_64(), // This is just type-checking for docs, so "target" doesn't matter
|
||||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
Threading::Multi,
|
||||
Threading::AllAvailable,
|
||||
) {
|
||||
Ok(loaded) => modules.push(loaded),
|
||||
Err(LoadingProblem::FormattedReport(report)) => {
|
||||
|
|
|
@ -129,7 +129,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
|
|||
|
||||
let file_path = Path::new(&file_path_str);
|
||||
|
||||
let loaded_module = load_module(file_path, Threading::Multi);
|
||||
let loaded_module = load_module(file_path, Threading::AllAvailable);
|
||||
|
||||
let mut var_store = VarStore::default();
|
||||
let dep_idents = IdentIds::exposed_builtins(8);
|
||||
|
|
|
@ -330,7 +330,7 @@ pub mod test_ed_model {
|
|||
writeln!(file, "{}", clean_code_str)
|
||||
.unwrap_or_else(|_| panic!("Failed to write {:?} to file: {:?}", clean_code_str, file));
|
||||
|
||||
let loaded_module = load_module(&temp_file_full_path, Threading::Multi);
|
||||
let loaded_module = load_module(&temp_file_full_path, Threading::AllAvailable);
|
||||
|
||||
let mut ed_model = init_dummy_model(
|
||||
clean_code_str,
|
||||
|
|
|
@ -74,7 +74,9 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
|
|||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn rust_main() -> i32 {
|
||||
let arg = env::args().nth(1).unwrap();
|
||||
let arg = env::args()
|
||||
.nth(1)
|
||||
.expect("Please pass a .false file as a command-line argument to the false interpreter!");
|
||||
let arg = RocStr::from(arg.as_str());
|
||||
|
||||
let size = unsafe { roc_main_size() } as usize;
|
||||
|
|
|
@ -29,4 +29,3 @@ roc_target = { path = "../compiler/roc_target" }
|
|||
roc_test_utils = { path = "../test_utils" }
|
||||
pretty_assertions = "1.0.0"
|
||||
indoc = "1.0.3"
|
||||
tempfile = "3.2.0"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue