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

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) => {
let mut buf;

View file

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

View file

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

View file

@ -2,29 +2,17 @@ use std::ffi::OsStr;
use std::path::{Path, PathBuf};
use crate::FormatMode;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_error_macros::{internal_error, user_error};
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_fmt::Buf;
use roc_module::called_via::{BinOp, UnaryOp};
use roc_parse::ast::{
AbilityMember, AssignedField, Collection, Expr, Has, HasClause, Pattern, Spaced, StrLiteral,
StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
};
use roc_parse::header::{
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, PackageEntry,
PackageName, PlatformHeader, PlatformRequires, To, TypedIdent,
};
use roc_fmt::spaces::RemoveSpaces;
use roc_fmt::{Ast, Buf};
use roc_parse::{
ast::{Def, Module},
ident::UppercaseIdent,
module::{self, module_defs},
parser::{Parser, SyntaxError},
state::State,
};
use roc_region::all::{Loc, Region};
fn flatten_directories(files: std::vec::Vec<PathBuf>) -> std::vec::Vec<PathBuf> {
let mut to_flatten = files;
@ -166,12 +154,6 @@ pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), Str
Ok(())
}
#[derive(Debug, PartialEq)]
struct Ast<'a> {
module: Module<'a>,
defs: Vec<'a, Loc<Def<'a>>>,
}
fn parse_all<'a>(arena: &'a Bump, src: &'a str) -> Result<Ast<'a>, SyntaxError<'a>> {
let (module, state) = module::parse_header(arena, State::new(src.as_bytes()))
.map_err(|e| SyntaxError::Header(e.problem))?;
@ -189,575 +171,3 @@ fn fmt_all<'a>(arena: &'a Bump, buf: &mut Buf<'a>, ast: &'a Ast) {
buf.fmt_end_of_file();
}
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
///
/// Currently this consists of:
/// * Removing newlines
/// * Removing comments
/// * Removing parens in Exprs
///
/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting)
/// - but there are currently several bugs where they're _not_ preserved.
/// TODO: ensure formatting retains comments
trait RemoveSpaces<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self;
}
impl<'a> RemoveSpaces<'a> for Ast<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
Ast {
module: self.module.remove_spaces(arena),
defs: {
let mut defs = Vec::with_capacity_in(self.defs.len(), arena);
for d in &self.defs {
defs.push(d.remove_spaces(arena))
}
defs
},
}
}
}
impl<'a> RemoveSpaces<'a> for Module<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match self {
Module::Interface { header } => Module::Interface {
header: InterfaceHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
before_header: &[],
after_interface_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
},
},
Module::App { header } => Module::App {
header: AppHeader {
name: header.name.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
to: header.to.remove_spaces(arena),
before_header: &[],
after_app_keyword: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
before_to: &[],
after_to: &[],
},
},
Module::Platform { header } => Module::Platform {
header: PlatformHeader {
name: header.name.remove_spaces(arena),
requires: header.requires.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
before_header: &[],
after_platform_keyword: &[],
before_requires: &[],
after_requires: &[],
before_exposes: &[],
after_exposes: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
},
},
Module::Hosted { header } => Module::Hosted {
header: HostedHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
generates: header.generates.remove_spaces(arena),
generates_with: header.generates_with.remove_spaces(arena),
before_header: &[],
after_hosted_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
before_generates: &[],
after_generates: &[],
before_with: &[],
after_with: &[],
},
},
}
}
}
impl<'a> RemoveSpaces<'a> for &'a str {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
self
}
}
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for To<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
To::ExistingPackage(a) => To::ExistingPackage(a),
To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
TypedIdent {
ident: self.ident.remove_spaces(arena),
spaces_before_colon: &[],
ann: self.ann.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PlatformRequires {
rigids: self.rigids.remove_spaces(arena),
signature: self.signature.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PackageEntry {
shorthand: self.shorthand,
spaces_after_shorthand: &[],
package_name: self.package_name.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
}
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
self.as_ref().map(|a| a.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let res = self.value.remove_spaces(arena);
Loc::at(Region::zero(), res)
}
}
impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
(self.0.remove_spaces(arena), self.1.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.items.len(), arena);
for item in self.items {
items.push(item.remove_spaces(arena));
}
Collection::with_items(items.into_bump_slice())
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.len(), arena);
for item in *self {
let res = item.remove_spaces(arena);
items.push(res);
}
items.into_bump_slice()
}
}
impl<'a> RemoveSpaces<'a> for UnaryOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for BinOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
arena.alloc((*self).remove_spaces(arena))
}
}
impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use TypeDef::*;
match *self {
Alias {
header: TypeHeader { name, vars },
ann,
} => Alias {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
ann: ann.remove_spaces(arena),
},
Opaque {
header: TypeHeader { name, vars },
typ,
} => Opaque {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
typ: typ.remove_spaces(arena),
},
Ability {
header: TypeHeader { name, vars },
loc_has,
members,
} => Ability {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
loc_has: loc_has.remove_spaces(arena),
members: members.remove_spaces(arena),
},
}
}
}
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use ValueDef::*;
match *self {
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
Body(a, b) => Body(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
AnnotatedBody {
ann_pattern,
ann_type,
comment: _,
body_pattern,
body_expr,
} => AnnotatedBody {
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
ann_type: arena.alloc(ann_type.remove_spaces(arena)),
comment: None,
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
},
Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))),
}
}
}
impl<'a> RemoveSpaces<'a> for Def<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Def::Type(def) => Def::Type(def.remove_spaces(arena)),
Def::Value(def) => Def::Value(def.remove_spaces(arena)),
Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Has<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Has::Has
}
}
impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
AbilityMember {
name: self.name.remove_spaces(arena),
typ: self.typ.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
WhenBranch {
patterns: self.patterns.remove_spaces(arena),
value: self.value.remove_spaces(arena),
guard: self.guard.remove_spaces(arena),
}
}
}
impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
AssignedField::Malformed(a) => AssignedField::Malformed(a),
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)),
StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrSegment::Plaintext(t) => StrSegment::Plaintext(t),
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for Expr<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Expr::Float(a) => Expr::Float(a),
Expr::Num(a) => Expr::Num(a),
Expr::NonBase10Int {
string,
base,
is_negative,
} => Expr::NonBase10Int {
string,
base,
is_negative,
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b),
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
update: arena.alloc(update.remove_spaces(arena)),
fields: fields.remove_spaces(arena),
},
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
Expr::Underscore(a) => Expr::Underscore(a),
Expr::Tag(a) => Expr::Tag(a),
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
Expr::Closure(a, b) => Expr::Closure(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Defs(a, b) => {
Expr::Defs(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::Backpassing(a, b, c) => Expr::Backpassing(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
arena.alloc(c.remove_spaces(arena)),
),
Expr::Expect(a, b) => Expr::Expect(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Apply(a, b, c) => Expr::Apply(
arena.alloc(a.remove_spaces(arena)),
b.remove_spaces(arena),
c,
),
Expr::BinOps(a, b) => {
Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::UnaryOp(a, b) => {
Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))),
Expr::When(a, b) => {
Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::ParensAround(a) => {
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
a.remove_spaces(arena)
}
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b),
Expr::MalformedClosure => Expr::MalformedClosure,
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
Expr::SingleQuote(a) => Expr::Num(a),
}
}
}
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Pattern::Identifier(a) => Pattern::Identifier(a),
Pattern::Tag(a) => Pattern::Tag(a),
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
Pattern::Apply(a, b) => Pattern::Apply(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)),
Pattern::RequiredField(a, b) => {
Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::OptionalField(a, b) => {
Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::NumLiteral(a) => Pattern::NumLiteral(a),
Pattern::NonBase10Literal {
string,
base,
is_negative,
} => Pattern::NonBase10Literal {
string,
base,
is_negative,
},
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
Pattern::Underscore(a) => Pattern::Underscore(a),
Pattern::Malformed(a) => Pattern::Malformed(a),
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b),
Pattern::QualifiedIdentifier { module_name, ident } => {
Pattern::QualifiedIdentifier { module_name, ident }
}
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
}
}
}
impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
TypeAnnotation::Function(a, b) => TypeAnnotation::Function(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
TypeAnnotation::As(a, _, c) => {
TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c)
}
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
fields: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion {
ext: ext.remove_spaces(arena),
tags: tags.remove_spaces(arena),
},
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
arena.alloc(annot.remove_spaces(arena)),
arena.alloc(has_clauses.remove_spaces(arena)),
),
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
}
}
}
impl<'a> RemoveSpaces<'a> for HasClause<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
HasClause {
var: self.var.remove_spaces(arena),
ability: self.ability.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Tag<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Tag::Apply { name, args } => Tag::Apply {
name: name.remove_spaces(arena),
args: args.remove_spaces(arena),
},
Tag::Malformed(a) => Tag::Malformed(a),
Tag::SpaceBefore(a, _) => a.remove_spaces(arena),
Tag::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}

View file

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

View file

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

View file

@ -14,10 +14,29 @@ mod cli_run {
known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError,
ValgrindErrorXWhat,
};
use const_format::concatcp;
use indoc::indoc;
use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_FORMAT, CMD_RUN};
use roc_test_utils::assert_multiline_str_eq;
use serial_test::serial;
use std::iter;
use std::path::{Path, PathBuf};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE);
const VALGRIND_FLAG: &str = concatcp!("--", roc_cli::FLAG_VALGRIND);
const LINKER_FLAG: &str = concatcp!("--", roc_cli::FLAG_LINKER);
const CHECK_FLAG: &str = concatcp!("--", roc_cli::FLAG_CHECK);
#[allow(dead_code)]
const TARGET_FLAG: &str = concatcp!("--", roc_cli::FLAG_TARGET);
#[derive(Debug, EnumIter)]
enum CliMode {
RocBuild,
RocRun,
Roc,
}
#[cfg(not(debug_assertions))]
use roc_collections::all::MutMap;
@ -65,7 +84,7 @@ mod cli_run {
}
fn check_compile_error(file: &Path, flags: &[&str], expected: &str) {
let compile_out = run_roc(&[&["check", file.to_str().unwrap()], flags].concat());
let compile_out = run_roc([CMD_CHECK, file.to_str().unwrap()].iter().chain(flags), &[]);
let err = compile_out.stdout.trim();
let err = strip_colors(err);
@ -77,19 +96,41 @@ mod cli_run {
}
fn check_format_check_as_expected(file: &Path, expects_success_exit_code: bool) {
let flags = &["--check"];
let out = run_roc(&[&["format", file.to_str().unwrap()], &flags[..]].concat());
if expects_success_exit_code {
assert!(out.status.success());
} else {
assert!(!out.status.success());
}
let out = run_roc([CMD_FORMAT, file.to_str().unwrap(), CHECK_FLAG], &[]);
assert_eq!(out.status.success(), expects_success_exit_code);
}
fn build_example(file: &Path, flags: &[&str]) -> Out {
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags].concat());
if !compile_out.stderr.is_empty() {
panic!("roc build had stderr: {}", compile_out.stderr);
fn run_roc_on<'a, I: IntoIterator<Item = &'a str>>(
file: &'a Path,
args: I,
stdin: &[&str],
input_file: Option<PathBuf>,
) -> Out {
let compile_out = match input_file {
Some(input_file) => run_roc(
// converting these all to String avoids lifetime issues
args.into_iter().map(|arg| arg.to_string()).chain([
file.to_str().unwrap().to_string(),
input_file.to_str().unwrap().to_string(),
]),
stdin,
),
None => run_roc(
args.into_iter().chain(iter::once(file.to_str().unwrap())),
stdin,
),
};
if !compile_out.stderr.is_empty() &&
// If there is any stderr, it should be reporting the runtime and that's it!
!(compile_out.stderr.starts_with("runtime: ")
&& compile_out.stderr.ends_with("ms\n"))
{
panic!(
"`roc` command had unexpected stderr: {}",
compile_out.stderr
);
}
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
@ -106,86 +147,104 @@ mod cli_run {
expected_ending: &str,
use_valgrind: bool,
) {
let mut all_flags = vec![];
all_flags.extend_from_slice(flags);
for cli_mode in CliMode::iter() {
let flags = {
let mut vec = flags.to_vec();
if use_valgrind {
all_flags.extend_from_slice(&["--valgrind"]);
}
if use_valgrind {
vec.push(VALGRIND_FLAG);
}
build_example(file, &all_flags[..]);
let out = if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = if let Some(input_file) = input_file {
run_with_valgrind(
stdin,
&[
file.with_file_name(executable_filename).to_str().unwrap(),
input_file.to_str().unwrap(),
],
)
} else {
run_with_valgrind(
stdin,
&[file.with_file_name(executable_filename).to_str().unwrap()],
)
vec.into_iter()
};
if valgrind_out.status.success() {
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr);
});
let out = match cli_mode {
CliMode::RocBuild => {
run_roc_on(file, iter::once(CMD_BUILD).chain(flags.clone()), &[], None);
if !memory_errors.is_empty() {
for error in memory_errors {
let ValgrindError {
kind,
what: _,
xwhat,
} = error;
println!("Valgrind Error: {}\n", kind);
if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = if let Some(ref input_file) = input_file {
run_with_valgrind(
stdin.clone().iter().copied(),
&[
file.with_file_name(executable_filename).to_str().unwrap(),
input_file.clone().to_str().unwrap(),
],
)
} else {
run_with_valgrind(
stdin.clone().iter().copied(),
&[file.with_file_name(executable_filename).to_str().unwrap()],
)
};
if let Some(ValgrindErrorXWhat {
text,
leakedbytes: _,
leakedblocks: _,
}) = xwhat
{
println!(" {}", text);
if valgrind_out.status.success() {
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr);
});
if !memory_errors.is_empty() {
for error in memory_errors {
let ValgrindError {
kind,
what: _,
xwhat,
} = error;
println!("Valgrind Error: {}\n", kind);
if let Some(ValgrindErrorXWhat {
text,
leakedbytes: _,
leakedblocks: _,
}) = xwhat
{
println!(" {}", text);
}
}
panic!("Valgrind reported memory errors");
}
} else {
let exit_code = match valgrind_out.status.code() {
Some(code) => format!("exit code {}", code),
None => "no exit code".to_string(),
};
panic!("`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", exit_code, valgrind_out.stdout, valgrind_out.stderr);
}
}
panic!("Valgrind reported memory errors");
}
} else {
let exit_code = match valgrind_out.status.code() {
Some(code) => format!("exit code {}", code),
None => "no exit code".to_string(),
};
panic!("`valgrind` exited with {}. valgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", exit_code, valgrind_out.stdout, valgrind_out.stderr);
valgrind_out
} else if let Some(ref input_file) = input_file {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin.iter().copied(),
&[input_file.to_str().unwrap()],
)
} else {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin.iter().copied(),
&[],
)
}
}
CliMode::Roc => run_roc_on(file, flags.clone(), stdin, input_file.clone()),
CliMode::RocRun => run_roc_on(
file,
iter::once(CMD_RUN).chain(flags.clone()),
stdin,
input_file.clone(),
),
};
if !&out.stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}",
expected_ending, out.stdout, out.stderr
);
}
valgrind_out
} else if let Some(input_file) = input_file {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin,
&[input_file.to_str().unwrap()],
)
} else {
run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(),
stdin,
&[],
)
};
if !&out.stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?} - stderr was: {:#?}",
expected_ending, out.stdout, out.stderr
);
assert!(out.status.success());
}
assert!(out.status.success());
}
#[cfg(feature = "wasm32-cli-run")]
@ -199,9 +258,13 @@ mod cli_run {
) {
assert_eq!(input_file, None, "Wasm does not support input files");
let mut flags = flags.to_vec();
flags.push("--target=wasm32");
flags.push(concatcp!(TARGET_FLAG, "=wasm32"));
let compile_out = run_roc(&[&["build", file.to_str().unwrap()], flags.as_slice()].concat());
let compile_out = run_roc(
[CMD_BUILD, file.to_str().unwrap()]
.iter()
.chain(flags.as_slice()),
);
if !compile_out.stderr.is_empty() {
panic!("{}", compile_out.stderr);
}
@ -258,7 +321,7 @@ mod cli_run {
}
"hello-gui" | "breakout" => {
// Since these require opening a window, we do `roc build` on them but don't run them.
build_example(&file_name, &["--optimize"]);
run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], None);
return;
}
@ -283,7 +346,7 @@ mod cli_run {
&file_name,
example.stdin,
example.executable_filename,
&["--optimize"],
&[OPTIMIZE_FLAG],
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
example.expected_ending,
example.use_valgrind,
@ -296,7 +359,7 @@ mod cli_run {
&file_name,
example.stdin,
example.executable_filename,
&["--linker", "legacy"],
&[LINKER_FLAG, "legacy"],
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
example.expected_ending,
example.use_valgrind,
@ -513,7 +576,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--optimize"],
&[OPTIMIZE_FLAG],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
benchmark.use_valgrind,
@ -555,7 +618,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--optimize"],
&[OPTIMIZE_FLAG],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
);
@ -587,7 +650,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--target=x86_32"],
[concatcp!(TARGET_FLAG, "=x86_32")],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
benchmark.use_valgrind,
@ -597,7 +660,7 @@ mod cli_run {
&file_name,
benchmark.stdin,
benchmark.executable_filename,
&["--target=x86_32", "--optimize"],
[concatcp!(TARGET_FLAG, "=x86_32"), OPTIMIZE_FLAG],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
benchmark.expected_ending,
benchmark.use_valgrind,
@ -816,7 +879,7 @@ mod cli_run {
&fixture_file("multi-dep-str", "Main.roc"),
&[],
"multi-dep-str",
&["--optimize"],
&[OPTIMIZE_FLAG],
None,
"I am Dep2.str2\n",
true,
@ -844,7 +907,7 @@ mod cli_run {
&fixture_file("multi-dep-thunk", "Main.roc"),
&[],
"multi-dep-thunk",
&["--optimize"],
&[OPTIMIZE_FLAG],
None,
"I am Dep2.value2\n",
true,

View file

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

View file

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

View file

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

View file

@ -106,6 +106,9 @@ comptime {
num.exportToIntCheckingMax(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max.");
num.exportToIntCheckingMaxAndMin(FROM, TO, ROC_BUILTINS ++ "." ++ NUM ++ ".int_to_" ++ @typeName(TO) ++ "_checking_max_and_min.");
}
num.exportRoundF32(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f32.");
num.exportRoundF64(FROM, ROC_BUILTINS ++ "." ++ NUM ++ ".round_f64.");
}
inline for (FLOATS) |T| {
@ -114,7 +117,6 @@ comptime {
num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan.");
num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite.");
num.exportRound(T, ROC_BUILTINS ++ "." ++ NUM ++ ".round.");
}
}

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

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_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int");
pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil");
pub const NUM_ROUND: IntrinsicName = float_intrinsic!("roc_builtins.num.round");
pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32");
pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64");
pub const NUM_BYTES_TO_U16: &str = "roc_builtins.num.bytes_to_u16";
pub const NUM_BYTES_TO_U32: &str = "roc_builtins.num.bytes_to_u32";

View file

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

View file

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

View file

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

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

View file

@ -10,6 +10,14 @@ pub mod pattern;
pub mod spaces;
use bumpalo::{collections::String, Bump};
use roc_parse::ast::{Def, Module};
use roc_region::all::Loc;
#[derive(Debug, PartialEq)]
pub struct Ast<'a> {
pub module: Module<'a>,
pub defs: bumpalo::collections::vec::Vec<'a, Loc<Def<'a>>>,
}
#[derive(Debug)]
pub struct Buf<'a> {

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.
pub const INDENT: u16 = 4;
@ -149,3 +164,575 @@ fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) {
}
buf.push_str(docs);
}
/// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting.
///
/// Currently this consists of:
/// * Removing newlines
/// * Removing comments
/// * Removing parens in Exprs
///
/// Long term, we actuall want this transform to preserve comments (so we can assert they're maintained by formatting)
/// - but there are currently several bugs where they're _not_ preserved.
/// TODO: ensure formatting retains comments
pub trait RemoveSpaces<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self;
}
impl<'a> RemoveSpaces<'a> for Ast<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
Ast {
module: self.module.remove_spaces(arena),
defs: {
let mut defs = Vec::with_capacity_in(self.defs.len(), arena);
for d in &self.defs {
defs.push(d.remove_spaces(arena))
}
defs
},
}
}
}
impl<'a> RemoveSpaces<'a> for Module<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match self {
Module::Interface { header } => Module::Interface {
header: InterfaceHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
before_header: &[],
after_interface_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
},
},
Module::App { header } => Module::App {
header: AppHeader {
name: header.name.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
provides_types: header.provides_types.map(|ts| ts.remove_spaces(arena)),
to: header.to.remove_spaces(arena),
before_header: &[],
after_app_keyword: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
before_to: &[],
after_to: &[],
},
},
Module::Platform { header } => Module::Platform {
header: PlatformHeader {
name: header.name.remove_spaces(arena),
requires: header.requires.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
packages: header.packages.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
provides: header.provides.remove_spaces(arena),
before_header: &[],
after_platform_keyword: &[],
before_requires: &[],
after_requires: &[],
before_exposes: &[],
after_exposes: &[],
before_packages: &[],
after_packages: &[],
before_imports: &[],
after_imports: &[],
before_provides: &[],
after_provides: &[],
},
},
Module::Hosted { header } => Module::Hosted {
header: HostedHeader {
name: header.name.remove_spaces(arena),
exposes: header.exposes.remove_spaces(arena),
imports: header.imports.remove_spaces(arena),
generates: header.generates.remove_spaces(arena),
generates_with: header.generates_with.remove_spaces(arena),
before_header: &[],
after_hosted_keyword: &[],
before_exposes: &[],
after_exposes: &[],
before_imports: &[],
after_imports: &[],
before_generates: &[],
after_generates: &[],
before_with: &[],
after_with: &[],
},
},
}
}
}
impl<'a> RemoveSpaces<'a> for &'a str {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
self
}
}
impl<'a, T: RemoveSpaces<'a> + Copy> RemoveSpaces<'a> for Spaced<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Spaced::Item(a) => Spaced::Item(a.remove_spaces(arena)),
Spaced::SpaceBefore(a, _) => a.remove_spaces(arena),
Spaced::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ExposedName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for ModuleName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageName<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for To<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
To::ExistingPackage(a) => To::ExistingPackage(a),
To::NewPackage(a) => To::NewPackage(a.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for TypedIdent<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
TypedIdent {
ident: self.ident.remove_spaces(arena),
spaces_before_colon: &[],
ann: self.ann.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for PlatformRequires<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PlatformRequires {
rigids: self.rigids.remove_spaces(arena),
signature: self.signature.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for UppercaseIdent<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for PackageEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
PackageEntry {
shorthand: self.shorthand,
spaces_after_shorthand: &[],
package_name: self.package_name.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for ImportsEntry<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
ImportsEntry::Module(a, b) => ImportsEntry::Module(a, b.remove_spaces(arena)),
ImportsEntry::Package(a, b, c) => ImportsEntry::Package(a, b, c.remove_spaces(arena)),
}
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Option<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
self.as_ref().map(|a| a.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for Loc<T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let res = self.value.remove_spaces(arena);
Loc::at(Region::zero(), res)
}
}
impl<'a, A: RemoveSpaces<'a>, B: RemoveSpaces<'a>> RemoveSpaces<'a> for (A, B) {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
(self.0.remove_spaces(arena), self.1.remove_spaces(arena))
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for Collection<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.items.len(), arena);
for item in self.items {
items.push(item.remove_spaces(arena));
}
Collection::with_items(items.into_bump_slice())
}
}
impl<'a, T: RemoveSpaces<'a> + std::fmt::Debug> RemoveSpaces<'a> for &'a [T] {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
let mut items = Vec::with_capacity_in(self.len(), arena);
for item in *self {
let res = item.remove_spaces(arena);
items.push(res);
}
items.into_bump_slice()
}
}
impl<'a> RemoveSpaces<'a> for UnaryOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a> RemoveSpaces<'a> for BinOp {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
*self
}
}
impl<'a, T: RemoveSpaces<'a>> RemoveSpaces<'a> for &'a T {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
arena.alloc((*self).remove_spaces(arena))
}
}
impl<'a> RemoveSpaces<'a> for TypeDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use TypeDef::*;
match *self {
Alias {
header: TypeHeader { name, vars },
ann,
} => Alias {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
ann: ann.remove_spaces(arena),
},
Opaque {
header: TypeHeader { name, vars },
typ,
} => Opaque {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
typ: typ.remove_spaces(arena),
},
Ability {
header: TypeHeader { name, vars },
loc_has,
members,
} => Ability {
header: TypeHeader {
name: name.remove_spaces(arena),
vars: vars.remove_spaces(arena),
},
loc_has: loc_has.remove_spaces(arena),
members: members.remove_spaces(arena),
},
}
}
}
impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
use ValueDef::*;
match *self {
Annotation(a, b) => Annotation(a.remove_spaces(arena), b.remove_spaces(arena)),
Body(a, b) => Body(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
AnnotatedBody {
ann_pattern,
ann_type,
comment: _,
body_pattern,
body_expr,
} => AnnotatedBody {
ann_pattern: arena.alloc(ann_pattern.remove_spaces(arena)),
ann_type: arena.alloc(ann_type.remove_spaces(arena)),
comment: None,
body_pattern: arena.alloc(body_pattern.remove_spaces(arena)),
body_expr: arena.alloc(body_expr.remove_spaces(arena)),
},
Expect(a) => Expect(arena.alloc(a.remove_spaces(arena))),
}
}
}
impl<'a> RemoveSpaces<'a> for Def<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Def::Type(def) => Def::Type(def.remove_spaces(arena)),
Def::Value(def) => Def::Value(def.remove_spaces(arena)),
Def::NotYetImplemented(a) => Def::NotYetImplemented(a),
Def::SpaceBefore(a, _) | Def::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Has<'a> {
fn remove_spaces(&self, _arena: &'a Bump) -> Self {
Has::Has
}
}
impl<'a> RemoveSpaces<'a> for AbilityMember<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
AbilityMember {
name: self.name.remove_spaces(arena),
typ: self.typ.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for WhenBranch<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
WhenBranch {
patterns: self.patterns.remove_spaces(arena),
value: self.value.remove_spaces(arena),
guard: self.guard.remove_spaces(arena),
}
}
}
impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for AssignedField<'a, T> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
AssignedField::RequiredValue(a, _, c) => AssignedField::RequiredValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::OptionalValue(a, _, c) => AssignedField::OptionalValue(
a.remove_spaces(arena),
arena.alloc([]),
arena.alloc(c.remove_spaces(arena)),
),
AssignedField::LabelOnly(a) => AssignedField::LabelOnly(a.remove_spaces(arena)),
AssignedField::Malformed(a) => AssignedField::Malformed(a),
AssignedField::SpaceBefore(a, _) => a.remove_spaces(arena),
AssignedField::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrLiteral::PlainLine(t) => StrLiteral::PlainLine(t),
StrLiteral::Line(t) => StrLiteral::Line(t.remove_spaces(arena)),
StrLiteral::Block(t) => StrLiteral::Block(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for StrSegment<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
StrSegment::Plaintext(t) => StrSegment::Plaintext(t),
StrSegment::Unicode(t) => StrSegment::Unicode(t.remove_spaces(arena)),
StrSegment::EscapedChar(c) => StrSegment::EscapedChar(c),
StrSegment::Interpolated(t) => StrSegment::Interpolated(t.remove_spaces(arena)),
}
}
}
impl<'a> RemoveSpaces<'a> for Expr<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Expr::Float(a) => Expr::Float(a),
Expr::Num(a) => Expr::Num(a),
Expr::NonBase10Int {
string,
base,
is_negative,
} => Expr::NonBase10Int {
string,
base,
is_negative,
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::Access(a, b) => Expr::Access(arena.alloc(a.remove_spaces(arena)), b),
Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
update: arena.alloc(update.remove_spaces(arena)),
fields: fields.remove_spaces(arena),
},
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
Expr::Underscore(a) => Expr::Underscore(a),
Expr::Tag(a) => Expr::Tag(a),
Expr::OpaqueRef(a) => Expr::OpaqueRef(a),
Expr::Closure(a, b) => Expr::Closure(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Defs(a, b) => {
Expr::Defs(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::Backpassing(a, b, c) => Expr::Backpassing(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
arena.alloc(c.remove_spaces(arena)),
),
Expr::Expect(a, b) => Expr::Expect(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Expr::Apply(a, b, c) => Expr::Apply(
arena.alloc(a.remove_spaces(arena)),
b.remove_spaces(arena),
c,
),
Expr::BinOps(a, b) => {
Expr::BinOps(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena)))
}
Expr::UnaryOp(a, b) => {
Expr::UnaryOp(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::If(a, b) => Expr::If(a.remove_spaces(arena), arena.alloc(b.remove_spaces(arena))),
Expr::When(a, b) => {
Expr::When(arena.alloc(a.remove_spaces(arena)), b.remove_spaces(arena))
}
Expr::ParensAround(a) => {
// The formatter can remove redundant parentheses, so also remove these when normalizing for comparison.
a.remove_spaces(arena)
}
Expr::MalformedIdent(a, b) => Expr::MalformedIdent(a, b),
Expr::MalformedClosure => Expr::MalformedClosure,
Expr::PrecedenceConflict(a) => Expr::PrecedenceConflict(a),
Expr::SpaceBefore(a, _) => a.remove_spaces(arena),
Expr::SpaceAfter(a, _) => a.remove_spaces(arena),
Expr::SingleQuote(a) => Expr::Num(a),
}
}
}
impl<'a> RemoveSpaces<'a> for Pattern<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Pattern::Identifier(a) => Pattern::Identifier(a),
Pattern::Tag(a) => Pattern::Tag(a),
Pattern::OpaqueRef(a) => Pattern::OpaqueRef(a),
Pattern::Apply(a, b) => Pattern::Apply(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
Pattern::RecordDestructure(a) => Pattern::RecordDestructure(a.remove_spaces(arena)),
Pattern::RequiredField(a, b) => {
Pattern::RequiredField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::OptionalField(a, b) => {
Pattern::OptionalField(a, arena.alloc(b.remove_spaces(arena)))
}
Pattern::NumLiteral(a) => Pattern::NumLiteral(a),
Pattern::NonBase10Literal {
string,
base,
is_negative,
} => Pattern::NonBase10Literal {
string,
base,
is_negative,
},
Pattern::FloatLiteral(a) => Pattern::FloatLiteral(a),
Pattern::StrLiteral(a) => Pattern::StrLiteral(a),
Pattern::Underscore(a) => Pattern::Underscore(a),
Pattern::Malformed(a) => Pattern::Malformed(a),
Pattern::MalformedIdent(a, b) => Pattern::MalformedIdent(a, b),
Pattern::QualifiedIdentifier { module_name, ident } => {
Pattern::QualifiedIdentifier { module_name, ident }
}
Pattern::SpaceBefore(a, _) => a.remove_spaces(arena),
Pattern::SpaceAfter(a, _) => a.remove_spaces(arena),
Pattern::SingleQuote(a) => Pattern::NumLiteral(a),
}
}
}
impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
TypeAnnotation::Function(a, b) => TypeAnnotation::Function(
arena.alloc(a.remove_spaces(arena)),
arena.alloc(b.remove_spaces(arena)),
),
TypeAnnotation::Apply(a, b, c) => TypeAnnotation::Apply(a, b, c.remove_spaces(arena)),
TypeAnnotation::BoundVariable(a) => TypeAnnotation::BoundVariable(a),
TypeAnnotation::As(a, _, c) => {
TypeAnnotation::As(arena.alloc(a.remove_spaces(arena)), &[], c)
}
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
fields: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::TagUnion { ext, tags } => TypeAnnotation::TagUnion {
ext: ext.remove_spaces(arena),
tags: tags.remove_spaces(arena),
},
TypeAnnotation::Inferred => TypeAnnotation::Inferred,
TypeAnnotation::Wildcard => TypeAnnotation::Wildcard,
TypeAnnotation::Where(annot, has_clauses) => TypeAnnotation::Where(
arena.alloc(annot.remove_spaces(arena)),
arena.alloc(has_clauses.remove_spaces(arena)),
),
TypeAnnotation::SpaceBefore(a, _) => a.remove_spaces(arena),
TypeAnnotation::SpaceAfter(a, _) => a.remove_spaces(arena),
TypeAnnotation::Malformed(a) => TypeAnnotation::Malformed(a),
}
}
}
impl<'a> RemoveSpaces<'a> for HasClause<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
HasClause {
var: self.var.remove_spaces(arena),
ability: self.ability.remove_spaces(arena),
}
}
}
impl<'a> RemoveSpaces<'a> for Tag<'a> {
fn remove_spaces(&self, arena: &'a Bump) -> Self {
match *self {
Tag::Apply { name, args } => Tag::Apply {
name: name.remove_spaces(arena),
args: args.remove_spaces(arena),
},
Tag::Malformed(a) => Tag::Malformed(a),
Tag::SpaceBefore(a, _) => a.remove_spaces(arena),
Tag::SpaceAfter(a, _) => a.remove_spaces(arena),
}
}
}

View file

@ -10,60 +10,153 @@ mod test_fmt {
use roc_fmt::def::fmt_def;
use roc_fmt::module::fmt_module;
use roc_fmt::Buf;
use roc_parse::ast::Module;
use roc_parse::module::{self, module_defs};
use roc_parse::parser::Parser;
use roc_parse::state::State;
use roc_test_utils::assert_multiline_str_eq;
// Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same
fn expect_format_expr_helper(input: &str, expected: &str) {
fn expr_formats_to(input: &str, expected: &str) {
let arena = Bump::new();
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
let input = input.trim();
let expected = expected.trim();
match roc_parse::test_helpers::parse_expr_with(&arena, input) {
Ok(actual) => {
use roc_fmt::spaces::RemoveSpaces;
let mut buf = Buf::new_in(&arena);
actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0);
assert_multiline_str_eq!(expected, buf.as_str());
let output = buf.as_str();
assert_multiline_str_eq!(expected, output);
let reparsed_ast = roc_parse::test_helpers::parse_expr_with(&arena, output).unwrap_or_else(|err| {
panic!(
"After formatting, the source code no longer parsed!\n\nParse error was: {:?}\n\nThe code that failed to parse:\n\n{}\n\n",
err, output
);
});
let ast_normalized = actual.remove_spaces(&arena);
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
// HACK!
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
// I don't have the patience to debug this right now, so let's leave it for another day...
// TODO: fix PartialEq impl on ast types
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
panic!(
"Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\
* * * Source code before formatting:\n{}\n\n\
* * * Source code after formatting:\n{}\n\n",
input,
output
);
}
// Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted
let mut reformatted_buf = Buf::new_in(&arena);
reparsed_ast.format_with_options(&mut reformatted_buf, Parens::NotNeeded, Newlines::Yes, 0);
if output != reformatted_buf.as_str() {
eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n");
assert_multiline_str_eq!(output, reformatted_buf.as_str());
}
}
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error)
};
}
fn expr_formats_to(input: &str, expected: &str) {
let input = input.trim_end();
let expected = expected.trim_end();
// First check that input formats to the expected version
expect_format_expr_helper(input, expected);
// Parse the expected result format it, asserting that it doesn't change
// It's important that formatting be stable / idempotent
expect_format_expr_helper(expected, expected);
}
fn expr_formats_same(input: &str) {
expr_formats_to(input, input);
}
fn fmt_module_and_defs<'a>(
arena: &Bump,
src: &str,
module: &Module<'a>,
state: State<'a>,
buf: &mut Buf<'_>,
) {
fmt_module(buf, module);
match module_defs().parse(&arena, state) {
Ok((_, loc_defs, _)) => {
for loc_def in loc_defs {
fmt_def(buf, arena.alloc(loc_def.value), 0);
}
}
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
}
}
// Not intended to be used directly in tests; please use module_formats_to or module_formats_same
fn expect_format_module_helper(src: &str, expected: &str) {
let arena = Bump::new();
let src = src.trim();
let expected = expected.trim();
match module::parse_header(&arena, State::new(src.as_bytes())) {
Ok((actual, state)) => {
use roc_fmt::spaces::RemoveSpaces;
let mut buf = Buf::new_in(&arena);
fmt_module(&mut buf, &actual);
fmt_module_and_defs(&arena, src, &actual, state, &mut buf);
match module_defs().parse(&arena, state) {
Ok((_, loc_defs, _)) => {
for loc_def in loc_defs {
fmt_def(&mut buf, arena.alloc(loc_def.value), 0);
}
}
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
let output = buf.as_str().trim();
let (reparsed_ast, state) = module::parse_header(&arena, State::new(output.as_bytes())).unwrap_or_else(|err| {
panic!(
"After formatting, the source code no longer parsed!\n\nParse error was: {:?}\n\nThe code that failed to parse:\n\n{}\n\n",
err, output
);
});
let ast_normalized = actual.remove_spaces(&arena);
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
// HACK!
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
// I don't have the patience to debug this right now, so let's leave it for another day...
// TODO: fix PartialEq impl on ast types
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
panic!(
"Formatting bug; formatting didn't reparse to the same AST (after removing spaces)\n\n\
* * * Source code before formatting:\n{}\n\n\
* * * Source code after formatting:\n{}\n\n",
src,
output
);
}
assert_multiline_str_eq!(expected, buf.as_str())
// Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted
let mut reformatted_buf = Buf::new_in(&arena);
fmt_module_and_defs(&arena, output, &reparsed_ast, state, &mut reformatted_buf);
let reformatted = reformatted_buf.as_str().trim();
if output != reformatted {
eprintln!("Formatting bug; formatting is not stable. Reformatting the formatted code changed it again, as follows:\n\n");
assert_multiline_str_eq!(output, reformatted);
}
// If everything was idempotent re-parsing worked, finally assert
// that the formatted code was what we expected it to be.
//
// Do this last because if there were any serious problems with the
// formatter (e.g. it wasn't idempotent), we want to know about
// those more than we want to know that the expectation failed!
assert_multiline_str_eq!(expected, output);
}
Err(error) => panic!("Unexpected parse failure when parsing this for module header formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
};
@ -680,7 +773,7 @@ mod test_fmt {
1,
]
list
list
"#
));
@ -693,7 +786,7 @@ mod test_fmt {
1,
]
list
list
"#
));
@ -706,7 +799,7 @@ mod test_fmt {
1,
]
list
list
"#
));
@ -720,7 +813,7 @@ mod test_fmt {
1,
]
list
list
"#
));
@ -737,7 +830,7 @@ mod test_fmt {
# comment 3
]
list
list
"#
));
@ -753,7 +846,7 @@ mod test_fmt {
# comment 3
]
list
list
"#
));
expr_formats_to(
@ -767,7 +860,7 @@ mod test_fmt {
1,
]
list
list
"#
),
indoc!(
@ -779,7 +872,7 @@ mod test_fmt {
1,
]
list
list
"#
),
);
@ -795,7 +888,7 @@ mod test_fmt {
]
list
list
"#
),
indoc!(
@ -807,7 +900,7 @@ mod test_fmt {
# comment
]
list
list
"#
),
);
@ -823,7 +916,7 @@ mod test_fmt {
1,
]
list
list
"#
),
indoc!(
@ -835,7 +928,7 @@ mod test_fmt {
1,
]
list
list
"#
),
);
@ -851,7 +944,7 @@ mod test_fmt {
1,
]
list
list
"#
),
indoc!(
@ -863,7 +956,7 @@ mod test_fmt {
1,
]
list
list
"#
),
);
@ -881,7 +974,7 @@ mod test_fmt {
1,
]
list
list
"#
),
indoc!(
@ -894,7 +987,7 @@ mod test_fmt {
1,
]
list
list
"#
),
);
@ -1444,7 +1537,7 @@ mod test_fmt {
3,
]
toList
toList
"#
));
@ -3939,6 +4032,39 @@ mod test_fmt {
);
}
#[test]
fn format_tui_package_config() {
// At one point this failed to reformat.
module_formats_to(
indoc!(
r#"
platform "tui"
requires { Model } { main : { init : ({} -> Model), update : (Model, Str -> Model), view : (Model -> Str) } }
exposes []
packages {}
imports []
provides [ mainForHost ]
mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View }
mainForHost = main
"#
),
indoc!(
r#"
platform "tui"
requires { Model } { main : { init : {} -> Model, update : Model, Str -> Model, view : Model -> Str } }
exposes []
packages {}
imports []
provides [ mainForHost ]
mainForHost : { init : ({} -> Model) as Init, update : (Model, Str -> Model) as Update, view : (Model -> Str) as View }
mainForHost = main
"#
),
);
}
#[test]
fn single_line_hosted() {
module_formats_same(indoc!(

View file

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

View file

@ -602,6 +602,7 @@ static LLVM_SIN: IntrinsicName = float_intrinsic!("llvm.sin");
static LLVM_COS: IntrinsicName = float_intrinsic!("llvm.cos");
static LLVM_CEILING: IntrinsicName = float_intrinsic!("llvm.ceil");
static LLVM_FLOOR: IntrinsicName = float_intrinsic!("llvm.floor");
static LLVM_ROUND: IntrinsicName = float_intrinsic!("llvm.round");
static LLVM_MEMSET_I64: &str = "llvm.memset.p0i8.i64";
static LLVM_MEMSET_I32: &str = "llvm.memset.p0i8.i32";
@ -7403,20 +7404,67 @@ fn build_float_unary_op<'a, 'ctx, 'env>(
}
}
}
NumCeiling => env.builder.build_cast(
InstructionOpcode::FPToSI,
env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]),
env.context.i64_type(),
"num_ceiling",
),
NumFloor => env.builder.build_cast(
InstructionOpcode::FPToSI,
env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]),
env.context.i64_type(),
"num_floor",
),
NumCeiling => {
let (return_signed, return_type) = match layout {
Layout::Builtin(Builtin::Int(int_width)) => (
int_width.is_signed(),
convert::int_type_from_int_width(env, *int_width),
),
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
};
let opcode = if return_signed {
InstructionOpcode::FPToSI
} else {
InstructionOpcode::FPToUI
};
env.builder.build_cast(
opcode,
env.call_intrinsic(&LLVM_CEILING[float_width], &[arg.into()]),
return_type,
"num_ceiling",
)
}
NumFloor => {
let (return_signed, return_type) = match layout {
Layout::Builtin(Builtin::Int(int_width)) => (
int_width.is_signed(),
convert::int_type_from_int_width(env, *int_width),
),
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
};
let opcode = if return_signed {
InstructionOpcode::FPToSI
} else {
InstructionOpcode::FPToUI
};
env.builder.build_cast(
opcode,
env.call_intrinsic(&LLVM_FLOOR[float_width], &[arg.into()]),
return_type,
"num_floor",
)
}
NumRound => {
let (return_signed, return_type) = match layout {
Layout::Builtin(Builtin::Int(int_width)) => (
int_width.is_signed(),
convert::int_type_from_int_width(env, *int_width),
),
_ => internal_error!("Ceiling return layout is not int: {:?}", layout),
};
let opcode = if return_signed {
InstructionOpcode::FPToSI
} else {
InstructionOpcode::FPToUI
};
env.builder.build_cast(
opcode,
env.call_intrinsic(&LLVM_ROUND[float_width], &[arg.into()]),
return_type,
"num_round",
)
}
NumIsFinite => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_IS_FINITE[float_width]),
NumRound => call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_ROUND[float_width]),
// trigonometry
NumSin => env.call_intrinsic(&LLVM_SIN[float_width], &[arg.into()]),

View file

@ -561,18 +561,6 @@ impl<'a> LowLevelCall<'a> {
NumCos => todo!("{:?}", self.lowlevel),
NumSqrtUnchecked => todo!("{:?}", self.lowlevel),
NumLogUnchecked => todo!("{:?}", self.lowlevel),
NumRound => {
self.load_args(backend);
match CodeGenNumType::for_symbol(backend, self.arguments[0]) {
F32 => {
self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F32])
}
F64 => {
self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F64])
}
_ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout),
}
}
NumToFloat => {
self.load_args(backend);
let ret_type = CodeGenNumType::from(self.ret_layout);
@ -592,35 +580,54 @@ impl<'a> LowLevelCall<'a> {
}
}
NumPow => todo!("{:?}", self.lowlevel),
NumCeiling => {
NumRound => {
self.load_args(backend);
match CodeGenNumType::from(self.ret_layout) {
I32 => {
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
let ret_type = CodeGenNumType::from(self.ret_layout);
let width = match ret_type {
CodeGenNumType::I32 => IntWidth::I32,
CodeGenNumType::I64 => IntWidth::I64,
CodeGenNumType::I128 => todo!("{:?} for I128", self.lowlevel),
_ => internal_error!("Invalid return type for round: {:?}", ret_type),
};
match arg_type {
F32 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F32[width]),
F64 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F64[width]),
_ => internal_error!("Invalid argument type for round: {:?}", arg_type),
}
}
NumCeiling | NumFloor => {
self.load_args(backend);
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
let ret_type = CodeGenNumType::from(self.ret_layout);
match (arg_type, self.lowlevel) {
(F32, NumCeiling) => {
backend.code_builder.f32_ceil();
backend.code_builder.i32_trunc_s_f32()
}
I64 => {
(F64, NumCeiling) => {
backend.code_builder.f64_ceil();
backend.code_builder.i64_trunc_s_f64()
}
(F32, NumFloor) => {
backend.code_builder.f32_floor();
}
(F64, NumFloor) => {
backend.code_builder.f64_floor();
}
_ => internal_error!("Invalid argument type for ceiling: {:?}", arg_type),
}
match (ret_type, arg_type) {
// TODO: unsigned truncation
(I32, F32) => backend.code_builder.i32_trunc_s_f32(),
(I32, F64) => backend.code_builder.i32_trunc_s_f64(),
(I64, F32) => backend.code_builder.i64_trunc_s_f32(),
(I64, F64) => backend.code_builder.i64_trunc_s_f64(),
(I128, _) => todo!("{:?} for I128", self.lowlevel),
_ => panic_ret_type(),
}
}
NumPowInt => todo!("{:?}", self.lowlevel),
NumFloor => {
self.load_args(backend);
match CodeGenNumType::from(self.ret_layout) {
I32 => {
backend.code_builder.f32_floor();
backend.code_builder.i32_trunc_s_f32()
}
I64 => {
backend.code_builder.f64_floor();
backend.code_builder.i64_trunc_s_f64()
}
_ => panic_ret_type(),
}
}
NumIsFinite => num_is_finite(backend, self.arguments[0]),
NumAtan => match self.ret_layout {

View file

@ -4,6 +4,3 @@ version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
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(),
target_info,
roc_reporting::report::RenderTarget::ColorTerminal,
Threading::Multi,
Threading::AllAvailable,
);
let module = res_module.unwrap();

View file

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

View file

@ -747,6 +747,7 @@ impl<'a> State<'a> {
ident_ids_by_module: SharedIdentIdsByModule,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget,
number_of_workers: usize,
) -> Self {
let arc_shorthands = Arc::new(Mutex::new(MutMap::default()));
@ -770,7 +771,7 @@ impl<'a> State<'a> {
declarations_by_id: MutMap::default(),
exposed_symbols_by_module: MutMap::default(),
timings: MutMap::default(),
layout_caches: std::vec::Vec::with_capacity(num_cpus::get()),
layout_caches: std::vec::Vec::with_capacity(number_of_workers),
cached_subs: Arc::new(Mutex::new(cached_subs)),
render,
}
@ -1099,7 +1100,8 @@ pub enum LoadResult<'a> {
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Threading {
Single,
Multi,
AllAvailable,
AtMost(usize),
}
/// The loading process works like this, starting from the given filename (e.g. "main.roc"):
@ -1157,10 +1159,32 @@ pub fn load<'a>(
render: RenderTarget,
threading: Threading,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
// When compiling to wasm, we cannot spawn extra threads
// so we have a single-threaded implementation
if threading == Threading::Single || cfg!(target_family = "wasm") {
load_single_threaded(
enum Threads {
Single,
Many(usize),
}
let threads = {
if cfg!(target_family = "wasm") {
// When compiling to wasm, we cannot spawn extra threads
// so we have a single-threaded implementation
Threads::Single
} else {
match std::thread::available_parallelism().map(|v| v.get()) {
Err(_) => Threads::Single,
Ok(0) => unreachable!("NonZeroUsize"),
Ok(1) => Threads::Single,
Ok(reported) => match threading {
Threading::Single => Threads::Single,
Threading::AllAvailable => Threads::Many(reported),
Threading::AtMost(at_most) => Threads::Many(Ord::min(reported, at_most)),
},
}
}
};
match threads {
Threads::Single => load_single_threaded(
arena,
load_start,
src_dir,
@ -1169,9 +1193,8 @@ pub fn load<'a>(
target_info,
cached_subs,
render,
)
} else {
load_multi_threaded(
),
Threads::Many(threads) => load_multi_threaded(
arena,
load_start,
src_dir,
@ -1180,7 +1203,8 @@ pub fn load<'a>(
target_info,
cached_subs,
render,
)
threads,
),
}
}
@ -1210,6 +1234,7 @@ pub fn load_single_threaded<'a>(
.send(root_msg)
.map_err(|_| LoadingProblem::MsgChannelDied)?;
let number_of_workers = 1;
let mut state = State::new(
root_id,
target_info,
@ -1219,6 +1244,7 @@ pub fn load_single_threaded<'a>(
ident_ids_by_module,
cached_subs,
render,
number_of_workers,
);
// We'll add tasks to this, and then worker threads will take tasks from it.
@ -1390,6 +1416,7 @@ fn load_multi_threaded<'a>(
target_info: TargetInfo,
cached_subs: MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>,
render: RenderTarget,
available_threads: usize,
) -> Result<LoadResult<'a>, LoadingProblem<'a>> {
let LoadStart {
arc_modules,
@ -1399,6 +1426,28 @@ fn load_multi_threaded<'a>(
..
} = load_start;
let (msg_tx, msg_rx) = bounded(1024);
msg_tx
.send(root_msg)
.map_err(|_| LoadingProblem::MsgChannelDied)?;
// Reserve one CPU for the main thread, and let all the others be eligible
// to spawn workers.
let available_workers = available_threads - 1;
let num_workers = match env::var("ROC_NUM_WORKERS") {
Ok(env_str) => env_str
.parse::<usize>()
.unwrap_or(available_workers)
.min(available_workers),
Err(_) => available_workers,
};
assert!(
num_workers >= 1,
"`load_multi_threaded` needs at least one worker"
);
let mut state = State::new(
root_id,
target_info,
@ -1408,28 +1457,9 @@ fn load_multi_threaded<'a>(
ident_ids_by_module,
cached_subs,
render,
num_workers,
);
let (msg_tx, msg_rx) = bounded(1024);
msg_tx
.send(root_msg)
.map_err(|_| LoadingProblem::MsgChannelDied)?;
// Reserve one CPU for the main thread, and let all the others be eligible
// to spawn workers. We use .max(2) to enforce that we always
// end up with at least 1 worker - since (.max(2) - 1) will
// always return a number that's at least 1. Using
// .max(2) on the initial number of CPUs instead of
// doing .max(1) on the entire expression guards against
// num_cpus returning 0, while also avoiding wrapping
// unsigned subtraction overflow.
let default_num_workers = num_cpus::get().max(2) - 1;
let num_workers = match env::var("ROC_NUM_WORKERS") {
Ok(env_str) => env_str.parse::<usize>().unwrap_or(default_num_workers),
Err(_) => default_num_workers,
};
// an arena for every worker, stored in an arena-allocated bumpalo vec to make the lifetimes work
let arenas = std::iter::repeat_with(Bump::new).take(num_workers);
let worker_arenas = arena.alloc(bumpalo::collections::Vec::from_iter_in(arenas, arena));

View file

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

View file

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

View file

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

View file

@ -10,10 +10,12 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_can::abilities::AbilitiesStore;
use roc_can::expr::{AnnotatedMark, ClosureData, IntValue};
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
use roc_collections::VecMap;
use roc_debug_flags::{
dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE,
ROC_PRINT_IR_AFTER_SPECIALIZATION,
};
use roc_error_macros::internal_error;
use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
@ -743,6 +745,157 @@ impl<'a> Specialized<'a> {
}
}
/// Uniquely determines the specialization of a polymorphic (non-proc) value symbol.
/// Two specializations are equivalent if their [`SpecializationMark`]s are equal.
#[derive(PartialEq, Eq, Debug, Clone, Copy)]
struct SpecializationMark<'a> {
/// The layout of the symbol itself.
layout: Layout<'a>,
/// If this symbol is a closure def, we must also keep track of what function it specializes,
/// because the [`layout`] field will only keep track of its closure and lambda set - which can
/// be the same for two different function specializations. For example,
///
/// id = if True then \x -> x else \y -> y
/// { a: id "", b: id 1u8 }
///
/// The lambda set and captures of `id` is the same in both usages inside the record, but the
/// reified specializations of `\x -> x` and `\y -> y` must be for Str and U8.
///
/// Note that this field is not relevant for anything that is not a function.
function_mark: Option<RawFunctionLayout<'a>>,
}
/// When walking a function body, we may encounter specialized usages of polymorphic symbols. For
/// example
///
/// myTag = A
/// use1 : [A, B]
/// use1 = myTag
/// use2 : [A, B, C]
/// use2 = myTag
///
/// We keep track of the specializations of `myTag` and create fresh symbols when there is more
/// than one, so that a unique def can be created for each.
#[derive(Default, Debug, Clone)]
struct SymbolSpecializations<'a>(
// THEORY:
// 1. the number of symbols in a def is very small
// 2. the number of specializations of a symbol in a def is even smaller (almost always only one)
// So, a linear VecMap is preferrable. Use a two-layered one to make (1) extraction of defs easy
// and (2) reads of a certain symbol be determined by its first occurrence, not its last.
VecMap<Symbol, VecMap<SpecializationMark<'a>, (Variable, Symbol)>>,
);
impl<'a> SymbolSpecializations<'a> {
/// Gets a specialization for a symbol, or creates a new one.
#[inline(always)]
fn get_or_insert(
&mut self,
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
symbol: Symbol,
specialization_var: Variable,
) -> Symbol {
let arena = env.arena;
let subs: &Subs = env.subs;
let layout = match layout_cache.from_var(arena, specialization_var, subs) {
Ok(layout) => layout,
// This can happen when the def symbol has a type error. In such cases just use the
// def symbol, which is erroring.
Err(_) => return symbol,
};
let is_closure = matches!(
subs.get_content_without_compacting(specialization_var),
Content::Structure(FlatType::Func(..))
);
let function_mark = if is_closure {
let fn_layout = match layout_cache.raw_from_var(arena, specialization_var, subs) {
Ok(layout) => layout,
// This can happen when the def symbol has a type error. In such cases just use the
// def symbol, which is erroring.
Err(_) => return symbol,
};
Some(fn_layout)
} else {
None
};
let specialization_mark = SpecializationMark {
layout,
function_mark,
};
let symbol_specializations = self.0.get_or_insert(symbol, Default::default);
// For the first specialization, always reuse the current symbol. The vast majority of defs
// only have one instance type, so this preserves readability of the IR.
// TODO: turn me off and see what breaks.
let needs_fresh_symbol = !symbol_specializations.is_empty();
let mut make_specialized_symbol = || {
if needs_fresh_symbol {
env.unique_symbol()
} else {
symbol
}
};
let (_var, specialized_symbol) = symbol_specializations
.get_or_insert(specialization_mark, || {
(specialization_var, make_specialized_symbol())
});
*specialized_symbol
}
/// Inserts a known specialization for a symbol. Returns the overwritten specialization, if any.
pub fn get_or_insert_known(
&mut self,
symbol: Symbol,
mark: SpecializationMark<'a>,
specialization_var: Variable,
specialization_symbol: Symbol,
) -> Option<(Variable, Symbol)> {
self.0
.get_or_insert(symbol, Default::default)
.insert(mark, (specialization_var, specialization_symbol))
}
/// Removes all specializations for a symbol, returning the type and symbol of each specialization.
pub fn remove(
&mut self,
symbol: Symbol,
) -> impl ExactSizeIterator<Item = (SpecializationMark<'a>, (Variable, Symbol))> {
self.0
.remove(&symbol)
.map(|(_, specializations)| specializations)
.unwrap_or_default()
.into_iter()
}
/// Expects and removes at most a single specialization symbol for the given requested symbol.
/// A symbol may have no specializations if it is never referenced in a body, so it is possible
/// for this to return None.
pub fn remove_single(&mut self, symbol: Symbol) -> Option<Symbol> {
let mut specializations = self.remove(symbol);
debug_assert!(
specializations.len() < 2,
"Symbol {:?} has multiple specializations",
symbol
);
specializations.next().map(|(_, (_, symbol))| symbol)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
#[derive(Clone, Debug)]
pub struct Procs<'a> {
pub partial_procs: PartialProcs<'a>,
@ -753,7 +906,7 @@ pub struct Procs<'a> {
specialized: Specialized<'a>,
pub runtime_errors: BumpMap<Symbol, &'a str>,
pub externals_we_need: BumpMap<ModuleId, ExternalSpecializations>,
pub needed_symbol_specializations: BumpMap<(Symbol, Layout<'a>), (Variable, Symbol)>,
symbol_specializations: SymbolSpecializations<'a>,
}
impl<'a> Procs<'a> {
@ -767,38 +920,9 @@ impl<'a> Procs<'a> {
specialized: Specialized::default(),
runtime_errors: BumpMap::new_in(arena),
externals_we_need: BumpMap::new_in(arena),
needed_symbol_specializations: BumpMap::new_in(arena),
symbol_specializations: Default::default(),
}
}
/// Expects and removes a single specialization symbol for the given requested symbol.
/// In debug builds, we assert that the layout of the specialization is the layout expected by
/// the requested symbol.
fn remove_single_symbol_specialization(
&mut self,
symbol: Symbol,
layout: Layout,
) -> Option<Symbol> {
let mut specialized_symbols = self
.needed_symbol_specializations
.drain_filter(|(sym, _), _| sym == &symbol);
let specialization_symbol = specialized_symbols
.next()
.map(|((_, specialized_layout), (_, specialized_symbol))| {
debug_assert_eq!(specialized_layout, layout, "Requested the single specialization of {:?}, but the specialization layout ({:?}) doesn't match the expected layout ({:?})", symbol, specialized_layout, layout);
specialized_symbol
});
debug_assert_eq!(
specialized_symbols.count(),
0,
"Symbol {:?} has multiple specializations",
symbol
);
specialization_symbol
}
}
#[derive(Clone, Debug, PartialEq)]
@ -2166,9 +2290,9 @@ pub fn specialize_all<'a>(
specialize_host_specializations(env, &mut procs, layout_cache, specializations_for_host);
debug_assert!(
procs.needed_symbol_specializations.is_empty(),
procs.symbol_specializations.is_empty(),
"{:?}",
&procs.needed_symbol_specializations
&procs.symbol_specializations
);
procs
@ -2503,11 +2627,10 @@ fn specialize_external<'a>(
// An argument from the closure list may have taken on a specialized symbol
// name during the evaluation of the def body. If this is the case, load the
// specialized name rather than the original captured name!
let mut get_specialized_name = |symbol, layout| {
let mut get_specialized_name = |symbol| {
procs
.needed_symbol_specializations
.remove(&(symbol, layout))
.map(|(_, specialized)| specialized)
.symbol_specializations
.remove_single(symbol)
.unwrap_or(symbol)
};
@ -2545,7 +2668,7 @@ fn specialize_external<'a>(
union_layout,
};
let symbol = get_specialized_name(**symbol, **layout);
let symbol = get_specialized_name(**symbol);
specialized_body = Stmt::Let(
symbol,
@ -2588,7 +2711,7 @@ fn specialize_external<'a>(
structure: Symbol::ARG_CLOSURE,
};
let symbol = get_specialized_name(**symbol, **layout);
let symbol = get_specialized_name(**symbol);
specialized_body = Stmt::Let(
symbol,
@ -2633,11 +2756,10 @@ fn specialize_external<'a>(
let proc_args: Vec<_> = proc_args
.iter()
.map(|&(layout, symbol)| {
// Grab the specialization symbol, if it exists.
let symbol = procs
.needed_symbol_specializations
// We can remove the specialization since this is the definition site.
.remove(&(symbol, layout))
.map(|(_, specialized_symbol)| specialized_symbol)
.symbol_specializations
.remove_single(symbol)
.unwrap_or(symbol);
(layout, symbol)
@ -3351,18 +3473,7 @@ pub fn with_hole<'a>(
);
let outer_symbol = env.unique_symbol();
let pattern_layout = layout_cache
.from_var(env.arena, def.expr_var, env.subs)
.expect("Pattern has no layout");
stmt = store_pattern(
env,
procs,
layout_cache,
&mono_pattern,
pattern_layout,
outer_symbol,
stmt,
);
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
// convert the def body, store in outer_symbol
with_hole(
@ -3405,7 +3516,9 @@ pub fn with_hole<'a>(
can_reuse_symbol(env, procs, &roc_can::expr::Expr::Var(symbol))
{
let real_symbol =
reuse_symbol_or_specialize(env, procs, layout_cache, symbol, variable);
procs
.symbol_specializations
.get_or_insert(env, layout_cache, symbol, variable);
symbol = real_symbol;
}
@ -3484,8 +3597,12 @@ pub fn with_hole<'a>(
match can_reuse_symbol(env, procs, &loc_arg_expr.value) {
// Opaques decay to their argument.
ReuseSymbol::Value(symbol) => {
let real_name =
reuse_symbol_or_specialize(env, procs, layout_cache, symbol, arg_var);
let real_name = procs.symbol_specializations.get_or_insert(
env,
layout_cache,
symbol,
arg_var,
);
let mut result = hole.clone();
substitute_in_exprs(arena, &mut result, assigned, real_name);
result
@ -3538,9 +3655,8 @@ pub fn with_hole<'a>(
can_fields.push(Field::Function(symbol, variable));
}
Value(symbol) => {
let reusable = reuse_symbol_or_specialize(
let reusable = procs.symbol_specializations.get_or_insert(
env,
procs,
layout_cache,
symbol,
field.var,
@ -4353,25 +4469,38 @@ pub fn with_hole<'a>(
}
}
}
Value(function_symbol) => match full_layout {
RawFunctionLayout::Function(arg_layouts, lambda_set, ret_layout) => {
let closure_data_symbol = function_symbol;
Value(function_symbol) => {
let function_symbol = procs.symbol_specializations.get_or_insert(
env,
layout_cache,
function_symbol,
fn_var,
);
result = match_on_lambda_set(
env,
lambda_set,
closure_data_symbol,
arg_symbols,
match full_layout {
RawFunctionLayout::Function(
arg_layouts,
lambda_set,
ret_layout,
assigned,
hole,
);
) => {
let closure_data_symbol = function_symbol;
result = match_on_lambda_set(
env,
lambda_set,
closure_data_symbol,
arg_symbols,
arg_layouts,
ret_layout,
assigned,
hole,
);
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
unreachable!("calling a non-closure layout")
}
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
unreachable!("calling a non-closure layout")
}
},
}
UnspecializedExpr(symbol) => {
match procs.ability_member_aliases.get(symbol).unwrap() {
&AbilityMember(member) => {
@ -5521,7 +5650,6 @@ pub fn from_can<'a>(
}
LetNonRec(def, cont, outer_annotation) => {
if let roc_can::pattern::Pattern::Identifier(symbol) = &def.loc_pattern.value {
// dbg!(symbol, &def.loc_expr.value);
match def.loc_expr.value {
roc_can::expr::Expr::Closure(closure_data) => {
register_capturing_closure(env, procs, layout_cache, *symbol, closure_data);
@ -5653,12 +5781,14 @@ pub fn from_can<'a>(
_ => {
let rest = from_can(env, variable, cont.value, procs, layout_cache);
let needs_def_specializations = procs
.needed_symbol_specializations
.keys()
.any(|(s, _)| s == symbol);
// Remove all the requested symbol specializations now, since this is the
// def site and hence we won't need them any higher up.
let mut needed_specializations =
procs.symbol_specializations.remove(*symbol);
if !needs_def_specializations {
if needed_specializations.len() == 0 {
// We don't need any specializations, that means this symbol is never
// referenced.
return with_hole(
env,
def.loc_expr.value,
@ -5674,16 +5804,9 @@ pub fn from_can<'a>(
let mut stmt = rest;
// Remove all the requested symbol specializations now, since this is the
// def site and hence we won't need them any higher up.
let mut needed_specializations = procs
.needed_symbol_specializations
.drain_filter(|(s, _), _| s == symbol)
.collect::<std::vec::Vec<_>>();
if needed_specializations.len() == 1 {
let ((_, _wanted_layout), (var, specialized_symbol)) =
needed_specializations.pop().unwrap();
let (_specialization_mark, (var, specialized_symbol)) =
needed_specializations.next().unwrap();
// Unify the expr_var with the requested specialization once.
let _res =
@ -5700,7 +5823,7 @@ pub fn from_can<'a>(
);
} else {
// Need to eat the cost and create a specialized version of the body for each specialization.
for ((_original_symbol, _wanted_layout), (var, specialized_symbol)) in
for (_specialization_mark, (var, specialized_symbol)) in
needed_specializations
{
use crate::copy::deep_copy_type_vars_into_expr;
@ -5744,89 +5867,50 @@ pub fn from_can<'a>(
Err(_) => todo!(),
};
if let Pattern::Identifier(symbol) = mono_pattern {
let mut hole =
env.arena
.alloc(from_can(env, variable, cont.value, procs, layout_cache));
if let Pattern::Identifier(_symbol) = mono_pattern {
internal_error!("Identifier patterns should be handled in a higher code pass!")
}
for (symbol, variable, expr) in assignments {
let stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole);
// convert the continuation
let mut stmt = from_can(env, variable, cont.value, procs, layout_cache);
hole = env.arena.alloc(stmt);
}
// layer on any default record fields
for (symbol, variable, expr) in assignments {
let specialization_symbol = procs
.symbol_specializations
.remove_single(symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(symbol);
let hole = env.arena.alloc(stmt);
stmt = with_hole(
env,
expr,
variable,
procs,
layout_cache,
specialization_symbol,
hole,
);
}
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
} else {
let outer_symbol = env.unique_symbol();
stmt = store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt);
// convert the def body, store in outer_symbol
with_hole(
env,
def.loc_expr.value,
def.expr_var,
procs,
layout_cache,
symbol,
hole,
outer_symbol,
env.arena.alloc(stmt),
)
} else {
// convert the continuation
let mut stmt = from_can(env, variable, cont.value, procs, layout_cache);
// layer on any default record fields
for (symbol, variable, expr) in assignments {
let layout = layout_cache
.from_var(env.arena, variable, env.subs)
.expect("Default field has no layout");
let specialization_symbol = procs
.remove_single_symbol_specialization(symbol, layout)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(symbol);
let hole = env.arena.alloc(stmt);
stmt = with_hole(
env,
expr,
variable,
procs,
layout_cache,
specialization_symbol,
hole,
);
}
let pattern_layout = layout_cache
.from_var(env.arena, def.expr_var, env.subs)
.expect("Pattern has no layout");
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
store_pattern(
env,
procs,
layout_cache,
&mono_pattern,
pattern_layout,
outer_symbol,
stmt,
)
} else {
let outer_symbol = env.unique_symbol();
stmt = store_pattern(
env,
procs,
layout_cache,
&mono_pattern,
pattern_layout,
outer_symbol,
stmt,
);
// convert the def body, store in outer_symbol
with_hole(
env,
def.loc_expr.value,
def.expr_var,
procs,
layout_cache,
outer_symbol,
env.arena.alloc(stmt),
)
}
}
}
@ -6380,19 +6464,10 @@ pub fn store_pattern<'a>(
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>,
pattern_layout: Layout,
outer_symbol: Symbol,
stmt: Stmt<'a>,
) -> Stmt<'a> {
match store_pattern_help(
env,
procs,
layout_cache,
can_pat,
pattern_layout,
outer_symbol,
stmt,
) {
match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) {
StorePattern::Productive(new) => new,
StorePattern::NotProductive(new) => new,
}
@ -6412,7 +6487,6 @@ fn store_pattern_help<'a>(
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
can_pat: &Pattern<'a>,
pattern_layout: Layout,
outer_symbol: Symbol,
mut stmt: Stmt<'a>,
) -> StorePattern<'a> {
@ -6423,7 +6497,8 @@ fn store_pattern_help<'a>(
// An identifier in a pattern can define at most one specialization!
// Remove any requested specializations for this name now, since this is the definition site.
let specialization_symbol = procs
.remove_single_symbol_specialization(*symbol, pattern_layout)
.symbol_specializations
.remove_single(*symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(*symbol);
@ -6444,16 +6519,8 @@ fn store_pattern_help<'a>(
return StorePattern::NotProductive(stmt);
}
NewtypeDestructure { arguments, .. } => match arguments.as_slice() {
[(pattern, layout)] => {
return store_pattern_help(
env,
procs,
layout_cache,
pattern,
*layout,
outer_symbol,
stmt,
);
[(pattern, _layout)] => {
return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
}
_ => {
let mut fields = Vec::with_capacity_in(arguments.len(), env.arena);
@ -6490,16 +6557,8 @@ fn store_pattern_help<'a>(
);
}
OpaqueUnwrap { argument, .. } => {
let (pattern, layout) = &**argument;
return store_pattern_help(
env,
procs,
layout_cache,
pattern,
*layout,
outer_symbol,
stmt,
);
let (pattern, _layout) = &**argument;
return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
}
RecordDestructure(destructs, [_single_field]) => {
@ -6507,7 +6566,8 @@ fn store_pattern_help<'a>(
match &destruct.typ {
DestructType::Required(symbol) => {
let specialization_symbol = procs
.remove_single_symbol_specialization(*symbol, destruct.layout)
.symbol_specializations
.remove_single(*symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(*symbol);
@ -6525,7 +6585,6 @@ fn store_pattern_help<'a>(
procs,
layout_cache,
guard_pattern,
destruct.layout,
outer_symbol,
stmt,
);
@ -6598,7 +6657,8 @@ fn store_tag_pattern<'a>(
Identifier(symbol) => {
// Pattern can define only one specialization
let symbol = procs
.remove_single_symbol_specialization(*symbol, arg_layout)
.symbol_specializations
.remove_single(*symbol)
.unwrap_or(*symbol);
// store immediately in the given symbol
@ -6619,15 +6679,7 @@ fn store_tag_pattern<'a>(
let symbol = env.unique_symbol();
// first recurse, continuing to unpack symbol
match store_pattern_help(
env,
procs,
layout_cache,
argument,
arg_layout,
symbol,
stmt,
) {
match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
StorePattern::Productive(new) => {
is_productive = true;
stmt = new;
@ -6687,7 +6739,8 @@ fn store_newtype_pattern<'a>(
Identifier(symbol) => {
// store immediately in the given symbol, removing it specialization if it had any
let specialization_symbol = procs
.remove_single_symbol_specialization(*symbol, arg_layout)
.symbol_specializations
.remove_single(*symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(*symbol);
@ -6714,15 +6767,7 @@ fn store_newtype_pattern<'a>(
let symbol = env.unique_symbol();
// first recurse, continuing to unpack symbol
match store_pattern_help(
env,
procs,
layout_cache,
argument,
arg_layout,
symbol,
stmt,
) {
match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
StorePattern::Productive(new) => {
is_productive = true;
stmt = new;
@ -6770,7 +6815,8 @@ fn store_record_destruct<'a>(
// A destructure can define at most one specialization!
// Remove any requested specializations for this name now, since this is the definition site.
let specialization_symbol = procs
.remove_single_symbol_specialization(*symbol, destruct.layout)
.symbol_specializations
.remove_single(*symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(*symbol);
@ -6785,7 +6831,8 @@ fn store_record_destruct<'a>(
DestructType::Guard(guard_pattern) => match &guard_pattern {
Identifier(symbol) => {
let specialization_symbol = procs
.remove_single_symbol_specialization(*symbol, destruct.layout)
.symbol_specializations
.remove_single(*symbol)
// Can happen when the symbol was never used under this body, and hence has no
// requested specialization.
.unwrap_or(*symbol);
@ -6823,15 +6870,7 @@ fn store_record_destruct<'a>(
_ => {
let symbol = env.unique_symbol();
match store_pattern_help(
env,
procs,
layout_cache,
guard_pattern,
destruct.layout,
symbol,
stmt,
) {
match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) {
StorePattern::Productive(new) => {
stmt = new;
stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt));
@ -6894,45 +6933,6 @@ fn can_reuse_symbol<'a>(
}
}
/// Reuses the specialized symbol for a given symbol and instance type. If no specialization symbol
/// yet exists, one is created.
fn reuse_symbol_or_specialize<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
symbol: Symbol,
var: Variable,
) -> Symbol {
let wanted_layout = match layout_cache.from_var(env.arena, var, env.subs) {
Ok(layout) => layout,
// This can happen when the def symbol has a type error. In such cases just use the
// def symbol, which is erroring.
Err(_) => return symbol,
};
// For the first specialization, always reuse the current symbol. The vast majority of defs
// only have one instance type, so this preserves readability of the IR.
let needs_fresh_symbol = procs
.needed_symbol_specializations
.keys()
.any(|(s, _)| *s == symbol);
let mut make_specialized_symbol = || {
if needs_fresh_symbol {
env.unique_symbol()
} else {
symbol
}
};
let (_, specialized_symbol) = procs
.needed_symbol_specializations
.entry((symbol, wanted_layout))
.or_insert_with(|| (var, make_specialized_symbol()));
*specialized_symbol
}
fn possible_reuse_symbol_or_specialize<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
@ -6942,7 +6942,9 @@ fn possible_reuse_symbol_or_specialize<'a>(
) -> Symbol {
match can_reuse_symbol(env, procs, expr) {
ReuseSymbol::Value(symbol) => {
reuse_symbol_or_specialize(env, procs, layout_cache, symbol, var)
procs
.symbol_specializations
.get_or_insert(env, layout_cache, symbol, var)
}
_ => env.unique_symbol(),
}
@ -6987,16 +6989,13 @@ where
let result = build_rest(env, procs, layout_cache);
// The specializations we wanted of the symbol on the LHS of this alias.
let needed_specializations_of_left = procs
.needed_symbol_specializations
.drain_filter(|(s, _), _| s == &left)
.collect::<std::vec::Vec<_>>();
let needed_specializations_of_left = procs.symbol_specializations.remove(left);
if procs.is_imported_module_thunk(right) {
// if this is an imported symbol, then we must make sure it is
// specialized, and wrap the original in a function pointer.
let mut result = result;
for (_, (variable, left)) in needed_specializations_of_left.into_iter() {
for (_, (variable, left)) in needed_specializations_of_left {
add_needed_external(procs, env, variable, right);
let res_layout = layout_cache.from_var(env.arena, variable, env.subs);
@ -7016,14 +7015,17 @@ where
// We need to lift all specializations of "left" to be specializations of "right".
let mut scratchpad_update_specializations = std::vec::Vec::new();
let left_had_specialization_symbols = !needed_specializations_of_left.is_empty();
let left_had_specialization_symbols = needed_specializations_of_left.len() > 0;
for ((_, layout), (specialized_var, specialized_sym)) in
needed_specializations_of_left.into_iter()
for (specialization_mark, (specialized_var, specialized_sym)) in
needed_specializations_of_left
{
let old_specialized_sym = procs
.needed_symbol_specializations
.insert((right, layout), (specialized_var, specialized_sym));
let old_specialized_sym = procs.symbol_specializations.get_or_insert_known(
right,
specialization_mark,
specialized_var,
specialized_sym,
);
if let Some((_, old_specialized_sym)) = old_specialized_sym {
scratchpad_update_specializations.push((old_specialized_sym, specialized_sym));

View file

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

View file

@ -6234,4 +6234,22 @@ mod solve_expr {
"F b -> b",
)
}
#[test]
fn alias_in_opaque() {
infer_eq_without_problem(
indoc!(
r#"
app "test" provides [ foo ] to "./platform"
MyError : [ Error ]
MyResult := Result U8 MyError
foo = @MyResult (Err Error)
"#
),
"MyResult",
)
}
}

View file

@ -3270,3 +3270,51 @@ fn dec_float_suffix() {
i128
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn ceiling_to_u32() {
assert_evals_to!(
indoc!(
r#"
n : U32
n = Num.ceiling 124.5
n
"#
),
125,
u32
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn floor_to_u32() {
assert_evals_to!(
indoc!(
r#"
n : U32
n = Num.floor 124.5
n
"#
),
124,
u32
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn round_to_u32() {
assert_evals_to!(
indoc!(
r#"
n : U32
n = Num.round 124.49
n
"#
),
124,
u32
);
}

View file

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

View file

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

View file

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

View file

@ -1123,8 +1123,17 @@ impl Type {
ext.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables);
}
}
DelayedAlias(AliasCommon { .. }) => {
// do nothing, yay
DelayedAlias(AliasCommon {
type_arguments,
lambda_set_variables,
symbol: _,
}) => {
debug_assert!(lambda_set_variables
.iter()
.all(|lambda_set| matches!(lambda_set.0, Type::Variable(..))));
type_arguments.iter_mut().for_each(|t| {
t.instantiate_aliases(region, aliases, var_store, new_lambda_set_variables)
});
}
HostExposedAlias {
type_arguments: type_args,

View file

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

View file

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

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 loaded_module = load_module(file_path, Threading::Multi);
let loaded_module = load_module(file_path, Threading::AllAvailable);
let mut var_store = VarStore::default();
let dep_idents = IdentIds::exposed_builtins(8);

View file

@ -330,7 +330,7 @@ pub mod test_ed_model {
writeln!(file, "{}", clean_code_str)
.unwrap_or_else(|_| panic!("Failed to write {:?} to file: {:?}", clean_code_str, file));
let loaded_module = load_module(&temp_file_full_path, Threading::Multi);
let loaded_module = load_module(&temp_file_full_path, Threading::AllAvailable);
let mut ed_model = init_dummy_model(
clean_code_str,

View file

@ -74,7 +74,9 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
let arg = env::args().nth(1).unwrap();
let arg = env::args()
.nth(1)
.expect("Please pass a .false file as a command-line argument to the false interpreter!");
let arg = RocStr::from(arg.as_str());
let size = unsafe { roc_main_size() } as usize;

View file

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