Merge remote-tracking branch 'origin/trunk' into hostgen

This commit is contained in:
Richard Feldman 2022-05-07 18:48:58 -04:00
commit 36f64d8496
No known key found for this signature in database
GPG key ID: 7E4127D1E4241798
45 changed files with 1801 additions and 1368 deletions

72
Cargo.lock generated
View file

@ -468,9 +468,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "3.1.15" version = "3.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d" checksum = "47582c09be7c8b32c0ab3a6181825ababb713fde6fff20fc573a3870dd45c6a0"
dependencies = [ dependencies = [
"atty", "atty",
"bitflags", "bitflags",
@ -516,6 +516,7 @@ name = "cli_utils"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"const_format",
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)", "criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
"rlimit", "rlimit",
"roc_cli", "roc_cli",
@ -1674,9 +1675,9 @@ dependencies = [
[[package]] [[package]]
name = "glyph_brush" name = "glyph_brush"
version = "0.7.3" version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21932fbf719272848eec4583740d978203c6e7da4c4e203358f5b95946c97409" checksum = "a69c65dd1f1fbb6209aa00f78636e436ad0a55b7d8e5de886d00720dcad9c6e2"
dependencies = [ dependencies = [
"glyph_brush_draw_cache", "glyph_brush_draw_cache",
"glyph_brush_layout", "glyph_brush_layout",
@ -1865,12 +1866,9 @@ dependencies = [
[[package]] [[package]]
name = "indoc" name = "indoc"
version = "1.0.4" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7906a9fababaeacb774f72410e497a1d18de916322e33797bb2cd29baa23c9e" checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e"
dependencies = [
"unindent",
]
[[package]] [[package]]
name = "inkwell" name = "inkwell"
@ -2688,9 +2686,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]] [[package]]
name = "ordered-float" name = "ordered-float"
version = "2.10.0" version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7940cf2ca942593318d07fcf2596cdca60a85c9e7fab408a5e21a4f9dcd40d87" checksum = "96bcbab4bfea7a59c2c0fe47211a1ac4e3e96bea6eb446d704f310bc5c732ae2"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]
@ -3085,9 +3083,9 @@ checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.37" version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" checksum = "9027b48e9d4c9175fa2218adf3557f91c1137021739951d4932f5f8268ac48aa"
dependencies = [ dependencies = [
"unicode-xid", "unicode-xid",
] ]
@ -3435,7 +3433,7 @@ name = "roc-bindgen"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"clap 3.1.15", "clap 3.1.17",
"indoc", "indoc",
"pretty_assertions", "pretty_assertions",
"roc_builtins", "roc_builtins",
@ -3546,7 +3544,6 @@ dependencies = [
"bumpalo", "bumpalo",
"indoc", "indoc",
"pretty_assertions", "pretty_assertions",
"roc_builtins",
"roc_collections", "roc_collections",
"roc_error_macros", "roc_error_macros",
"roc_exhaustive", "roc_exhaustive",
@ -3556,7 +3553,6 @@ dependencies = [
"roc_region", "roc_region",
"roc_types", "roc_types",
"static_assertions 1.1.0", "static_assertions 1.1.0",
"ven_graph",
] ]
[[package]] [[package]]
@ -3564,7 +3560,7 @@ name = "roc_cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"clap 3.1.15", "clap 3.1.17",
"cli_utils", "cli_utils",
"const_format", "const_format",
"criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)", "criterion 0.3.5 (git+https://github.com/Anton-4/criterion.rs)",
@ -3590,6 +3586,8 @@ dependencies = [
"roc_target", "roc_target",
"roc_test_utils", "roc_test_utils",
"serial_test", "serial_test",
"strum",
"strum_macros",
"target-lexicon", "target-lexicon",
"tempfile", "tempfile",
"wasmer", "wasmer",
@ -3645,7 +3643,6 @@ name = "roc_docs"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"indoc",
"peg", "peg",
"pretty_assertions", "pretty_assertions",
"pulldown-cmark", "pulldown-cmark",
@ -3663,8 +3660,6 @@ dependencies = [
"roc_target", "roc_target",
"roc_types", "roc_types",
"snafu", "snafu",
"tempfile",
"uuid",
] ]
[[package]] [[package]]
@ -3813,9 +3808,6 @@ dependencies = [
[[package]] [[package]]
name = "roc_ident" name = "roc_ident"
version = "0.1.0" version = "0.1.0"
dependencies = [
"arrayvec 0.7.2",
]
[[package]] [[package]]
name = "roc_linker" name = "roc_linker"
@ -3823,7 +3815,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bincode", "bincode",
"bumpalo", "bumpalo",
"clap 3.1.15", "clap 3.1.17",
"iced-x86", "iced-x86",
"memmap2 0.5.3", "memmap2 0.5.3",
"object 0.26.2", "object 0.26.2",
@ -3858,8 +3850,6 @@ dependencies = [
"crossbeam", "crossbeam",
"indoc", "indoc",
"maplit", "maplit",
"morphic_lib",
"num_cpus",
"parking_lot 0.12.0", "parking_lot 0.12.0",
"pretty_assertions", "pretty_assertions",
"roc_builtins", "roc_builtins",
@ -3879,7 +3869,6 @@ dependencies = [
"roc_test_utils", "roc_test_utils",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"tempfile",
"ven_pretty", "ven_pretty",
] ]
@ -3887,7 +3876,6 @@ dependencies = [
name = "roc_module" name = "roc_module"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"arrayvec 0.7.2",
"bumpalo", "bumpalo",
"lazy_static", "lazy_static",
"roc_collections", "roc_collections",
@ -3904,7 +3892,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"hashbrown 0.11.2", "hashbrown 0.11.2",
"morphic_lib",
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
@ -4047,7 +4034,6 @@ dependencies = [
"roc_target", "roc_target",
"roc_test_utils", "roc_test_utils",
"roc_types", "roc_types",
"tempfile",
"ven_pretty", "ven_pretty",
] ]
@ -4690,6 +4676,25 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 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]] [[package]]
name = "syn" name = "syn"
version = "1.0.92" version = "1.0.92"
@ -4778,7 +4783,6 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"indoc", "indoc",
"pretty_assertions",
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
@ -5016,12 +5020,6 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
[[package]]
name = "unindent"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "514672a55d7380da379785a4d70ca8386c8883ff7eaae877be4d2081cebe73d8"
[[package]] [[package]]
name = "utf8parse" name = "utf8parse"
version = "0.2.0" version = "0.2.0"

View file

@ -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) => { Ok(types) => {
let mut buf; let mut buf;

View file

@ -85,6 +85,8 @@ indoc = "1.0.3"
serial_test = "0.5.1" serial_test = "0.5.1"
criterion = { git = "https://github.com/Anton-4/criterion.rs"} criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "../cli_utils" } cli_utils = { path = "../cli_utils" }
strum = "0.24.0"
strum_macros = "0.24"
# Wasmer singlepass compiler only works on x86_64. # Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dev-dependencies] [target.'cfg(target_arch = "x86_64")'.dev-dependencies]

View file

@ -350,6 +350,7 @@ pub fn check_file(
src_dir: PathBuf, src_dir: PathBuf,
roc_file_path: PathBuf, roc_file_path: PathBuf,
emit_timings: bool, emit_timings: bool,
threading: Threading,
) -> Result<(program::Problems, Duration), LoadingProblem> { ) -> Result<(program::Problems, Duration), LoadingProblem> {
let compilation_start = SystemTime::now(); let compilation_start = SystemTime::now();
@ -368,7 +369,7 @@ pub fn check_file(
target_info, target_info,
// TODO: expose this from CLI? // TODO: expose this from CLI?
RenderTarget::ColorTerminal, RenderTarget::ColorTerminal,
Threading::Multi, threading,
)?; )?;
let buf = &mut String::with_capacity(1024); let buf = &mut String::with_capacity(1024);

View file

@ -2,29 +2,17 @@ use std::ffi::OsStr;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use crate::FormatMode; use crate::FormatMode;
use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_error_macros::{internal_error, user_error}; use roc_error_macros::{internal_error, user_error};
use roc_fmt::def::fmt_def; use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module; use roc_fmt::module::fmt_module;
use roc_fmt::Buf; use roc_fmt::spaces::RemoveSpaces;
use roc_module::called_via::{BinOp, UnaryOp}; use roc_fmt::{Ast, Buf};
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_parse::{ use roc_parse::{
ast::{Def, Module},
ident::UppercaseIdent,
module::{self, module_defs}, module::{self, module_defs},
parser::{Parser, SyntaxError}, parser::{Parser, SyntaxError},
state::State, state::State,
}; };
use roc_region::all::{Loc, Region};
fn flatten_directories(files: std::vec::Vec<PathBuf>) -> std::vec::Vec<PathBuf> { fn flatten_directories(files: std::vec::Vec<PathBuf>) -> std::vec::Vec<PathBuf> {
let mut to_flatten = files; let mut to_flatten = files;
@ -166,12 +154,6 @@ pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), Str
Ok(()) 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>> { 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())) let (module, state) = module::parse_header(arena, State::new(src.as_bytes()))
.map_err(|e| SyntaxError::Header(e.problem))?; .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(); 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),
}
}
}

View file

@ -3,16 +3,15 @@ extern crate const_format;
use build::BuiltFile; use build::BuiltFile;
use bumpalo::Bump; use bumpalo::Bump;
use clap::Command; use clap::{Arg, ArgMatches, Command};
use clap::{Arg, ArgMatches};
use roc_build::link::LinkType; use roc_build::link::LinkType;
use roc_error_macros::user_error; use roc_error_macros::user_error;
use roc_load::{LoadingProblem, Threading}; use roc_load::{LoadingProblem, Threading};
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use std::env; use std::env;
use std::ffi::OsStr;
use std::io; use std::io;
use std::path::Path; use std::path::{Path, PathBuf};
use std::path::PathBuf;
use std::process; use std::process;
use target_lexicon::BinaryFormat; use target_lexicon::BinaryFormat;
use target_lexicon::{ use target_lexicon::{
@ -35,6 +34,7 @@ pub const CMD_FORMAT: &str = "format";
pub const FLAG_DEBUG: &str = "debug"; pub const FLAG_DEBUG: &str = "debug";
pub const FLAG_DEV: &str = "dev"; pub const FLAG_DEV: &str = "dev";
pub const FLAG_OPTIMIZE: &str = "optimize"; 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_OPT_SIZE: &str = "opt-size";
pub const FLAG_LIB: &str = "lib"; pub const FLAG_LIB: &str = "lib";
pub const FLAG_NO_LINK: &str = "no-link"; pub const FLAG_NO_LINK: &str = "no-link";
@ -58,6 +58,14 @@ pub fn build_app<'a>() -> Command<'a> {
.requires(ROC_FILE) .requires(ROC_FILE)
.required(false); .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) let flag_opt_size = Arg::new(FLAG_OPT_SIZE)
.long(FLAG_OPT_SIZE) .long(FLAG_OPT_SIZE)
.help("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)") .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"]) .possible_values(["true", "false"])
.required(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") let app = Command::new("roc")
.version(concatcp!(VERSION, "\n")) .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!") .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) .subcommand(Command::new(CMD_BUILD)
.about("Build a binary from the given .roc file, but don't run it") .about("Build a binary from the given .roc file, but don't run it")
.arg(flag_optimize.clone()) .arg(flag_optimize.clone())
.arg(flag_max_threads.clone())
.arg(flag_opt_size.clone()) .arg(flag_opt_size.clone())
.arg(flag_dev.clone()) .arg(flag_dev.clone())
.arg(flag_debug.clone()) .arg(flag_debug.clone())
@ -132,6 +151,7 @@ pub fn build_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(ROC_FILE) Arg::new(ROC_FILE)
.help("The .roc file to build") .help("The .roc file to build")
.allow_invalid_utf8(true)
.required(true), .required(true),
) )
) )
@ -141,6 +161,7 @@ pub fn build_app<'a>() -> Command<'a> {
.subcommand(Command::new(CMD_RUN) .subcommand(Command::new(CMD_RUN)
.about("Run a .roc file even if it has build errors") .about("Run a .roc file even if it has build errors")
.arg(flag_optimize.clone()) .arg(flag_optimize.clone())
.arg(flag_max_threads.clone())
.arg(flag_opt_size.clone()) .arg(flag_opt_size.clone())
.arg(flag_dev.clone()) .arg(flag_dev.clone())
.arg(flag_debug.clone()) .arg(flag_debug.clone())
@ -148,11 +169,8 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_linker.clone()) .arg(flag_linker.clone())
.arg(flag_precompiled.clone()) .arg(flag_precompiled.clone())
.arg(flag_valgrind.clone()) .arg(flag_valgrind.clone())
.arg( .arg(roc_file_to_run.clone().required(true))
Arg::new(ROC_FILE) .arg(args_for_app.clone())
.help("The .roc file of an app to run")
.required(true),
)
) )
.subcommand(Command::new(CMD_FORMAT) .subcommand(Command::new(CMD_FORMAT)
.about("Format a .roc file using standard Roc formatting") .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) .subcommand(Command::new(CMD_CHECK)
.about("Check the code for problems, but doesnt build or run it") .about("Check the code for problems, but doesnt build or run it")
.arg(flag_time.clone()) .arg(flag_time.clone())
.arg(flag_max_threads.clone())
.arg( .arg(
Arg::new(ROC_FILE) Arg::new(ROC_FILE)
.help("The .roc file of an app to check") .help("The .roc file of an app to check")
.allow_invalid_utf8(true)
.required(true), .required(true),
) )
) )
@ -193,6 +213,7 @@ pub fn build_app<'a>() -> Command<'a> {
) )
.trailing_var_arg(true) .trailing_var_arg(true)
.arg(flag_optimize) .arg(flag_optimize)
.arg(flag_max_threads.clone())
.arg(flag_opt_size) .arg(flag_opt_size)
.arg(flag_dev) .arg(flag_dev)
.arg(flag_debug) .arg(flag_debug)
@ -200,17 +221,8 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_linker) .arg(flag_linker)
.arg(flag_precompiled) .arg(flag_precompiled)
.arg(flag_valgrind) .arg(flag_valgrind)
.arg( .arg(roc_file_to_run.required(false))
Arg::new(ROC_FILE) .arg(args_for_app);
.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),
);
if cfg!(feature = "editor") { if cfg!(feature = "editor") {
app.subcommand( app.subcommand(
@ -236,8 +248,8 @@ pub fn docs(files: Vec<PathBuf>) {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub enum BuildConfig { pub enum BuildConfig {
BuildOnly, BuildOnly,
BuildAndRun { roc_file_arg_index: usize }, BuildAndRun,
BuildAndRunIfNoErrors { roc_file_arg_index: usize }, BuildAndRunIfNoErrors,
} }
pub enum FormatMode { pub enum FormatMode {
@ -255,7 +267,7 @@ pub fn build(
use BuildConfig::*; use BuildConfig::*;
let arena = Bump::new(); 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 original_cwd = std::env::current_dir()?;
let opt_level = match ( let opt_level = match (
@ -272,6 +284,16 @@ pub fn build(
let emit_debug_info = matches.is_present(FLAG_DEBUG); let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME); 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 // Use surgical linking when supported, or when explicitly requested with --linker surgical
let surgically_link = if matches.is_present(FLAG_LINKER) { let surgically_link = if matches.is_present(FLAG_LINKER) {
matches.value_of(FLAG_LINKER) == Some("surgical") matches.value_of(FLAG_LINKER) == Some("surgical")
@ -321,7 +343,7 @@ pub fn build(
surgically_link, surgically_link,
precompiled, precompiled,
target_valgrind, target_valgrind,
Threading::Multi, threading,
); );
match res_binary_path { match res_binary_path {
@ -372,7 +394,7 @@ pub fn build(
// Return a nonzero exit code if there were problems // Return a nonzero exit code if there were problems
Ok(problems.exit_code()) Ok(problems.exit_code())
} }
BuildAndRun { roc_file_arg_index } => { BuildAndRun => {
if problems.errors > 0 || problems.warnings > 0 { if problems.errors > 0 || problems.warnings > 0 {
println!( println!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nRunning program anyway…\n\n\x1B[36m{}\x1B[39m", "\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( let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
arena,
&original_cwd, roc_run(arena, &original_cwd, triple, args, &binary_path)
triple,
roc_file_arg_index,
&binary_path,
)
} }
BuildAndRunIfNoErrors { roc_file_arg_index } => { BuildAndRunIfNoErrors => {
if problems.errors == 0 { if problems.errors == 0 {
if problems.warnings > 0 { if problems.warnings > 0 {
println!( println!(
@ -427,13 +445,9 @@ pub fn build(
); );
} }
roc_run( let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
arena,
&original_cwd, roc_run(arena, &original_cwd, triple, args, &binary_path)
triple,
roc_file_arg_index,
&binary_path,
)
} else { } else {
println!( 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", "\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" "warnings"
}, },
total_time.as_millis(), total_time.as_millis(),
filename filename.to_string_lossy()
); );
Ok(problems.exit_code()) Ok(problems.exit_code())
@ -479,17 +493,14 @@ pub fn build(
} }
} }
#[cfg(target_family = "unix")] fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
fn roc_run(
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it! arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
cwd: &Path, cwd: &Path,
triple: Triple, triple: Triple,
roc_file_arg_index: usize, args: I,
binary_path: &Path, binary_path: &Path,
) -> io::Result<i32> { ) -> io::Result<i32> {
use std::os::unix::process::CommandExt; match triple.architecture {
let mut cmd = match triple.architecture {
Architecture::Wasm32 => { Architecture::Wasm32 => {
// If possible, report the generated executable name relative to the current dir. // If possible, report the generated executable name relative to the current dir.
let generated_filename = binary_path let generated_filename = binary_path
@ -500,19 +511,44 @@ fn roc_run(
// since the process is about to exit anyway. // since the process is about to exit anyway.
std::mem::forget(arena); std::mem::forget(arena);
let args = std::env::args() if cfg!(target_family = "unix") {
.skip(roc_file_arg_index) use std::os::unix::ffi::OsStrExt;
.collect::<Vec<_>>();
run_with_wasmer(generated_filename, &args); run_with_wasmer(
return Ok(0); 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.",
)
}),
);
} }
_ => std::process::Command::new(&binary_path),
};
if let Architecture::Wasm32 = triple.architecture { Ok(0)
cmd.arg(binary_path);
} }
_ => {
if cfg!(target_family = "unix") {
roc_run_unix(cwd, args, binary_path)
} else {
roc_run_non_unix(arena, cwd, args, 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 // Forward all the arguments after the .roc file argument
// to the new process. This way, you can do things like: // to the new process. This way, you can do things like:
@ -521,11 +557,9 @@ fn roc_run(
// //
// ...and have it so that app.roc will receive only `foo`, // ...and have it so that app.roc will receive only `foo`,
// `bar`, and `baz` as its arguments. // `bar`, and `baz` as its arguments.
for (index, arg) in std::env::args().enumerate() { for arg in args {
if index > roc_file_arg_index {
cmd.arg(arg); cmd.arg(arg);
} }
}
// This is much faster than spawning a subprocess if we're on a UNIX system! // This is much faster than spawning a subprocess if we're on a UNIX system!
let err = cmd.current_dir(cwd).exec(); let err = cmd.current_dir(cwd).exec();
@ -536,29 +570,36 @@ fn roc_run(
Err(err) Err(err)
} }
#[cfg(not(target_family = "unix"))] fn roc_run_non_unix<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
fn roc_run(cmd: &mut Command) -> io::Result<i32> { _arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
// Run the compiled app _cwd: &Path,
let exit_status = cmd _args: I,
.spawn() _binary_path: &Path,
.unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err)) ) -> io::Result<i32> {
.wait() todo!("TODO support running roc programs on non-UNIX targets");
.expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app"); // let mut cmd = std::process::Command::new(&binary_path);
// `roc [FILE]` exits with the same status code as the app it ran. // // Run the compiled app
// // let exit_status = cmd
// If you want to know whether there were compilation problems // .spawn()
// via status code, use either `roc build` or `roc check` instead! // .unwrap_or_else(|err| panic!("Failed to run app after building it: {:?}", err))
match exit_status.code() { // .wait()
Some(code) => Ok(code), // .expect("TODO gracefully handle block_on failing when `roc` spawns a subprocess for the compiled app");
None => {
todo!("TODO gracefully handle the `roc [FILE]` subprocess terminating with a signal."); // // `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")] #[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}; use wasmer::{Instance, Module, Store};
let store = Store::default(); let store = Store::default();
@ -589,8 +630,8 @@ fn run_with_wasmer(wasm_path: &std::path::Path, args: &[String]) {
} }
#[cfg(not(feature = "run-wasm32"))] #[cfg(not(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) {
println!("Running wasm files not support"); println!("Running wasm files is not supported on this target.");
} }
#[derive(Debug, Copy, Clone, PartialEq, Eq)] #[derive(Debug, Copy, Clone, PartialEq, Eq)]

View file

@ -6,7 +6,7 @@ use roc_cli::{
FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, ROC_FILE, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, ROC_FILE,
}; };
use roc_error_macros::user_error; use roc_error_macros::user_error;
use roc_load::LoadingProblem; use roc_load::{LoadingProblem, Threading};
use std::fs::{self, FileType}; use std::fs::{self, FileType};
use std::io; use std::io;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -27,45 +27,33 @@ fn main() -> io::Result<()> {
let exit_code = match matches.subcommand() { let exit_code = match matches.subcommand() {
None => { None => {
match matches.index_of(ROC_FILE) { if matches.is_present(ROC_FILE) {
Some(arg_index) => {
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
build( build(
&matches, &matches,
BuildConfig::BuildAndRunIfNoErrors { roc_file_arg_index }, BuildConfig::BuildAndRunIfNoErrors,
Triple::host(), Triple::host(),
LinkType::Executable, LinkType::Executable,
) )
} } else {
None => {
launch_editor(None)?; launch_editor(None)?;
Ok(0) Ok(0)
} }
} }
}
Some((CMD_RUN, matches)) => { Some((CMD_RUN, matches)) => {
match matches.index_of(ROC_FILE) { if matches.is_present(ROC_FILE) {
Some(arg_index) => {
let roc_file_arg_index = arg_index + 1; // Not sure why this +1 is necessary, but it is!
build( build(
matches, matches,
BuildConfig::BuildAndRun { roc_file_arg_index }, BuildConfig::BuildAndRun,
Triple::host(), Triple::host(),
LinkType::Executable, LinkType::Executable,
) )
} } else {
None => {
eprintln!("What .roc file do you want to run? Specify it at the end of the `roc run` command."); 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)) => { Some((CMD_BUILD, matches)) => {
let target: Target = matches.value_of_t(FLAG_TARGET).unwrap_or_default(); let target: Target = matches.value_of_t(FLAG_TARGET).unwrap_or_default();
@ -90,11 +78,21 @@ fn main() -> io::Result<()> {
let arena = bumpalo::Bump::new(); let arena = bumpalo::Bump::new();
let emit_timings = matches.is_present(FLAG_TIME); 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 roc_file_path = PathBuf::from(filename);
let src_dir = roc_file_path.parent().unwrap().to_owned(); 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)) => { Ok((problems, total_time)) => {
println!( println!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.", "\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.",

View file

@ -14,10 +14,29 @@ mod cli_run {
known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError, known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError,
ValgrindErrorXWhat, ValgrindErrorXWhat,
}; };
use const_format::concatcp;
use indoc::indoc; use indoc::indoc;
use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_FORMAT, CMD_RUN};
use roc_test_utils::assert_multiline_str_eq; use roc_test_utils::assert_multiline_str_eq;
use serial_test::serial; use serial_test::serial;
use std::iter;
use std::path::{Path, PathBuf}; 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))] #[cfg(not(debug_assertions))]
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
@ -65,7 +84,7 @@ mod cli_run {
} }
fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { 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 = compile_out.stdout.trim();
let err = strip_colors(err); 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) { fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
let flags = &["--check"]; let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[]);
let out = run_roc(&[&["format", file.to_str().unwrap()], &flags[..]].concat());
if expects_success_exit_code { assert_eq!(out.status.success(), expects_success_exit_code);
assert!(out.status.success());
} else {
assert!(!out.status.success());
}
} }
fn build_example(file: &Path, flags: &[&str]) -> Out { fn run_roc_on<'a, I: IntoIterator<Item = &'a str>>(
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); file: &'a Path,
if !compile_out.stderr.is_empty() { args: I,
panic!("roc build had stderr: {}", compile_out.stderr); 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); assert!(compile_out.status.success(), "bad status {:?}", compile_out);
@ -106,27 +147,33 @@ mod cli_run {
expected_ending: &str, expected_ending: &str,
use_valgrind: bool, use_valgrind: bool,
) { ) {
let mut all_flags = vec![]; for cli_mode in CliMode::iter() {
all_flags.extend_from_slice(flags); let flags = {
let mut vec = flags.to_vec();
if use_valgrind { if use_valgrind {
all_flags.extend_from_slice(&["--valgrind"]); vec.push(VALGRIND_FLAG);
} }
build_example(file, &all_flags[..]); vec.into_iter()
};
let out = if use_valgrind && ALLOW_VALGRIND { let out = match cli_mode {
let (valgrind_out, raw_xml) = if let Some(input_file) = input_file { CliMode::RocBuild => {
run_roc_on(file, iter::once(CMD_BUILD).chain(flags.clone()), &[], None);
if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = if let Some(ref input_file) = input_file {
run_with_valgrind( run_with_valgrind(
stdin, stdin.clone().iter().copied(),
&[ &[
file.with_file_name(executable_filename).to_str().unwrap(), file.with_file_name(executable_filename).to_str().unwrap(),
input_file.to_str().unwrap(), input_file.clone().to_str().unwrap(),
], ],
) )
} else { } else {
run_with_valgrind( run_with_valgrind(
stdin, stdin.clone().iter().copied(),
&[file.with_file_name(executable_filename).to_str().unwrap()], &[file.with_file_name(executable_filename).to_str().unwrap()],
) )
}; };
@ -166,27 +213,39 @@ mod cli_run {
} }
valgrind_out valgrind_out
} else if let Some(input_file) = input_file { } else if let Some(ref input_file) = input_file {
run_cmd( run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(), file.with_file_name(executable_filename).to_str().unwrap(),
stdin, stdin.iter().copied(),
&[input_file.to_str().unwrap()], &[input_file.to_str().unwrap()],
) )
} else { } else {
run_cmd( run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(), file.with_file_name(executable_filename).to_str().unwrap(),
stdin, 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) { if !&out.stdout.ends_with(expected_ending) {
panic!( panic!(
"expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}", "expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}",
expected_ending, out.stdout, out.stderr expected_ending, out.stdout, out.stderr
); );
} }
assert!(out.status.success()); assert!(out.status.success());
} }
}
#[cfg(feature = "wasm32-cli-run")] #[cfg(feature = "wasm32-cli-run")]
fn check_wasm_output_with_stdin( fn check_wasm_output_with_stdin(
@ -199,9 +258,13 @@ mod cli_run {
) { ) {
assert_eq!(input_file, None, "Wasm does not support input files"); assert_eq!(input_file, None, "Wasm does not support input files");
let mut flags = flags.to_vec(); 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() { if !compile_out.stderr.is_empty() {
panic!("{}", compile_out.stderr); panic!("{}", compile_out.stderr);
} }
@ -258,7 +321,7 @@ mod cli_run {
} }
"hello-gui" | "breakout" => { "hello-gui" | "breakout" => {
// Since these require opening a window, we do `roc build` on them but don't run them. // 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; return;
} }
@ -283,7 +346,7 @@ mod cli_run {
&file_name, &file_name,
example.stdin, example.stdin,
example.executable_filename, example.executable_filename,
&["--optimize"], &[OPTIMIZE_FLAG],
example.input_file.and_then(|file| Some(example_file(dir_name, file))), example.input_file.and_then(|file| Some(example_file(dir_name, file))),
example.expected_ending, example.expected_ending,
example.use_valgrind, example.use_valgrind,
@ -296,7 +359,7 @@ mod cli_run {
&file_name, &file_name,
example.stdin, example.stdin,
example.executable_filename, example.executable_filename,
&["--linker", "legacy"], &[LINKER_FLAG, "legacy"],
example.input_file.and_then(|file| Some(example_file(dir_name, file))), example.input_file.and_then(|file| Some(example_file(dir_name, file))),
example.expected_ending, example.expected_ending,
example.use_valgrind, example.use_valgrind,
@ -513,7 +576,7 @@ mod cli_run {
&file_name, &file_name,
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&["--optimize"], &[OPTIMIZE_FLAG],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
@ -555,7 +618,7 @@ mod cli_run {
&file_name, &file_name,
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&["--optimize"], &[OPTIMIZE_FLAG],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
); );
@ -587,7 +650,7 @@ mod cli_run {
&file_name, &file_name,
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&["--target=x86_32"], [concatcp!(TARGET_FLAG, "=x86_32")],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
@ -597,7 +660,7 @@ mod cli_run {
&file_name, &file_name,
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, 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.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
@ -816,7 +879,7 @@ mod cli_run {
&fixture_file("multi-dep-str", "Main.roc"), &fixture_file("multi-dep-str", "Main.roc"),
&[], &[],
"multi-dep-str", "multi-dep-str",
&["--optimize"], &[OPTIMIZE_FLAG],
None, None,
"I am Dep2.str2\n", "I am Dep2.str2\n",
true, true,
@ -844,7 +907,7 @@ mod cli_run {
&fixture_file("multi-dep-thunk", "Main.roc"), &fixture_file("multi-dep-thunk", "Main.roc"),
&[], &[],
"multi-dep-thunk", "multi-dep-thunk",
&["--optimize"], &[OPTIMIZE_FLAG],
None, None,
"I am Dep2.value2\n", "I am Dep2.value2\n",
true, true,

View file

@ -20,6 +20,7 @@ serde = { version = "1.0.130", features = ["derive"] }
serde-xml-rs = "0.5.1" serde-xml-rs = "0.5.1"
strip-ansi-escapes = "0.1.1" strip-ansi-escapes = "0.1.1"
tempfile = "3.2.0" tempfile = "3.2.0"
const_format = "0.2.22"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
rlimit = "0.6.2" rlimit = "0.6.2"

View file

@ -1,9 +1,13 @@
use crate::helpers::{example_file, run_cmd, run_roc}; use crate::helpers::{example_file, run_cmd, run_roc};
use const_format::concatcp;
use criterion::{black_box, measurement::Measurement, BenchmarkGroup}; use criterion::{black_box, measurement::Measurement, BenchmarkGroup};
use roc_cli::CMD_BUILD;
use std::{path::Path, thread}; use std::{path::Path, thread};
const CFOLD_STACK_SIZE: usize = 8192 * 100000; const CFOLD_STACK_SIZE: usize = 8192 * 100000;
const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE);
fn exec_bench_w_input<T: Measurement>( fn exec_bench_w_input<T: Measurement>(
file: &Path, file: &Path,
stdin_str: &'static str, stdin_str: &'static str,
@ -11,9 +15,10 @@ fn exec_bench_w_input<T: Measurement>(
expected_ending: &str, expected_ending: &str,
bench_group_opt: Option<&mut BenchmarkGroup<T>>, bench_group_opt: Option<&mut BenchmarkGroup<T>>,
) { ) {
let flags: &[&str] = &["--optimize"]; let compile_out = run_roc(
[CMD_BUILD, OPTIMIZE_FLAG, file.to_str().unwrap()],
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat()); &[stdin_str],
);
if !compile_out.stderr.is_empty() { if !compile_out.stderr.is_empty() {
panic!("{}", compile_out.stderr); panic!("{}", compile_out.stderr);
@ -45,12 +50,12 @@ fn check_cmd_output(
let out = if cmd_str.contains("cfold") { let out = if cmd_str.contains("cfold") {
let child = thread::Builder::new() let child = thread::Builder::new()
.stack_size(CFOLD_STACK_SIZE) .stack_size(CFOLD_STACK_SIZE)
.spawn(move || run_cmd(&cmd_str, &[stdin_str], &[])) .spawn(move || run_cmd(&cmd_str, [stdin_str], &[]))
.unwrap(); .unwrap();
child.join().unwrap() child.join().unwrap()
} else { } else {
run_cmd(&cmd_str, &[stdin_str], &[]) run_cmd(&cmd_str, [stdin_str], &[])
}; };
if !&out.stdout.ends_with(expected_ending) { if !&out.stdout.ends_with(expected_ending) {
@ -93,12 +98,12 @@ fn bench_cmd<T: Measurement>(
if let Some(bench_group) = bench_group_opt { if let Some(bench_group) = bench_group_opt {
bench_group.bench_function(&format!("Benchmarking {:?}", executable_filename), |b| { 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 { } else {
run_cmd( run_cmd(
black_box(file.with_file_name(executable_filename).to_str().unwrap()), black_box(file.with_file_name(executable_filename).to_str().unwrap()),
black_box(&[stdin_str]), black_box([stdin_str]),
&[], &[],
); );
} }

View file

@ -7,6 +7,7 @@ extern crate tempfile;
use serde::Deserialize; use serde::Deserialize;
use serde_xml_rs::from_str; use serde_xml_rs::from_str;
use std::env; use std::env;
use std::ffi::OsStr;
use std::io::Read; use std::io::Read;
use std::io::Write; use std::io::Write;
use std::path::PathBuf; use std::path::PathBuf;
@ -44,18 +45,34 @@ pub fn path_to_roc_binary() -> PathBuf {
path path
} }
#[allow(dead_code)] pub fn run_roc<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(args: I, stdin_vals: &[&str]) -> Out {
pub fn run_roc(args: &[&str]) -> Out {
let mut cmd = Command::new(path_to_roc_binary()); let mut cmd = Command::new(path_to_roc_binary());
for arg in args { for arg in args {
cmd.arg(arg); cmd.arg(arg);
} }
let output = cmd let mut child = cmd
.output() .stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to execute compiled `roc` binary in CLI test"); .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 { Out {
stdout: String::from_utf8(output.stdout).unwrap(), stdout: String::from_utf8(output.stdout).unwrap(),
stderr: String::from_utf8(output.stderr).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<'a, I: IntoIterator<Item = &'a str>>(
pub fn run_cmd(cmd_name: &str, stdin_vals: &[&str], args: &[&str]) -> Out { cmd_name: &str,
stdin_vals: I,
args: &[&str],
) -> Out {
let mut cmd = Command::new(cmd_name); let mut cmd = Command::new(cmd_name);
for arg in args { 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<'a, I: IntoIterator<Item = &'a str>>(
pub fn run_with_valgrind(stdin_vals: &[&str], args: &[&str]) -> (Out, String) { stdin_vals: I,
args: &[&str],
) -> (Out, String) {
//TODO: figure out if there is a better way to get the valgrind executable. //TODO: figure out if there is a better way to get the valgrind executable.
let mut cmd = Command::new("valgrind"); let mut cmd = Command::new("valgrind");
let named_tempfile = let named_tempfile =

View file

@ -106,6 +106,9 @@ comptime {
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max."); 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.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| { inline for (FLOATS) |T| {
@ -114,7 +117,6 @@ comptime {
num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan."); num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan.");
num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite."); num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite.");
num.exportRound(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round.");
} }
} }

View file

@ -90,10 +90,19 @@ pub fn exportAtan(comptime T: type, comptime name: []const u8) void {
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); @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 { comptime var f = struct {
fn func(input: T) callconv(.C) i64 { fn func(input: f32) callconv(.C) T {
return @floatToInt(i64, (@round(input))); 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; }.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });

View file

@ -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_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_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_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_U16: &str = "roc_builtins.num.bytes_to_u16";
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32"; pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";

View file

@ -14,8 +14,6 @@ roc_module = { path = "../module" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
ven_graph = { path = "../../vendor/pathfinding" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
static_assertions = "1.1.0" static_assertions = "1.1.0"
bitvec = "1" bitvec = "1"

View file

@ -192,17 +192,12 @@ fn sort_type_defs_before_introduction(
} }
// find the strongly connected components and their relations // find the strongly connected components and their relations
let nodes: Vec<_> = (0..capacity as u32).collect(); matrix
.strongly_connected_components_all()
let mut output = Vec::with_capacity(capacity); .groups()
.flat_map(|group| group.iter_ones())
for group in matrix.strongly_connected_components(&nodes).groups() { .map(|index| symbols[index])
for index in group.iter_ones() { .collect()
output.push(symbols[index])
}
}
output
} }
#[inline(always)] #[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 // We first perform SCC based on any reference, both variable usage and calls
// considering both value definitions and function bodies. This will spot any // considering both value definitions and function bodies. This will spot any
// recursive relations between any 2 definitions. // recursive relations between any 2 definitions.
let sccs = def_ordering let sccs = def_ordering.references.strongly_connected_components_all();
.references
.strongly_connected_components(&nodes);
let mut declarations = Vec::new(); let mut declarations = Vec::new();
@ -838,10 +829,9 @@ pub(crate) fn sort_can_defs(
// boom = \{} -> boom {} // boom = \{} -> boom {}
// //
// In general we cannot spot faulty recursion (halting problem) so this is our best attempt // 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 let direct_sccs = def_ordering
.direct_references .direct_references
.strongly_connected_components(&nodes); .strongly_connected_components_subset(group);
let declaration = if direct_sccs.groups().count() == 1 { let declaration = if direct_sccs.groups().count() == 1 {
// all defs are part of the same direct cycle, that is invalid! // 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 mut solved_aliases = bitvec::vec::BitVec::<usize>::repeat(false, capacity);
let group: Vec<_> = (0u32..capacity as u32).collect(); let sccs = matrix.strongly_connected_components_all();
let sccs = matrix.strongly_connected_components(&group);
// scratchpad to store aliases that are modified in the current iteration. // 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 // Only used when there is are more than one alias in a group. See below why

View file

@ -129,8 +129,14 @@ impl ReferenceMatrix {
TopologicalSort::Groups { groups } TopologicalSort::Groups { groups }
} }
/// Get the strongly-connected components of the set of input nodes. /// Get the strongly-connected components all nodes in the matrix
pub fn strongly_connected_components(&self, nodes: &[u32]) -> Sccs { 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); let mut params = Params::new(self.length, nodes);
'outer: loop { 'outer: loop {
@ -176,15 +182,15 @@ struct Params {
p: Vec<u32>, p: Vec<u32>,
s: Vec<u32>, s: Vec<u32>,
scc: Sccs, scc: Sccs,
scca: Vec<u32>, scca: BitVec,
} }
impl Params { impl Params {
fn new(length: usize, group: &[u32]) -> Self { fn new(length: usize, group: &BitSlice) -> Self {
let mut preorders = vec![Preorder::Removed; length]; let mut preorders = vec![Preorder::Removed; length];
for value in group { for index in group.iter_ones() {
preorders[*value as usize] = Preorder::Empty; preorders[index] = Preorder::Empty;
} }
Self { Self {
@ -196,7 +202,7 @@ impl Params {
matrix: ReferenceMatrix::new(length), matrix: ReferenceMatrix::new(length),
components: 0, 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); params.p.push(v as u32);
for w in bitvec[v * length..][..length].iter_ones() { for w in bitvec[v * length..][..length].iter_ones() {
if !params.scca.contains(&(w as u32)) { if !params.scca[w] {
match params.preorders[w] { match params.preorders[w] {
Preorder::Filled(pw) => loop { Preorder::Filled(pw) => loop {
let index = *params.p.last().unwrap(); let index = *params.p.last().unwrap();
@ -241,7 +247,7 @@ fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) {
.scc .scc
.matrix .matrix
.set_row_col(params.scc.components, node as usize, true); .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; params.preorders[node as usize] = Preorder::Removed;
if node as usize == v { if node as usize == v {
break; break;

View file

@ -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> { impl<K: PartialEq, V> VecMap<K, V> {
pub fn with_capacity(capacity: usize) -> Self { pub fn with_capacity(capacity: usize) -> Self {
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 { pub fn is_empty(&self) -> bool {
debug_assert_eq!(self.keys.len(), self.values.len()); debug_assert_eq!(self.keys.len(), self.values.len());
self.keys.is_empty() self.keys.is_empty()
@ -58,15 +60,9 @@ impl<K: PartialEq, V> VecMap<K, V> {
self.keys.contains(key) self.keys.contains(key)
} }
pub fn remove(&mut self, key: &K) { pub fn remove(&mut self, key: &K) -> Option<(K, V)> {
match self.keys.iter().position(|x| x == key) { let index = self.keys.iter().position(|x| x == key)?;
None => { Some(self.swap_remove(index))
// just do nothing
}
Some(index) => {
self.swap_remove(index);
}
}
} }
pub fn get(&self, key: &K) -> Option<&V> { 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) { match self.keys.iter().position(|x| x == &key) {
Some(index) => &mut self.values[index], Some(index) => &mut self.values[index],
None => { 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()) 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() self.keys.iter()
} }
pub fn values(&self) -> impl Iterator<Item = &V> { pub fn values(&self) -> impl ExactSizeIterator<Item = &V> {
self.values.iter() self.values.iter()
} }
@ -159,6 +155,7 @@ impl<K, V> IntoIterator for VecMap<K, V> {
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
IntoIter { IntoIter {
len: self.len(),
keys: self.keys.into_iter(), keys: self.keys.into_iter(),
values: self.values.into_iter(), values: self.values.into_iter(),
} }
@ -166,6 +163,7 @@ impl<K, V> IntoIterator for VecMap<K, V> {
} }
pub struct IntoIter<K, V> { pub struct IntoIter<K, V> {
len: usize,
keys: std::vec::IntoIter<K>, keys: std::vec::IntoIter<K>,
values: std::vec::IntoIter<V>, 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
}
}

View file

@ -10,6 +10,14 @@ pub mod pattern;
pub mod spaces; pub mod spaces;
use bumpalo::{collections::String, Bump}; 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)] #[derive(Debug)]
pub struct Buf<'a> { pub struct Buf<'a> {

View file

@ -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. /// The number of spaces to indent.
pub const INDENT: u16 = 4; pub const INDENT: u16 = 4;
@ -149,3 +164,575 @@ fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
} }
buf.push_str(docs); 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),
}
}
}

View file

@ -10,60 +10,153 @@ mod test_fmt {
use roc_fmt::def::fmt_def; use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module; use roc_fmt::module::fmt_module;
use roc_fmt::Buf; use roc_fmt::Buf;
use roc_parse::ast::Module;
use roc_parse::module::{self, module_defs}; use roc_parse::module::{self, module_defs};
use roc_parse::parser::Parser; use roc_parse::parser::Parser;
use roc_parse::state::State; use roc_parse::state::State;
use roc_test_utils::assert_multiline_str_eq; 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 // 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(); 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) => { Ok(actual) => {
use roc_fmt::spaces::RemoveSpaces;
let mut buf = Buf::new_in(&arena); let mut buf = Buf::new_in(&arena);
actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0); 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) 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) { fn expr_formats_same(input: &str) {
expr_formats_to(input, input); expr_formats_to(input, input);
} }
// Not intended to be used directly in tests; please use module_formats_to or module_formats_same fn fmt_module_and_defs<'a>(
fn expect_format_module_helper(src: &str, expected: &str) { arena: &Bump,
let arena = Bump::new(); src: &str,
match module::parse_header(&arena, State::new(src.as_bytes())) { module: &Module<'a>,
Ok((actual, state)) => { state: State<'a>,
let mut buf = Buf::new_in(&arena); buf: &mut Buf<'_>,
) {
fmt_module(&mut buf, &actual); fmt_module(buf, module);
match module_defs().parse(&arena, state) { match module_defs().parse(&arena, state) {
Ok((_, loc_defs, _)) => { Ok((_, loc_defs, _)) => {
for loc_def in loc_defs { for loc_def in loc_defs {
fmt_def(&mut buf, arena.alloc(loc_def.value), 0); 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) Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
} }
assert_multiline_str_eq!(expected, buf.as_str()) }
// 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_and_defs(&arena, src, &actual, state, &mut buf);
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
);
}
// 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) 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)
}; };
@ -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] #[test]
fn single_line_hosted() { fn single_line_hosted() {
module_formats_same(indoc!( module_formats_same(indoc!(

View file

@ -554,7 +554,7 @@ trait Backend<'a> {
} }
LowLevel::NumRound => self.build_fn_call( LowLevel::NumRound => self.build_fn_call(
sym, sym,
bitcode::NUM_ROUND[FloatWidth::F64].to_string(), bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(),
args, args,
arg_layouts, arg_layouts,
ret_layout, ret_layout,

View file

@ -602,6 +602,7 @@ static LLVM_SIN: IntrinsicName = float_intrinsic!("llvm.sin");
static LLVM_COS: IntrinsicName = float_intrinsic!("llvm.cos"); static LLVM_COS: IntrinsicName = float_intrinsic!("llvm.cos");
static LLVM_CEILING: IntrinsicName = float_intrinsic!("llvm.ceil"); static LLVM_CEILING: IntrinsicName = float_intrinsic!("llvm.ceil");
static LLVM_FLOOR: IntrinsicName = float_intrinsic!("llvm.floor"); 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_I64: &str = "llvm.memset.p0i8.i64";
static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32"; 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( NumCeiling => {
InstructionOpcode::FPToSI, 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()]), env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]),
env.context.i64_type(), return_type,
"num_ceiling", "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),
), ),
NumFloor => env.builder.build_cast( _ => internal_error!("Ceiling return layout is not int: {:?}", layout),
InstructionOpcode::FPToSI, };
let opcode = if return_signed {
InstructionOpcode::FPToSI
} else {
InstructionOpcode::FPToUI
};
env.builder.build_cast(
opcode,
env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]), env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]),
env.context.i64_type(), return_type,
"num_floor", "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]), 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 // trigonometry
NumSin => env.call_intrinsic(&LLVM_SIN[float_width], &[arg.into()]), NumSin => env.call_intrinsic(&LLVM_SIN[float_width], &[arg.into()]),

View file

@ -561,18 +561,6 @@ impl<'a> LowLevelCall<'a> {
NumCos => todo!("{:?}", self.lowlevel), NumCos => todo!("{:?}", self.lowlevel),
NumSqrtUnchecked => todo!("{:?}", self.lowlevel), NumSqrtUnchecked => todo!("{:?}", self.lowlevel),
NumLogUnchecked => 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 => { NumToFloat => {
self.load_args(backend); self.load_args(backend);
let ret_type = CodeGenNumType::from(self.ret_layout); let ret_type = CodeGenNumType::from(self.ret_layout);
@ -592,35 +580,54 @@ impl<'a> LowLevelCall<'a> {
} }
} }
NumPow => todo!("{:?}", self.lowlevel), NumPow => todo!("{:?}", self.lowlevel),
NumCeiling => { NumRound => {
self.load_args(backend); self.load_args(backend);
match CodeGenNumType::from(self.ret_layout) { let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
I32 => { 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.f32_ceil();
backend.code_builder.i32_trunc_s_f32()
} }
I64 => { (F64, NumCeiling) => {
backend.code_builder.f64_ceil(); 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(), _ => panic_ret_type(),
} }
} }
NumPowInt => todo!("{:?}", self.lowlevel), 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]), NumIsFinite => num_is_finite(backend, self.arguments[0]),
NumAtan => match self.ret_layout { NumAtan => match self.ret_layout {

View file

@ -4,6 +4,3 @@ version = "0.1.0"
authors = ["The Roc Contributors"] authors = ["The Roc Contributors"]
license = "UPL-1.0" license = "UPL-1.0"
edition = "2018" edition = "2018"
[dependencies]
arrayvec = "0.7.2"

View file

@ -38,7 +38,7 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) {
Default::default(), Default::default(),
target_info, target_info,
roc_reporting::report::RenderTarget::ColorTerminal, roc_reporting::report::RenderTarget::ColorTerminal,
Threading::Multi, Threading::AllAvailable,
); );
let module = res_module.unwrap(); let module = res_module.unwrap();

View file

@ -22,15 +22,12 @@ roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
roc_reporting = { path = "../../reporting" } roc_reporting = { path = "../../reporting" }
roc_debug_flags = { path = "../debug_flags" } roc_debug_flags = { path = "../debug_flags" }
morphic_lib = { path = "../../vendor/morphic_lib" }
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
parking_lot = "0.12" parking_lot = "0.12"
crossbeam = "0.8.1" crossbeam = "0.8.1"
num_cpus = "1.13.0"
[dev-dependencies] [dev-dependencies]
tempfile = "3.2.0"
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"
maplit = "1.0.2" maplit = "1.0.2"
indoc = "1.0.3" indoc = "1.0.3"

View file

@ -747,6 +747,7 @@ impl<'a> State<'a> {
ident_ids_by_module: SharedIdentIdsByModule, ident_ids_by_module: SharedIdentIdsByModule,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>, cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget, render: RenderTarget,
number_of_workers: usize,
) -> Self { ) -> Self {
let arc_shorthands = Arc::new(Mutex::new(MutMap::default())); let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
@ -770,7 +771,7 @@ impl<'a> State<'a> {
declarations_by_id: MutMap::default(), declarations_by_id: MutMap::default(),
exposed_symbols_by_module: MutMap::default(), exposed_symbols_by_module: MutMap::default(),
timings: 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)), cached_subs: Arc::new(Mutex::new(cached_subs)),
render, render,
} }
@ -1099,7 +1100,8 @@ pub enum LoadResult<'a> {
#[derive(Copy, Clone, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Threading { pub enum Threading {
Single, Single,
Multi, AllAvailable,
AtMost(usize),
} }
/// The loading process works like this, starting from the given filename (e.g. "main.roc"): /// The loading process works like this, starting from the given filename (e.g. "main.roc"):
@ -1157,21 +1159,32 @@ pub fn load<'a>(
render: RenderTarget, render: RenderTarget,
threading: Threading, threading: Threading,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> { ) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
enum Threads {
Single,
Many(usize),
}
let threads = {
if cfg!(target_family = "wasm") {
// When compiling to wasm, we cannot spawn extra threads // When compiling to wasm, we cannot spawn extra threads
// so we have a single-threaded implementation // so we have a single-threaded implementation
if threading == Threading::Single || cfg!(target_family = "wasm") { Threads::Single
load_single_threaded(
arena,
load_start,
src_dir,
exposed_types,
goal_phase,
target_info,
cached_subs,
render,
)
} else { } else {
load_multi_threaded( 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, arena,
load_start, load_start,
src_dir, src_dir,
@ -1180,7 +1193,18 @@ pub fn load<'a>(
target_info, target_info,
cached_subs, cached_subs,
render, render,
) ),
Threads::Many(threads) => load_multi_threaded(
arena,
load_start,
src_dir,
exposed_types,
goal_phase,
target_info,
cached_subs,
render,
threads,
),
} }
} }
@ -1210,6 +1234,7 @@ pub fn load_single_threaded<'a>(
.send(root_msg) .send(root_msg)
.map_err(|_| LoadingProblem::MsgChannelDied)?; .map_err(|_| LoadingProblem::MsgChannelDied)?;
let number_of_workers = 1;
let mut state = State::new( let mut state = State::new(
root_id, root_id,
target_info, target_info,
@ -1219,6 +1244,7 @@ pub fn load_single_threaded<'a>(
ident_ids_by_module, ident_ids_by_module,
cached_subs, cached_subs,
render, render,
number_of_workers,
); );
// We'll add tasks to this, and then worker threads will take tasks from it. // 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, target_info: TargetInfo,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>, cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget, render: RenderTarget,
available_threads: usize,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> { ) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let LoadStart { let LoadStart {
arc_modules, arc_modules,
@ -1399,6 +1426,28 @@ fn load_multi_threaded<'a>(
.. ..
} = load_start; } = 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( let mut state = State::new(
root_id, root_id,
target_info, target_info,
@ -1408,28 +1457,9 @@ fn load_multi_threaded<'a>(
ident_ids_by_module, ident_ids_by_module,
cached_subs, cached_subs,
render, 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 // 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 arenas = std::iter::repeat_with(Bump::new).take(num_workers);
let worker_arenas = arena.alloc(bumpalo::collections::Vec::from_iter_in(arenas, arena)); let worker_arenas = arena.alloc(bumpalo::collections::Vec::from_iter_in(arenas, arena));

View file

@ -14,4 +14,3 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
lazy_static = "1.4.0" lazy_static = "1.4.0"
static_assertions = "1.1.0" static_assertions = "1.1.0"
snafu = { version = "0.6.10", features = ["backtraces"] } snafu = { version = "0.6.10", features = ["backtraces"] }
arrayvec = "0.7.2"

View file

@ -21,7 +21,6 @@ roc_target = { path = "../roc_target" }
roc_error_macros = {path="../../error_macros"} roc_error_macros = {path="../../error_macros"}
roc_debug_flags = {path="../debug_flags"} roc_debug_flags = {path="../debug_flags"}
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] } hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
ven_graph = { path = "../../vendor/pathfinding" } ven_graph = { path = "../../vendor/pathfinding" }

View file

@ -1210,15 +1210,8 @@ pub fn optimize_when<'a>(
// bind the fields referenced in the pattern. For guards this happens separately, so // bind the fields referenced in the pattern. For guards this happens separately, so
// the pattern variables are defined when evaluating the guard. // the pattern variables are defined when evaluating the guard.
if !has_guard { if !has_guard {
branch = crate::ir::store_pattern( branch =
env, crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch);
procs,
layout_cache,
&pattern,
cond_layout,
cond_symbol,
branch,
);
} }
let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, 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), body: arena.alloc(decide),
}; };
crate::ir::store_pattern( crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, join)
env,
procs,
layout_cache,
&pattern,
cond_layout,
cond_symbol,
join,
)
} }
Chain { Chain {
test_chain, test_chain,

View file

@ -10,10 +10,12 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_can::abilities::AbilitiesStore; use roc_can::abilities::AbilitiesStore;
use roc_can::expr::{AnnotatedMark, ClosureData, IntValue}; use roc_can::expr::{AnnotatedMark, ClosureData, IntValue};
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
use roc_collections::VecMap;
use roc_debug_flags::{ use roc_debug_flags::{
dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE,
ROC_PRINT_IR_AFTER_SPECIALIZATION, ROC_PRINT_IR_AFTER_SPECIALIZATION,
}; };
use roc_error_macros::internal_error;
use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId}; use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; 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)] #[derive(Clone, Debug)]
pub struct Procs<'a> { pub struct Procs<'a> {
pub partial_procs: PartialProcs<'a>, pub partial_procs: PartialProcs<'a>,
@ -753,7 +906,7 @@ pub struct Procs<'a> {
specialized: Specialized<'a>, specialized: Specialized<'a>,
pub runtime_errors: BumpMap<Symbol, &'a str>, pub runtime_errors: BumpMap<Symbol, &'a str>,
pub externals_we_need: BumpMap<ModuleId, ExternalSpecializations>, 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> { impl<'a> Procs<'a> {
@ -767,38 +920,9 @@ impl<'a> Procs<'a> {
specialized: Specialized::default(), specialized: Specialized::default(),
runtime_errors: BumpMap::new_in(arena), runtime_errors: BumpMap::new_in(arena),
externals_we_need: 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)] #[derive(Clone, Debug, PartialEq)]
@ -2166,9 +2290,9 @@ pub fn specialize_all<'a>(
specialize_host_specializations(env, &mut procs, layout_cache, specializations_for_host); specialize_host_specializations(env, &mut procs, layout_cache, specializations_for_host);
debug_assert!( debug_assert!(
procs.needed_symbol_specializations.is_empty(), procs.symbol_specializations.is_empty(),
"{:?}", "{:?}",
&procs.needed_symbol_specializations &procs.symbol_specializations
); );
procs procs
@ -2503,11 +2627,10 @@ fn specialize_external<'a>(
// An argument from the closure list may have taken on a specialized symbol // 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 // name during the evaluation of the def body. If this is the case, load the
// specialized name rather than the original captured name! // specialized name rather than the original captured name!
let mut get_specialized_name = |symbol, layout| { let mut get_specialized_name = |symbol| {
procs procs
.needed_symbol_specializations .symbol_specializations
.remove(&(symbol, layout)) .remove_single(symbol)
.map(|(_, specialized)| specialized)
.unwrap_or(symbol) .unwrap_or(symbol)
}; };
@ -2545,7 +2668,7 @@ fn specialize_external<'a>(
union_layout, union_layout,
}; };
let symbol = get_specialized_name(**symbol, **layout); let symbol = get_specialized_name(**symbol);
specialized_body = Stmt::Let( specialized_body = Stmt::Let(
symbol, symbol,
@ -2588,7 +2711,7 @@ fn specialize_external<'a>(
structure: Symbol::ARG_CLOSURE, structure: Symbol::ARG_CLOSURE,
}; };
let symbol = get_specialized_name(**symbol, **layout); let symbol = get_specialized_name(**symbol);
specialized_body = Stmt::Let( specialized_body = Stmt::Let(
symbol, symbol,
@ -2633,11 +2756,10 @@ fn specialize_external<'a>(
let proc_args: Vec<_> = proc_args let proc_args: Vec<_> = proc_args
.iter() .iter()
.map(|&(layout, symbol)| { .map(|&(layout, symbol)| {
// Grab the specialization symbol, if it exists.
let symbol = procs let symbol = procs
.needed_symbol_specializations .symbol_specializations
// We can remove the specialization since this is the definition site. .remove_single(symbol)
.remove(&(symbol, layout))
.map(|(_, specialized_symbol)| specialized_symbol)
.unwrap_or(symbol); .unwrap_or(symbol);
(layout, symbol) (layout, symbol)
@ -3351,18 +3473,7 @@ pub fn with_hole<'a>(
); );
let outer_symbol = env.unique_symbol(); let outer_symbol = env.unique_symbol();
let pattern_layout = layout_cache stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
.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,
);
// convert the def body, store in outer_symbol // convert the def body, store in outer_symbol
with_hole( with_hole(
@ -3405,7 +3516,9 @@ pub fn with_hole<'a>(
can_reuse_symbol(env, procs, &roc_can::expr::Expr::Var(symbol)) can_reuse_symbol(env, procs, &roc_can::expr::Expr::Var(symbol))
{ {
let real_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; symbol = real_symbol;
} }
@ -3484,8 +3597,12 @@ pub fn with_hole<'a>(
match can_reuse_symbol(env, procs, &loc_arg_expr.value) { match can_reuse_symbol(env, procs, &loc_arg_expr.value) {
// Opaques decay to their argument. // Opaques decay to their argument.
ReuseSymbol::Value(symbol) => { ReuseSymbol::Value(symbol) => {
let real_name = let real_name = procs.symbol_specializations.get_or_insert(
reuse_symbol_or_specialize(env, procs, layout_cache, symbol, arg_var); env,
layout_cache,
symbol,
arg_var,
);
let mut result = hole.clone(); let mut result = hole.clone();
substitute_in_exprs(arena, &mut result, assigned, real_name); substitute_in_exprs(arena, &mut result, assigned, real_name);
result result
@ -3538,9 +3655,8 @@ pub fn with_hole<'a>(
can_fields.push(Field::Function(symbol, variable)); can_fields.push(Field::Function(symbol, variable));
} }
Value(symbol) => { Value(symbol) => {
let reusable = reuse_symbol_or_specialize( let reusable = procs.symbol_specializations.get_or_insert(
env, env,
procs,
layout_cache, layout_cache,
symbol, symbol,
field.var, field.var,
@ -4353,8 +4469,20 @@ pub fn with_hole<'a>(
} }
} }
} }
Value(function_symbol) => match full_layout { Value(function_symbol) => {
RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => { let function_symbol = procs.symbol_specializations.get_or_insert(
env,
layout_cache,
function_symbol,
fn_var,
);
match full_layout {
RawFunctionLayout::Function(
arg_layouts,
lambda_set,
ret_layout,
) => {
let closure_data_symbol = function_symbol; let closure_data_symbol = function_symbol;
result = match_on_lambda_set( result = match_on_lambda_set(
@ -4371,7 +4499,8 @@ pub fn with_hole<'a>(
RawFunctionLayout::ZeroArgumentThunk(_) => { RawFunctionLayout::ZeroArgumentThunk(_) => {
unreachable!("calling a non-closure layout") unreachable!("calling a non-closure layout")
} }
}, }
}
UnspecializedExpr(symbol) => { UnspecializedExpr(symbol) => {
match procs.ability_member_aliases.get(symbol).unwrap() { match procs.ability_member_aliases.get(symbol).unwrap() {
&AbilityMember(member) => { &AbilityMember(member) => {
@ -5521,7 +5650,6 @@ pub fn from_can<'a>(
} }
LetNonRec(def, cont, outer_annotation) => { LetNonRec(def, cont, outer_annotation) => {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value { if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
// dbg!(symbol, &def.loc_expr.value);
match def.loc_expr.value { match def.loc_expr.value {
roc_can::expr::Expr::Closure(closure_data) => { roc_can::expr::Expr::Closure(closure_data) => {
register_capturing_closure(env, procs, layout_cache, *symbol, 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 rest = from_can(env, variable, cont.value, procs, layout_cache);
let needs_def_specializations = procs // Remove all the requested symbol specializations now, since this is the
.needed_symbol_specializations // def site and hence we won't need them any higher up.
.keys() let mut needed_specializations =
.any(|(s, _)| s == symbol); 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( return with_hole(
env, env,
def.loc_expr.value, def.loc_expr.value,
@ -5674,16 +5804,9 @@ pub fn from_can<'a>(
let mut stmt = rest; 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 { if needed_specializations.len() == 1 {
let ((_, _wanted_layout), (var, specialized_symbol)) = let (_specialization_mark, (var, specialized_symbol)) =
needed_specializations.pop().unwrap(); needed_specializations.next().unwrap();
// Unify the expr_var with the requested specialization once. // Unify the expr_var with the requested specialization once.
let _res = let _res =
@ -5700,7 +5823,7 @@ pub fn from_can<'a>(
); );
} else { } else {
// Need to eat the cost and create a specialized version of the body for each specialization. // 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 needed_specializations
{ {
use crate::copy::deep_copy_type_vars_into_expr; use crate::copy::deep_copy_type_vars_into_expr;
@ -5744,37 +5867,18 @@ pub fn from_can<'a>(
Err(_) => todo!(), Err(_) => todo!(),
}; };
if let Pattern::Identifier(symbol) = mono_pattern { if let Pattern::Identifier(_symbol) = mono_pattern {
let mut hole = internal_error!("Identifier patterns should be handled in a higher code pass!")
env.arena
.alloc(from_can(env, variable, cont.value, procs, layout_cache));
for (symbol, variable, expr) in assignments {
let stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole);
hole = env.arena.alloc(stmt);
} }
with_hole(
env,
def.loc_expr.value,
def.expr_var,
procs,
layout_cache,
symbol,
hole,
)
} else {
// convert the continuation // convert the continuation
let mut stmt = from_can(env, variable, cont.value, procs, layout_cache); let mut stmt = from_can(env, variable, cont.value, procs, layout_cache);
// layer on any default record fields // layer on any default record fields
for (symbol, variable, expr) in assignments { 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 let specialization_symbol = procs
.remove_single_symbol_specialization(symbol, layout) .symbol_specializations
.remove_single(symbol)
// Can happen when the symbol was never used under this body, and hence has no // Can happen when the symbol was never used under this body, and hence has no
// requested specialization. // requested specialization.
.unwrap_or(symbol); .unwrap_or(symbol);
@ -5791,30 +5895,11 @@ pub fn from_can<'a>(
); );
} }
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 { if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
store_pattern( store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
env,
procs,
layout_cache,
&mono_pattern,
pattern_layout,
outer_symbol,
stmt,
)
} else { } else {
let outer_symbol = env.unique_symbol(); let outer_symbol = env.unique_symbol();
stmt = store_pattern( stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
env,
procs,
layout_cache,
&mono_pattern,
pattern_layout,
outer_symbol,
stmt,
);
// convert the def body, store in outer_symbol // convert the def body, store in outer_symbol
with_hole( with_hole(
@ -5828,7 +5913,6 @@ pub fn from_can<'a>(
) )
} }
} }
}
_ => { _ => {
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
@ -6380,19 +6464,10 @@ pub fn store_pattern<'a>(
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>, can_pat: &Pattern<'a>,
pattern_layout: Layout,
outer_symbol: Symbol, outer_symbol: Symbol,
stmt: Stmt<'a>, stmt: Stmt<'a>,
) -> Stmt<'a> { ) -> Stmt<'a> {
match store_pattern_help( match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) {
env,
procs,
layout_cache,
can_pat,
pattern_layout,
outer_symbol,
stmt,
) {
StorePattern::Productive(new) => new, StorePattern::Productive(new) => new,
StorePattern::NotProductive(new) => new, StorePattern::NotProductive(new) => new,
} }
@ -6412,7 +6487,6 @@ fn store_pattern_help<'a>(
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>, can_pat: &Pattern<'a>,
pattern_layout: Layout,
outer_symbol: Symbol, outer_symbol: Symbol,
mut stmt: Stmt<'a>, mut stmt: Stmt<'a>,
) -> StorePattern<'a> { ) -> StorePattern<'a> {
@ -6423,7 +6497,8 @@ fn store_pattern_help<'a>(
// An identifier in a pattern can define at most one specialization! // 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. // Remove any requested specializations for this name now, since this is the definition site.
let specialization_symbol = procs 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 // Can happen when the symbol was never used under this body, and hence has no
// requested specialization. // requested specialization.
.unwrap_or(*symbol); .unwrap_or(*symbol);
@ -6444,16 +6519,8 @@ fn store_pattern_help<'a>(
return StorePattern::NotProductive(stmt); return StorePattern::NotProductive(stmt);
} }
NewtypeDestructure { arguments, .. } => match arguments.as_slice() { NewtypeDestructure { arguments, .. } => match arguments.as_slice() {
[(pattern, layout)] => { [(pattern, _layout)] => {
return store_pattern_help( return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
env,
procs,
layout_cache,
pattern,
*layout,
outer_symbol,
stmt,
);
} }
_ => { _ => {
let mut fields = Vec::with_capacity_in(arguments.len(), env.arena); let mut fields = Vec::with_capacity_in(arguments.len(), env.arena);
@ -6490,16 +6557,8 @@ fn store_pattern_help<'a>(
); );
} }
OpaqueUnwrap { argument, .. } => { OpaqueUnwrap { argument, .. } => {
let (pattern, layout) = &**argument; let (pattern, _layout) = &**argument;
return store_pattern_help( return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
env,
procs,
layout_cache,
pattern,
*layout,
outer_symbol,
stmt,
);
} }
RecordDestructure(destructs, [_single_field]) => { RecordDestructure(destructs, [_single_field]) => {
@ -6507,7 +6566,8 @@ fn store_pattern_help<'a>(
match &destruct.typ { match &destruct.typ {
DestructType::Required(symbol) => { DestructType::Required(symbol) => {
let specialization_symbol = procs 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 // Can happen when the symbol was never used under this body, and hence has no
// requested specialization. // requested specialization.
.unwrap_or(*symbol); .unwrap_or(*symbol);
@ -6525,7 +6585,6 @@ fn store_pattern_help<'a>(
procs, procs,
layout_cache, layout_cache,
guard_pattern, guard_pattern,
destruct.layout,
outer_symbol, outer_symbol,
stmt, stmt,
); );
@ -6598,7 +6657,8 @@ fn store_tag_pattern<'a>(
Identifier(symbol) => { Identifier(symbol) => {
// Pattern can define only one specialization // Pattern can define only one specialization
let symbol = procs let symbol = procs
.remove_single_symbol_specialization(*symbol, arg_layout) .symbol_specializations
.remove_single(*symbol)
.unwrap_or(*symbol); .unwrap_or(*symbol);
// store immediately in the given symbol // store immediately in the given symbol
@ -6619,15 +6679,7 @@ fn store_tag_pattern<'a>(
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
// first recurse, continuing to unpack symbol // first recurse, continuing to unpack symbol
match store_pattern_help( match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
env,
procs,
layout_cache,
argument,
arg_layout,
symbol,
stmt,
) {
StorePattern::Productive(new) => { StorePattern::Productive(new) => {
is_productive = true; is_productive = true;
stmt = new; stmt = new;
@ -6687,7 +6739,8 @@ fn store_newtype_pattern<'a>(
Identifier(symbol) => { Identifier(symbol) => {
// store immediately in the given symbol, removing it specialization if it had any // store immediately in the given symbol, removing it specialization if it had any
let specialization_symbol = procs 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 // Can happen when the symbol was never used under this body, and hence has no
// requested specialization. // requested specialization.
.unwrap_or(*symbol); .unwrap_or(*symbol);
@ -6714,15 +6767,7 @@ fn store_newtype_pattern<'a>(
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
// first recurse, continuing to unpack symbol // first recurse, continuing to unpack symbol
match store_pattern_help( match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
env,
procs,
layout_cache,
argument,
arg_layout,
symbol,
stmt,
) {
StorePattern::Productive(new) => { StorePattern::Productive(new) => {
is_productive = true; is_productive = true;
stmt = new; stmt = new;
@ -6770,7 +6815,8 @@ fn store_record_destruct<'a>(
// A destructure can define at most one specialization! // A destructure can define at most one specialization!
// Remove any requested specializations for this name now, since this is the definition site. // Remove any requested specializations for this name now, since this is the definition site.
let specialization_symbol = procs 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 // Can happen when the symbol was never used under this body, and hence has no
// requested specialization. // requested specialization.
.unwrap_or(*symbol); .unwrap_or(*symbol);
@ -6785,7 +6831,8 @@ fn store_record_destruct<'a>(
DestructType::Guard(guard_pattern) => match &guard_pattern { DestructType::Guard(guard_pattern) => match &guard_pattern {
Identifier(symbol) => { Identifier(symbol) => {
let specialization_symbol = procs 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 // Can happen when the symbol was never used under this body, and hence has no
// requested specialization. // requested specialization.
.unwrap_or(*symbol); .unwrap_or(*symbol);
@ -6823,15 +6870,7 @@ fn store_record_destruct<'a>(
_ => { _ => {
let symbol = env.unique_symbol(); let symbol = env.unique_symbol();
match store_pattern_help( match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) {
env,
procs,
layout_cache,
guard_pattern,
destruct.layout,
symbol,
stmt,
) {
StorePattern::Productive(new) => { StorePattern::Productive(new) => {
stmt = new; stmt = new;
stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt)); 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>( fn possible_reuse_symbol_or_specialize<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
@ -6942,7 +6942,9 @@ fn possible_reuse_symbol_or_specialize<'a>(
) -> Symbol { ) -> Symbol {
match can_reuse_symbol(env, procs, expr) { match can_reuse_symbol(env, procs, expr) {
ReuseSymbol::Value(symbol) => { 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(), _ => env.unique_symbol(),
} }
@ -6987,16 +6989,13 @@ where
let result = build_rest(env, procs, layout_cache); let result = build_rest(env, procs, layout_cache);
// The specializations we wanted of the symbol on the LHS of this alias. // The specializations we wanted of the symbol on the LHS of this alias.
let needed_specializations_of_left = procs let needed_specializations_of_left = procs.symbol_specializations.remove(left);
.needed_symbol_specializations
.drain_filter(|(s, _), _| s == &left)
.collect::<std::vec::Vec<_>>();
if procs.is_imported_module_thunk(right) { if procs.is_imported_module_thunk(right) {
// if this is an imported symbol, then we must make sure it is // if this is an imported symbol, then we must make sure it is
// specialized, and wrap the original in a function pointer. // specialized, and wrap the original in a function pointer.
let mut result = result; 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); add_needed_external(procs, env, variable, right);
let res_layout = layout_cache.from_var(env.arena, variable, env.subs); 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". // We need to lift all specializations of "left" to be specializations of "right".
let mut scratchpad_update_specializations = std::vec::Vec::new(); 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 for (specialization_mark, (specialized_var, specialized_sym)) in
needed_specializations_of_left.into_iter() needed_specializations_of_left
{ {
let old_specialized_sym = procs let old_specialized_sym = procs.symbol_specializations.get_or_insert_known(
.needed_symbol_specializations right,
.insert((right, layout), (specialized_var, specialized_sym)); specialization_mark,
specialized_var,
specialized_sym,
);
if let Some((_, old_specialized_sym)) = old_specialized_sym { if let Some((_, old_specialized_sym)) = old_specialized_sym {
scratchpad_update_specializations.push((old_specialized_sym, specialized_sym)); scratchpad_update_specializations.push((old_specialized_sym, specialized_sym));

View file

@ -476,10 +476,13 @@ impl Pools {
self.0.iter() self.0.iter()
} }
pub fn split_last(&self) -> (&Vec<Variable>, &[Vec<Variable>]) { pub fn split_last(mut self) -> (Vec<Variable>, Vec<Vec<Variable>>) {
self.0 let last = self
.split_last() .0
.unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools")) .pop()
.unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools"));
(last, self.0)
} }
pub fn extend_to(&mut self, n: usize) { pub fn extend_to(&mut self, n: usize) {
@ -737,8 +740,7 @@ fn solve(
// pop pool // pop pool
generalize(subs, young_mark, visit_mark, next_rank, pools); generalize(subs, young_mark, visit_mark, next_rank, pools);
debug_assert!(pools.get(next_rank).is_empty());
pools.get_mut(next_rank).clear();
// check that things went well // check that things went well
dbg_do!(ROC_VERIFY_RIGID_LET_GENERALIZED, { dbg_do!(ROC_VERIFY_RIGID_LET_GENERALIZED, {
@ -2426,7 +2428,7 @@ fn generalize(
young_rank: Rank, young_rank: Rank,
pools: &mut Pools, 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); let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars);
// Get the ranks right for each entry. // 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 // For variables that have rank lowerer than young_rank, register them in
// the appropriate old pool if they are not redundant. // the appropriate old pool if they are not redundant.
for vars in all_but_last_pool { for vars in all_but_last_pool {
for &var in vars { for var in vars {
if !subs.redundant(var) { if !subs.redundant(var) {
let rank = subs.get_rank(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, // For variables with rank young_rank, if rank < young_rank: register in old pool,
// otherwise generalize // otherwise generalize
for &var in last_pool { for var in last_pool.drain(..) {
if !subs.redundant(var) { if !subs.redundant(var) {
let desc_rank = subs.get_rank(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. /// Sort the variables into buckets by rank.
#[inline]
fn pool_to_rank_table( fn pool_to_rank_table(
subs: &mut Subs, subs: &mut Subs,
young_mark: Mark, young_mark: Mark,
young_rank: Rank, young_rank: Rank,
young_vars: &[Variable], mut young_vars: Vec<Variable>,
) -> Pools { ) -> Pools {
let mut pools = Pools::new(young_rank.into_usize() + 1); let mut pools = Pools::new(young_rank.into_usize() + 1);
// the vast majority of young variables have young_rank // the vast majority of young variables have young_rank
// using `retain` here prevents many `pools.get_mut(young_rank)` lookups let mut i = 0;
let mut young_vars = young_vars.to_vec(); while i < young_vars.len() {
young_vars.retain(|var| { let var = young_vars[i];
let rank = subs.get_rank_set_mark(*var, young_mark); let rank = subs.get_rank_set_mark(var, young_mark);
if rank != young_rank { if rank != young_rank {
debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); debug_assert!(rank.into_usize() < young_rank.into_usize() + 1);
pools.get_mut(rank).push(*var); pools.get_mut(rank).push(var);
false
// swap an element in; don't increment i
young_vars.swap_remove(i);
} else { } else {
true i += 1;
}
} }
});
std::mem::swap(pools.get_mut(young_rank), &mut young_vars); std::mem::swap(pools.get_mut(young_rank), &mut young_vars);

View file

@ -6234,4 +6234,22 @@ mod solve_expr {
"F b -> b", "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",
)
}
} }

View file

@ -3270,3 +3270,51 @@ fn dec_float_suffix() {
i128 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
);
}

View file

@ -3267,7 +3267,6 @@ fn polymophic_expression_captured_inside_closure() {
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
#[ignore = "Compile polymorphic functions"]
fn issue_2322() { fn issue_2322() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -3421,7 +3420,6 @@ fn polymorphic_def_used_in_closure() {
#[test] #[test]
#[cfg(any(feature = "gen-llvm"))] #[cfg(any(feature = "gen-llvm"))]
#[ignore = "This still doesn't work... yet"]
fn polymorphic_lambda_set_usage() { fn polymorphic_lambda_set_usage() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
@ -3429,6 +3427,7 @@ fn polymorphic_lambda_set_usage() {
id1 = \x -> x id1 = \x -> x
id2 = \y -> y id2 = \y -> y
id = if True then id1 else id2 id = if True then id1 else id2
id 9u8 id 9u8
"# "#
), ),
@ -3436,3 +3435,21 @@ fn polymorphic_lambda_set_usage() {
u8 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
)
}

View file

@ -60,7 +60,7 @@ fn create_llvm_module<'a>(
Default::default(), Default::default(),
target_info, target_info,
RenderTarget::ColorTerminal, RenderTarget::ColorTerminal,
Threading::Multi, Threading::AllAvailable,
); );
let mut loaded = match loaded { let mut loaded = match loaded {

View file

@ -19,6 +19,5 @@ roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
roc_reporting = { path = "../../reporting" } roc_reporting = { path = "../../reporting" }
test_mono_macros = { path = "../test_mono_macros" } test_mono_macros = { path = "../test_mono_macros" }
pretty_assertions = "1.0.0"
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.8.0", features = ["collections"] }
indoc = "1.0.3" indoc = "1.0.3"

View file

@ -1123,8 +1123,17 @@ impl Type {
ext.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables); ext.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables);
} }
} }
DelayedAlias(AliasCommon { .. }) => { DelayedAlias(AliasCommon {
// do nothing, yay 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 { HostExposedAlias {
type_arguments: type_args, type_arguments: type_args,

View file

@ -28,6 +28,3 @@ peg = "0.8.0"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"
tempfile = "3.2.0"
uuid = { version = "0.8.2", features = ["v4"] }
indoc = "1.0.3"

View file

@ -435,7 +435,7 @@ pub fn load_modules_for_files(filenames: Vec<PathBuf>) -> Vec<LoadedModule> {
Default::default(), Default::default(),
roc_target::TargetInfo::default_x86_64(), // This is just type-checking for docs, so "target" doesn't matter roc_target::TargetInfo::default_x86_64(), // This is just type-checking for docs, so "target" doesn't matter
roc_reporting::report::RenderTarget::ColorTerminal, roc_reporting::report::RenderTarget::ColorTerminal,
Threading::Multi, Threading::AllAvailable,
) { ) {
Ok(loaded) => modules.push(loaded), Ok(loaded) => modules.push(loaded),
Err(LoadingProblem::FormattedReport(report)) => { Err(LoadingProblem::FormattedReport(report)) => {

View file

@ -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 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 mut var_store = VarStore::default();
let dep_idents = IdentIds::exposed_builtins(8); let dep_idents = IdentIds::exposed_builtins(8);

View file

@ -330,7 +330,7 @@ pub mod test_ed_model {
writeln!(file, "{}", clean_code_str) writeln!(file, "{}", clean_code_str)
.unwrap_or_else(|_| panic!("Failed to write {:?} to file: {:?}", clean_code_str, file)); .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( let mut ed_model = init_dummy_model(
clean_code_str, clean_code_str,

View file

@ -74,7 +74,9 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
#[no_mangle] #[no_mangle]
pub extern "C" fn rust_main() -> i32 { 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 arg = RocStr::from(arg.as_str());
let size = unsafe { roc_main_size() } as usize; let size = unsafe { roc_main_size() } as usize;

View file

@ -29,4 +29,3 @@ roc_target = { path = "../compiler/roc_target" }
roc_test_utils = { path = "../test_utils" } roc_test_utils = { path = "../test_utils" }
pretty_assertions = "1.0.0" pretty_assertions = "1.0.0"
indoc = "1.0.3" indoc = "1.0.3"
tempfile = "3.2.0"