diff --git a/.cargo/config b/.cargo/config index ff64fd130a..d9e675b5bd 100644 --- a/.cargo/config +++ b/.cargo/config @@ -10,6 +10,11 @@ test-gen-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --featur rustflags = ["-Copt-level=s", "-Clto=fat"] [env] +# Gives us the path of the workspace root for use in cargo tests without having +# to compute it per-package. +# https://github.com/rust-lang/cargo/issues/3946#issuecomment-973132993 +ROC_WORKSPACE_DIR = { value = "", relative = true } + # Debug flags. Keep this up-to-date with compiler/debug_flags/src/lib.rs. # Set = "1" to turn a debug flag on. ROC_PRETTY_PRINT_ALIAS_CONTENTS = "0" diff --git a/AUTHORS b/AUTHORS index ec21790a44..9b5b888121 100644 --- a/AUTHORS +++ b/AUTHORS @@ -80,3 +80,4 @@ Kas Buunk Oskar Hahn Nuno Ferreira Mfon Eti-mfon +Drake Bennion diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 57e16af83a..54dd29a18f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -14,7 +14,7 @@ Most contributors execute the following commands befor pushing their code: ``` cargo test cargo fmt --all -- --check -cargo clippy --workspace --tests -- -D warnings +cargo clippy --workspace --tests -- --deny warnings ``` Execute `cargo fmt --all` to fix the formatting. diff --git a/Cargo.lock b/Cargo.lock index 9f6afa7ccc..0ab4e44b24 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -523,6 +523,7 @@ dependencies = [ "roc_collections", "roc_load", "roc_module", + "roc_reporting", "serde", "serde-xml-rs", "strip-ansi-escapes", @@ -1165,6 +1166,17 @@ dependencies = [ "generic-array 0.14.5", ] +[[package]] +name = "dircpy" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70b2666334bac0698c34f849a823a049800f9fe86a950cfd192e2d2a817da920" +dependencies = [ + "jwalk", + "log", + "walkdir", +] + [[package]] name = "directories-next" version = "2.0.0" @@ -1992,6 +2004,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jwalk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "172752e853a067cbce46427de8470ddf308af7fd8ceaf9b682ef31a5021b6bb9" +dependencies = [ + "crossbeam", + "rayon", +] + [[package]] name = "khronos-egl" version = "4.1.0" @@ -3434,6 +3456,9 @@ version = "0.1.0" dependencies = [ "bumpalo", "clap 3.1.17", + "cli_utils", + "ctor", + "dircpy", "indoc", "pretty_assertions", "roc_builtins", @@ -3446,10 +3471,10 @@ dependencies = [ "roc_reporting", "roc_std", "roc_target", + "roc_test_utils", "roc_types", "target-lexicon", "tempfile", - "ven_graph", ] [[package]] @@ -4039,6 +4064,7 @@ dependencies = [ "roc_problem", "roc_region", "roc_solve", + "roc_std", "roc_target", "roc_test_utils", "roc_types", diff --git a/Earthfile b/Earthfile index 6ad2394b0d..f1c76aa8c3 100644 --- a/Earthfile +++ b/Earthfile @@ -69,7 +69,9 @@ check-clippy: FROM +build-rust-test RUN cargo clippy -V RUN --mount=type=cache,target=$SCCACHE_DIR \ - cargo clippy --workspace --tests -- -D warnings + cargo clippy --workspace --tests -- --deny warnings + RUN --mount=type=cache,target=$SCCACHE_DIR \ + cargo clippy --workspace --tests --release -- --deny warnings check-rustfmt: FROM +build-rust-test @@ -83,6 +85,7 @@ check-typos: test-rust: FROM +build-rust-test + ENV ROC_WORKSPACE_DIR=/earthbuild ENV RUST_BACKTRACE=1 # for race condition problem with cli test ENV ROC_NUM_WORKERS=1 diff --git a/ast/Cargo.toml b/ast/Cargo.toml index d1384b96ee..546b8dd81a 100644 --- a/ast/Cargo.toml +++ b/ast/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_ast" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" description = "AST as used by the editor and (soon) docs. In contrast to the compiler, these types do not keep track of a location in a file." [dependencies] diff --git a/ast/src/lang/core/expr/expr_to_expr2.rs b/ast/src/lang/core/expr/expr_to_expr2.rs index 3d7230589d..7f1e6d4e03 100644 --- a/ast/src/lang/core/expr/expr_to_expr2.rs +++ b/ast/src/lang/core/expr/expr_to_expr2.rs @@ -77,25 +77,28 @@ pub fn expr_to_expr2<'a>( } Num(string) => { match finish_parsing_num(string) { - Ok(ParsedNumResult::UnknownNum(int, _) | ParsedNumResult::Int(int, _)) => { + Ok(( + parsed, + ParsedNumResult::UnknownNum(int, _) | ParsedNumResult::Int(int, _), + )) => { let expr = Expr2::SmallInt { number: IntVal::I64(match int { IntValue::U128(_) => todo!(), - IntValue::I128(n) => n as i64, // FIXME + IntValue::I128(n) => i128::from_ne_bytes(n) as i64, // FIXME }), var: env.var_store.fresh(), // TODO non-hardcode style: IntStyle::Decimal, - text: PoolStr::new(string, env.pool), + text: PoolStr::new(parsed, env.pool), }; (expr, Output::default()) } - Ok(ParsedNumResult::Float(float, _)) => { + Ok((parsed, ParsedNumResult::Float(float, _))) => { let expr = Expr2::Float { number: FloatVal::F64(float), var: env.var_store.fresh(), - text: PoolStr::new(string, env.pool), + text: PoolStr::new(parsed, env.pool), }; (expr, Output::default()) @@ -126,7 +129,7 @@ pub fn expr_to_expr2<'a>( let expr = Expr2::SmallInt { number: IntVal::I64(match int { IntValue::U128(_) => todo!(), - IntValue::I128(n) => n as i64, // FIXME + IntValue::I128(n) => i128::from_ne_bytes(n) as i64, // FIXME }), var: env.var_store.fresh(), // TODO non-hardcode diff --git a/ast/src/lang/core/pattern.rs b/ast/src/lang/core/pattern.rs index d217fbb937..7dfbe8c722 100644 --- a/ast/src/lang/core/pattern.rs +++ b/ast/src/lang/core/pattern.rs @@ -193,22 +193,22 @@ pub fn to_pattern2<'a>( let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } - Ok(ParsedNumResult::UnknownNum(int, _bound)) => { + Ok((_, ParsedNumResult::UnknownNum(int, _bound))) => { Pattern2::NumLiteral( env.var_store.fresh(), match int { IntValue::U128(_) => todo!(), - IntValue::I128(n) => n as i64, // FIXME + IntValue::I128(n) => i128::from_ne_bytes(n) as i64, // FIXME }, ) } - Ok(ParsedNumResult::Int(int, _bound)) => { + Ok((_, ParsedNumResult::Int(int, _bound))) => { Pattern2::IntLiteral(IntVal::I64(match int { IntValue::U128(_) => todo!(), - IntValue::I128(n) => n as i64, // FIXME + IntValue::I128(n) => i128::from_ne_bytes(n) as i64, // FIXME })) } - Ok(ParsedNumResult::Float(int, _bound)) => { + Ok((_, ParsedNumResult::Float(int, _bound))) => { Pattern2::FloatLiteral(FloatVal::F64(int)) } }, @@ -228,7 +228,7 @@ pub fn to_pattern2<'a>( Ok((int, _bound)) => { let int = match int { IntValue::U128(_) => todo!(), - IntValue::I128(n) => n as i64, // FIXME + IntValue::I128(n) => i128::from_ne_bytes(n) as i64, // FIXME }; if *is_negative { Pattern2::IntLiteral(IntVal::I64(-int)) diff --git a/ast/src/solve_type.rs b/ast/src/solve_type.rs index 5b05fdb518..f4f72e1774 100644 --- a/ast/src/solve_type.rs +++ b/ast/src/solve_type.rs @@ -1849,18 +1849,8 @@ fn deep_copy_var_help( } RangedNumber(typ, vars) => { - let mut new_vars = Vec::with_capacity(vars.len()); - - for var_index in vars { - let var = subs[var_index]; - let new_var = deep_copy_var_help(subs, max_rank, pools, var); - new_vars.push(new_var); - } - - let new_slice = VariableSubsSlice::insert_into_subs(subs, new_vars.drain(..)); - let new_real_type = deep_copy_var_help(subs, max_rank, pools, typ); - let new_content = RangedNumber(new_real_type, new_slice); + let new_content = RangedNumber(new_real_type, vars); subs.set(copy, make_descriptor(new_content)); diff --git a/bindgen/Cargo.toml b/bindgen/Cargo.toml index 37b7f4def1..eedb8cbcc9 100644 --- a/bindgen/Cargo.toml +++ b/bindgen/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" repository = "https://github.com/rtfeldman/roc" -edition = "2018" +edition = "2021" description = "A CLI for roc-bindgen" [[bin]] @@ -26,7 +26,6 @@ roc_collections = { path = "../compiler/collections" } roc_target = { path = "../compiler/roc_target" } roc_error_macros = { path = "../error_macros" } bumpalo = { version = "3.8.0", features = ["collections"] } -ven_graph = { path = "../vendor/pathfinding" } target-lexicon = "0.12.3" clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions", "derive"] } @@ -34,3 +33,7 @@ clap = { version = "3.1.15", default-features = false, features = ["std", "color pretty_assertions = "1.0.0" tempfile = "3.2.0" indoc = "1.0.3" +cli_utils = { path = "../cli_utils" } +roc_test_utils = { path = "../test_utils" } +dircpy = "0.3.9" +ctor = "0.1.22" diff --git a/bindgen/src/bindgen.rs b/bindgen/src/bindgen.rs index 54b646ae0c..a405b5128f 100644 --- a/bindgen/src/bindgen.rs +++ b/bindgen/src/bindgen.rs @@ -209,10 +209,8 @@ fn add_struct>( return types.add(RocType::TransparentWrapper { name, content }); } }; - let mut sortables = bumpalo::collections::Vec::with_capacity_in( - 2 + fields_iter.size_hint().1.unwrap_or_default(), - env.arena, - ); + let mut sortables = + bumpalo::collections::Vec::with_capacity_in(2 + fields_iter.size_hint().0, env.arena); for (label, field_var) in std::iter::once(first_field) .chain(std::iter::once(second_field)) diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs index a27d75a3e4..bce623a34c 100644 --- a/bindgen/src/bindgen_rs.rs +++ b/bindgen/src/bindgen_rs.rs @@ -1,4 +1,5 @@ use roc_mono::layout::UnionLayout; +use roc_target::TargetInfo; use crate::types::{RocTagUnion, RocType, TypeId, Types}; use std::{ @@ -139,15 +140,11 @@ fn write_tag_union( let tag_names = tags.iter().map(|(name, _)| name).cloned().collect(); let discriminant_name = write_discriminant(name, tag_names, types, buf)?; let typ = types.get(type_id); - - // The tag union's variant union, e.g. - // - // #[repr(C)] - // union union_MyTagUnion { - // Bar: u128, - // Foo: core::mem::ManuallyDrop, - // } - let variant_name = format!("union_{name}"); + // TODO also do this for other targets. Remember, these can change based on more + // than just pointer width; e.g. on wasm, the alignments of U16 and U8 are both 4! + let target_info = TargetInfo::from(&target_lexicon::Triple::host()); + let discriminant_offset = RocTagUnion::discriminant_offset(tags, types, target_info); + let size = typ.size(types, target_info); { // No deriving for unions; we have to add the impls ourselves! @@ -156,7 +153,7 @@ fn write_tag_union( buf, r#" #[repr(C)] -pub union {variant_name} {{"# +pub union {name} {{"# )?; for (tag_name, opt_payload_id) in tags { @@ -181,39 +178,53 @@ pub union {variant_name} {{"# } } + // When there's no alignment padding after the largest variant, + // the compiler will make extra room for the discriminant. + // We need that to be reflected in the overall size of the enum, + // so add an extra variant with the appropriate size. + // + // (Do this even if theoretically shouldn't be necessary, since + // there's no runtime cost and it more explicitly syncs the + // union's size with what we think it should be.) + writeln!(buf, " _size_with_discriminant: [u8; {size}],")?; + buf.write_str("}\n")?; } - // The tag union struct itself, e.g. - // - // #[repr(C)] - // pub struct MyTagUnion { - // variant: variant_MyTagUnion, - // tag: tag_MyTagUnion, - // } - { - // no deriving because it contains a union; we have to - // generate the impls explicitly! - - writeln!( - buf, - r#" -#[repr(C)] -pub struct {name} {{ - variant: {variant_name}, - tag: {discriminant_name}, -}}"#, - )?; - } - // The impl for the tag union { + // An old design, which ended up not working out, was that the tag union + // was a struct containing two fields: one for the `union`, and another + // for the discriminant. + // + // The problem with this was alignment; e.g. if you have one variant with a + // RocStr in it and another with an I128, then the `union` has a size of 32B + // and the discriminant is right after it - making the size of the whole struct + // round up to 48B total, since it has an alignment of 16 from the I128. + // + // However, Roc will generate the more efficient thing here: the whole thing will + // be 32B, and the discriminant will appear at offset 24 - right after the end of + // the RocStr. The current design recognizes this and works with it, by representing + // the entire structure as a union and manually setting the tag at the appropriate offset. write!( buf, r#" impl {name} {{ pub fn tag(&self) -> {discriminant_name} {{ - self.tag + unsafe {{ + let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); + + core::mem::transmute::(*bytes.as_ptr().add({discriminant_offset})) + }} + }} + + /// Internal helper + fn set_discriminant(&mut self, tag: {discriminant_name}) {{ + let discriminant_ptr: *mut {discriminant_name} = (self as *mut {name}).cast(); + + unsafe {{ + *(discriminant_ptr.add({discriminant_offset})) = tag; + }} }} "# )?; @@ -238,9 +249,9 @@ impl {name} {{ if payload_type.has_pointer(types) { ( "core::mem::ManuallyDrop::new(payload)", - format!("core::mem::ManuallyDrop::take(&mut self.variant.{tag_name})",), + format!("core::mem::ManuallyDrop::take(&mut self.{tag_name})",), // Since this is a ManuallyDrop, our `as_` method will need - // to dereference the variant (e.g. `&self.variant.Foo`) + // to dereference the variant (e.g. `&self.Foo`) "&", // we need `mut self` for the argument because of ManuallyDrop "mut self", @@ -248,9 +259,9 @@ impl {name} {{ } else { ( "payload", - format!("self.variant.{tag_name}"), + format!("self.{tag_name}"), // Since this is not a ManuallyDrop, our `as_` method will not - // want to dereference the variant (e.g. `self.variant.Foo` with no '&') + // want to dereference the variant (e.g. `self.Foo` with no '&') "", // we don't need `mut self` unless we need ManuallyDrop "self", @@ -263,12 +274,13 @@ impl {name} {{ r#" /// Construct a tag named {tag_name}, with the appropriate payload pub fn {tag_name}(payload: {payload_type_name}) -> Self {{ - Self {{ - tag: {discriminant_name}::{tag_name}, - variant: {variant_name} {{ - {tag_name}: {init_payload} - }}, - }} + let mut answer = Self {{ + {tag_name}: {init_payload} + }}; + + answer.set_discriminant({discriminant_name}::{tag_name}); + + answer }}"#, )?; @@ -277,8 +289,10 @@ impl {name} {{ // Don't use indoc because this must be indented once! r#" /// Unsafely assume the given {name} has a .tag() of {tag_name} and convert it to {tag_name}'s payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return {tag_name}. pub unsafe fn into_{tag_name}({self_for_into}) -> {payload_type_name} {{ + debug_assert_eq!(self.tag(), {discriminant_name}::{tag_name}); {get_payload} }}"#, )?; @@ -288,9 +302,11 @@ impl {name} {{ // Don't use indoc because this must be indented once! r#" /// Unsafely assume the given {name} has a .tag() of {tag_name} and return its payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return {tag_name}. pub unsafe fn as_{tag_name}(&self) -> {ref_if_needed}{payload_type_name} {{ - {ref_if_needed}self.variant.{tag_name} + debug_assert_eq!(self.tag(), {discriminant_name}::{tag_name}); + {ref_if_needed}self.{tag_name} }}"#, )?; } else { @@ -298,18 +314,14 @@ impl {name} {{ buf, // Don't use indoc because this must be indented once! r#" - /// Construct a tag named {tag_name} - pub fn {tag_name}() -> Self {{ - Self {{ - tag: {discriminant_name}::{tag_name}, - variant: unsafe {{ - core::mem::transmute::< - core::mem::MaybeUninit<{variant_name}>, - {variant_name}, - >(core::mem::MaybeUninit::uninit()) - }}, - }} - }}"#, + /// A tag named {tag_name}, which has no payload. + pub const {tag_name}: Self = unsafe {{ + let mut bytes = [0; core::mem::size_of::<{name}>()]; + + bytes[{discriminant_offset}] = {discriminant_name}::{tag_name} as u8; + + core::mem::transmute::<[u8; core::mem::size_of::<{name}>()], {name}>(bytes) + }};"#, )?; writeln!( @@ -318,7 +330,7 @@ impl {name} {{ r#" /// Other `into_` methods return a payload, but since the {tag_name} tag /// has no payload, this does nothing and is only here for completeness. - pub fn into_{tag_name}(self) -> () {{ + pub fn into_{tag_name}(self) {{ () }}"#, )?; @@ -329,7 +341,7 @@ impl {name} {{ r#" /// Other `as` methods return a payload, but since the {tag_name} tag /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_{tag_name}(&self) -> () {{ + pub unsafe fn as_{tag_name}(&self) {{ () }}"#, )?; @@ -341,26 +353,22 @@ impl {name} {{ // The Drop impl for the tag union { - write!( + writeln!( buf, r#" impl Drop for {name} {{ - fn drop(&mut self) {{ - match self.tag {{ -"# + fn drop(&mut self) {{"# )?; write_impl_tags( - 3, + 2, tags.iter(), &discriminant_name, buf, |tag_name, opt_payload_id| { match opt_payload_id { Some(payload_id) if types.get(payload_id).has_pointer(types) => { - format!( - "unsafe {{ core::mem::ManuallyDrop::drop(&mut self.variant.{tag_name}) }},", - ) + format!("unsafe {{ core::mem::ManuallyDrop::drop(&mut self.{tag_name}) }},",) } _ => { // If it had no payload, or if the payload had no pointers, @@ -373,36 +381,33 @@ impl Drop for {name} {{ writeln!( buf, - r#" }} - }} + r#" }} }}"# )?; } // The PartialEq impl for the tag union { - write!( + writeln!( buf, r#" impl PartialEq for {name} {{ fn eq(&self, other: &Self) -> bool {{ - if self.tag != other.tag {{ + if self.tag() != other.tag() {{ return false; }} - unsafe {{ - match self.tag {{ -"# + unsafe {{"# )?; write_impl_tags( - 4, + 3, tags.iter(), &discriminant_name, buf, |tag_name, opt_payload_id| { if opt_payload_id.is_some() { - format!("self.variant.{tag_name} == other.variant.{tag_name},") + format!("self.{tag_name} == other.{tag_name},") } else { // if the tags themselves had been unequal, we already would have // early-returned with false, so this means the tags were equal @@ -414,8 +419,7 @@ impl PartialEq for {name} {{ writeln!( buf, - r#" }} - }} + r#" }} }} }}"# )?; @@ -427,99 +431,17 @@ impl PartialEq for {name} {{ // The PartialOrd impl for the tag union { - write!( + writeln!( buf, r#" impl PartialOrd for {name} {{ fn partial_cmp(&self, other: &Self) -> Option {{ - match self.tag.partial_cmp(&other.tag) {{ + match self.tag().partial_cmp(&other.tag()) {{ Some(core::cmp::Ordering::Equal) => {{}} not_eq => return not_eq, }} - unsafe {{ - match self.tag {{ -"# - )?; - - write_impl_tags( - 4, - tags.iter(), - &discriminant_name, - buf, - |tag_name, opt_payload_id| { - if opt_payload_id.is_some() { - format!("self.variant.{tag_name}.partial_cmp(&other.variant.{tag_name}),",) - } else { - // if the tags themselves had been unequal, we already would have - // early-returned, so this means the tags were equal and there's - // no payload; return Equal! - "Some(core::cmp::Ordering::Equal),".to_string() - } - }, - )?; - - writeln!( - buf, - r#" }} - }} - }} -}}"# - )?; - } - - // The Ord impl for the tag union - { - write!( - buf, - r#" -impl Ord for {name} {{ - fn cmp(&self, other: &Self) -> core::cmp::Ordering {{ - match self.tag.cmp(&other.tag) {{ - core::cmp::Ordering::Equal => {{}} - not_eq => return not_eq, - }} - - unsafe {{ - match self.tag {{ -"# - )?; - - write_impl_tags( - 4, - tags.iter(), - &discriminant_name, - buf, - |tag_name, opt_payload_id| { - if opt_payload_id.is_some() { - format!("self.variant.{tag_name}.cmp(&other.variant.{tag_name}),",) - } else { - // if the tags themselves had been unequal, we already would have - // early-returned, so this means the tags were equal and there's - // no payload; return Equal! - "core::cmp::Ordering::Equal,".to_string() - } - }, - )?; - - writeln!( - buf, - r#" }} - }} - }} -}}"# - )?; - } - - // The Clone impl for the tag union - { - write!( - buf, - r#" -impl Clone for {name} {{ - fn clone(&self) -> Self {{ - match self.tag {{ -"# + unsafe {{"# )?; write_impl_tags( @@ -529,28 +451,12 @@ impl Clone for {name} {{ buf, |tag_name, opt_payload_id| { if opt_payload_id.is_some() { - format!( - r#"Self {{ - variant: {variant_name} {{ - {tag_name}: unsafe {{ self.variant.{tag_name}.clone() }}, - }}, - tag: {discriminant_name}::{tag_name}, - }},"#, - ) + format!("self.{tag_name}.partial_cmp(&other.{tag_name}),",) } else { - // when there's no payload, we set the clone's `variant` field to - // garbage memory - format!( - r#"Self {{ - variant: unsafe {{ - core::mem::transmute::< - core::mem::MaybeUninit<{variant_name}>, - {variant_name}, - >(core::mem::MaybeUninit::uninit()) - }}, - tag: {discriminant_name}::{tag_name}, - }},"#, - ) + // if the tags themselves had been unequal, we already would have + // early-returned, so this means the tags were equal and there's + // no payload; return Equal! + "Some(core::cmp::Ordering::Equal),".to_string() } }, )?; @@ -563,44 +469,173 @@ impl Clone for {name} {{ )?; } - if !typ.has_pointer(types) { - writeln!(buf, "impl Copy for {name} {{}}\n")?; - } - - // The Debug impl for the tag union + // The Ord impl for the tag union { - write!( + writeln!( buf, r#" -impl core::fmt::Debug for {name} {{ - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {{ - f.write_str("{name}::")?; +impl Ord for {name} {{ + fn cmp(&self, other: &Self) -> core::cmp::Ordering {{ + match self.tag().cmp(&other.tag()) {{ + core::cmp::Ordering::Equal => {{}} + not_eq => return not_eq, + }} - unsafe {{ - match self.tag {{ -"# + unsafe {{"# )?; write_impl_tags( - 4, + 3, tags.iter(), &discriminant_name, buf, |tag_name, opt_payload_id| { if opt_payload_id.is_some() { - format!( - r#"f.debug_tuple("{tag_name}").field(&self.variant.{tag_name}).finish(),"#, - ) + format!("self.{tag_name}.cmp(&other.{tag_name}),",) } else { - format!(r#"f.write_str("{tag_name}"),"#) + // if the tags themselves had been unequal, we already would have + // early-returned, so this means the tags were equal and there's + // no payload; return Equal! + "core::cmp::Ordering::Equal,".to_string() } }, )?; writeln!( buf, - r#" }} - }} + r#" }} + }} +}}"# + )?; + } + + // The Clone impl for the tag union + { + writeln!( + buf, + r#" +impl Clone for {name} {{ + fn clone(&self) -> Self {{ + let mut answer = unsafe {{"# + )?; + + write_impl_tags( + 3, + tags.iter(), + &discriminant_name, + buf, + |tag_name, opt_payload_id| { + if opt_payload_id.is_some() { + format!( + r#"Self {{ + {tag_name}: self.{tag_name}.clone(), + }},"#, + ) + } else { + // when there's no payload, initialize to garbage memory. + format!( + r#"core::mem::transmute::< + core::mem::MaybeUninit<{name}>, + {name}, + >(core::mem::MaybeUninit::uninit()),"#, + ) + } + }, + )?; + + writeln!( + buf, + r#" + }}; + + answer.set_discriminant(self.tag()); + + answer + }} +}}"# + )?; + } + + if !typ.has_pointer(types) { + writeln!(buf, "impl Copy for {name} {{}}\n")?; + } + + // The Hash impl for the tag union + { + writeln!( + buf, + r#" +impl core::hash::Hash for {name} {{ + fn hash(&self, state: &mut H) {{"# + )?; + + write_impl_tags( + 2, + tags.iter(), + &discriminant_name, + buf, + |tag_name, opt_payload_id| { + let hash_tag = format!("{discriminant_name}::{tag_name}.hash(state)"); + + if opt_payload_id.is_some() { + format!( + r#"unsafe {{ + {hash_tag}; + self.{tag_name}.hash(state); + }},"# + ) + } else { + format!("{},", hash_tag) + } + }, + )?; + + writeln!( + buf, + r#" }} +}}"# + )?; + } + + // The Debug impl for the tag union + { + writeln!( + buf, + r#" +impl core::fmt::Debug for {name} {{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {{ + f.write_str("{name}::")?; + + unsafe {{"# + )?; + + write_impl_tags( + 3, + tags.iter(), + &discriminant_name, + buf, + |tag_name, opt_payload_id| match opt_payload_id { + Some(payload_id) => { + // If it's a ManuallyDrop, we need a `*` prefix to dereference it + // (because otherwise we're using ManuallyDrop's Debug instance + // rather than the Debug instance of the value it wraps). + let deref_str = if types.get(payload_id).has_pointer(types) { + "&*" + } else { + "&" + }; + + format!( + r#"f.debug_tuple("{tag_name}").field({deref_str}self.{tag_name}).finish(),"#, + ) + } + None => format!(r#"f.write_str("{tag_name}"),"#), + }, + )?; + + writeln!( + buf, + r#" }} }} }} "# @@ -621,20 +656,32 @@ fn write_impl_tags< buf: &mut String, to_branch_str: F, ) -> fmt::Result { + for _ in 0..indentations { + buf.write_str(INDENT)?; + } + + buf.write_str("match self.tag() {\n")?; + for (tag_name, opt_payload_id) in tags { let branch_str = to_branch_str(tag_name, *opt_payload_id); - for _ in 0..indentations { + for _ in 0..(indentations + 1) { buf.write_str(INDENT)?; } writeln!(buf, "{discriminant_name}::{tag_name} => {branch_str}")?; } + for _ in 0..indentations { + buf.write_str(INDENT)?; + } + + buf.write_str("}\n")?; + Ok(()) } -fn write_enumeration, S: AsRef>( +fn write_enumeration, S: AsRef + fmt::Display>( name: &str, typ: &RocType, tags: I, @@ -651,11 +698,28 @@ fn write_enumeration, S: AsRef>( // e.g. "#[repr(u8)]\npub enum Foo {\n" writeln!(buf, "#[repr(u{})]\npub enum {name} {{", tag_bytes * 8)?; - for (index, name) in tags.enumerate() { - writeln!(buf, "{INDENT}{} = {index},", name.as_ref())?; + let mut debug_buf = String::new(); + + for (index, tag_name) in tags.enumerate() { + writeln!(buf, "{INDENT}{tag_name} = {index},")?; + + debug_buf.push_str(&format!( + r#"{INDENT}{INDENT}{INDENT}Self::{tag_name} => f.write_str("{name}::{tag_name}"), +"# + )); } - buf.write_str("}\n") + writeln!( + buf, + r#"}} + +impl core::fmt::Debug for {name} {{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {{ + match self {{ +{debug_buf} }} + }} +}}"# + ) } fn write_struct( @@ -682,7 +746,7 @@ fn write_struct( for (label, field_id) in fields { writeln!( buf, - "{INDENT}{}: {},", + "{INDENT}pub {}: {},", label.as_str(), type_name(*field_id, types) )?; @@ -699,15 +763,15 @@ fn type_name(id: TypeId, types: &Types) -> String { RocType::U16 => "u16".to_string(), RocType::U32 => "u32".to_string(), RocType::U64 => "u64".to_string(), - RocType::U128 => "u128".to_string(), + RocType::U128 => "roc_std::U128".to_string(), RocType::I8 => "i8".to_string(), RocType::I16 => "i16".to_string(), RocType::I32 => "i32".to_string(), RocType::I64 => "i64".to_string(), - RocType::I128 => "i128".to_string(), + RocType::I128 => "roc_std::I128".to_string(), RocType::F32 => "f32".to_string(), RocType::F64 => "f64".to_string(), - RocType::F128 => "f128".to_string(), + RocType::F128 => "roc_std::F128".to_string(), RocType::Bool => "bool".to_string(), RocType::RocDec => "roc_std::RocDec".to_string(), RocType::RocStr => "roc_std::RocStr".to_string(), @@ -739,10 +803,6 @@ fn write_derive(typ: &RocType, types: &Types, buf: &mut String) -> fmt::Result { if !typ.has_enumeration(types) { buf.write_str("Debug, Default, ")?; - } else if matches!(typ, RocType::TagUnion(RocTagUnion::Enumeration { .. })) { - // Actual enumerations get Debug (but still not Default), - // but other tag unions do not. - buf.write_str("Debug, ")?; } if !typ.has_float(types) { @@ -854,8 +914,11 @@ impl {name} {{ // Don't use indoc because this must be indented once! r#" /// Unsafely assume the given {name} has a .tag() of {non_null_tag} and convert it to {non_null_tag}'s payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return {non_null_tag}. pub unsafe fn into_{non_null_tag}(self) -> {payload_type_name} {{ + debug_assert_eq!(self.tag(), {discriminant_name}::{non_null_tag}); + let payload = {assign_payload}; let align = core::mem::align_of::<{payload_type_name}>() as u32; @@ -870,8 +933,10 @@ impl {name} {{ // Don't use indoc because this must be indented once! r#" /// Unsafely assume the given {name} has a .tag() of {non_null_tag} and return its payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return {non_null_tag}. pub unsafe fn as_{non_null_tag}(&self) -> {ref_if_needed}{payload_type_name} {{ + debug_assert_eq!(self.tag(), {discriminant_name}::{non_null_tag}); {ref_if_needed}*self.pointer }}"#, )?; @@ -904,7 +969,7 @@ impl {name} {{ r#" /// Other `into_` methods return a payload, but since the {null_tag} tag /// has no payload, this does nothing and is only here for completeness. - pub fn into_{null_tag}(self) -> () {{ + pub fn into_{null_tag}(self) {{ () }}"#, )?; @@ -915,7 +980,7 @@ impl {name} {{ r#" /// Other `as` methods return a payload, but since the {null_tag} tag /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_{null_tag}(&self) -> () {{ + pub unsafe fn as_{null_tag}(&self) {{ () }}"#, )?; diff --git a/bindgen/src/types.rs b/bindgen/src/types.rs index c814c18072..8884d9526a 100644 --- a/bindgen/src/types.rs +++ b/bindgen/src/types.rs @@ -5,7 +5,6 @@ use roc_mono::layout::UnionLayout; use roc_std::RocDec; use roc_target::TargetInfo; use std::convert::TryInto; -use ven_graph::topological_sort; #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct TypeId(usize); @@ -52,16 +51,28 @@ impl Types { } pub fn sorted_ids(&self) -> Vec { - // TODO: instead use the bitvec matrix type we use in the Roc compiler - - // it's more efficient and also would bring us one step closer to dropping - // the dependency on this topological_sort implementation! - topological_sort(self.ids(), |id| match self.deps.get(id) { - Some(dep_ids) => dep_ids.to_vec(), - None => Vec::new(), - }) - .unwrap_or_else(|err| { - unreachable!("Cyclic type definitions: {:?}", err); - }) + use roc_collections::{ReferenceMatrix, TopologicalSort}; + + let mut matrix = ReferenceMatrix::new(self.by_id.len()); + + for type_id in self.ids() { + for dep in self.deps.get(&type_id).iter().flat_map(|x| x.iter()) { + matrix.set_row_col(type_id.0, dep.0, true); + } + } + + match matrix.topological_sort_into_groups() { + TopologicalSort::Groups { groups } => groups + .into_iter() + .flatten() + .rev() + .map(|n| TypeId(n as usize)) + .collect(), + TopologicalSort::HasCycles { + groups: _, + nodes_in_cycle, + } => unreachable!("Cyclic type definitions: {:?}", nodes_in_cycle), + } } pub fn iter(&self) -> impl ExactSizeIterator { @@ -248,6 +259,95 @@ impl RocType { } } + pub fn size(&self, types: &Types, target_info: TargetInfo) -> usize { + use std::mem::size_of; + + match self { + RocType::Bool => size_of::(), + RocType::I8 => size_of::(), + RocType::U8 => size_of::(), + RocType::I16 => size_of::(), + RocType::U16 => size_of::(), + RocType::I32 => size_of::(), + RocType::U32 => size_of::(), + RocType::I64 => size_of::(), + RocType::U64 => size_of::(), + RocType::I128 => size_of::(), + RocType::U128 => size_of::(), + RocType::F32 => size_of::(), + RocType::F64 => size_of::(), + RocType::F128 => todo!(), + RocType::RocDec => size_of::(), + RocType::RocStr | RocType::RocList(_) | RocType::RocDict(_, _) | RocType::RocSet(_) => { + 3 * target_info.ptr_size() + } + RocType::RocBox(_) => target_info.ptr_size(), + RocType::TagUnion(tag_union) => match tag_union { + RocTagUnion::Enumeration { tags, .. } => size_for_tag_count(tags.len()), + RocTagUnion::NonRecursive { tags, .. } => { + // The "unpadded" size (without taking alignment into account) + // is the sum of all the sizes of the fields. + let size_unpadded = tags.iter().fold(0, |total, (_, opt_payload_id)| { + if let Some(payload_id) = opt_payload_id { + let payload = types.get(*payload_id); + + total + payload.size(types, target_info) + } else { + total + } + }); + + // Round up to the next multiple of alignment, to incorporate + // any necessary alignment padding. + // + // e.g. if we have a record with a Str and a U8, that would be a + // size_unpadded of 25, because Str is three 8-byte pointers and U8 is 1 byte, + // but the 8-byte alignment of the pointers means we'll round 25 up to 32. + let discriminant_align = align_for_tag_count(tags.len(), target_info); + let align = self.alignment(types, target_info).max(discriminant_align); + let size_padded = (size_unpadded / align) * align; + + if size_unpadded == size_padded { + // We don't have any alignment padding, which means we can't + // put the discriminant in the padding and the compiler will + // add extra space for it. + let discriminant_size = size_for_tag_count(tags.len()); + + size_padded + discriminant_size.max(align) + } else { + size_padded + } + } + RocTagUnion::Recursive { .. } => todo!(), + RocTagUnion::NonNullableUnwrapped { .. } => todo!(), + RocTagUnion::NullableWrapped { .. } => todo!(), + RocTagUnion::NullableUnwrapped { .. } => todo!(), + }, + RocType::Struct { fields, .. } => { + // The "unpadded" size (without taking alignment into account) + // is the sum of all the sizes of the fields. + let size_unpadded = fields.iter().fold(0, |total, (_, field_id)| { + let field = types.get(*field_id); + + total + field.size(types, target_info) + }); + + // Round up to the next multiple of alignment, to incorporate + // any necessary alignment padding. + // + // e.g. if we have a record with a Str and a U8, that would be a + // size_unpadded of 25, because Str is three 8-byte pointers and U8 is 1 byte, + // but the 8-byte alignment of the pointers means we'll round 25 up to 32. + let align = self.alignment(types, target_info); + + (size_unpadded / align) * align + } + RocType::TransparentWrapper { content, .. } => { + types.get(*content).size(types, target_info) + } + } + } + pub fn alignment(&self, types: &Types, target_info: TargetInfo) -> usize { match self { RocType::RocStr @@ -260,7 +360,7 @@ impl RocType { RocType::TagUnion(RocTagUnion::NonRecursive { tags, .. }) => { // The smallest alignment this could possibly have is based on the number of tags - e.g. // 0 tags is an empty union (so, alignment 0), 1-255 tags has a u8 tag (so, alignment 1), etc. - let mut align = align_for_tag_count(tags.len()); + let mut align = align_for_tag_count(tags.len(), target_info); for (_, payloads) in tags { for id in payloads { @@ -276,7 +376,7 @@ impl RocType { // // Unlike a regular tag union, a recursive one also includes a pointer. let ptr_align = target_info.ptr_alignment_bytes(); - let mut align = ptr_align.max(align_for_tag_count(tags.len())); + let mut align = ptr_align.max(align_for_tag_count(tags.len(), target_info)); for (_, payloads) in tags { for id in payloads { @@ -292,7 +392,8 @@ impl RocType { // // Unlike a regular tag union, a recursive one also includes a pointer. let ptr_align = target_info.ptr_alignment_bytes(); - let mut align = ptr_align.max(align_for_tag_count(non_null_tags.len())); + let mut align = + ptr_align.max(align_for_tag_count(non_null_tags.len(), target_info)); for (_, _, payloads) in non_null_tags { for id in payloads { @@ -336,18 +437,40 @@ impl RocType { } } -fn align_for_tag_count(num_tags: usize) -> usize { +fn size_for_tag_count(num_tags: usize) -> usize { if num_tags == 0 { // empty tag union 0 } else if num_tags < u8::MAX as usize { - align_of::() + IntWidth::U8.stack_size() as usize } else if num_tags < u16::MAX as usize { - align_of::() + IntWidth::U16.stack_size() as usize } else if num_tags < u32::MAX as usize { - align_of::() + IntWidth::U32.stack_size() as usize } else if num_tags < u64::MAX as usize { - align_of::() + IntWidth::U64.stack_size() as usize + } else { + panic!( + "Too many tags. You can't have more than {} tags in a tag union!", + u64::MAX + ); + } +} + +/// Returns the alignment of the discriminant based on the target +/// (e.g. on wasm, these are always 4) +fn align_for_tag_count(num_tags: usize, target_info: TargetInfo) -> usize { + if num_tags == 0 { + // empty tag union + 0 + } else if num_tags < u8::MAX as usize { + IntWidth::U8.alignment_bytes(target_info) as usize + } else if num_tags < u16::MAX as usize { + IntWidth::U16.alignment_bytes(target_info) as usize + } else if num_tags < u32::MAX as usize { + IntWidth::U32.alignment_bytes(target_info) as usize + } else if num_tags < u64::MAX as usize { + IntWidth::U64.alignment_bytes(target_info) as usize } else { panic!( "Too many tags. You can't have more than {} tags in a tag union!", @@ -407,3 +530,66 @@ pub enum RocTagUnion { non_null_payload: TypeId, }, } + +impl RocTagUnion { + /// The byte offset where the discriminant is located within the tag union's + /// in-memory representation. So if you take a pointer to the tag union itself, + /// and add discriminant_offset to it, you'll have a pointer to the discriminant. + /// + /// This is only useful when given tags from RocTagUnion::Recursive or + /// RocTagUnion::NonRecursive - other tag types do not store their discriminants + /// as plain numbers at a fixed offset! + pub fn discriminant_offset( + tags: &[(String, Option)], + types: &Types, + target_info: TargetInfo, + ) -> usize { + tags.iter() + .fold(0, |max_size, (_, opt_tag_id)| match opt_tag_id { + Some(tag_id) => { + let size_unpadded = match types.get(*tag_id) { + // For structs (that is, payloads), we actually want + // to get the size *before* alignment padding is taken + // into account, since the discriminant is + // stored after those bytes. + RocType::Struct { fields, .. } => { + fields.iter().fold(0, |total, (_, field_id)| { + let field = types.get(*field_id); + + total + field.size(types, target_info) + }) + } + typ => max_size.max(typ.size(types, target_info)), + }; + + max_size.max(size_unpadded) + } + + None => max_size, + }) + } +} + +#[test] +fn sizes_agree_with_roc_std() { + use std::mem::size_of; + + let target_info = TargetInfo::from(&target_lexicon::Triple::host()); + let mut types = Types::default(); + + assert_eq!( + RocType::RocStr.size(&types, target_info), + size_of::(), + ); + + assert_eq!( + RocType::RocList(types.add(RocType::RocStr)).size(&types, target_info), + size_of::>(), + ); + + // TODO enable this once we have RocDict in roc_std + // assert_eq!( + // RocType::RocDict.size(&types, target_info), + // size_of::(), + // ); +} diff --git a/bindgen/templates/header.rs b/bindgen/templates/header.rs index 64e3921945..d363777d17 100644 --- a/bindgen/templates/header.rs +++ b/bindgen/templates/header.rs @@ -4,4 +4,5 @@ #![allow(unused_imports)] #![allow(non_snake_case)] #![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] #![allow(clippy::undocumented_unsafe_blocks)] diff --git a/bindgen/tests/fixture-templates/rust/Cargo.lock b/bindgen/tests/fixture-templates/rust/Cargo.lock new file mode 100644 index 0000000000..ebd0a436cf --- /dev/null +++ b/bindgen/tests/fixture-templates/rust/Cargo.lock @@ -0,0 +1,30 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "host" +version = "0.1.0" +dependencies = [ + "libc", + "roc_std", +] + +[[package]] +name = "libc" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" + +[[package]] +name = "roc_std" +version = "0.1.0" +dependencies = [ + "static_assertions", +] + +[[package]] +name = "static_assertions" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f406d6ee68db6796e11ffd7b4d171864c58b7451e79ef9460ea33c287a1f89a7" diff --git a/bindgen/tests/fixture-templates/rust/Cargo.toml b/bindgen/tests/fixture-templates/rust/Cargo.toml new file mode 100644 index 0000000000..2bc066786d --- /dev/null +++ b/bindgen/tests/fixture-templates/rust/Cargo.toml @@ -0,0 +1,33 @@ +# ⚠️ READ THIS BEFORE MODIFYING THIS FILE! ⚠️ +# +# This file is a fixture template. If the file you're looking at is +# in the fixture-templates/ directory, then you're all set - go ahead +# and modify it, and it will modify all the fixture tests. +# +# If this file is in the fixtures/ directory, on the other hand, then +# it is gitignored and will be overwritten the next time tests run. +# So you probably don't want to modify it by hand! Instead, modify the +# file with the same name in the fixture-templates/ directory. + +[package] +name = "host" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2018" +links = "app" + +[lib] +name = "host" +path = "src/lib.rs" +crate-type = ["staticlib", "rlib"] + +[[bin]] +name = "host" +path = "src/main.rs" + +[dependencies] +roc_std = { path = "../../../../roc_std" } +libc = "0.2" + +[workspace] diff --git a/bindgen/tests/fixture-templates/rust/build.rs b/bindgen/tests/fixture-templates/rust/build.rs new file mode 100644 index 0000000000..7dcf8da4e9 --- /dev/null +++ b/bindgen/tests/fixture-templates/rust/build.rs @@ -0,0 +1,15 @@ +// ⚠️ READ THIS BEFORE MODIFYING THIS FILE! ⚠️ +// +// This file is a fixture template. If the file you're looking at is +// in the fixture-templates/ directory, then you're all set - go ahead +// and modify it, and it will modify all the fixture tests. +// +// If this file is in the fixtures/ directory, on the other hand, then +// it is gitignored and will be overwritten the next time tests run. +// So you probably don't want to modify it by hand! Instead, modify the +// file with the same name in the fixture-templates/ directory. + +fn main() { + println!("cargo:rustc-link-lib=dylib=app"); + println!("cargo:rustc-link-search=."); +} diff --git a/bindgen/tests/fixture-templates/rust/host.c b/bindgen/tests/fixture-templates/rust/host.c new file mode 100644 index 0000000000..3914d3f6ee --- /dev/null +++ b/bindgen/tests/fixture-templates/rust/host.c @@ -0,0 +1,14 @@ +// ⚠️ READ THIS BEFORE MODIFYING THIS FILE! ⚠️ +// +// This file is a fixture template. If the file you're looking at is +// in the fixture-templates/ directory, then you're all set - go ahead +// and modify it, and it will modify all the fixture tests. +// +// If this file is in the fixtures/ directory, on the other hand, then +// it is gitignored and will be overwritten the next time tests run. +// So you probably don't want to modify it by hand! Instead, modify the +// file with the same name in the fixture-templates/ directory. + +extern int rust_main(); + +int main() { return rust_main(); } diff --git a/bindgen/tests/fixture-templates/rust/src/main.rs b/bindgen/tests/fixture-templates/rust/src/main.rs new file mode 100644 index 0000000000..51175f934b --- /dev/null +++ b/bindgen/tests/fixture-templates/rust/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + std::process::exit(host::rust_main()); +} diff --git a/bindgen/tests/fixtures/.gitignore b/bindgen/tests/fixtures/.gitignore new file mode 100644 index 0000000000..cc22f3ac1f --- /dev/null +++ b/bindgen/tests/fixtures/.gitignore @@ -0,0 +1,12 @@ +Cargo.lock +Cargo.toml +build.rs +host.c +bindings.rs +roc_externs.rs +main.rs +app +dynhost +libapp.so +metadata +preprocessedhost diff --git a/bindgen/tests/fixtures/basic-record/Package-Config.roc b/bindgen/tests/fixtures/basic-record/Package-Config.roc new file mode 100644 index 0000000000..b948f08db0 --- /dev/null +++ b/bindgen/tests/fixtures/basic-record/Package-Config.roc @@ -0,0 +1,11 @@ +platform "test-platform" + requires {} { main : _ } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + +MyRcd : { a : U64, b : U128 } + +mainForHost : MyRcd +mainForHost = main diff --git a/bindgen/tests/fixtures/basic-record/app.roc b/bindgen/tests/fixtures/basic-record/app.roc new file mode 100644 index 0000000000..ebd26222ea --- /dev/null +++ b/bindgen/tests/fixtures/basic-record/app.roc @@ -0,0 +1,6 @@ +app "app" + packages { pf: "." } + imports [] + provides [ main ] to pf + +main = { a: 1995, b: 42 } diff --git a/bindgen/tests/fixtures/basic-record/src/lib.rs b/bindgen/tests/fixtures/basic-record/src/lib.rs new file mode 100644 index 0000000000..15e3ecd7e5 --- /dev/null +++ b/bindgen/tests/fixtures/basic-record/src/lib.rs @@ -0,0 +1,93 @@ +mod bindings; + +extern "C" { + #[link_name = "roc__mainForHost_1_exposed_generic"] + fn roc_main(_: *mut bindings::MyRcd); +} + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + use std::cmp::Ordering; + use std::collections::hash_set::HashSet; + + let record = unsafe { + let mut ret: core::mem::MaybeUninit = core::mem::MaybeUninit::uninit(); + + roc_main(ret.as_mut_ptr()); + + ret.assume_init() + }; + + // Verify that the record has all the expected traits. + + assert!(record == record); // PartialEq + assert!(record.clone() == record.clone()); // Clone + + // Since this is a move, later uses of `record` will fail unless `record` has Copy + let rec2 = record; // Copy + + assert!(rec2 != Default::default()); // Default + assert!(record.partial_cmp(&record) == Some(Ordering::Equal)); // PartialOrd + assert!(record.cmp(&record) == Ordering::Equal); // Ord + + let mut set = HashSet::new(); + + set.insert(record); // Eq, Hash + set.insert(rec2); + + assert_eq!(set.len(), 1); + + println!("Record was: {:?}", record); // Debug + + // Exit code + 0 +} + +// Externs required by roc_std and by the Roc app + +use core::ffi::c_void; +use std::ffi::CStr; +use std::os::raw::c_char; + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + match tag_id { + 0 => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + _ => todo!(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} diff --git a/bindgen/tests/fixtures/enumeration/Package-Config.roc b/bindgen/tests/fixtures/enumeration/Package-Config.roc new file mode 100644 index 0000000000..3aad390f3d --- /dev/null +++ b/bindgen/tests/fixtures/enumeration/Package-Config.roc @@ -0,0 +1,11 @@ +platform "test-platform" + requires {} { main : _ } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + +MyEnum : [ Foo, Bar, Baz ] + +mainForHost : MyEnum +mainForHost = main diff --git a/bindgen/tests/fixtures/enumeration/app.roc b/bindgen/tests/fixtures/enumeration/app.roc new file mode 100644 index 0000000000..4d4a9d28e6 --- /dev/null +++ b/bindgen/tests/fixtures/enumeration/app.roc @@ -0,0 +1,6 @@ +app "app" + packages { pf: "." } + imports [] + provides [ main ] to pf + +main = Foo diff --git a/bindgen/tests/fixtures/enumeration/src/lib.rs b/bindgen/tests/fixtures/enumeration/src/lib.rs new file mode 100644 index 0000000000..b63cee5ce8 --- /dev/null +++ b/bindgen/tests/fixtures/enumeration/src/lib.rs @@ -0,0 +1,97 @@ +mod bindings; + +extern "C" { + #[link_name = "roc__mainForHost_1_exposed_generic"] + fn roc_main(_: *mut bindings::MyEnum); +} + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + use std::cmp::Ordering; + use std::collections::hash_set::HashSet; + + let tag_union = unsafe { + let mut ret: core::mem::MaybeUninit = core::mem::MaybeUninit::uninit(); + + roc_main(ret.as_mut_ptr()); + + ret.assume_init() + }; + + // Verify that it has all the expected traits. + + assert!(tag_union == tag_union); // PartialEq + assert!(tag_union.clone() == tag_union.clone()); // Clone + + // Since this is a move, later uses of `tag_union` will fail unless `tag_union` has Copy + let union2 = tag_union; // Copy + + assert!(tag_union.partial_cmp(&tag_union) == Some(Ordering::Equal)); // PartialOrd + assert!(tag_union.cmp(&tag_union) == Ordering::Equal); // Ord + + let mut set = HashSet::new(); + + set.insert(tag_union); // Eq, Hash + set.insert(union2); + + assert_eq!(set.len(), 1); + + println!( + "tag_union was: {:?}, Bar is: {:?}, Baz is: {:?}", + tag_union, + bindings::MyEnum::Bar, + bindings::MyEnum::Baz, + ); // Debug + + // Exit code + 0 +} + +// Externs required by roc_std and by the Roc app + +use core::ffi::c_void; +use std::ffi::CStr; +use std::os::raw::c_char; + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + match tag_id { + 0 => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + _ => todo!(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} diff --git a/bindgen/tests/fixtures/nested-record/Package-Config.roc b/bindgen/tests/fixtures/nested-record/Package-Config.roc new file mode 100644 index 0000000000..ccd4f6f569 --- /dev/null +++ b/bindgen/tests/fixtures/nested-record/Package-Config.roc @@ -0,0 +1,13 @@ +platform "test-platform" + requires {} { main : _ } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + +Outer : { x : Inner, y : Str, z : List U8 } + +Inner : { a : U16, b : F32 } + +mainForHost : Outer +mainForHost = main diff --git a/bindgen/tests/fixtures/nested-record/app.roc b/bindgen/tests/fixtures/nested-record/app.roc new file mode 100644 index 0000000000..2b7a1b0c1a --- /dev/null +++ b/bindgen/tests/fixtures/nested-record/app.roc @@ -0,0 +1,6 @@ +app "app" + packages { pf: "." } + imports [] + provides [ main ] to pf + +main = { x: { a: 5, b: 24 }, y: "foo", z: [ 1, 2 ] } diff --git a/bindgen/tests/fixtures/nested-record/src/lib.rs b/bindgen/tests/fixtures/nested-record/src/lib.rs new file mode 100644 index 0000000000..a660d079a9 --- /dev/null +++ b/bindgen/tests/fixtures/nested-record/src/lib.rs @@ -0,0 +1,95 @@ +mod bindings; + +extern "C" { + #[link_name = "roc__mainForHost_1_exposed_generic"] + fn roc_main(_: *mut bindings::Outer); +} + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + use std::cmp::Ordering; + + let outer = unsafe { + let mut ret: core::mem::MaybeUninit = core::mem::MaybeUninit::uninit(); + + roc_main(ret.as_mut_ptr()); + + ret.assume_init() + }; + + // Verify that `inner` has all the expected traits. + { + let inner = outer.x; + + assert!(inner == inner); // PartialEq + assert!(inner.clone() == inner.clone()); // Clone + + // Since this is a move, later uses of `inner` will fail unless `inner` has Copy + let inner2 = inner; // Copy + + assert!(inner2 != Default::default()); // Default + assert!(inner.partial_cmp(&inner) == Some(Ordering::Equal)); // PartialOrd + } + + // Verify that `outer` has all the expected traits. + { + assert!(outer == outer); // PartialEq + assert!(outer.clone() == outer.clone()); // Clone + assert!(outer != Default::default()); // Default + assert!(outer.partial_cmp(&outer) == Some(Ordering::Equal)); // PartialOrd + } + + println!("Record was: {:?}", outer); + + // Exit code + 0 +} + +// Externs required by roc_std and by the Roc app + +use core::ffi::c_void; +use std::ffi::CStr; +use std::os::raw::c_char; + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + match tag_id { + 0 => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + _ => todo!(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} diff --git a/bindgen/tests/fixtures/union-with-padding/Package-Config.roc b/bindgen/tests/fixtures/union-with-padding/Package-Config.roc new file mode 100644 index 0000000000..ea6969d59a --- /dev/null +++ b/bindgen/tests/fixtures/union-with-padding/Package-Config.roc @@ -0,0 +1,17 @@ +platform "test-platform" + requires {} { main : _ } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + +# This case is important to test because the U128 +# gives the whole struct an alignment of 16, but the +# Str is the largest variant, so the whole union has +# a size of 32 (due to alignment, rounded up from Str's 24), +# and the discriminant is stored in the 8+ bytes of padding +# that all variants have. +NonRecursive : [ Foo Str, Bar U128, Blah I32, Baz ] + +mainForHost : NonRecursive +mainForHost = main diff --git a/bindgen/tests/fixtures/union-with-padding/app.roc b/bindgen/tests/fixtures/union-with-padding/app.roc new file mode 100644 index 0000000000..dd8c9c8e5c --- /dev/null +++ b/bindgen/tests/fixtures/union-with-padding/app.roc @@ -0,0 +1,6 @@ +app "app" + packages { pf: "." } + imports [] + provides [ main ] to pf + +main = Foo "This is a test" diff --git a/bindgen/tests/fixtures/union-with-padding/src/lib.rs b/bindgen/tests/fixtures/union-with-padding/src/lib.rs new file mode 100644 index 0000000000..495063820c --- /dev/null +++ b/bindgen/tests/fixtures/union-with-padding/src/lib.rs @@ -0,0 +1,98 @@ +mod bindings; + +extern "C" { + #[link_name = "roc__mainForHost_1_exposed_generic"] + fn roc_main(_: *mut bindings::NonRecursive); +} + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + use std::cmp::Ordering; + use std::collections::hash_set::HashSet; + + let tag_union = unsafe { + let mut ret: core::mem::MaybeUninit = + core::mem::MaybeUninit::uninit(); + + roc_main(ret.as_mut_ptr()); + + ret.assume_init() + }; + + // Verify that it has all the expected traits. + + assert!(tag_union == tag_union); // PartialEq + assert!(tag_union.clone() == tag_union.clone()); // Clone + + assert!(tag_union.partial_cmp(&tag_union) == Some(Ordering::Equal)); // PartialOrd + assert!(tag_union.cmp(&tag_union) == Ordering::Equal); // Ord + + println!( + "tag_union was: {:?}\n`Foo \"small str\"` is: {:?}\n`Foo \"A long enough string to not be small\"` is: {:?}\n`Bar 123` is: {:?}\n`Baz` is: {:?}\n`Blah 456` is: {:?}", + tag_union, + bindings::NonRecursive::Foo("small str".into()), + bindings::NonRecursive::Foo("A long enough string to not be small".into()), + bindings::NonRecursive::Bar(123.into()), + bindings::NonRecursive::Baz, + bindings::NonRecursive::Blah(456), + ); // Debug + + let mut set = HashSet::new(); + + set.insert(tag_union.clone()); // Eq, Hash + set.insert(tag_union); + + assert_eq!(set.len(), 1); + + // Exit code + 0 +} + +// Externs required by roc_std and by the Roc app + +use core::ffi::c_void; +use std::ffi::CStr; +use std::os::raw::c_char; + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + match tag_id { + 0 => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + _ => todo!(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} diff --git a/bindgen/tests/fixtures/union-without-padding/Package-Config.roc b/bindgen/tests/fixtures/union-without-padding/Package-Config.roc new file mode 100644 index 0000000000..ce74845995 --- /dev/null +++ b/bindgen/tests/fixtures/union-without-padding/Package-Config.roc @@ -0,0 +1,15 @@ +platform "test-platform" + requires {} { main : _ } + exposes [] + packages {} + imports [] + provides [ mainForHost ] + +# This case is important to test because there's no padding +# after the largest variant, so the compiler adds an extra u8 +# (rounded up to alignment, so an an extra 8 bytes) in which +# to store the discriminant. We have to bindgen accordingly! +NonRecursive : [ Foo Str, Bar I64, Blah I32, Baz ] + +mainForHost : NonRecursive +mainForHost = main diff --git a/bindgen/tests/fixtures/union-without-padding/app.roc b/bindgen/tests/fixtures/union-without-padding/app.roc new file mode 100644 index 0000000000..dd8c9c8e5c --- /dev/null +++ b/bindgen/tests/fixtures/union-without-padding/app.roc @@ -0,0 +1,6 @@ +app "app" + packages { pf: "." } + imports [] + provides [ main ] to pf + +main = Foo "This is a test" diff --git a/bindgen/tests/fixtures/union-without-padding/src/lib.rs b/bindgen/tests/fixtures/union-without-padding/src/lib.rs new file mode 100644 index 0000000000..21dee37341 --- /dev/null +++ b/bindgen/tests/fixtures/union-without-padding/src/lib.rs @@ -0,0 +1,98 @@ +mod bindings; + +extern "C" { + #[link_name = "roc__mainForHost_1_exposed_generic"] + fn roc_main(_: *mut bindings::NonRecursive); +} + +#[no_mangle] +pub extern "C" fn rust_main() -> i32 { + use std::cmp::Ordering; + use std::collections::hash_set::HashSet; + + let tag_union = unsafe { + let mut ret: core::mem::MaybeUninit = + core::mem::MaybeUninit::uninit(); + + roc_main(ret.as_mut_ptr()); + + ret.assume_init() + }; + + // Verify that it has all the expected traits. + + assert!(tag_union == tag_union); // PartialEq + assert!(tag_union.clone() == tag_union.clone()); // Clone + + assert!(tag_union.partial_cmp(&tag_union) == Some(Ordering::Equal)); // PartialOrd + assert!(tag_union.cmp(&tag_union) == Ordering::Equal); // Ord + + println!( + "tag_union was: {:?}\n`Foo \"small str\"` is: {:?}\n`Foo \"A long enough string to not be small\"` is: {:?}\n`Bar 123` is: {:?}\n`Baz` is: {:?}\n`Blah 456` is: {:?}", + tag_union, + bindings::NonRecursive::Foo("small str".into()), + bindings::NonRecursive::Foo("A long enough string to not be small".into()), + bindings::NonRecursive::Bar(123), + bindings::NonRecursive::Baz, + bindings::NonRecursive::Blah(456), + ); // Debug + + let mut set = HashSet::new(); + + set.insert(tag_union.clone()); // Eq, Hash + set.insert(tag_union); + + assert_eq!(set.len(), 1); + + // Exit code + 0 +} + +// Externs required by roc_std and by the Roc app + +use core::ffi::c_void; +use std::ffi::CStr; +use std::os::raw::c_char; + +#[no_mangle] +pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { + return libc::malloc(size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_realloc( + c_ptr: *mut c_void, + new_size: usize, + _old_size: usize, + _alignment: u32, +) -> *mut c_void { + return libc::realloc(c_ptr, new_size); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { + return libc::free(c_ptr); +} + +#[no_mangle] +pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { + match tag_id { + 0 => { + let slice = CStr::from_ptr(c_ptr as *const c_char); + let string = slice.to_str().unwrap(); + eprintln!("Roc hit a panic: {}", string); + std::process::exit(1); + } + _ => todo!(), + } +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void { + libc::memcpy(dst, src, n) +} + +#[no_mangle] +pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void { + libc::memset(dst, c, n) +} diff --git a/bindgen/tests/gen_rs.rs b/bindgen/tests/gen_rs.rs index 4405ef2309..f7ebbe3d37 100644 --- a/bindgen/tests/gen_rs.rs +++ b/bindgen/tests/gen_rs.rs @@ -4,87 +4,44 @@ extern crate pretty_assertions; #[macro_use] extern crate indoc; -use roc_bindgen::bindgen_rs; -use roc_bindgen::load::load_types; -use roc_load::Threading; -use std::fs::File; -use std::io::Write; -use std::path::PathBuf; +mod helpers; -fn generate_bindings(decl_src: &str) -> String { - use tempfile::tempdir; +#[cfg(test)] +mod test_gen_rs { + use crate::helpers::generate_bindings; - let mut src = indoc!( - r#" - platform "main" - requires {} { nothing : {} } - exposes [] - packages {} - imports [] - provides [ main ] - - "# - ) - .to_string(); - - src.push_str(decl_src); - - let types = { - let dir = tempdir().expect("Unable to create tempdir"); - let filename = PathBuf::from("Package-Config.roc"); - let file_path = dir.path().join(filename); - let full_file_path = file_path.clone(); - let mut file = File::create(file_path).unwrap(); - writeln!(file, "{}", &src).unwrap(); - - let result = load_types(full_file_path, dir.path(), Threading::Single); - - dir.close().expect("Unable to close tempdir"); - - result.expect("had problems loading") - }; - - // Reuse the `src` allocation since we're done with it. - let mut buf = src; - buf.clear(); - - bindgen_rs::write_types(&types, &mut buf).expect("I/O error when writing bindgen string"); - - buf -} - -#[test] -fn record_aliased() { - let module = indoc!( - r#" - MyRcd : { a : U64, b : U128 } + #[test] + fn record_aliased() { + let module = indoc!( + r#" + MyRcd : { a : U64, b : I128 } main : MyRcd - main = { a: 1u64, b: 2u128 } + main = { a: 1u64, b: 2i128 } "# - ); + ); - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" #[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(C)] pub struct MyRcd { - b: u128, - a: u64, + pub b: roc_std::I128, + pub a: u64, } "# - ) - ); -} + ) + ); + } -#[test] -fn nested_record_aliased() { - let module = indoc!( - r#" + #[test] + fn nested_record_aliased() { + let module = indoc!( + r#" Outer : { x : Inner, y : Str, z : List U8 } Inner : { a : U16, b : F32 } @@ -92,101 +49,101 @@ fn nested_record_aliased() { main : Outer main = { x: { a: 5, b: 24 }, y: "foo", z: [ 1, 2 ] } "# - ); + ); - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" #[derive(Clone, Debug, Default, PartialEq, PartialOrd)] #[repr(C)] pub struct Outer { - y: roc_std::RocStr, - z: roc_std::RocList, - x: Inner, + pub y: roc_std::RocStr, + pub z: roc_std::RocList, + pub x: Inner, } #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] #[repr(C)] pub struct Inner { - b: f32, - a: u16, + pub b: f32, + pub a: u16, } "# - ) - ); -} + ) + ); + } -#[test] -fn record_anonymous() { - let module = "main = { a: 1u64, b: 2u128 }"; + #[test] + fn record_anonymous() { + let module = "main = { a: 1u64, b: 2u128 }"; - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" #[derive(Clone, Copy, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(C)] pub struct R1 { - b: u128, - a: u64, + pub b: roc_std::U128, + pub a: u64, } "# - ) - ); -} + ) + ); + } -#[test] -fn nested_record_anonymous() { - let module = r#"main = { x: { a: 5u16, b: 24f32 }, y: "foo", z: [ 1u8, 2 ] }"#; + #[test] + fn nested_record_anonymous() { + let module = r#"main = { x: { a: 5u16, b: 24f32 }, y: "foo", z: [ 1u8, 2 ] }"#; - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" #[derive(Clone, Debug, Default, PartialEq, PartialOrd)] #[repr(C)] pub struct R1 { - y: roc_std::RocStr, - z: roc_std::RocList, - x: R2, + pub y: roc_std::RocStr, + pub z: roc_std::RocList, + pub x: R2, } #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd)] #[repr(C)] pub struct R2 { - b: f32, - a: u16, + pub b: f32, + pub a: u16, } "# - ) - ); -} + ) + ); + } -#[test] -fn tag_union_aliased() { - let module = indoc!( - r#" + #[test] + fn tag_union_aliased() { + let module = indoc!( + r#" NonRecursive : [ Foo Str, Bar U128, Blah I32, Baz ] main : NonRecursive main = Foo "blah" "# - ); + ); - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" - #[derive(Clone, Copy, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(u8)] pub enum tag_NonRecursive { Bar = 0, @@ -195,139 +152,169 @@ fn tag_union_aliased() { Foo = 3, } - #[repr(C)] - pub union union_NonRecursive { - Bar: u128, - Blah: i32, - Foo: core::mem::ManuallyDrop, + impl core::fmt::Debug for tag_NonRecursive { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Bar => f.write_str("tag_NonRecursive::Bar"), + Self::Baz => f.write_str("tag_NonRecursive::Baz"), + Self::Blah => f.write_str("tag_NonRecursive::Blah"), + Self::Foo => f.write_str("tag_NonRecursive::Foo"), + } + } } #[repr(C)] - pub struct NonRecursive { - variant: union_NonRecursive, - tag: tag_NonRecursive, + pub union NonRecursive { + Bar: roc_std::U128, + Blah: i32, + Foo: core::mem::ManuallyDrop, + _size_with_discriminant: [u8; 32], } impl NonRecursive { pub fn tag(&self) -> tag_NonRecursive { - self.tag + unsafe { + let bytes = core::mem::transmute::<&Self, &[u8; core::mem::size_of::()]>(self); + + core::mem::transmute::(*bytes.as_ptr().add(24)) + } + } + + /// Internal helper + fn set_discriminant(&mut self, tag: tag_NonRecursive) { + let discriminant_ptr: *mut tag_NonRecursive = (self as *mut NonRecursive).cast(); + + unsafe { + *(discriminant_ptr.add(24)) = tag; + } } /// Construct a tag named Bar, with the appropriate payload - pub fn Bar(payload: u128) -> Self { - Self { - tag: tag_NonRecursive::Bar, - variant: union_NonRecursive { - Bar: payload - }, - } + pub fn Bar(payload: roc_std::U128) -> Self { + let mut answer = Self { + Bar: payload + }; + + answer.set_discriminant(tag_NonRecursive::Bar); + + answer } /// Unsafely assume the given NonRecursive has a .tag() of Bar and convert it to Bar's payload. - /// (always examine .tag() first to make sure this is the correct variant!) - pub unsafe fn into_Bar(self) -> u128 { - self.variant.Bar + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return Bar. + pub unsafe fn into_Bar(self) -> roc_std::U128 { + debug_assert_eq!(self.tag(), tag_NonRecursive::Bar); + self.Bar } /// Unsafely assume the given NonRecursive has a .tag() of Bar and return its payload. - /// (always examine .tag() first to make sure this is the correct variant!) - pub unsafe fn as_Bar(&self) -> u128 { - self.variant.Bar + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return Bar. + pub unsafe fn as_Bar(&self) -> roc_std::U128 { + debug_assert_eq!(self.tag(), tag_NonRecursive::Bar); + self.Bar } - /// Construct a tag named Baz - pub fn Baz() -> Self { - Self { - tag: tag_NonRecursive::Baz, - variant: unsafe { - core::mem::transmute::< - core::mem::MaybeUninit, - union_NonRecursive, - >(core::mem::MaybeUninit::uninit()) - }, - } - } + /// A tag named Baz, which has no payload. + pub const Baz: Self = unsafe { + let mut bytes = [0; core::mem::size_of::()]; + + bytes[24] = tag_NonRecursive::Baz as u8; + + core::mem::transmute::<[u8; core::mem::size_of::()], NonRecursive>(bytes) + }; /// Other `into_` methods return a payload, but since the Baz tag /// has no payload, this does nothing and is only here for completeness. - pub fn into_Baz(self) -> () { + pub fn into_Baz(self) { () } /// Other `as` methods return a payload, but since the Baz tag /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_Baz(&self) -> () { + pub unsafe fn as_Baz(&self) { () } /// Construct a tag named Blah, with the appropriate payload pub fn Blah(payload: i32) -> Self { - Self { - tag: tag_NonRecursive::Blah, - variant: union_NonRecursive { - Blah: payload - }, - } + let mut answer = Self { + Blah: payload + }; + + answer.set_discriminant(tag_NonRecursive::Blah); + + answer } /// Unsafely assume the given NonRecursive has a .tag() of Blah and convert it to Blah's payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return Blah. pub unsafe fn into_Blah(self) -> i32 { - self.variant.Blah + debug_assert_eq!(self.tag(), tag_NonRecursive::Blah); + self.Blah } /// Unsafely assume the given NonRecursive has a .tag() of Blah and return its payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return Blah. pub unsafe fn as_Blah(&self) -> i32 { - self.variant.Blah + debug_assert_eq!(self.tag(), tag_NonRecursive::Blah); + self.Blah } /// Construct a tag named Foo, with the appropriate payload pub fn Foo(payload: roc_std::RocStr) -> Self { - Self { - tag: tag_NonRecursive::Foo, - variant: union_NonRecursive { - Foo: core::mem::ManuallyDrop::new(payload) - }, - } + let mut answer = Self { + Foo: core::mem::ManuallyDrop::new(payload) + }; + + answer.set_discriminant(tag_NonRecursive::Foo); + + answer } /// Unsafely assume the given NonRecursive has a .tag() of Foo and convert it to Foo's payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return Foo. pub unsafe fn into_Foo(mut self) -> roc_std::RocStr { - core::mem::ManuallyDrop::take(&mut self.variant.Foo) + debug_assert_eq!(self.tag(), tag_NonRecursive::Foo); + core::mem::ManuallyDrop::take(&mut self.Foo) } /// Unsafely assume the given NonRecursive has a .tag() of Foo and return its payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return Foo. pub unsafe fn as_Foo(&self) -> &roc_std::RocStr { - &self.variant.Foo + debug_assert_eq!(self.tag(), tag_NonRecursive::Foo); + &self.Foo } } impl Drop for NonRecursive { fn drop(&mut self) { - match self.tag { + match self.tag() { tag_NonRecursive::Bar => {} tag_NonRecursive::Baz => {} tag_NonRecursive::Blah => {} - tag_NonRecursive::Foo => unsafe { core::mem::ManuallyDrop::drop(&mut self.variant.Foo) }, + tag_NonRecursive::Foo => unsafe { core::mem::ManuallyDrop::drop(&mut self.Foo) }, } } } impl PartialEq for NonRecursive { fn eq(&self, other: &Self) -> bool { - if self.tag != other.tag { + if self.tag() != other.tag() { return false; } unsafe { - match self.tag { - tag_NonRecursive::Bar => self.variant.Bar == other.variant.Bar, + match self.tag() { + tag_NonRecursive::Bar => self.Bar == other.Bar, tag_NonRecursive::Baz => true, - tag_NonRecursive::Blah => self.variant.Blah == other.variant.Blah, - tag_NonRecursive::Foo => self.variant.Foo == other.variant.Foo, + tag_NonRecursive::Blah => self.Blah == other.Blah, + tag_NonRecursive::Foo => self.Foo == other.Foo, } } } @@ -337,17 +324,17 @@ fn tag_union_aliased() { impl PartialOrd for NonRecursive { fn partial_cmp(&self, other: &Self) -> Option { - match self.tag.partial_cmp(&other.tag) { + match self.tag().partial_cmp(&other.tag()) { Some(core::cmp::Ordering::Equal) => {} not_eq => return not_eq, } unsafe { - match self.tag { - tag_NonRecursive::Bar => self.variant.Bar.partial_cmp(&other.variant.Bar), + match self.tag() { + tag_NonRecursive::Bar => self.Bar.partial_cmp(&other.Bar), tag_NonRecursive::Baz => Some(core::cmp::Ordering::Equal), - tag_NonRecursive::Blah => self.variant.Blah.partial_cmp(&other.variant.Blah), - tag_NonRecursive::Foo => self.variant.Foo.partial_cmp(&other.variant.Foo), + tag_NonRecursive::Blah => self.Blah.partial_cmp(&other.Blah), + tag_NonRecursive::Foo => self.Foo.partial_cmp(&other.Foo), } } } @@ -355,17 +342,17 @@ fn tag_union_aliased() { impl Ord for NonRecursive { fn cmp(&self, other: &Self) -> core::cmp::Ordering { - match self.tag.cmp(&other.tag) { + match self.tag().cmp(&other.tag()) { core::cmp::Ordering::Equal => {} not_eq => return not_eq, } unsafe { - match self.tag { - tag_NonRecursive::Bar => self.variant.Bar.cmp(&other.variant.Bar), + match self.tag() { + tag_NonRecursive::Bar => self.Bar.cmp(&other.Bar), tag_NonRecursive::Baz => core::cmp::Ordering::Equal, - tag_NonRecursive::Blah => self.variant.Blah.cmp(&other.variant.Blah), - tag_NonRecursive::Foo => self.variant.Foo.cmp(&other.variant.Foo), + tag_NonRecursive::Blah => self.Blah.cmp(&other.Blah), + tag_NonRecursive::Foo => self.Foo.cmp(&other.Foo), } } } @@ -373,33 +360,46 @@ fn tag_union_aliased() { impl Clone for NonRecursive { fn clone(&self) -> Self { - match self.tag { - tag_NonRecursive::Bar => Self { - variant: union_NonRecursive { - Bar: unsafe { self.variant.Bar.clone() }, - }, - tag: tag_NonRecursive::Bar, + let mut answer = unsafe { + match self.tag() { + tag_NonRecursive::Bar => Self { + Bar: self.Bar.clone(), + }, + tag_NonRecursive::Baz => core::mem::transmute::< + core::mem::MaybeUninit, + NonRecursive, + >(core::mem::MaybeUninit::uninit()), + tag_NonRecursive::Blah => Self { + Blah: self.Blah.clone(), + }, + tag_NonRecursive::Foo => Self { + Foo: self.Foo.clone(), + }, + } + + }; + + answer.set_discriminant(self.tag()); + + answer + } + } + + impl core::hash::Hash for NonRecursive { + fn hash(&self, state: &mut H) { + match self.tag() { + tag_NonRecursive::Bar => unsafe { + tag_NonRecursive::Bar.hash(state); + self.Bar.hash(state); }, - tag_NonRecursive::Baz => Self { - variant: unsafe { - core::mem::transmute::< - core::mem::MaybeUninit, - union_NonRecursive, - >(core::mem::MaybeUninit::uninit()) - }, - tag: tag_NonRecursive::Baz, + tag_NonRecursive::Baz => tag_NonRecursive::Baz.hash(state), + tag_NonRecursive::Blah => unsafe { + tag_NonRecursive::Blah.hash(state); + self.Blah.hash(state); }, - tag_NonRecursive::Blah => Self { - variant: union_NonRecursive { - Blah: unsafe { self.variant.Blah.clone() }, - }, - tag: tag_NonRecursive::Blah, - }, - tag_NonRecursive::Foo => Self { - variant: union_NonRecursive { - Foo: unsafe { self.variant.Foo.clone() }, - }, - tag: tag_NonRecursive::Foo, + tag_NonRecursive::Foo => unsafe { + tag_NonRecursive::Foo.hash(state); + self.Foo.hash(state); }, } } @@ -410,127 +410,146 @@ fn tag_union_aliased() { f.write_str("NonRecursive::")?; unsafe { - match self.tag { - tag_NonRecursive::Bar => f.debug_tuple("Bar").field(&self.variant.Bar).finish(), + match self.tag() { + tag_NonRecursive::Bar => f.debug_tuple("Bar").field(&self.Bar).finish(), tag_NonRecursive::Baz => f.write_str("Baz"), - tag_NonRecursive::Blah => f.debug_tuple("Blah").field(&self.variant.Blah).finish(), - tag_NonRecursive::Foo => f.debug_tuple("Foo").field(&self.variant.Foo).finish(), + tag_NonRecursive::Blah => f.debug_tuple("Blah").field(&self.Blah).finish(), + tag_NonRecursive::Foo => f.debug_tuple("Foo").field(&*self.Foo).finish(), } } } } "# - ) - ); -} + ) + ); + } -#[test] -fn tag_union_enumeration() { - let module = indoc!( - r#" - NonRecursive : [ Blah, Foo, Bar, ] + #[test] + fn tag_union_enumeration() { + let module = indoc!( + r#" + Enumeration : [ Blah, Foo, Bar, ] - main : NonRecursive + main : Enumeration main = Foo "# - ); + ); - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" - #[derive(Clone, Copy, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(u8)] - pub enum NonRecursive { + pub enum Enumeration { Bar = 0, Blah = 1, Foo = 2, } - "# - ) - ); -} -#[test] -fn single_tag_union_with_payloads() { - let module = indoc!( - r#" + impl core::fmt::Debug for Enumeration { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Bar => f.write_str("Enumeration::Bar"), + Self::Blah => f.write_str("Enumeration::Blah"), + Self::Foo => f.write_str("Enumeration::Foo"), + } + } + } + "# + ) + ); + } + + #[test] + fn single_tag_union_with_payloads() { + let module = indoc!( + r#" UserId : [ Id U32 Str ] main : UserId main = Id 42 "blah" "# - ); + ); - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" #[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(C)] pub struct UserId { - f1: roc_std::RocStr, - f0: u32, + pub f1: roc_std::RocStr, + pub f0: u32, } "# - ) - ); -} + ) + ); + } -#[test] -fn single_tag_union_with_one_payload_field() { - let module = indoc!( - r#" + #[test] + fn single_tag_union_with_one_payload_field() { + let module = indoc!( + r#" UserId : [ Id Str ] main : UserId main = Id "blah" "# - ); + ); - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" #[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(transparent)] pub struct UserId(roc_std::RocStr); "# - ) - ); -} + ) + ); + } -#[test] -fn cons_list_of_strings() { - let module = indoc!( - r#" + #[test] + fn cons_list_of_strings() { + let module = indoc!( + r#" StrConsList : [ Nil, Cons Str StrConsList ] main : StrConsList main = Cons "Hello, " (Cons "World!" Nil) "# - ); + ); - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" - #[derive(Clone, Copy, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(u8)] pub enum tag_StrConsList { Cons = 0, Nil = 1, } + impl core::fmt::Debug for tag_StrConsList { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Cons => f.write_str("tag_StrConsList::Cons"), + Self::Nil => f.write_str("tag_StrConsList::Nil"), + } + } + } + #[derive(Clone, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(C)] pub struct StrConsList { @@ -561,8 +580,11 @@ fn cons_list_of_strings() { } /// Unsafely assume the given StrConsList has a .tag() of Cons and convert it to Cons's payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return Cons. pub unsafe fn into_Cons(self) -> roc_std::RocStr { + debug_assert_eq!(self.tag(), tag_StrConsList::Cons); + let payload = core::mem::ManuallyDrop::take(&mut *self.pointer); let align = core::mem::align_of::() as u32; @@ -572,8 +594,10 @@ fn cons_list_of_strings() { } /// Unsafely assume the given StrConsList has a .tag() of Cons and return its payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return Cons. pub unsafe fn as_Cons(&self) -> &roc_std::RocStr { + debug_assert_eq!(self.tag(), tag_StrConsList::Cons); &*self.pointer } @@ -586,13 +610,13 @@ fn cons_list_of_strings() { /// Other `into_` methods return a payload, but since the Nil tag /// has no payload, this does nothing and is only here for completeness. - pub fn into_Nil(self) -> () { + pub fn into_Nil(self) { () } /// Other `as` methods return a payload, but since the Nil tag /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_Nil(&self) -> () { + pub unsafe fn as_Nil(&self) { () } } @@ -625,34 +649,43 @@ fn cons_list_of_strings() { } "# - ) - ); -} + ) + ); + } -#[test] -fn cons_list_of_ints() { - let module = indoc!( - r#" + #[test] + fn cons_list_of_ints() { + let module = indoc!( + r#" IntConsList : [ Empty, Prepend U16 IntConsList ] main : IntConsList main = Prepend 42 (Prepend 26 Empty) "# - ); + ); - assert_eq!( - generate_bindings(module) - .strip_prefix('\n') - .unwrap_or_default(), - indoc!( - r#" - #[derive(Clone, Copy, Debug, Eq, Ord, Hash, PartialEq, PartialOrd)] + assert_eq!( + generate_bindings(module) + .strip_prefix('\n') + .unwrap_or_default(), + indoc!( + r#" + #[derive(Clone, Copy, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(u8)] pub enum tag_IntConsList { Empty = 0, Prepend = 1, } + impl core::fmt::Debug for tag_IntConsList { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Empty => f.write_str("tag_IntConsList::Empty"), + Self::Prepend => f.write_str("tag_IntConsList::Prepend"), + } + } + } + #[derive(Clone, Eq, Ord, Hash, PartialEq, PartialOrd)] #[repr(C)] pub struct IntConsList { @@ -683,8 +716,11 @@ fn cons_list_of_ints() { } /// Unsafely assume the given IntConsList has a .tag() of Prepend and convert it to Prepend's payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return Prepend. pub unsafe fn into_Prepend(self) -> u16 { + debug_assert_eq!(self.tag(), tag_IntConsList::Prepend); + let payload = *self.pointer; let align = core::mem::align_of::() as u32; @@ -694,8 +730,10 @@ fn cons_list_of_ints() { } /// Unsafely assume the given IntConsList has a .tag() of Prepend and return its payload. - /// (always examine .tag() first to make sure this is the correct variant!) + /// (Always examine .tag() first to make sure this is the correct variant!) + /// Panics in debug builds if the .tag() doesn't return Prepend. pub unsafe fn as_Prepend(&self) -> u16 { + debug_assert_eq!(self.tag(), tag_IntConsList::Prepend); *self.pointer } @@ -708,13 +746,13 @@ fn cons_list_of_ints() { /// Other `into_` methods return a payload, but since the Empty tag /// has no payload, this does nothing and is only here for completeness. - pub fn into_Empty(self) -> () { + pub fn into_Empty(self) { () } /// Other `as` methods return a payload, but since the Empty tag /// has no payload, this does nothing and is only here for completeness. - pub unsafe fn as_Empty(&self) -> () { + pub unsafe fn as_Empty(&self) { () } } @@ -747,6 +785,7 @@ fn cons_list_of_ints() { } "# - ) - ); + ) + ); + } } diff --git a/bindgen/tests/helpers/mod.rs b/bindgen/tests/helpers/mod.rs new file mode 100644 index 0000000000..d35c2af0a8 --- /dev/null +++ b/bindgen/tests/helpers/mod.rs @@ -0,0 +1,82 @@ +use roc_bindgen::bindgen_rs; +use roc_bindgen::load::load_types; +use roc_load::Threading; +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +#[allow(dead_code)] +pub fn generate_bindings(decl_src: &str) -> String { + use tempfile::tempdir; + + let mut src = indoc!( + r#" + platform "main" + requires {} { nothing : {} } + exposes [] + packages {} + imports [] + provides [ main ] + + "# + ) + .to_string(); + + src.push_str(decl_src); + + let types = { + let dir = tempdir().expect("Unable to create tempdir"); + let filename = PathBuf::from("Package-Config.roc"); + let file_path = dir.path().join(filename); + let full_file_path = file_path.clone(); + let mut file = File::create(file_path).unwrap(); + writeln!(file, "{}", &src).unwrap(); + + let result = load_types(full_file_path, dir.path(), Threading::Single); + + dir.close().expect("Unable to close tempdir"); + + result.expect("had problems loading") + }; + + // Reuse the `src` allocation since we're done with it. + let mut buf = src; + buf.clear(); + + bindgen_rs::write_types(&types, &mut buf).expect("I/O error when writing bindgen string"); + + buf +} + +#[allow(dead_code)] +pub fn fixtures_dir(dir_name: &str) -> PathBuf { + let mut path = root_dir(); + + // Descend into cli/tests/fixtures/{dir_name} + path.push("bindgen"); + path.push("tests"); + path.push("fixtures"); + path.push(dir_name); + + path +} + +#[allow(dead_code)] +pub fn root_dir() -> PathBuf { + let mut path = env::current_exe().ok().unwrap(); + + // Get rid of the filename in target/debug/deps/cli_run-99c65e4e9a1fbd06 + path.pop(); + + // If we're in deps/ get rid of deps/ in target/debug/deps/ + if path.ends_with("deps") { + path.pop(); + } + + // Get rid of target/debug/ so we're back at the project root + path.pop(); + path.pop(); + + path +} diff --git a/bindgen/tests/test_bindgen_cli.rs b/bindgen/tests/test_bindgen_cli.rs new file mode 100644 index 0000000000..49a0493566 --- /dev/null +++ b/bindgen/tests/test_bindgen_cli.rs @@ -0,0 +1,227 @@ +#[macro_use] +extern crate pretty_assertions; + +#[macro_use] +extern crate indoc; + +extern crate dircpy; +extern crate roc_collections; + +mod helpers; + +#[cfg(test)] +mod bindgen_cli_run { + use crate::helpers::{fixtures_dir, root_dir}; + use cli_utils::helpers::{run_bindgen, run_roc, Out}; + use std::fs; + use std::path::Path; + use std::process::Command; + + // All of these tests rely on `target/` for the `cli` crate being up-to-date, + // so do a `cargo build` on it first! + #[ctor::ctor] + fn init() { + let args = if cfg!(debug_assertions) { + vec!["build"] + } else { + vec!["build", "--release"] + }; + + println!( + "Running `cargo {}` on the `cli` crate before running the tests. This may take a bit!", + args.join(" ") + ); + + let output = Command::new("cargo") + .args(args) + .current_dir(root_dir().join("cli")) + .output() + .unwrap_or_else(|err| { + panic!( + "Failed to `cargo build` roc CLI for bindgen CLI tests - error was: {:?}", + err + ) + }); + + assert!(output.status.success()); + } + + /// This macro does two things. + /// + /// First, it generates and runs a separate test for each of the given + /// expected stdout endings. Each of these should test a particular .roc file + /// in the fixtures/ directory. The fixtures themselves run assertions too, but + /// the stdout check verifies that we're actually running the code we think we are; + /// without it, it would be possible that the fixtures are just exiting without running + /// any assertions, and we would have no way to find out! + /// + /// Second, this generates an extra test which (non-recursively) traverses the + /// fixtures/ directory and verifies that each of the .roc files in there + /// has had a corresponding test generated in the previous step. This test + /// will fail if we ever add a new .roc file to fixtures/ and forget to + /// add a test for it here! + macro_rules! fixtures { + ($($test_name:ident:$fixture_dir:expr => $ends_with:expr,)+) => { + $( + #[test] + #[allow(non_snake_case)] + fn $test_name() { + let dir = fixtures_dir($fixture_dir); + + generate_bindings_for(&dir, std::iter::empty()); + let out = run_app(&dir.join("app.roc"), std::iter::empty()); + + assert!(out.status.success()); + assert_eq!(out.stderr, ""); + assert!( + out.stdout.ends_with($ends_with), + "Unexpected stdout ending - expected {:?} but stdout was: {:?}", + $ends_with, + out.stdout + ); + } + )* + + #[test] + fn all_fixtures_have_tests() { + use roc_collections::VecSet; + + let mut all_fixtures: VecSet = VecSet::default(); + + $( + all_fixtures.insert($fixture_dir.to_string()); + )* + + check_for_tests(&mut all_fixtures); + } + } + } + + fixtures! { + basic_record:"basic-record" => "Record was: MyRcd { b: 42, a: 1995 }\n", + nested_record:"nested-record" => "Record was: Outer { y: \"foo\", z: [1, 2], x: Inner { b: 24.0, a: 5 } }\n", + enumeration:"enumeration" => "tag_union was: MyEnum::Foo, Bar is: MyEnum::Bar, Baz is: MyEnum::Baz\n", + union_with_padding:"union-with-padding" => indoc!(r#" + tag_union was: NonRecursive::Foo("This is a test") + `Foo "small str"` is: NonRecursive::Foo("small str") + `Foo "A long enough string to not be small"` is: NonRecursive::Foo("A long enough string to not be small") + `Bar 123` is: NonRecursive::Bar(123) + `Baz` is: NonRecursive::Baz + `Blah 456` is: NonRecursive::Blah(456) + "#), + union_without_padding:"union-without-padding" => indoc!(r#" + tag_union was: NonRecursive::Foo("This is a test") + `Foo "small str"` is: NonRecursive::Foo("small str") + `Foo "A long enough string to not be small"` is: NonRecursive::Foo("A long enough string to not be small") + `Bar 123` is: NonRecursive::Bar(123) + `Baz` is: NonRecursive::Baz + `Blah 456` is: NonRecursive::Blah(456) + "#), + } + + fn check_for_tests(all_fixtures: &mut roc_collections::VecSet) { + use roc_collections::VecSet; + + // todo!("Remove a bunch of duplication - don't have a ton of files in there."); + + let fixtures = fixtures_dir(""); + let entries = std::fs::read_dir(fixtures.as_path()).unwrap_or_else(|err| { + panic!( + "Error trying to read {} as a fixtures directory: {}", + fixtures.to_string_lossy(), + err + ); + }); + + for entry in entries { + let entry = entry.unwrap(); + + if entry.file_type().unwrap().is_dir() { + let fixture_dir_name = entry.file_name().into_string().unwrap(); + + if !all_fixtures.remove(&fixture_dir_name) { + panic!( + "The bindgen fixture directory {} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", + entry.path().to_string_lossy() + ); + } + } + } + + assert_eq!(all_fixtures, &mut VecSet::default()); + } + + fn generate_bindings_for<'a, I: IntoIterator>( + platform_dir: &'a Path, + args: I, + ) -> Out { + let package_config = platform_dir.join("Package-Config.roc"); + let bindings_file = platform_dir.join("src").join("bindings.rs"); + let fixture_templates_dir = platform_dir + .parent() + .unwrap() + .parent() + .unwrap() + .join("fixture-templates"); + + // Copy the rust template from the templates directory into the fixture dir. + dircpy::CopyBuilder::new(fixture_templates_dir.join("rust"), platform_dir) + .overwrite(true) // overwrite any files that were already present + .run() + .unwrap(); + + // Delete the bindings file to make sure we're actually regenerating it! + if bindings_file.exists() { + fs::remove_file(&bindings_file) + .expect("Unable to remove bindings.rs in order to regenerate it in the test"); + } + + // Generate a fresh bindings.rs for this platform + let bindgen_out = run_bindgen( + // converting these all to String avoids lifetime issues + args.into_iter().map(|arg| arg.to_string()).chain([ + package_config.to_str().unwrap().to_string(), + bindings_file.to_str().unwrap().to_string(), + ]), + ); + + // If there is any stderr, it should be reporting the runtime and that's it! + if !(bindgen_out.stderr.is_empty() + || bindgen_out.stderr.starts_with("runtime: ") && bindgen_out.stderr.ends_with("ms\n")) + { + panic!( + "`roc-bindgen` command had unexpected stderr: {}", + bindgen_out.stderr + ); + } + + assert!(bindgen_out.status.success(), "bad status {:?}", bindgen_out); + + bindgen_out + } + + fn run_app<'a, I: IntoIterator>(app_file: &'a Path, args: I) -> Out { + // Generate bindings.rs for this platform + let compile_out = run_roc( + // converting these all to String avoids lifetime issues + args.into_iter() + .map(|arg| arg.to_string()) + .chain([app_file.to_str().unwrap().to_string()]), + &[], + ); + + // If there is any stderr, it should be reporting the runtime and that's it! + if !(compile_out.stderr.is_empty() + || 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); + + compile_out + } +} diff --git a/ci/bench-runner/Cargo.toml b/ci/bench-runner/Cargo.toml index 02e775065f..8449107831 100644 --- a/ci/bench-runner/Cargo.toml +++ b/ci/bench-runner/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bench-runner" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9cedf556fd..848564ac35 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" repository = "https://github.com/rtfeldman/roc" -edition = "2018" +edition = "2021" description = "A CLI for Roc" default-run = "roc" diff --git a/cli/benches/time_bench.rs b/cli/benches/time_bench.rs index 0a99bfd154..7f301634b4 100644 --- a/cli/benches/time_bench.rs +++ b/cli/benches/time_bench.rs @@ -24,7 +24,7 @@ fn bench_group_wall_time(c: &mut Criterion) { group.sample_size(nr_of_runs); - let bench_funcs: Vec>) -> ()> = vec![ + let bench_funcs: Vec>)> = vec![ bench_nqueens, // queens 11 bench_cfold, // e = mkExpr 17 1 bench_deriv, // nest deriv 8 f diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index fb94c79cce..cc23988ec4 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -11,7 +11,7 @@ extern crate roc_module; mod cli_run { use cli_utils::helpers::{ example_file, examples_dir, extract_valgrind_errors, fixture_file, fixtures_dir, - known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError, + known_bad_file, run_cmd, run_roc, run_with_valgrind, strip_colors, Out, ValgrindError, ValgrindErrorXWhat, }; use const_format::concatcp; @@ -68,21 +68,6 @@ mod cli_run { use_valgrind: bool, } - fn strip_colors(str: &str) -> String { - use roc_reporting::report::ANSI_STYLE_CODES; - str.replace(ANSI_STYLE_CODES.red, "") - .replace(ANSI_STYLE_CODES.green, "") - .replace(ANSI_STYLE_CODES.yellow, "") - .replace(ANSI_STYLE_CODES.blue, "") - .replace(ANSI_STYLE_CODES.magenta, "") - .replace(ANSI_STYLE_CODES.cyan, "") - .replace(ANSI_STYLE_CODES.white, "") - .replace(ANSI_STYLE_CODES.bold, "") - .replace(ANSI_STYLE_CODES.underline, "") - .replace(ANSI_STYLE_CODES.reset, "") - .replace(ANSI_STYLE_CODES.color_reset, "") - } - fn check_compile_error(file: &Path, flags: &[&str], expected: &str) { let compile_out = run_roc([CMD_CHECK, file.to_str().unwrap()].iter().chain(flags), &[]); let err = compile_out.stdout.trim(); @@ -922,7 +907,7 @@ mod cli_run { r#" ── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─ - I cannot find a `d` value + Nothing is named `d` in this scope. 10│ _ <- await (line d) ^ diff --git a/cli/tests/fixtures/.gitignore b/cli/tests/fixtures/.gitignore index f159786dd3..e80387874e 100644 --- a/cli/tests/fixtures/.gitignore +++ b/cli/tests/fixtures/.gitignore @@ -1,3 +1,7 @@ app *.o *.dSYM +dynhost +libapp.so +metadata +preprocessedhost diff --git a/cli_utils/Cargo.toml b/cli_utils/Cargo.toml index 0f009564c7..1c21da3cd8 100644 --- a/cli_utils/Cargo.toml +++ b/cli_utils/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" repository = "https://github.com/rtfeldman/roc" -edition = "2018" +edition = "2021" description = "Shared code for cli tests and benchmarks" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,6 +12,7 @@ description = "Shared code for cli tests and benchmarks" [dependencies] roc_cli = { path = "../cli" } roc_collections = { path = "../compiler/collections" } +roc_reporting = { path = "../reporting" } roc_load = { path = "../compiler/load" } roc_module = { path = "../compiler/module" } bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/cli_utils/src/helpers.rs b/cli_utils/src/helpers.rs index 96d330a8b6..c9c2adbab9 100644 --- a/cli_utils/src/helpers.rs +++ b/cli_utils/src/helpers.rs @@ -10,6 +10,7 @@ use std::env; use std::ffi::OsStr; use std::io::Read; use std::io::Write; +use std::path::Path; use std::path::PathBuf; use std::process::{Command, ExitStatus, Stdio}; use tempfile::NamedTempFile; @@ -21,7 +22,66 @@ pub struct Out { pub status: ExitStatus, } +pub fn run_roc(args: I, stdin_vals: &[&str]) -> Out +where + I: IntoIterator, + S: AsRef, +{ + let roc_binary_path = path_to_roc_binary(); + + // If we don't have a /target/release/roc, rebuild it! + if !roc_binary_path.exists() { + // Remove the /target/release/roc part + let root_project_dir = roc_binary_path + .parent() + .unwrap() + .parent() + .unwrap() + .parent() + .unwrap(); + + // cargo build --bin roc + // (with --release iff the test is being built with --release) + let args = if cfg!(debug_assertions) { + vec!["build", "--bin", "roc"] + } else { + vec!["build", "--release", "--bin", "roc"] + }; + + let output = Command::new("cargo") + .current_dir(root_project_dir) + .args(args) + .output() + .unwrap(); + + if !output.status.success() { + panic!("cargo build --release --bin roc failed. stdout was:\n\n{:?}\n\nstderr was:\n\n{:?}\n", + output.stdout, + output.stderr + ); + } + } + + run_with_stdin(&roc_binary_path, args, stdin_vals) +} + +pub fn run_bindgen(args: I) -> Out +where + I: IntoIterator, + S: AsRef, +{ + run_with_stdin(&path_to_bindgen_binary(), args, &[]) +} + pub fn path_to_roc_binary() -> PathBuf { + path_to_binary("roc") +} + +pub fn path_to_bindgen_binary() -> PathBuf { + path_to_binary("roc-bindgen") +} + +pub fn path_to_binary(binary_name: &str) -> PathBuf { // Adapted from https://github.com/volta-cli/volta/blob/cefdf7436a15af3ce3a38b8fe53bb0cfdb37d3dd/tests/acceptance/support/sandbox.rs#L680 // by the Volta Contributors - license information can be found in // the LEGAL_DETAILS file in the root directory of this distribution. @@ -40,13 +100,33 @@ pub fn path_to_roc_binary() -> PathBuf { }) .unwrap_or_else(|| panic!("CARGO_BIN_PATH wasn't set, and couldn't be inferred from context. Can't run CLI tests.")); - path.push("roc"); + path.push(binary_name); path } -pub fn run_roc, S: AsRef>(args: I, stdin_vals: &[&str]) -> Out { - let mut cmd = Command::new(path_to_roc_binary()); +pub fn strip_colors(str: &str) -> String { + use roc_reporting::report::ANSI_STYLE_CODES; + + str.replace(ANSI_STYLE_CODES.red, "") + .replace(ANSI_STYLE_CODES.green, "") + .replace(ANSI_STYLE_CODES.yellow, "") + .replace(ANSI_STYLE_CODES.blue, "") + .replace(ANSI_STYLE_CODES.magenta, "") + .replace(ANSI_STYLE_CODES.cyan, "") + .replace(ANSI_STYLE_CODES.white, "") + .replace(ANSI_STYLE_CODES.bold, "") + .replace(ANSI_STYLE_CODES.underline, "") + .replace(ANSI_STYLE_CODES.reset, "") + .replace(ANSI_STYLE_CODES.color_reset, "") +} + +pub fn run_with_stdin(path: &Path, args: I, stdin_vals: &[&str]) -> Out +where + I: IntoIterator, + S: AsRef, +{ + let mut cmd = Command::new(path); for arg in args { cmd.arg(arg); @@ -57,7 +137,12 @@ pub fn run_roc, S: AsRef>(args: I, stdin_vals: .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn() - .expect("failed to execute compiled `roc` binary in CLI test"); + .unwrap_or_else(|err| { + panic!( + "failed to execute compiled binary {} in CLI test: {err}", + path.to_string_lossy() + ) + }); { let stdin = child.stdin.as_mut().expect("Failed to open stdin"); @@ -71,7 +156,7 @@ pub fn run_roc, S: AsRef>(args: I, stdin_vals: let output = child .wait_with_output() - .expect("failed to get output for compiled `roc` binary in CLI test"); + .expect("failed to get output for compiled binary in CLI test"); Out { stdout: String::from_utf8(output.stdout).unwrap(), diff --git a/code_markup/Cargo.toml b/code_markup/Cargo.toml index 07eb0aa4d7..0a18868e9b 100644 --- a/code_markup/Cargo.toml +++ b/code_markup/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_code_markup" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" description = "Our own markup language for Roc code. Used by the editor and the docs." [dependencies] diff --git a/compiler/alias_analysis/Cargo.toml b/compiler/alias_analysis/Cargo.toml index 8777595cbf..98fd17f6f8 100644 --- a/compiler/alias_analysis/Cargo.toml +++ b/compiler/alias_analysis/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Roc Contributors"] -edition = "2018" +edition = "2021" license = "UPL-1.0" name = "roc_alias_analysis" version = "0.1.0" diff --git a/compiler/alias_analysis/src/lib.rs b/compiler/alias_analysis/src/lib.rs index 2d1ad68ff6..7b8c34d6eb 100644 --- a/compiler/alias_analysis/src/lib.rs +++ b/compiler/alias_analysis/src/lib.rs @@ -28,7 +28,11 @@ pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] { #[inline(always)] fn debug() -> bool { - use roc_debug_flags::{dbg_do, ROC_DEBUG_ALIAS_ANALYSIS}; + use roc_debug_flags::dbg_do; + + #[cfg(debug_assertions)] + use roc_debug_flags::ROC_DEBUG_ALIAS_ANALYSIS; + dbg_do!(ROC_DEBUG_ALIAS_ANALYSIS, { return true; }); diff --git a/compiler/arena_pool/Cargo.toml b/compiler/arena_pool/Cargo.toml index 88dcca43f3..f3d0aa6e08 100644 --- a/compiler/arena_pool/Cargo.toml +++ b/compiler/arena_pool/Cargo.toml @@ -4,5 +4,5 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" repository = "https://github.com/rtfeldman/roc" -edition = "2018" +edition = "2021" description = "A CLI for Roc" diff --git a/compiler/build/Cargo.toml b/compiler/build/Cargo.toml index 883361fe45..17ff3d5609 100644 --- a/compiler/build/Cargo.toml +++ b/compiler/build/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_build" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/builtins/Cargo.toml b/compiler/builtins/Cargo.toml index 36b973cc9e..e14f07a0bd 100644 --- a/compiler/builtins/Cargo.toml +++ b/compiler/builtins/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_builtins" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/builtins/README.md b/compiler/builtins/README.md index 9954fc4c87..761a726c9e 100644 --- a/compiler/builtins/README.md +++ b/compiler/builtins/README.md @@ -48,15 +48,15 @@ Since `List.repeat` is implemented entirely as low level functions, its `body` i ## Connecting the definition to the implementation ### module/src/low_level.rs -This `LowLevel` thing connects the builtin defined in this module to its implementation. Its referenced in `can/src/builtins.rs` and it is used in `gen/src/llvm/build.rs`. +This `LowLevel` thing connects the builtin defined in this module to its implementation. It's referenced in `can/src/builtins.rs` and it is used in `gen/src/llvm/build.rs`. ## Bottom level LLVM values and functions ### gen/src/llvm/build.rs -This is where bottom-level functions that need to be written as LLVM are created. If the function leads to a tag thats a good sign it should not be written here in `build.rs`. If its simple fundamental stuff like `INT_ADD` then it certainly should be written here. +This is where bottom-level functions that need to be written as LLVM are created. If the function leads to a tag thats a good sign it should not be written here in `build.rs`. If it's simple fundamental stuff like `INT_ADD` then it certainly should be written here. ## Letting the compiler know these functions exist ### builtins/src/std.rs -Its one thing to actually write these functions, its _another_ thing to let the Roc compiler know they exist as part of the standard library. You have to tell the compiler "Hey, this function exists, and it has this type signature". That happens in `std.rs`. +It's one thing to actually write these functions, it's _another_ thing to let the Roc compiler know they exist as part of the standard library. You have to tell the compiler "Hey, this function exists, and it has this type signature". That happens in `std.rs`. ## Specifying how we pass args to the function ### builtins/mono/src/borrow.rs diff --git a/compiler/builtins/roc/Bool.roc b/compiler/builtins/roc/Bool.roc index 0a2ad7ecfc..3e9118320b 100644 --- a/compiler/builtins/roc/Bool.roc +++ b/compiler/builtins/roc/Bool.roc @@ -1,6 +1,6 @@ interface Bool exposes [ Bool, and, or, not, isEq, isNotEq ] - imports [ ] + imports [] Bool : [ True, False ] @@ -56,7 +56,6 @@ and : Bool, Bool -> Bool ## In Roc, this is not the case. See the performance notes for [Bool.and] for details. or : Bool, Bool -> Bool # xor : Bool, Bool -> Bool # currently unimplemented - ## Returns `False` when given `True`, and vice versa. not : Bool -> Bool diff --git a/compiler/builtins/roc/Box.roc b/compiler/builtins/roc/Box.roc index 631740797c..ccf178f7af 100644 --- a/compiler/builtins/roc/Box.roc +++ b/compiler/builtins/roc/Box.roc @@ -1,6 +1,6 @@ interface Box exposes [ box, unbox ] - imports [ ] + imports [] box : a -> Box a unbox : Box a -> a diff --git a/compiler/builtins/roc/Dict.roc b/compiler/builtins/roc/Dict.roc index d5b1ba07e2..4a72687a31 100644 --- a/compiler/builtins/roc/Dict.roc +++ b/compiler/builtins/roc/Dict.roc @@ -17,7 +17,7 @@ interface Dict ] imports [ - Bool.{ Bool } + Bool.{ Bool }, ] ## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you can associate keys with values. @@ -38,7 +38,7 @@ interface Dict ## |> Dict.insert "Delhi" 16_787_941 ## |> Dict.insert "Amsterdam" 872_680 ## -## ### Accessing keys or values +## ### Accessing keys or values ## ## We can use [Dict.keys] and [Dict.values] functions to get only the keys or only the values. ## @@ -68,8 +68,6 @@ interface Dict ## When comparing two dictionaries for equality, they are `==` only if their both their contents and their ## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on ## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering. - - ## An empty dictionary. empty : Dict k v single : k, v -> Dict k v diff --git a/compiler/builtins/roc/Encode.roc b/compiler/builtins/roc/Encode.roc new file mode 100644 index 0000000000..0f6ea9e80a --- /dev/null +++ b/compiler/builtins/roc/Encode.roc @@ -0,0 +1,65 @@ +interface Encode + exposes + [ + Encoder, + Encoding, + toEncoder, + EncoderFormatting, + u8, + u16, + u32, + u64, + u128, + i8, + i16, + i32, + i64, + i128, + f32, + f64, + dec, + bool, + string, + list, + custom, + appendWith, + append, + toBytes, + ] + imports + [] + +Encoder fmt := List U8, fmt -> List U8 | fmt has EncoderFormatting + +Encoding has + toEncoder : val -> Encoder fmt | val has Encoding, fmt has EncoderFormatting + +EncoderFormatting has + u8 : U8 -> Encoder fmt | fmt has EncoderFormatting + u16 : U16 -> Encoder fmt | fmt has EncoderFormatting + u32 : U32 -> Encoder fmt | fmt has EncoderFormatting + u64 : U64 -> Encoder fmt | fmt has EncoderFormatting + u128 : U128 -> Encoder fmt | fmt has EncoderFormatting + i8 : I8 -> Encoder fmt | fmt has EncoderFormatting + i16 : I16 -> Encoder fmt | fmt has EncoderFormatting + i32 : I32 -> Encoder fmt | fmt has EncoderFormatting + i64 : I64 -> Encoder fmt | fmt has EncoderFormatting + i128 : I128 -> Encoder fmt | fmt has EncoderFormatting + f32 : F32 -> Encoder fmt | fmt has EncoderFormatting + f64 : F64 -> Encoder fmt | fmt has EncoderFormatting + dec : Dec -> Encoder fmt | fmt has EncoderFormatting + bool : Bool -> Encoder fmt | fmt has EncoderFormatting + string : Str -> Encoder fmt | fmt has EncoderFormatting + list : List elem, (elem -> Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting + +custom : (List U8, fmt -> List U8) -> Encoder fmt | fmt has EncoderFormatting +custom = \encoder -> @Encoder encoder + +appendWith : List U8, Encoder fmt, fmt -> List U8 | fmt has EncoderFormatting +appendWith = \lst, @Encoder doEncoding, fmt -> doEncoding lst fmt + +append : List U8, val, fmt -> List U8 | val has Encoding, fmt has EncoderFormatting +append = \lst, val, fmt -> appendWith lst (toEncoder val) fmt + +toBytes : val, fmt -> List U8 | val has Encoding, fmt has EncoderFormatting +toBytes = \val, fmt -> appendWith [] (toEncoder val) fmt diff --git a/compiler/builtins/roc/Json.roc b/compiler/builtins/roc/Json.roc new file mode 100644 index 0000000000..a8f7059de1 --- /dev/null +++ b/compiler/builtins/roc/Json.roc @@ -0,0 +1,83 @@ +interface Json + exposes + [ + Json, + format, + ] + imports + [ + Encode.{ + custom, + appendWith, + u8, + u16, + u32, + u64, + u128, + i8, + i16, + i32, + i64, + i128, + f32, + f64, + dec, + bool, + string, + list, + }, + ] + +Json := {} + +format = @Json {} + +numToBytes = \n -> + n |> Num.toStr |> Str.toUtf8 + +# impl EncoderFormatting for Json +u8 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +u16 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +u32 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +u64 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +u128 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +i8 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +i16 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +i32 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +i64 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +i128 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +f32 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +f64 = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +dec = \n -> custom \bytes, @Json {} -> List.concat bytes (numToBytes n) + +bool = \b -> custom \bytes, @Json {} -> + if + b + then + List.concat bytes (Str.toUtf8 "true") + else + List.concat bytes (Str.toUtf8 "false") + +string = \s -> custom \bytes, @Json {} -> + List.append bytes (Num.toU8 '"') + |> List.concat (Str.toUtf8 s) + |> List.append (Num.toU8 '"') + +list = \lst, encodeElem -> + custom \bytes, @Json {} -> + head = List.append bytes (Num.toU8 '[') + withList = List.walk lst head (\bytes1, elem -> appendWith bytes1 (encodeElem elem) (@Json {})) + + List.append withList (Num.toU8 ']') diff --git a/compiler/builtins/roc/List.roc b/compiler/builtins/roc/List.roc index 33c3cc415d..2f838aa3d4 100644 --- a/compiler/builtins/roc/List.roc +++ b/compiler/builtins/roc/List.roc @@ -53,11 +53,10 @@ interface List ] imports [ - Bool.{ Bool } + Bool.{ Bool }, ] ## Types - ## A sequential list of values. ## ## >>> [ 1, 2, 3 ] # a list of numbers @@ -193,10 +192,9 @@ interface List ## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, [List.map], [List.walk], and [List.keepIf] would all need to traverse every element in the list and build up the result from scratch. These operations are all ## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, [List.map], [List.keepIf], and [List.set] can all be optimized to perform in-place mutations. ## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way [List] worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using [List] under the hood! - ## Check if the list is empty. ## -## >>> List.isEmpty [ 1, 2, 3 ] +## >>> List.isEmpty [ 1, 2, 3 ] ## ## >>> List.isEmpty [] isEmpty : List a -> Bool @@ -321,7 +319,7 @@ walk : List elem, state, (state, elem -> state) -> state ## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`, ## `fold`, `foldRight`, or `foldr`. -walkBackwards : List elem, state, (state, elem -> state) -> state +walkBackwards : List elem, state, (state, elem -> state) -> state ## Same as [List.walk], except you can stop walking early. ## @@ -406,7 +404,7 @@ keepOks : List before, (before -> Result after *) -> List after ## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) ## >>> ## >>> List.keepErrs [ "", "a", "bc", "", "d", "ef", "" ] -keepErrs: List before, (before -> Result * after) -> List after +keepErrs : List before, (before -> Result * after) -> List after ## Convert each element in the list to something new, by calling a conversion ## function on each of them. Then return a new list of the converted values. @@ -445,7 +443,7 @@ mapWithIndex : List a, (a, Nat -> b) -> List b ## ## >>> List.range 2 8 range : Int a, Int a -> List (Int a) -sortWith : List a, (a, a -> [ LT, EQ, GT ] ) -> List a +sortWith : List a, (a, a -> [ LT, EQ, GT ]) -> List a ## Sorts a list in ascending order (lowest to highest), using a function which ## specifies a way to represent each element as a number. @@ -541,41 +539,35 @@ drop : List elem, Nat -> List elem ## To replace the element at a given index, instead of dropping it, see [List.set]. dropAt : List elem, Nat -> List elem -min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* +min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* min = \list -> when List.first list is Ok initial -> Ok (minHelp list initial) - Err ListWasEmpty -> Err ListWasEmpty - -minHelp : List (Num a), Num a -> Num a +minHelp : List (Num a), Num a -> Num a minHelp = \list, initial -> List.walk list initial \bestSoFar, current -> if current < bestSoFar then current - else bestSoFar -max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* +max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* max = \list -> when List.first list is Ok initial -> Ok (maxHelp list initial) - Err ListWasEmpty -> Err ListWasEmpty - -maxHelp : List (Num a), Num a -> Num a +maxHelp : List (Num a), Num a -> Num a maxHelp = \list, initial -> List.walk list initial \bestSoFar, current -> if current > bestSoFar then current - else bestSoFar @@ -616,4 +608,4 @@ intersperse : List elem, elem -> List elem ## than the given index, # and the `others` list will be all the others. (This ## means if you give an index of 0, the `before` list will be empty and the ## `others` list will have the same elements as the original list.) -split : List elem, Nat -> { before: List elem, others: List elem } +split : List elem, Nat -> { before : List elem, others : List elem } diff --git a/compiler/builtins/roc/Num.roc b/compiler/builtins/roc/Num.roc index 80fcb7afb0..809b74c300 100644 --- a/compiler/builtins/roc/Num.roc +++ b/compiler/builtins/roc/Num.roc @@ -4,46 +4,36 @@ interface Num Num, Int, Frac, - Integer, FloatingPoint, - I128, I64, I32, I16, I8, - U128, U64, U32, U16, U8, - Signed128, Signed64, Signed32, Signed16, Signed8, - Unsigned128, Unsigned64, Unsigned32, Unsigned16, Unsigned8, - Nat, Dec, - F32, F64, - Natural, Decimal, - Binary32, Binary64, - abs, neg, add, @@ -155,7 +145,7 @@ interface Num ] imports [ - Bool.{ Bool } + Bool.{ Bool }, ] ## Represents a number that could be either an [Int] or a [Frac]. @@ -343,7 +333,6 @@ Num range := range ## ## As such, it's very important to design your code not to exceed these bounds! ## If you need to do math outside these bounds, consider using a larger numeric size. - Int range : Num (Integer range) ## A fixed-size number with a fractional component. @@ -501,7 +490,6 @@ F32 : Num (FloatingPoint Binary32) Dec : Num (FloatingPoint Decimal) # ------- Functions - ## Convert a number to a [Str]. ## ## This is the same as calling `Num.format {}` - so for more details on @@ -875,7 +863,6 @@ subChecked : Num a, Num a -> Result (Num a) [ Overflow ]* mulWrap : Int range, Int range -> Int range # mulSaturated : Num a, Num a -> Num a - ## Multiply two numbers and check for overflow. ## ## This is the same as [Num.mul] except if the operation overflows, instead of @@ -1086,7 +1073,6 @@ minF64 = -1.7976931348623157e308 maxF64 : F64 maxF64 = 1.7976931348623157e308 - ## Converts an [Int] to an [I8]. If the given number can't be precisely represented in an [I8], ## the returned number may be different from the given number. toI8 : Int * -> I8 @@ -1142,9 +1128,7 @@ toNatChecked : Int * -> Result Nat [ OutOfBounds ]* toF32Checked : Num * -> Result F32 [ OutOfBounds ]* toF64Checked : Num * -> Result F64 [ OutOfBounds ]* - # Special Floating-Point operations - ## When given a [F64] or [F32] value, returns `False` if that value is ## [*NaN*](Num.isNaN), ∞ or -∞, and `True` otherwise. ## @@ -1152,8 +1136,7 @@ toF64Checked : Num * -> Result F64 [ OutOfBounds ]* ## ## This is the opposite of [isInfinite], except when given [*NaN*](Num.isNaN). Both ## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN). -#isFinite : Frac * -> Bool - +# isFinite : Frac * -> Bool ## When given a [F64] or [F32] value, returns `True` if that value is either ## ∞ or -∞, and `False` otherwise. ## @@ -1161,8 +1144,7 @@ toF64Checked : Num * -> Result F64 [ OutOfBounds ]* ## ## This is the opposite of [isFinite], except when given [*NaN*](Num.isNaN). Both ## [isFinite] and [isInfinite] return `False` for [*NaN*](Num.isNaN). -#isInfinite : Frac * -> Bool - +# isInfinite : Frac * -> Bool ## When given a [F64] or [F32] value, returns `True` if that value is ## *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)), and `False` otherwise. ## @@ -1185,21 +1167,17 @@ toF64Checked : Num * -> Result F64 [ OutOfBounds ]* ## Note that you should never put a *NaN* into a [Set], or use it as the key in ## a [Dict]. The result is entries that can never be removed from those ## collections! See the documentation for [Set.add] and [Dict.insert] for details. -#isNaN : Frac * -> Bool - - +# isNaN : Frac * -> Bool ## Returns the higher of two numbers. ## ## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* ## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) -#max : Num a, Num a -> Num a - +# max : Num a, Num a -> Num a ## Returns the lower of two numbers. ## ## If either argument is [*NaN*](Num.isNaN), returns `False` no matter what. (*NaN* ## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).) -#min : Num a, Num a -> Num a - +# min : Num a, Num a -> Num a # Branchless implementation that works for all numeric types: # # let is_lt = arg1 < arg2; @@ -1209,57 +1187,46 @@ toF64Checked : Num * -> Result F64 [ OutOfBounds ]* # 1, 1 -> (0 - 1) + 1 == 0 # Eq # 5, 1 -> (0 - 0) + 1 == 1 # Gt # 1, 5 -> (1 - 0) + 1 == 2 # Lt - ## Returns `Lt` if the first number is less than the second, `Gt` if ## the first is greater than the second, and `Eq` if they're equal. ## ## Although this can be passed to `List.sort`, you'll get better performance ## by using `List.sortAsc` or `List.sortDesc` instead. -#compare : Num a, Num a -> [ Lt, Eq, Gt ] - +# compare : Num a, Num a -> [ Lt, Eq, Gt ] ## [Endianness](https://en.wikipedia.org/wiki/Endianness) # Endi : [ Big, Little, Native ] - ## The `Endi` argument does not matter for [U8] and [I8], since they have ## only one byte. # toBytes : Num *, Endi -> List U8 - ## when Num.parseBytes bytes Big is ## Ok { val: f64, rest } -> ... ## Err (ExpectedNum (Frac Binary64)) -> ... # parseBytes : List U8, Endi -> Result { val : Num a, rest : List U8 } [ ExpectedNum a ]* - ## when Num.fromBytes bytes Big is ## Ok f64 -> ... ## Err (ExpectedNum (Frac Binary64)) -> ... # fromBytes : List U8, Endi -> Result (Num a) [ ExpectedNum a ]* - # Bit shifts - ## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) left. ## ## `a << b` is shorthand for `Num.shl a b`. -#shl : Int a, Int a -> Int a - +# shl : Int a, Int a -> Int a ## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) left. ## ## This is called `shlWrap` because any bits shifted ## off the beginning of the number will be wrapped around to ## the end. (In contrast, [shl] replaces discarded bits with zeroes.) -#shlWrap : Int a, Int a -> Int a - +# shlWrap : Int a, Int a -> Int a ## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) right. ## ## `a >> b` is shorthand for `Num.shr a b`. -#shr : Int a, Int a -> Int a - +# shr : Int a, Int a -> Int a ## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right. ## ## This is called `shrWrap` because any bits shifted ## off the end of the number will be wrapped around to ## the beginning. (In contrast, [shr] replaces discarded bits with zeroes.) -#shrWrap : Int a, Int a -> Int a - +# shrWrap : Int a, Int a -> Int a # ## Convert a number into a [Str], formatted with the given options. # ## # ## Default options: diff --git a/compiler/builtins/roc/Result.roc b/compiler/builtins/roc/Result.roc index 06046299a2..8e27ddbb84 100644 --- a/compiler/builtins/roc/Result.roc +++ b/compiler/builtins/roc/Result.roc @@ -12,8 +12,10 @@ Result ok err : [ Ok ok, Err err ] isOk : Result ok err -> Bool isOk = \result -> when result is - Ok _ -> True - Err _ -> False + Ok _ -> + True + Err _ -> + False ## Return True if the result indicates a failure, else return False ## @@ -21,8 +23,10 @@ isOk = \result -> isErr : Result ok err -> Bool isErr = \result -> when result is - Ok _ -> False - Err _ -> True + Ok _ -> + False + Err _ -> + True ## If the result is `Ok`, return the value it holds. Otherwise, return ## the given default value. @@ -33,8 +37,10 @@ isErr = \result -> withDefault : Result ok err, ok -> ok withDefault = \result, default -> when result is - Ok value -> value - Err _ -> default + Ok value -> + value + Err _ -> + default ## If the result is `Ok`, transform the value it holds by running a conversion ## function on it. Then return a new `Ok` holding the transformed value. @@ -50,8 +56,10 @@ withDefault = \result, default -> map : Result a err, (a -> b) -> Result b err map = \result, transform -> when result is - Ok v -> Ok (transform v) - Err e -> Err e + Ok v -> + Ok (transform v) + Err e -> + Err e ## If the result is `Err`, transform the value it holds by running a conversion ## function on it. Then return a new `Err` holding the transformed value. @@ -64,8 +72,10 @@ map = \result, transform -> mapErr : Result ok a, (a -> b) -> Result ok b mapErr = \result, transform -> when result is - Ok v -> Ok v - Err e -> Err (transform e) + Ok v -> + Ok v + Err e -> + Err (transform e) ## If the result is `Ok`, transform the entire result by running a conversion ## function on the value the `Ok` holds. Then return that new result. @@ -78,5 +88,7 @@ mapErr = \result, transform -> after : Result a err, (a -> Result b err) -> Result b err after = \result, transform -> when result is - Ok v -> transform v - Err e -> Err e + Ok v -> + transform v + Err e -> + Err e diff --git a/compiler/builtins/roc/Str.roc b/compiler/builtins/roc/Str.roc index 06d59eac53..b5ad09c12e 100644 --- a/compiler/builtins/roc/Str.roc +++ b/compiler/builtins/roc/Str.roc @@ -1,6 +1,6 @@ interface Str - exposes - [ + exposes + [ concat, Utf8Problem, Utf8ByteProblem, @@ -18,7 +18,6 @@ interface Str trim, trimLeft, trimRight, - toDec, toF64, toF32, @@ -41,7 +40,6 @@ interface Str ## Dealing with text is a deep topic, so by design, Roc's `Str` module sticks ## to the basics. ## - ## ### Unicode ## ## Unicode can represent text values which span multiple languages, symbols, and emoji. @@ -111,8 +109,6 @@ interface Str ## and you can use it as many times as you like inside a string. The name ## between the parentheses must refer to a `Str` value that is currently in ## scope, and it must be a name - it can't be an arbitrary expression like a function call. - - Utf8ByteProblem : [ InvalidStartByte, @@ -191,7 +187,6 @@ toUtf8 : Str -> List U8 # fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* # fromUtf8Range : List U8 -> Result Str [ BadUtf8 Utf8Problem Nat, OutOfBounds ]* - fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8ByteProblem Nat ]* fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [ BadUtf8 Utf8ByteProblem Nat, OutOfBounds ]* diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index a78e562ec2..8862661791 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -99,6 +99,7 @@ impl IntWidth { matches!(self, I8 | I16 | I32 | I64 | I128) } + pub const fn stack_size(&self) -> u32 { use IntWidth::*; diff --git a/compiler/builtins/src/roc.rs b/compiler/builtins/src/roc.rs index 139ae5a24c..430a9135ed 100644 --- a/compiler/builtins/src/roc.rs +++ b/compiler/builtins/src/roc.rs @@ -11,6 +11,8 @@ pub fn module_source(module_id: ModuleId) -> &'static str { ModuleId::SET => SET, ModuleId::BOX => BOX, ModuleId::BOOL => BOOL, + ModuleId::ENCODE => ENCODE, + ModuleId::JSON => JSON, _ => panic!( "ModuleId {:?} is not part of the standard library", module_id @@ -26,3 +28,5 @@ const DICT: &str = include_str!("../roc/Dict.roc"); const SET: &str = include_str!("../roc/Set.roc"); const BOX: &str = include_str!("../roc/Box.roc"); const BOOL: &str = include_str!("../roc/Bool.roc"); +const ENCODE: &str = include_str!("../roc/Encode.roc"); +const JSON: &str = include_str!("../roc/Json.roc"); diff --git a/compiler/can/Cargo.toml b/compiler/can/Cargo.toml index b57aba90ad..79d99d746e 100644 --- a/compiler/can/Cargo.toml +++ b/compiler/can/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_can" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/can/src/abilities.rs b/compiler/can/src/abilities.rs index 7189680560..980222beba 100644 --- a/compiler/can/src/abilities.rs +++ b/compiler/can/src/abilities.rs @@ -1,4 +1,5 @@ -use roc_collections::all::MutMap; +use roc_collections::{all::MutMap, VecMap, VecSet}; +use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::{subs::Variable, types::Type}; @@ -12,18 +13,42 @@ pub struct MemberVariables { pub flex_vars: Vec, } +#[derive(Debug, Clone)] +pub enum MemberTypeInfo { + /// The member and its signature is defined locally, in the module the store is created for. + /// We need to instantiate and introduce this during solving. + Local { + signature_var: Variable, + signature: Type, + variables: MemberVariables, + }, + /// The member was defined in another module, so we'll import its variable when it's time to + /// solve. At that point we'll resolve `var` here. + Imported { signature_var: Option }, +} + /// Stores information about an ability member definition, including the parent ability, the /// defining type, and what type variables need to be instantiated with instances of the ability. // TODO: SoA and put me in an arena #[derive(Debug, Clone)] pub struct AbilityMemberData { pub parent_ability: Symbol, - pub signature_var: Variable, - pub signature: Type, - pub variables: MemberVariables, pub region: Region, + pub typ: MemberTypeInfo, } +impl AbilityMemberData { + pub fn signature_var(&self) -> Option { + match self.typ { + MemberTypeInfo::Local { signature_var, .. } => Some(signature_var), + MemberTypeInfo::Imported { signature_var } => signature_var, + } + } +} + +/// (member, specialization type) -> specialization +pub type SolvedSpecializations = VecMap<(Symbol, Symbol), MemberSpecialization>; + /// A particular specialization of an ability member. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct MemberSpecialization { @@ -66,7 +91,7 @@ pub struct AbilitiesStore { /// Maps a tuple (member, type) specifying that `type` declares an implementation of an ability /// member `member`, to the exact symbol that implements the ability. - declared_specializations: MutMap<(Symbol, Symbol), MemberSpecialization>, + declared_specializations: SolvedSpecializations, next_specialization_id: u32, @@ -77,24 +102,16 @@ pub struct AbilitiesStore { impl AbilitiesStore { /// Records the definition of an ability, including its members. - pub fn register_ability( - &mut self, - ability: Symbol, - members: Vec<(Symbol, Region, Variable, Type, MemberVariables)>, - ) { + pub fn register_ability(&mut self, ability: Symbol, members: I) + where + I: IntoIterator, + I::IntoIter: ExactSizeIterator, + { + let members = members.into_iter(); let mut members_vec = Vec::with_capacity(members.len()); - for (member, region, signature_var, signature, variables) in members.into_iter() { + for (member, member_data) in members { members_vec.push(member); - let old_member = self.ability_members.insert( - member, - AbilityMemberData { - parent_ability: ability, - signature_var, - signature, - region, - variables, - }, - ); + let old_member = self.ability_members.insert(member, member_data); debug_assert!(old_member.is_none(), "Replacing existing member definition"); } let old_ability = self.members_of_ability.insert(ability, members_vec); @@ -171,6 +188,13 @@ impl AbilitiesStore { self.ability_members.get(&member) } + /// Iterator over all abilities and their members that this store knows about. + pub fn iter_abilities(&self) -> impl Iterator { + self.members_of_ability + .iter() + .map(|(k, v)| (*k, v.as_slice())) + } + /// Returns an iterator over pairs ((ability member, type), specialization) specifying that /// "ability member" has a "specialization" for type "type". pub fn iter_specializations( @@ -184,8 +208,6 @@ impl AbilitiesStore { self.declared_specializations.get(&(member, typ)).copied() } - /// Returns pairs of (type, ability member) specifying that "ability member" has a - /// specialization with type "type". pub fn members_of_ability(&self, ability: Symbol) -> Option<&[Symbol]> { self.members_of_ability.get(&ability).map(|v| v.as_ref()) } @@ -199,7 +221,8 @@ impl AbilitiesStore { } pub fn insert_resolved(&mut self, id: SpecializationId, specialization: Symbol) { - debug_assert!(self.is_specialization_name(specialization)); + // May not be a thing in mono + // debug_assert!(self.is_specialization_name(specialization)); let old_specialization = self.resolved_specializations.insert(id, specialization); @@ -222,4 +245,119 @@ impl AbilitiesStore { pub fn get_resolved(&self, id: SpecializationId) -> Option { self.resolved_specializations.get(&id).copied() } + + /// Creates a store from [`self`] that closes over the abilities/members given by the + /// imported `symbols`, and their specializations (if any). + pub fn closure_from_imported(&self, symbols: &VecSet) -> Self { + let Self { + members_of_ability, + ability_members, + declared_specializations, + + // Covered by `declared_specializations` + specialization_to_root: _, + + // Taking closure for a new module, so specialization IDs can be fresh + next_specialization_id: _, + resolved_specializations: _, + } = self; + + let mut new = Self::default(); + + // 1. Figure out the abilities we need to introduce. + let mut abilities_to_introduce = VecSet::with_capacity(2); + symbols.iter().for_each(|symbol| { + if let Some(member_data) = ability_members.get(symbol) { + // If the symbol is member of an ability, we need to capture the entire ability. + abilities_to_introduce.insert(member_data.parent_ability); + } + if members_of_ability.contains_key(symbol) { + abilities_to_introduce.insert(*symbol); + } + }); + + // 2. Add each ability, and any specializations of its members we know about. + for ability in abilities_to_introduce.into_iter() { + let members = members_of_ability.get(&ability).unwrap(); + let mut imported_member_data = Vec::with_capacity(members.len()); + for member in members { + let mut member_data = ability_members.get(member).unwrap().clone(); + // All external members need to be marked as imported. We'll figure out their real + // type variables when it comes time to solve the module we're currently importing + // into. + member_data.typ = MemberTypeInfo::Imported { + signature_var: None, + }; + + imported_member_data.push((*member, member_data)); + } + + new.register_ability(ability, imported_member_data); + + // Add any specializations of the ability's members we know about. + declared_specializations + .iter() + .filter(|((member, _), _)| members.contains(member)) + .for_each(|(&(member, typ), &specialization)| { + new.register_specializing_symbol(specialization.symbol, member); + new.register_specialization_for_type(member, typ, specialization); + }); + } + + new + } + + pub fn union(&mut self, other: Self) { + let Self { + members_of_ability: other_members_of_ability, + ability_members: mut other_ability_members, + specialization_to_root, + declared_specializations, + next_specialization_id, + resolved_specializations, + } = other; + + for (ability, members) in other_members_of_ability.into_iter() { + if let Some(my_members) = self.members_of_ability(ability) { + debug_assert!( + my_members == members, + "Two abilities have different definitions, definitely a bug" + ); + } + let member_data = members + .into_iter() + .map(|member| (member, other_ability_members.remove(&member).unwrap())); + self.register_ability(ability, member_data); + } + + for (specialization, member) in specialization_to_root.into_iter() { + let old_root = self.specialization_to_root.insert(specialization, member); + debug_assert!(old_root.is_none() || old_root.unwrap() == member); + } + + for ((member, typ), specialization) in declared_specializations.into_iter() { + let old_specialization = self + .declared_specializations + .insert((member, typ), specialization); + debug_assert!( + old_specialization.is_none() || old_specialization.unwrap() == specialization + ); + } + + debug_assert!(next_specialization_id == 0); + debug_assert!(self.next_specialization_id == 0); + debug_assert!(resolved_specializations.is_empty()); + debug_assert!(self.resolved_specializations.is_empty()); + } + + pub fn resolved_imported_member_var(&mut self, member: Symbol, var: Variable) { + let member_data = self.ability_members.get_mut(&member).unwrap(); + match &mut member_data.typ { + MemberTypeInfo::Imported { signature_var } => { + let old = signature_var.replace(var); + debug_assert!(old.is_none(), "Replacing existing variable!"); + } + _ => internal_error!("{:?} is not imported!", member), + } + } } diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 0a567e12e7..f116609c92 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -267,7 +267,7 @@ pub fn canonicalize_annotation( annotation: &TypeAnnotation, region: Region, var_store: &mut VarStore, - abilities_in_scope: &[Symbol], + pending_abilities_in_scope: &[Symbol], ) -> Annotation { let mut introduced_variables = IntroducedVariables::default(); let mut references = VecSet::default(); @@ -284,7 +284,7 @@ pub fn canonicalize_annotation( var_store, &mut introduced_variables, clause, - abilities_in_scope, + pending_abilities_in_scope, &mut references, ); if let Err(err_type) = opt_err { @@ -320,7 +320,7 @@ pub fn canonicalize_annotation( } } -fn make_apply_symbol( +pub(crate) fn make_apply_symbol( env: &mut Env, region: Region, scope: &mut Scope, @@ -330,13 +330,13 @@ fn make_apply_symbol( if module_name.is_empty() { // Since module_name was empty, this is an unqualified type. // Look it up in scope! - let ident: Ident = (*ident).into(); - match scope.lookup(&ident, region) { + match scope.lookup_str(ident, region) { Ok(symbol) => Ok(symbol), Err(problem) => { env.problem(roc_problem::can::Problem::RuntimeError(problem)); + let ident: Ident = (*ident).into(); Err(Type::Erroneous(Problem::UnrecognizedIdent(ident))) } } @@ -908,7 +908,7 @@ fn canonicalize_has_clause( var_store: &mut VarStore, introduced_variables: &mut IntroducedVariables, clause: &Loc>, - abilities_in_scope: &[Symbol], + pending_abilities_in_scope: &[Symbol], references: &mut VecSet, ) -> Result<(), Type> { let Loc { @@ -927,7 +927,12 @@ fn canonicalize_has_clause( let ability = match ability.value { TypeAnnotation::Apply(module_name, ident, _type_arguments) => { let symbol = make_apply_symbol(env, ability.region, scope, module_name, ident)?; - if !abilities_in_scope.contains(&symbol) { + + // Ability defined locally, whose members we are constructing right now... + if !pending_abilities_in_scope.contains(&symbol) + // or an ability that was imported from elsewhere + && !scope.abilities_store.is_ability(symbol) + { let region = ability.region; env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region }); return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region))); diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index e8508e12a7..cbff764422 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -1,7 +1,7 @@ use crate::def::Def; use crate::expr::{self, AnnotatedMark, ClosureData, Expr::*, IntValue}; use crate::expr::{Expr, Field, Recursive}; -use crate::num::{FloatBound, IntBound, IntWidth, NumericBound}; +use crate::num::{FloatBound, IntBound, IntWidth, NumBound}; use crate::pattern::Pattern; use roc_collections::all::SendMap; use roc_module::called_via::CalledVia; @@ -5414,8 +5414,8 @@ fn defn_help( }) } -fn num_no_bound() -> NumericBound { - NumericBound::None +fn num_no_bound() -> NumBound { + NumBound::None } fn int_no_bound() -> IntBound { @@ -5436,7 +5436,7 @@ where num_var, precision_var, ii.to_string().into_boxed_str(), - IntValue::I128(ii), + IntValue::I128(ii.to_ne_bytes()), bound, ) } @@ -5453,12 +5453,12 @@ fn frac(num_var: Variable, precision_var: Variable, f: f64, bound: FloatBound) - } #[inline(always)] -fn num>(num_var: Variable, i: I, bound: NumericBound) -> Expr { +fn num>(num_var: Variable, i: I, bound: NumBound) -> Expr { let i = i.into(); Num( num_var, i.to_string().into_boxed_str(), - IntValue::I128(i), + IntValue::I128(i.to_ne_bytes()), bound, ) } diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 9bb15c1ef4..29017e077d 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1,6 +1,9 @@ +use crate::abilities::AbilityMemberData; +use crate::abilities::MemberTypeInfo; use crate::abilities::MemberVariables; use crate::annotation::canonicalize_annotation; use crate::annotation::find_type_def_symbols; +use crate::annotation::make_apply_symbol; use crate::annotation::IntroducedVariables; use crate::annotation::OwnedNamedOrAble; use crate::env::Env; @@ -31,6 +34,7 @@ use roc_problem::can::{CycleEntry, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::IllegalCycleMark; use roc_types::subs::{VarStore, Variable}; +use roc_types::types::AliasCommon; use roc_types::types::AliasKind; use roc_types::types::AliasVar; use roc_types::types::LambdaSet; @@ -58,7 +62,6 @@ pub struct Annotation { pub(crate) struct CanDefs { defs: Vec>, def_ordering: DefOrdering, - pub(crate) abilities_in_scope: Vec, aliases: VecMap, } @@ -100,12 +103,19 @@ impl PendingValueDef<'_> { #[derive(Debug, Clone)] enum PendingTypeDef<'a> { - /// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively. + /// A structural type alias, e.g. `Ints : List Int` Alias { name: Loc, vars: Vec>, ann: &'a Loc>, - kind: AliasKind, + }, + + /// An opaque type alias, e.g. `Age := U32`. + Opaque { + name: Loc, + vars: Vec>, + ann: &'a Loc>, + derived: Option<&'a Loc>>, }, Ability { @@ -145,6 +155,17 @@ impl PendingTypeDef<'_> { Some((name.value, region)) } + PendingTypeDef::Opaque { + name, + vars: _, + ann, + derived, + } => { + let end = derived.map(|d| d.region).unwrap_or(ann.region); + let region = Region::span_across(&name.region, &end); + + Some((name.value, region)) + } PendingTypeDef::Ability { name, .. } => Some((name.value, name.region)), PendingTypeDef::InvalidAlias { symbol, region, .. } => Some((*symbol, *region)), PendingTypeDef::ShadowedAlias { .. } => None, @@ -206,6 +227,202 @@ fn sort_type_defs_before_introduction( .collect() } +#[inline(always)] +#[allow(clippy::too_many_arguments)] +fn canonicalize_alias<'a>( + env: &mut Env<'a>, + output: &mut Output, + var_store: &mut VarStore, + scope: &mut Scope, + pending_abilities_in_scope: &[Symbol], + + name: Loc, + ann: &'a Loc>, + vars: &[Loc], + kind: AliasKind, +) -> Result { + let symbol = name.value; + let can_ann = canonicalize_annotation( + env, + scope, + &ann.value, + ann.region, + var_store, + pending_abilities_in_scope, + ); + + // Record all the annotation's references in output.references.lookups + for symbol in can_ann.references { + output.references.insert_type_lookup(symbol); + } + + let mut can_vars: Vec> = Vec::with_capacity(vars.len()); + let mut is_phantom = false; + + let IntroducedVariables { + named, + able, + wildcards, + inferred, + .. + } = can_ann.introduced_variables; + + let mut named: Vec<_> = (named.into_iter().map(OwnedNamedOrAble::Named)) + .chain(able.into_iter().map(OwnedNamedOrAble::Able)) + .collect(); + for loc_lowercase in vars.iter() { + let opt_index = named + .iter() + .position(|nv| nv.ref_name() == &loc_lowercase.value); + match opt_index { + Some(index) => { + // This is a valid lowercase rigid var for the type def. + let named_variable = named.swap_remove(index); + let var = named_variable.variable(); + let opt_bound_ability = named_variable.opt_ability(); + let name = named_variable.name(); + + can_vars.push(Loc { + value: AliasVar { + name, + var, + opt_bound_ability, + }, + region: loc_lowercase.region, + }); + } + None => { + is_phantom = true; + + env.problems.push(Problem::PhantomTypeArgument { + typ: symbol, + variable_region: loc_lowercase.region, + variable_name: loc_lowercase.value.clone(), + }); + } + } + } + + if is_phantom { + // Bail out + return Err(()); + } + + let num_unbound = named.len() + wildcards.len() + inferred.len(); + if num_unbound > 0 { + let one_occurrence = named + .iter() + .map(|nv| Loc::at(nv.first_seen(), nv.variable())) + .chain(wildcards) + .chain(inferred) + .next() + .unwrap() + .region; + + env.problems.push(Problem::UnboundTypeVariable { + typ: symbol, + num_unbound, + one_occurrence, + kind, + }); + + // Bail out + return Err(()); + } + + Ok(create_alias( + symbol, + name.region, + can_vars.clone(), + can_ann.typ, + kind, + )) +} + +#[inline(always)] +#[allow(clippy::too_many_arguments)] +fn canonicalize_opaque<'a>( + env: &mut Env<'a>, + output: &mut Output, + var_store: &mut VarStore, + scope: &mut Scope, + pending_abilities_in_scope: &[Symbol], + + name: Loc, + ann: &'a Loc>, + vars: &[Loc], + derives: Option<&'a Loc>>, +) -> Result { + let alias = canonicalize_alias( + env, + output, + var_store, + scope, + pending_abilities_in_scope, + name, + ann, + vars, + AliasKind::Opaque, + )?; + + if let Some(derives) = derives { + let derives = derives.value.collection(); + + let mut can_derives = vec![]; + + for derived in derives.items { + let region = derived.region; + match derived.value.extract_spaces().item { + ast::TypeAnnotation::Apply(module_name, ident, []) => { + match make_apply_symbol(env, region, scope, module_name, ident) { + Ok(ability) if ability.is_builtin_ability() => { + can_derives.push(Loc::at(region, ability)); + } + Ok(_) => { + // Register the problem but keep going, we may still be able to compile the + // program even if a derive is missing. + env.problem(Problem::IllegalDerive(region)); + } + Err(_) => { + // This is bad apply; an error will have been reported for it + // already. + } + } + } + _ => { + // Register the problem but keep going, we may still be able to compile the + // program even if a derive is missing. + env.problem(Problem::IllegalDerive(region)); + } + } + } + + if !can_derives.is_empty() { + // Fresh instance of this opaque to be checked for derivability during solving. + let fresh_inst = Type::DelayedAlias(AliasCommon { + symbol: name.value, + type_arguments: alias + .type_variables + .iter() + .map(|_| Type::Variable(var_store.fresh())) + .collect(), + lambda_set_variables: alias + .lambda_set_variables + .iter() + .map(|_| LambdaSet(Type::Variable(var_store.fresh()))) + .collect(), + }); + + let old = output + .pending_derives + .insert(name.value, (fresh_inst, can_derives)); + debug_assert!(old.is_none()); + } + } + + Ok(alias) +} + #[inline(always)] pub(crate) fn canonicalize_defs<'a>( env: &mut Env<'a>, @@ -250,17 +467,22 @@ pub(crate) fn canonicalize_defs<'a>( } enum TypeDef<'a> { - AliasLike( + Alias( Loc, Vec>, &'a Loc>, - AliasKind, + ), + Opaque( + Loc, + Vec>, + &'a Loc>, + Option<&'a Loc>>, ), Ability(Loc, &'a [AbilityMember<'a>]), } let mut type_defs = MutMap::default(); - let mut abilities_in_scope = Vec::new(); + let mut pending_abilities_in_scope = Vec::new(); let mut referenced_type_symbols = VecMap::default(); @@ -273,17 +495,27 @@ pub(crate) fn canonicalize_defs<'a>( } match pending_def { - PendingTypeDef::Alias { - name, - vars, - ann, - kind, - } => { + PendingTypeDef::Alias { name, vars, ann } => { let referenced_symbols = find_type_def_symbols(scope, &ann.value); referenced_type_symbols.insert(name.value, referenced_symbols); - type_defs.insert(name.value, TypeDef::AliasLike(name, vars, ann, kind)); + type_defs.insert(name.value, TypeDef::Alias(name, vars, ann)); + } + PendingTypeDef::Opaque { + name, + vars, + ann, + derived, + } => { + let referenced_symbols = find_type_def_symbols(scope, &ann.value); + + referenced_type_symbols.insert(name.value, referenced_symbols); + // Don't need to insert references for derived types, because these can only contain + // builtin abilities, and hence do not affect the type def sorting. We'll insert + // references of usages when canonicalizing the derives. + + type_defs.insert(name.value, TypeDef::Opaque(name, vars, ann, derived)); } PendingTypeDef::Ability { name, members } => { let mut referenced_symbols = Vec::with_capacity(2); @@ -297,7 +529,7 @@ pub(crate) fn canonicalize_defs<'a>( referenced_type_symbols.insert(name.value, referenced_symbols); type_defs.insert(name.value, TypeDef::Ability(name, members)); - abilities_in_scope.push(name.value); + pending_abilities_in_scope.push(name.value); } PendingTypeDef::InvalidAlias { .. } | PendingTypeDef::InvalidAbility { .. } @@ -313,105 +545,40 @@ pub(crate) fn canonicalize_defs<'a>( for type_name in sorted { match type_defs.remove(&type_name).unwrap() { - TypeDef::AliasLike(name, vars, ann, kind) => { - let symbol = name.value; - let can_ann = canonicalize_annotation( + TypeDef::Alias(name, vars, ann) => { + let alias = canonicalize_alias( env, - scope, - &ann.value, - ann.region, + &mut output, var_store, - &abilities_in_scope, + scope, + &pending_abilities_in_scope, + name, + ann, + &vars, + AliasKind::Structural, ); - // Record all the annotation's references in output.references.lookups - for symbol in can_ann.references { - output.references.insert_type_lookup(symbol); + if let Ok(alias) = alias { + aliases.insert(name.value, alias); } + } - let mut can_vars: Vec> = Vec::with_capacity(vars.len()); - let mut is_phantom = false; - - let IntroducedVariables { - named, - able, - wildcards, - inferred, - .. - } = can_ann.introduced_variables; - - let mut named: Vec<_> = (named.into_iter().map(OwnedNamedOrAble::Named)) - .chain(able.into_iter().map(OwnedNamedOrAble::Able)) - .collect(); - for loc_lowercase in vars.iter() { - let opt_index = named - .iter() - .position(|nv| nv.ref_name() == &loc_lowercase.value); - match opt_index { - Some(index) => { - // This is a valid lowercase rigid var for the type def. - let named_variable = named.swap_remove(index); - let var = named_variable.variable(); - let opt_bound_ability = named_variable.opt_ability(); - let name = named_variable.name(); - - can_vars.push(Loc { - value: AliasVar { - name, - var, - opt_bound_ability, - }, - region: loc_lowercase.region, - }); - } - None => { - is_phantom = true; - - env.problems.push(Problem::PhantomTypeArgument { - typ: symbol, - variable_region: loc_lowercase.region, - variable_name: loc_lowercase.value.clone(), - }); - } - } - } - - if is_phantom { - // Bail out - continue; - } - - let num_unbound = named.len() + wildcards.len() + inferred.len(); - if num_unbound > 0 { - let one_occurrence = named - .iter() - .map(|nv| Loc::at(nv.first_seen(), nv.variable())) - .chain(wildcards) - .chain(inferred) - .next() - .unwrap() - .region; - - env.problems.push(Problem::UnboundTypeVariable { - typ: symbol, - num_unbound, - one_occurrence, - kind, - }); - - // Bail out - continue; - } - - let alias = create_alias( - symbol, - name.region, - can_vars.clone(), - can_ann.typ.clone(), - kind, + TypeDef::Opaque(name, vars, ann, derived) => { + let alias_and_derives = canonicalize_opaque( + env, + &mut output, + var_store, + scope, + &pending_abilities_in_scope, + name, + ann, + &vars, + derived, ); - aliases.insert(symbol, alias); + if let Ok(alias) = alias_and_derives { + aliases.insert(name.value, alias); + } } TypeDef::Ability(name, members) => { @@ -443,7 +610,7 @@ pub(crate) fn canonicalize_defs<'a>( var_store, scope, abilities, - &abilities_in_scope, + &pending_abilities_in_scope, pattern_type, ); @@ -511,7 +678,6 @@ pub(crate) fn canonicalize_defs<'a>( var_store, pattern_type, &mut aliases, - &abilities_in_scope, ); output = temp_output.output; @@ -525,7 +691,6 @@ pub(crate) fn canonicalize_defs<'a>( CanDefs { defs, def_ordering, - abilities_in_scope, // The result needs a thread-safe `SendMap` aliases, }, @@ -542,7 +707,7 @@ fn resolve_abilities<'a>( var_store: &mut VarStore, scope: &mut Scope, abilities: MutMap, &[AbilityMember])>, - abilities_in_scope: &[Symbol], + pending_abilities_in_scope: &[Symbol], pattern_type: PatternType, ) { for (loc_ability_name, members) in abilities.into_values() { @@ -555,7 +720,7 @@ fn resolve_abilities<'a>( &member.typ.value, member.typ.region, var_store, - abilities_in_scope, + pending_abilities_in_scope, ); // Record all the annotation's references in output.references.lookups @@ -647,10 +812,15 @@ fn resolve_abilities<'a>( can_members.push(( member_sym, - name_region, - var_store.fresh(), - member_annot.typ, - variables, + AbilityMemberData { + parent_ability: loc_ability_name.value, + region: name_region, + typ: MemberTypeInfo::Local { + variables, + signature: member_annot.typ, + signature_var: var_store.fresh(), + }, + }, )); } @@ -760,7 +930,6 @@ pub(crate) fn sort_can_defs_new( defs, def_ordering, aliases, - abilities_in_scope, } = defs; // TODO: inefficient, but I want to make this what CanDefs contains in the future @@ -768,8 +937,6 @@ pub(crate) fn sort_can_defs_new( let mut declarations = Declarations::with_capacity(defs.len()); - output.abilities_in_scope = abilities_in_scope; - for (symbol, alias) in aliases.into_iter() { output.aliases.insert(symbol, alias); } @@ -962,11 +1129,8 @@ pub(crate) fn sort_can_defs( mut defs, def_ordering, aliases, - abilities_in_scope, } = defs; - output.abilities_in_scope = abilities_in_scope; - for (symbol, alias) in aliases.into_iter() { output.aliases.insert(symbol, alias); } @@ -1203,10 +1367,12 @@ fn canonicalize_pending_value_def<'a>( var_store: &mut VarStore, pattern_type: PatternType, aliases: &mut VecMap, - abilities_in_scope: &[Symbol], ) -> DefOutput { use PendingValueDef::*; + // All abilities should be resolved by the time we're canonicalizing value defs. + let pending_abilities_in_scope = &[]; + let output = match pending_def { AnnotationOnly(_, loc_can_pattern, loc_ann) => { // Make types for the body expr, even if we won't end up having a body. @@ -1221,7 +1387,7 @@ fn canonicalize_pending_value_def<'a>( &loc_ann.value, loc_ann.region, var_store, - abilities_in_scope, + pending_abilities_in_scope, ); // Record all the annotation's references in output.references.lookups @@ -1318,7 +1484,7 @@ fn canonicalize_pending_value_def<'a>( &loc_ann.value, loc_ann.region, var_store, - abilities_in_scope, + pending_abilities_in_scope, ); // Record all the annotation's references in output.references.lookups @@ -1527,6 +1693,86 @@ fn decl_to_let(decl: Declaration, loc_ret: Loc) -> Expr { } } +fn to_pending_alias_or_opaque<'a>( + env: &mut Env<'a>, + scope: &mut Scope, + name: &'a Loc<&'a str>, + vars: &'a [Loc>], + ann: &'a Loc>, + opt_derived: Option<&'a Loc>>, + kind: AliasKind, +) -> PendingTypeDef<'a> { + let shadow_kind = match kind { + AliasKind::Structural => ShadowKind::Alias, + AliasKind::Opaque => ShadowKind::Opaque, + }; + + let region = Region::span_across(&name.region, &ann.region); + + match scope.introduce_without_shadow_symbol(&Ident::from(name.value), region) { + Ok(symbol) => { + let mut can_rigids: Vec> = Vec::with_capacity(vars.len()); + + for loc_var in vars.iter() { + match loc_var.value { + ast::Pattern::Identifier(name) + if name.chars().next().unwrap().is_lowercase() => + { + let lowercase = Lowercase::from(name); + can_rigids.push(Loc { + value: lowercase, + region: loc_var.region, + }); + } + _ => { + // any other pattern in this position is a syntax error. + let problem = Problem::InvalidAliasRigid { + alias_name: symbol, + region: loc_var.region, + }; + env.problems.push(problem); + + return PendingTypeDef::InvalidAlias { + kind, + symbol, + region, + }; + } + } + } + + let name = Loc { + region: name.region, + value: symbol, + }; + + match kind { + AliasKind::Structural => PendingTypeDef::Alias { + name, + vars: can_rigids, + ann, + }, + AliasKind::Opaque => PendingTypeDef::Opaque { + name, + vars: can_rigids, + ann, + derived: opt_derived, + }, + } + } + + Err((original_region, loc_shadowed_symbol)) => { + env.problem(Problem::Shadowing { + original_region, + shadow: loc_shadowed_symbol, + kind: shadow_kind, + }); + + PendingTypeDef::ShadowedAlias + } + } +} + fn to_pending_type_def<'a>( env: &mut Env<'a>, def: &'a ast::TypeDef<'a>, @@ -1539,75 +1785,20 @@ fn to_pending_type_def<'a>( Alias { header: TypeHeader { name, vars }, ann, - } - | Opaque { + } => to_pending_alias_or_opaque(env, scope, name, vars, ann, None, AliasKind::Structural), + Opaque { header: TypeHeader { name, vars }, typ: ann, - } => { - let (kind, shadow_kind) = if matches!(def, Alias { .. }) { - (AliasKind::Structural, ShadowKind::Alias) - } else { - (AliasKind::Opaque, ShadowKind::Opaque) - }; - - let region = Region::span_across(&name.region, &ann.region); - - match scope.introduce_without_shadow_symbol(&Ident::from(name.value), region) { - Ok(symbol) => { - let mut can_rigids: Vec> = Vec::with_capacity(vars.len()); - - for loc_var in vars.iter() { - match loc_var.value { - ast::Pattern::Identifier(name) - if name.chars().next().unwrap().is_lowercase() => - { - let lowercase = Lowercase::from(name); - can_rigids.push(Loc { - value: lowercase, - region: loc_var.region, - }); - } - _ => { - // any other pattern in this position is a syntax error. - let problem = Problem::InvalidAliasRigid { - alias_name: symbol, - region: loc_var.region, - }; - env.problems.push(problem); - - return PendingTypeDef::InvalidAlias { - kind, - symbol, - region, - }; - } - } - } - - let name = Loc { - region: name.region, - value: symbol, - }; - - PendingTypeDef::Alias { - name, - vars: can_rigids, - ann, - kind, - } - } - - Err((original_region, loc_shadowed_symbol)) => { - env.problem(Problem::Shadowing { - original_region, - shadow: loc_shadowed_symbol, - kind: shadow_kind, - }); - - PendingTypeDef::ShadowedAlias - } - } - } + derived, + } => to_pending_alias_or_opaque( + env, + scope, + name, + vars, + ann, + derived.as_ref(), + AliasKind::Opaque, + ), Ability { header, members, .. diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 4f8df9b559..6e634b9c39 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -5,7 +5,7 @@ use crate::def::{can_defs_with_return, Annotation, Def}; use crate::env::Env; use crate::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_num, float_expr_from_result, - int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumericBound, + int_expr_from_result, num_expr_from_result, FloatBound, IntBound, NumBound, }; use crate::pattern::{canonicalize_pattern, BindingsFromPattern, Pattern}; use crate::procedure::References; @@ -25,6 +25,9 @@ use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type}; use std::fmt::{Debug, Display}; use std::{char, u32}; +/// Derives that an opaque type has claimed, to checked and recorded after solving. +pub type PendingDerives = VecMap>)>; + #[derive(Clone, Default, Debug)] pub struct Output { pub references: References, @@ -32,7 +35,7 @@ pub struct Output { pub introduced_variables: IntroducedVariables, pub aliases: VecMap, pub non_closures: VecSet, - pub abilities_in_scope: Vec, + pub pending_derives: PendingDerives, } impl Output { @@ -47,20 +50,29 @@ impl Output { .union_owned(other.introduced_variables); self.aliases.extend(other.aliases); self.non_closures.extend(other.non_closures); + + { + let expected_derives_size = self.pending_derives.len() + other.pending_derives.len(); + self.pending_derives.extend(other.pending_derives); + debug_assert!( + expected_derives_size == self.pending_derives.len(), + "Derives overwritten from nested scope - something is very wrong" + ); + } } } #[derive(Clone, Debug, PartialEq)] pub enum IntValue { - I128(i128), - U128(u128), + I128([u8; 16]), + U128([u8; 16]), } impl Display for IntValue { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - IntValue::I128(n) => Display::fmt(&n, f), - IntValue::U128(n) => Display::fmt(&n, f), + IntValue::I128(n) => Display::fmt(&i128::from_ne_bytes(*n), f), + IntValue::U128(n) => Display::fmt(&u128::from_ne_bytes(*n), f), } } } @@ -71,7 +83,7 @@ pub enum Expr { // Num stores the `a` variable in `Num a`. Not the same as the variable // stored in Int and Float below, which is strictly for better error messages - Num(Variable, Box, IntValue, NumericBound), + Num(Variable, Box, IntValue, NumBound), // Int and Float store a variable to generate better error messages Int(Variable, Variable, Box, IntValue, IntBound), @@ -205,10 +217,13 @@ pub enum Expr { lambda_set_variables: Vec, }, - // Test + /// Test Expect(Box>, Box>), - // Compiles, but will crash if reached + /// Rendered as empty box in editor + TypedHole(Variable), + + /// Compiles, but will crash if reached RuntimeError(RuntimeError), } @@ -248,7 +263,9 @@ impl Expr { }, &Self::OpaqueRef { name, .. } => Category::OpaqueWrap(name), Self::Expect(..) => Category::Expect, - Self::RuntimeError(..) => Category::Unknown, + + // these nodes place no constraints on the expression's type + Self::TypedHole(_) | Self::RuntimeError(..) => Category::Unknown, } } } @@ -423,12 +440,7 @@ pub fn canonicalize_expr<'a>( let (expr, output) = match expr { &ast::Expr::Num(str) => { - let answer = num_expr_from_result( - var_store, - finish_parsing_num(str).map(|result| (str, result)), - region, - env, - ); + let answer = num_expr_from_result(var_store, finish_parsing_num(str), region, env); (answer, Output::default()) } @@ -1326,7 +1338,7 @@ fn canonicalize_var_lookup( let can_expr = if module_name.is_empty() { // Since module_name was empty, this is an unqualified var. // Look it up in scope! - match scope.lookup(&(*ident).into(), region) { + match scope.lookup_str(ident, region) { Ok(symbol) => { output.references.insert_value_lookup(symbol); @@ -1397,6 +1409,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> | other @ Var(_) | other @ AbilityMember(..) | other @ RunLowLevel { .. } + | other @ TypedHole { .. } | other @ ForeignCall { .. } => other, List { diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 9d2d17759b..f16548f48d 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -3,7 +3,7 @@ use crate::annotation::canonicalize_annotation; use crate::def::{canonicalize_defs, Def}; use crate::effect_module::HostedGeneratedFunctions; use crate::env::Env; -use crate::expr::{ClosureData, Declarations, Expr, Output}; +use crate::expr::{ClosureData, Declarations, Expr, Output, PendingDerives}; use crate::operator::desugar_def; use crate::pattern::{BindingsFromPattern, Pattern}; use crate::scope::Scope; @@ -51,6 +51,7 @@ pub struct ModuleOutput { pub referenced_values: VecSet, pub referenced_types: VecSet, pub symbols_from_requires: Vec<(Loc, Loc)>, + pub pending_derives: PendingDerives, pub scope: Scope, } @@ -166,13 +167,14 @@ pub fn canonicalize_module_defs<'a>( exposed_ident_ids: IdentIds, dep_idents: &'a IdentIdsByModule, aliases: MutMap, + imported_abilities_state: AbilitiesStore, exposed_imports: MutMap, exposed_symbols: &VecSet, symbols_from_requires: &[(Loc, Loc>)], var_store: &mut VarStore, ) -> ModuleOutput { let mut can_exposed_imports = MutMap::default(); - let mut scope = Scope::new(home, exposed_ident_ids); + let mut scope = Scope::new(home, exposed_ident_ids, imported_abilities_state); let mut env = Env::new(home, dep_idents, module_ids); let num_deps = dep_idents.len(); @@ -254,12 +256,12 @@ pub fn canonicalize_module_defs<'a>( { // These are not aliases but Apply's and we make sure they are always in scope } else { - // This is a type alias + // This is a type alias or ability // the symbol should already be added to the scope when this module is canonicalized debug_assert!( - scope.contains_alias(symbol), - "The {:?} is not a type alias known in {:?}", + scope.contains_alias(symbol) || scope.abilities_store.is_ability(symbol), + "The {:?} is not a type alias or ability known in {:?}", symbol, home ); @@ -289,6 +291,8 @@ pub fn canonicalize_module_defs<'a>( PatternType::TopLevelDef, ); + let pending_derives = output.pending_derives; + // See if any of the new idents we defined went unused. // If any were unused and also not exposed, report it. for (symbol, region) in symbols_introduced { @@ -354,16 +358,24 @@ pub fn canonicalize_module_defs<'a>( let (mut declarations, mut output) = crate::def::sort_can_defs_new(&mut env, var_store, defs, new_output); + debug_assert!( + output.pending_derives.is_empty(), + "I thought pending derives are only found during def introduction" + ); + let symbols_from_requires = symbols_from_requires .iter() .map(|(symbol, loc_ann)| { + // We've already canonicalized the module, so there are no pending abilities. + let pending_abilities_in_scope = &[]; + let ann = canonicalize_annotation( &mut env, &mut scope, &loc_ann.value, loc_ann.region, var_store, - &output.abilities_in_scope, + pending_abilities_in_scope, ); ann.add_to( @@ -560,8 +572,16 @@ pub fn canonicalize_module_defs<'a>( aliases.insert(symbol, alias); } - for member in scope.abilities_store.root_ability_members().keys() { - exposed_but_not_defined.remove(member); + for (ability, members) in scope + .abilities_store + .iter_abilities() + .filter(|(ab, _)| ab.module_id() == home) + { + exposed_but_not_defined.remove(&ability); + members.iter().for_each(|member| { + debug_assert!(member.module_id() == home); + exposed_but_not_defined.remove(member); + }); } // By this point, all exposed symbols should have been removed from @@ -655,6 +675,7 @@ pub fn canonicalize_module_defs<'a>( exposed_imports: can_exposed_imports, problems: env.problems, symbols_from_requires, + pending_derives, lookups, } } @@ -789,6 +810,7 @@ fn fix_values_captured_in_closure_expr( | Var(_) | AbilityMember(..) | EmptyRecord + | TypedHole { .. } | RuntimeError(_) | ZeroArgumentTag { .. } | Accessor { .. } => {} diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index cd2074ca3b..9a7041f8b4 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -5,8 +5,9 @@ use roc_problem::can::Problem; use roc_problem::can::RuntimeError::*; use roc_problem::can::{FloatErrorKind, IntErrorKind}; use roc_region::all::Region; +pub use roc_types::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumBound, SignDemand}; use roc_types::subs::VarStore; -use std::i64; + use std::str; #[inline(always)] @@ -103,14 +104,18 @@ pub fn float_expr_from_result( pub enum ParsedNumResult { Int(IntValue, IntBound), Float(f64, FloatBound), - UnknownNum(IntValue, NumericBound), + UnknownNum(IntValue, NumBound), } #[inline(always)] -pub fn finish_parsing_num(raw: &str) -> Result { +pub fn finish_parsing_num(raw: &str) -> Result<(&str, ParsedNumResult), (&str, IntErrorKind)> { // Ignore underscores. let radix = 10; - from_str_radix(raw.replace('_', "").as_str(), radix).map_err(|e| (raw, e)) + let (_, raw_without_suffix) = parse_literal_suffix(raw); + match from_str_radix(raw.replace('_', "").as_str(), radix) { + Ok(result) => Ok((raw_without_suffix, result)), + Err(e) => Err((raw, e)), + } } #[inline(always)] @@ -135,8 +140,8 @@ pub fn finish_parsing_base( .and_then(|parsed| match parsed { ParsedNumResult::Float(..) => Err(IntErrorKind::FloatSuffix), ParsedNumResult::Int(val, bound) => Ok((val, bound)), - ParsedNumResult::UnknownNum(val, NumericBound::None) => Ok((val, IntBound::None)), - ParsedNumResult::UnknownNum(val, NumericBound::AtLeastIntOrFloat { sign, width }) => { + ParsedNumResult::UnknownNum(val, NumBound::None) => Ok((val, IntBound::None)), + ParsedNumResult::UnknownNum(val, NumBound::AtLeastIntOrFloat { sign, width }) => { Ok((val, IntBound::AtLeast { sign, width })) } }) @@ -223,7 +228,7 @@ fn from_str_radix(src: &str, radix: u32) -> Result IntValue::I128(result), + Ok(result) => IntValue::I128(result.to_ne_bytes()), Err(pie) => match pie.kind() { StdIEK::Empty => return Err(IntErrorKind::Empty), StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit), @@ -231,7 +236,7 @@ fn from_str_radix(src: &str, radix: u32) -> Result { // try a u128 match u128::from_str_radix(src, radix) { - Ok(result) => IntValue::U128(result), + Ok(result) => IntValue::U128(result.to_ne_bytes()), Err(pie) => match pie.kind() { StdIEK::InvalidDigit => return Err(IntErrorKind::InvalidDigit), StdIEK::PosOverflow => return Err(IntErrorKind::Overflow), @@ -248,7 +253,11 @@ fn from_str_radix(src: &str, radix: u32) -> Result (lower_bound_of_int(num), num < 0), + IntValue::I128(bytes) => { + let num = i128::from_ne_bytes(bytes); + + (lower_bound_of_int(num), num < 0) + } IntValue::U128(_) => (IntWidth::U128, false), }; @@ -262,7 +271,7 @@ fn from_str_radix(src: &str, radix: u32) -> Result Result n as f64, - IntValue::U128(n) => n as f64, + IntValue::I128(n) => i128::from_ne_bytes(n) as f64, + IntValue::U128(n) => i128::from_ne_bytes(n) as f64, }, FloatBound::Exact(fw), )) @@ -344,169 +353,3 @@ fn lower_bound_of_int(result: i128) -> IntWidth { } } } - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -enum IntSign { - Unsigned, - Signed, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum IntWidth { - U8, - U16, - U32, - U64, - U128, - I8, - I16, - I32, - I64, - I128, - Nat, -} - -impl IntWidth { - /// Returns the `IntSign` and bit width of a variant. - fn sign_and_width(&self) -> (IntSign, u32) { - use IntSign::*; - use IntWidth::*; - match self { - U8 => (Unsigned, 8), - U16 => (Unsigned, 16), - U32 => (Unsigned, 32), - U64 => (Unsigned, 64), - U128 => (Unsigned, 128), - I8 => (Signed, 8), - I16 => (Signed, 16), - I32 => (Signed, 32), - I64 => (Signed, 64), - I128 => (Signed, 128), - // TODO: this is platform specific! - Nat => (Unsigned, 64), - } - } - - fn type_str(&self) -> &'static str { - use IntWidth::*; - match self { - U8 => "U8", - U16 => "U16", - U32 => "U32", - U64 => "U64", - U128 => "U128", - I8 => "I8", - I16 => "I16", - I32 => "I32", - I64 => "I64", - I128 => "I128", - Nat => "Nat", - } - } - - fn max_value(&self) -> u128 { - use IntWidth::*; - match self { - U8 => u8::MAX as u128, - U16 => u16::MAX as u128, - U32 => u32::MAX as u128, - U64 => u64::MAX as u128, - U128 => u128::MAX, - I8 => i8::MAX as u128, - I16 => i16::MAX as u128, - I32 => i32::MAX as u128, - I64 => i64::MAX as u128, - I128 => i128::MAX as u128, - // TODO: this is platform specific! - Nat => u64::MAX as u128, - } - } - - fn min_value(&self) -> i128 { - use IntWidth::*; - match self { - U8 | U16 | U32 | U64 | U128 | Nat => 0, - I8 => i8::MIN as i128, - I16 => i16::MIN as i128, - I32 => i32::MIN as i128, - I64 => i64::MIN as i128, - I128 => i128::MIN, - } - } - - /// Checks if `self` represents superset of integers that `lower_bound` represents, on a particular - /// side of the integers relative to 0. - /// - /// If `is_negative` is true, the negative side is checked; otherwise the positive side is checked. - pub fn is_superset(&self, lower_bound: &Self, is_negative: bool) -> bool { - use IntSign::*; - - if is_negative { - match (self.sign_and_width(), lower_bound.sign_and_width()) { - ((Signed, us), (Signed, lower_bound)) => us >= lower_bound, - // Unsigned ints can never represent negative numbers; signed (non-zero width) - // ints always can. - ((Unsigned, _), (Signed, _)) => false, - ((Signed, _), (Unsigned, _)) => true, - // Trivially true; both can only express 0. - ((Unsigned, _), (Unsigned, _)) => true, - } - } else { - match (self.sign_and_width(), lower_bound.sign_and_width()) { - ((Signed, us), (Signed, lower_bound)) - | ((Unsigned, us), (Unsigned, lower_bound)) => us >= lower_bound, - - // Unsigned ints with the same bit width as their unsigned counterparts can always - // express 2x more integers on the positive side as unsigned ints. - ((Unsigned, us), (Signed, lower_bound)) => us >= lower_bound, - - // ...but that means signed int widths can represent less than their unsigned - // counterparts, so the below is true iff the bit width is strictly greater. E.g. - // i16 is a superset of u8, but i16 is not a superset of u16. - ((Signed, us), (Unsigned, lower_bound)) => us > lower_bound, - } - } - } -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum FloatWidth { - Dec, - F32, - F64, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum SignDemand { - /// Can be signed or unsigned. - NoDemand, - /// Must be signed. - Signed, -} - -/// Describes a bound on the width of an integer. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum IntBound { - /// There is no bound on the width. - None, - /// Must have an exact width. - Exact(IntWidth), - /// Must have a certain sign and a minimum width. - AtLeast { sign: SignDemand, width: IntWidth }, -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum FloatBound { - None, - Exact(FloatWidth), -} - -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum NumericBound { - None, - /// Must be an integer of a certain size, or any float. - AtLeastIntOrFloat { - sign: SignDemand, - width: IntWidth, - }, -} diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index cf30e4e9b0..a55a415133 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -2,8 +2,8 @@ use crate::annotation::freshen_opaque_def; use crate::env::Env; use crate::expr::{canonicalize_expr, unescape_char, Expr, IntValue, Output}; use crate::num::{ - finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound, - NumericBound, ParsedNumResult, + finish_parsing_base, finish_parsing_float, finish_parsing_num, FloatBound, IntBound, NumBound, + ParsedNumResult, }; use crate::scope::Scope; use roc_module::ident::{Ident, Lowercase, TagName}; @@ -55,7 +55,7 @@ pub enum Pattern { ext_var: Variable, destructs: Vec>, }, - NumLiteral(Variable, Box, IntValue, NumericBound), + NumLiteral(Variable, Box, IntValue, NumBound), IntLiteral(Variable, Variable, Box, IntValue, IntBound), FloatLiteral(Variable, Variable, Box, f64, FloatBound), StrLiteral(Box), @@ -191,15 +191,20 @@ pub fn canonicalize_def_header_pattern<'a>( Identifier(name) => { match scope.introduce_or_shadow_ability_member((*name).into(), region) { Ok((symbol, shadowing_ability_member)) => { - output.references.insert_bound(symbol); let can_pattern = match shadowing_ability_member { // A fresh identifier. - None => Pattern::Identifier(symbol), + None => { + output.references.insert_bound(symbol); + Pattern::Identifier(symbol) + } // Likely a specialization of an ability. - Some(ability_member_name) => Pattern::AbilityMemberSpecialization { - ident: symbol, - specializes: ability_member_name, - }, + Some(ability_member_name) => { + output.references.insert_value_lookup(ability_member_name); + Pattern::AbilityMemberSpecialization { + ident: symbol, + specializes: ability_member_name, + } + } }; Loc::at(region, can_pattern) } @@ -360,20 +365,20 @@ pub fn canonicalize_pattern<'a>( let problem = MalformedPatternProblem::MalformedInt; malformed_pattern(env, problem, region) } - Ok(ParsedNumResult::UnknownNum(int, bound)) => { - Pattern::NumLiteral(var_store.fresh(), (str).into(), int, bound) + Ok((parsed, ParsedNumResult::UnknownNum(int, bound))) => { + Pattern::NumLiteral(var_store.fresh(), (parsed).into(), int, bound) } - Ok(ParsedNumResult::Int(int, bound)) => Pattern::IntLiteral( + Ok((parsed, ParsedNumResult::Int(int, bound))) => Pattern::IntLiteral( var_store.fresh(), var_store.fresh(), - (str).into(), + (parsed).into(), int, bound, ), - Ok(ParsedNumResult::Float(float, bound)) => Pattern::FloatLiteral( + Ok((parsed, ParsedNumResult::Float(float, bound))) => Pattern::FloatLiteral( var_store.fresh(), var_store.fresh(), - (str).into(), + (parsed).into(), float, bound, ), @@ -397,11 +402,15 @@ pub fn canonicalize_pattern<'a>( malformed_pattern(env, problem, region) } Ok((int, bound)) => { + use std::ops::Neg; + let sign_str = if is_negative { "-" } else { "" }; let int_str = format!("{}{}", sign_str, int).into_boxed_str(); let i = match int { // Safety: this is fine because I128::MAX = |I128::MIN| - 1 - IntValue::I128(n) if is_negative => IntValue::I128(-n), + IntValue::I128(n) if is_negative => { + IntValue::I128(i128::from_ne_bytes(n).neg().to_ne_bytes()) + } IntValue::I128(n) => IntValue::I128(n), IntValue::U128(_) => unreachable!(), }; diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index 52867942b8..9a07c8c885 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -32,7 +32,11 @@ pub struct Scope { } impl Scope { - pub fn new(home: ModuleId, initial_ident_ids: IdentIds) -> Scope { + pub fn new( + home: ModuleId, + initial_ident_ids: IdentIds, + starting_abilities_store: AbilitiesStore, + ) -> Scope { let imports = Symbol::default_in_scope() .into_iter() .map(|(a, (b, c))| (a, b, c)) @@ -43,22 +47,25 @@ impl Scope { exposed_ident_count: initial_ident_ids.len(), locals: ScopedIdentIds::from_ident_ids(home, initial_ident_ids), aliases: VecMap::default(), - // TODO(abilities): default abilities in scope - abilities_store: AbilitiesStore::default(), + abilities_store: starting_abilities_store, imports, } } pub fn lookup(&self, ident: &Ident, region: Region) -> Result { + self.lookup_str(ident.as_str(), region) + } + + pub fn lookup_str(&self, ident: &str, region: Region) -> Result { use ContainsIdent::*; - match self.scope_contains_ident(ident.as_str()) { + match self.scope_contains_ident(ident) { InScope(symbol, _) => Ok(symbol), NotInScope(_) | NotPresent => { let error = RuntimeError::LookupNotInScope( Loc { region, - value: ident.clone(), + value: Ident::from(ident), }, self.idents_in_scope().map(|v| v.as_ref().into()).collect(), ); @@ -560,7 +567,11 @@ mod test { #[test] fn scope_contains_introduced() { let _register_module_debug_names = ModuleIds::default(); - let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + let mut scope = Scope::new( + ModuleId::ATTR, + IdentIds::default(), + AbilitiesStore::default(), + ); let region = Region::zero(); let ident = Ident::from("mezolit"); @@ -575,7 +586,11 @@ mod test { #[test] fn second_introduce_shadows() { let _register_module_debug_names = ModuleIds::default(); - let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + let mut scope = Scope::new( + ModuleId::ATTR, + IdentIds::default(), + AbilitiesStore::default(), + ); let region1 = Region::from_pos(Position { offset: 10 }); let region2 = Region::from_pos(Position { offset: 20 }); @@ -600,7 +615,11 @@ mod test { #[test] fn inner_scope_does_not_influence_outer() { let _register_module_debug_names = ModuleIds::default(); - let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + let mut scope = Scope::new( + ModuleId::ATTR, + IdentIds::default(), + AbilitiesStore::default(), + ); let region = Region::zero(); let ident = Ident::from("uránia"); @@ -617,7 +636,11 @@ mod test { #[test] fn default_idents_in_scope() { let _register_module_debug_names = ModuleIds::default(); - let scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + let scope = Scope::new( + ModuleId::ATTR, + IdentIds::default(), + AbilitiesStore::default(), + ); let idents: Vec<_> = scope.idents_in_scope().collect(); @@ -640,7 +663,11 @@ mod test { #[test] fn idents_with_inner_scope() { let _register_module_debug_names = ModuleIds::default(); - let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + let mut scope = Scope::new( + ModuleId::ATTR, + IdentIds::default(), + AbilitiesStore::default(), + ); let idents: Vec<_> = scope.idents_in_scope().collect(); @@ -707,7 +734,11 @@ mod test { #[test] fn import_is_in_scope() { let _register_module_debug_names = ModuleIds::default(); - let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + let mut scope = Scope::new( + ModuleId::ATTR, + IdentIds::default(), + AbilitiesStore::default(), + ); let ident = Ident::from("product"); let symbol = Symbol::LIST_PRODUCT; @@ -725,7 +756,11 @@ mod test { #[test] fn shadow_of_import() { let _register_module_debug_names = ModuleIds::default(); - let mut scope = Scope::new(ModuleId::ATTR, IdentIds::default()); + let mut scope = Scope::new( + ModuleId::ATTR, + IdentIds::default(), + AbilitiesStore::default(), + ); let ident = Ident::from("product"); let symbol = Symbol::LIST_PRODUCT; diff --git a/compiler/can/src/traverse.rs b/compiler/can/src/traverse.rs index 5938c2fc3a..71f61f7f82 100644 --- a/compiler/can/src/traverse.rs +++ b/compiler/can/src/traverse.rs @@ -168,6 +168,7 @@ pub fn walk_expr(visitor: &mut V, expr: &Expr, var: Variable) { visitor.visit_expr(&e1.value, e1.region, Variable::NULL); visitor.visit_expr(&e2.value, e2.region, Variable::NULL); } + Expr::TypedHole(_) => { /* terminal */ } Expr::RuntimeError(..) => { /* terminal */ } } } diff --git a/compiler/can/tests/helpers/mod.rs b/compiler/can/tests/helpers/mod.rs index 038ae2cef1..04903b266e 100644 --- a/compiler/can/tests/helpers/mod.rs +++ b/compiler/can/tests/helpers/mod.rs @@ -55,7 +55,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut // rules multiple times unnecessarily. let loc_expr = operator::desugar_expr(arena, &loc_expr); - let mut scope = Scope::new(home, IdentIds::default()); + let mut scope = Scope::new(home, IdentIds::default(), Default::default()); scope.add_alias( Symbol::NUM_INT, Region::zero(), diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index bed11bdd66..5f7820a2f2 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -68,7 +68,7 @@ mod test_can { match actual_out.loc_expr.value { Expr::Int(_, _, _, actual, _) => { - assert_eq!(IntValue::I128(expected), actual); + assert_eq!(IntValue::I128(expected.to_ne_bytes()), actual); } actual => { panic!("Expected an Num.Int *, but got: {:?}", actual); @@ -82,7 +82,7 @@ mod test_can { match actual_out.loc_expr.value { Expr::Num(_, _, actual, _) => { - assert_eq!(IntValue::I128(expected), actual); + assert_eq!(IntValue::I128(expected.to_ne_bytes()), actual); } actual => { panic!("Expected a Num, but got: {:?}", actual); diff --git a/compiler/collections/Cargo.toml b/compiler/collections/Cargo.toml index 6cd46e1101..454e0bc8b6 100644 --- a/compiler/collections/Cargo.toml +++ b/compiler/collections/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_collections" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] im = "15.0.0" diff --git a/compiler/collections/src/lib.rs b/compiler/collections/src/lib.rs index b41a0ad0a0..9bce46c2f2 100644 --- a/compiler/collections/src/lib.rs +++ b/compiler/collections/src/lib.rs @@ -10,7 +10,7 @@ mod vec_map; mod vec_set; pub use all::{default_hasher, BumpMap, ImEntry, ImMap, ImSet, MutMap, MutSet, SendMap}; -pub use reference_matrix::{ReferenceMatrix, Sccs}; +pub use reference_matrix::{ReferenceMatrix, Sccs, TopologicalSort}; pub use small_string_interner::SmallStringInterner; pub use vec_map::VecMap; pub use vec_set::VecSet; diff --git a/compiler/collections/src/reference_matrix.rs b/compiler/collections/src/reference_matrix.rs index 155e327030..206772e276 100644 --- a/compiler/collections/src/reference_matrix.rs +++ b/compiler/collections/src/reference_matrix.rs @@ -154,7 +154,7 @@ impl ReferenceMatrix { } } -#[allow(dead_code)] +#[derive(Debug)] pub enum TopologicalSort { /// There were no cycles, all nodes have been partitioned into groups Groups { groups: Vec> }, diff --git a/compiler/collections/src/vec_map.rs b/compiler/collections/src/vec_map.rs index 8060bd008d..6f07863436 100644 --- a/compiler/collections/src/vec_map.rs +++ b/compiler/collections/src/vec_map.rs @@ -123,16 +123,7 @@ impl VecMap { } } -impl std::iter::FromIterator<(K, V)> for VecMap { - fn from_iter>(iter: T) -> Self { - let mut this = Self::default(); - this.extend(iter); - - this - } -} - -impl Extend<(K, V)> for VecMap { +impl Extend<(K, V)> for VecMap { #[inline(always)] fn extend>(&mut self, iter: T) { let it = iter.into_iter(); @@ -193,3 +184,12 @@ impl ExactSizeIterator for IntoIter { self.len } } + +impl std::iter::FromIterator<(K, V)> for VecMap { + fn from_iter>(iter: T) -> Self { + let mut this = Self::default(); + this.extend(iter); + + this + } +} diff --git a/compiler/collections/src/vec_set.rs b/compiler/collections/src/vec_set.rs index d139272e89..7dcca27c30 100644 --- a/compiler/collections/src/vec_set.rs +++ b/compiler/collections/src/vec_set.rs @@ -1,3 +1,5 @@ +use std::iter::FromIterator; + #[derive(Clone, Debug, PartialEq)] pub struct VecSet { elements: Vec, @@ -55,13 +57,15 @@ impl VecSet { self.elements.contains(value) } - pub fn remove(&mut self, value: &T) { + /// Performs a swap_remove if the element was present in the set, + /// then returns whether the value was present in the set. + pub fn remove(&mut self, value: &T) -> bool { match self.elements.iter().position(|x| x == value) { - None => { - // just do nothing - } + None => false, Some(index) => { self.elements.swap_remove(index); + + true } } } @@ -99,6 +103,14 @@ impl Extend for VecSet { } } +impl FromIterator for VecSet { + fn from_iter>(iter: T) -> Self { + let mut set = VecSet::default(); + set.extend(iter); + set + } +} + impl IntoIterator for VecSet { type Item = T; diff --git a/compiler/constrain/Cargo.toml b/compiler/constrain/Cargo.toml index d5c4059c63..94ebd2ce4b 100644 --- a/compiler/constrain/Cargo.toml +++ b/compiler/constrain/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_constrain" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index 9f1b4c918d..17128e9de1 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -1,9 +1,10 @@ use arrayvec::ArrayVec; use roc_can::constraint::{Constraint, Constraints}; use roc_can::expected::Expected::{self, *}; -use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumericBound, SignDemand}; +use roc_can::num::{FloatBound, FloatWidth, IntBound, IntWidth, NumBound, SignDemand}; use roc_module::symbol::Symbol; use roc_region::all::Region; +use roc_types::num::NumericRange; use roc_types::subs::Variable; use roc_types::types::Type::{self, *}; use roc_types::types::{AliasKind, Category}; @@ -19,14 +20,18 @@ pub fn add_numeric_bound_constr( region: Region, category: Category, ) -> Type { - let range = bound.bounded_range(); - + let range = bound.numeric_bound(); let total_num_type = num_type; - match range.len() { - 0 => total_num_type, - 1 => { - let actual_type = Variable(range[0]); + use roc_types::num::{float_width_to_variable, int_width_to_variable}; + + match range { + NumericBound::None => { + // no additional constraints + total_num_type + } + NumericBound::FloatExact(width) => { + let actual_type = Variable(float_width_to_variable(width)); let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region); let because_suffix = constraints.equal_types(total_num_type.clone(), expected, category, region); @@ -35,7 +40,17 @@ pub fn add_numeric_bound_constr( total_num_type } - _ => RangedNumber(Box::new(total_num_type), range), + NumericBound::IntExact(width) => { + let actual_type = Variable(int_width_to_variable(width)); + let expected = Expected::ForReason(Reason::NumericLiteralSuffix, actual_type, region); + let because_suffix = + constraints.equal_types(total_num_type.clone(), expected, category, region); + + num_constraints.extend([because_suffix]); + + total_num_type + } + NumericBound::Range(range) => RangedNumber(Box::new(total_num_type), range), } } @@ -117,7 +132,7 @@ pub fn num_literal( num_var: Variable, expected: Expected, region: Region, - bound: NumericBound, + bound: NumBound, ) -> Constraint { let open_number_type = crate::builtins::num_num(Type::Variable(num_var)); @@ -264,83 +279,57 @@ pub fn num_num(typ: Type) -> Type { } pub trait TypedNumericBound { - fn bounded_range(&self) -> Vec; + fn numeric_bound(&self) -> NumericBound; } impl TypedNumericBound for IntBound { - fn bounded_range(&self) -> Vec { + fn numeric_bound(&self) -> NumericBound { match self { - IntBound::None => vec![], - IntBound::Exact(w) => vec![match w { - IntWidth::U8 => Variable::U8, - IntWidth::U16 => Variable::U16, - IntWidth::U32 => Variable::U32, - IntWidth::U64 => Variable::U64, - IntWidth::U128 => Variable::U128, - IntWidth::I8 => Variable::I8, - IntWidth::I16 => Variable::I16, - IntWidth::I32 => Variable::I32, - IntWidth::I64 => Variable::I64, - IntWidth::I128 => Variable::I128, - IntWidth::Nat => Variable::NAT, - }], - IntBound::AtLeast { sign, width } => { - let whole_range: &[(IntWidth, Variable)] = match sign { - SignDemand::NoDemand => { - &[ - (IntWidth::I8, Variable::I8), - (IntWidth::U8, Variable::U8), - (IntWidth::I16, Variable::I16), - (IntWidth::U16, Variable::U16), - (IntWidth::I32, Variable::I32), - (IntWidth::U32, Variable::U32), - (IntWidth::I64, Variable::I64), - (IntWidth::Nat, Variable::NAT), // FIXME: Nat's order here depends on the platform! - (IntWidth::U64, Variable::U64), - (IntWidth::I128, Variable::I128), - (IntWidth::U128, Variable::U128), - ] - } - SignDemand::Signed => &[ - (IntWidth::I8, Variable::I8), - (IntWidth::I16, Variable::I16), - (IntWidth::I32, Variable::I32), - (IntWidth::I64, Variable::I64), - (IntWidth::I128, Variable::I128), - ], - }; - whole_range - .iter() - .skip_while(|(lower_bound, _)| *lower_bound != *width) - .map(|(_, var)| *var) - .collect() - } + IntBound::None => NumericBound::None, + IntBound::Exact(w) => NumericBound::IntExact(*w), + IntBound::AtLeast { + sign: SignDemand::NoDemand, + width, + } => NumericBound::Range(NumericRange::IntAtLeastEitherSign(*width)), + IntBound::AtLeast { + sign: SignDemand::Signed, + width, + } => NumericBound::Range(NumericRange::IntAtLeastSigned(*width)), } } } impl TypedNumericBound for FloatBound { - fn bounded_range(&self) -> Vec { + fn numeric_bound(&self) -> NumericBound { match self { - FloatBound::None => vec![], - FloatBound::Exact(w) => vec![match w { - FloatWidth::Dec => Variable::DEC, - FloatWidth::F32 => Variable::F32, - FloatWidth::F64 => Variable::F64, - }], + FloatBound::None => NumericBound::None, + FloatBound::Exact(w) => NumericBound::FloatExact(*w), } } } -impl TypedNumericBound for NumericBound { - fn bounded_range(&self) -> Vec { +impl TypedNumericBound for NumBound { + fn numeric_bound(&self) -> NumericBound { match self { - NumericBound::None => vec![], - &NumericBound::AtLeastIntOrFloat { sign, width } => { - let mut range = IntBound::AtLeast { sign, width }.bounded_range(); - range.extend_from_slice(&[Variable::F32, Variable::F64, Variable::DEC]); - range - } + NumBound::None => NumericBound::None, + &NumBound::AtLeastIntOrFloat { + sign: SignDemand::NoDemand, + width, + } => NumericBound::Range(NumericRange::NumAtLeastEitherSign(width)), + &NumBound::AtLeastIntOrFloat { + sign: SignDemand::Signed, + width, + } => NumericBound::Range(NumericRange::NumAtLeastSigned(width)), } } } + +/// A bound placed on a number because of its literal value. +/// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NumericBound { + None, + FloatExact(FloatWidth), + IntExact(IntWidth), + Range(NumericRange), +} diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 612741dcd6..941559efe7 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1176,6 +1176,15 @@ pub fn constrain_expr( arg_cons.push(eq); constraints.exists_many(vars, arg_cons) } + TypedHole(var) => { + // store the expected type for this position + constraints.equal_types_var( + *var, + expected, + Category::Storage(std::file!(), std::line!()), + region, + ) + } RuntimeError(_) => { // Runtime Errors have no constraints because they're going to crash. Constraint::True diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index e890e1a9b6..844fdac860 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -1,6 +1,6 @@ use crate::expr::{constrain_def_make_constraint, constrain_def_pattern, Env}; use roc_builtins::std::StdLib; -use roc_can::abilities::AbilitiesStore; +use roc_can::abilities::{AbilitiesStore, MemberTypeInfo, SolvedSpecializations}; use roc_can::constraint::{Constraint, Constraints}; use roc_can::expected::Expected; use roc_can::expr::Declarations; @@ -53,6 +53,10 @@ impl ExposedByModule { output } + + pub fn iter_all(&self) -> impl Iterator { + self.exposed.iter() + } } #[derive(Clone, Debug, Default)] @@ -70,7 +74,7 @@ impl ExposedForModule { for symbol in it { let module = exposed_by_module.exposed.get(&symbol.module_id()); - if let Some(ExposedModuleTypes::Valid { .. }) = module { + if let Some(ExposedModuleTypes { .. }) = module { imported_values.push(*symbol); } else { continue; @@ -86,12 +90,10 @@ impl ExposedForModule { /// The types of all exposed values/functions of a module #[derive(Clone, Debug)] -pub enum ExposedModuleTypes { - Invalid, - Valid { - stored_vars_by_symbol: Vec<(Symbol, Variable)>, - storage_subs: roc_types::subs::StorageSubs, - }, +pub struct ExposedModuleTypes { + pub stored_vars_by_symbol: Vec<(Symbol, Variable)>, + pub storage_subs: roc_types::subs::StorageSubs, + pub solved_specializations: SolvedSpecializations, } pub fn constrain_module( @@ -179,46 +181,53 @@ pub fn frontload_ability_constraints( mut constraint: Constraint, ) -> Constraint { for (member_name, member_data) in abilities_store.root_ability_members().iter() { - let rigids = Default::default(); - let mut env = Env { - home, - rigids, - resolutions_to_make: vec![], - }; - let pattern = Loc::at_zero(roc_can::pattern::Pattern::Identifier(*member_name)); + if let MemberTypeInfo::Local { + signature_var, + variables: vars, + signature, + } = &member_data.typ + { + let signature_var = *signature_var; + let rigids = Default::default(); + let mut env = Env { + home, + rigids, + resolutions_to_make: vec![], + }; + let pattern = Loc::at_zero(roc_can::pattern::Pattern::Identifier(*member_name)); - let mut def_pattern_state = constrain_def_pattern( - constraints, - &mut env, - &pattern, - Type::Variable(member_data.signature_var), - ); + let mut def_pattern_state = constrain_def_pattern( + constraints, + &mut env, + &pattern, + Type::Variable(signature_var), + ); - debug_assert!(env.resolutions_to_make.is_empty()); + debug_assert!(env.resolutions_to_make.is_empty()); - def_pattern_state.vars.push(member_data.signature_var); + def_pattern_state.vars.push(signature_var); - let vars = &member_data.variables; - let rigid_variables = vars.rigid_vars.iter().chain(vars.able_vars.iter()).copied(); - let infer_variables = vars.flex_vars.iter().copied(); + let rigid_variables = vars.rigid_vars.iter().chain(vars.able_vars.iter()).copied(); + let infer_variables = vars.flex_vars.iter().copied(); - def_pattern_state - .constraints - .push(constraints.equal_types_var( - member_data.signature_var, - Expected::NoExpectation(member_data.signature.clone()), - Category::Storage(file!(), line!()), - Region::zero(), - )); + def_pattern_state + .constraints + .push(constraints.equal_types_var( + signature_var, + Expected::NoExpectation(signature.clone()), + Category::Storage(file!(), line!()), + Region::zero(), + )); - constraint = constrain_def_make_constraint( - constraints, - rigid_variables, - infer_variables, - Constraint::True, - constraint, - def_pattern_state, - ); + constraint = constrain_def_make_constraint( + constraints, + rigid_variables, + infer_variables, + Constraint::True, + constraint, + def_pattern_state, + ); + } } constraint } diff --git a/compiler/exhaustive/Cargo.toml b/compiler/exhaustive/Cargo.toml index 7fa1a63bda..a0cbbaf717 100644 --- a/compiler/exhaustive/Cargo.toml +++ b/compiler/exhaustive/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_exhaustive" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/exhaustive/src/lib.rs b/compiler/exhaustive/src/lib.rs index a01e0a7033..f432dd2270 100644 --- a/compiler/exhaustive/src/lib.rs +++ b/compiler/exhaustive/src/lib.rs @@ -7,7 +7,6 @@ use roc_module::{ symbol::Symbol, }; use roc_region::all::Region; -use roc_std::RocDec; use self::Pattern::*; @@ -74,13 +73,13 @@ pub enum Pattern { #[derive(Clone, Debug, PartialEq, Eq)] pub enum Literal { - Int(i128), - U128(u128), + Int([u8; 16]), + U128([u8; 16]), Bit(bool), Byte(u8), /// Stores the float bits Float(u64), - Decimal(RocDec), + Decimal([u8; 16]), Str(Box), } diff --git a/compiler/fmt/Cargo.toml b/compiler/fmt/Cargo.toml index 815891ba59..a402a79d81 100644 --- a/compiler/fmt/Cargo.toml +++ b/compiler/fmt/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_fmt" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index d9f1e61fed..14582bbe32 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -4,7 +4,8 @@ use crate::{ Buf, }; use roc_parse::ast::{ - AssignedField, Collection, Expr, ExtractSpaces, HasClause, Tag, TypeAnnotation, TypeHeader, + AssignedField, Collection, Derived, Expr, ExtractSpaces, HasClause, Tag, TypeAnnotation, + TypeHeader, }; use roc_parse::ident::UppercaseIdent; use roc_region::all::Loc; @@ -43,6 +44,16 @@ pub enum Newlines { Yes, } +impl Newlines { + pub fn from_bool(yes: bool) -> Self { + if yes { + Self::Yes + } else { + Self::No + } + } +} + pub trait Formattable { fn is_multiline(&self) -> bool; @@ -316,9 +327,10 @@ impl<'a> Formattable for TypeAnnotation<'a> { Where(annot, has_clauses) => { annot.format_with_options(buf, parens, newlines, indent); - buf.push_str(" "); + buf.spaces(1); for (i, has) in has_clauses.iter().enumerate() { - buf.push_str(if i == 0 { "| " } else { ", " }); + buf.push(if i == 0 { '|' } else { ',' }); + buf.spaces(1); has.format_with_options(buf, parens, newlines, indent); } } @@ -541,8 +553,49 @@ impl<'a> Formattable for HasClause<'a> { indent: u16, ) { buf.push_str(self.var.value.extract_spaces().item); - buf.push_str(" has "); + buf.spaces(1); + buf.push_str("has"); + buf.spaces(1); self.ability .format_with_options(buf, parens, newlines, indent); } } + +impl<'a> Formattable for Derived<'a> { + fn is_multiline(&self) -> bool { + match self { + Derived::SpaceAfter(..) | Derived::SpaceBefore(..) => true, + Derived::Has(derived) => derived.is_multiline(), + } + } + + fn format_with_options<'buf>( + &self, + buf: &mut Buf<'buf>, + parens: Parens, + newlines: Newlines, + indent: u16, + ) { + match self { + Derived::Has(derived) => { + if newlines == Newlines::Yes { + buf.newline(); + buf.indent(indent); + } + buf.push_str("has"); + buf.spaces(1); + fmt_collection(buf, indent, '[', ']', *derived, newlines); + } + Derived::SpaceBefore(derived, spaces) => { + buf.newline(); + buf.indent(indent); + fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); + derived.format_with_options(buf, parens, Newlines::No, indent) + } + Derived::SpaceAfter(derived, spaces) => { + derived.format_with_options(buf, parens, newlines, indent); + fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); + } + } + } +} diff --git a/compiler/fmt/src/def.rs b/compiler/fmt/src/def.rs index 72f95b0dab..8c4236cbfd 100644 --- a/compiler/fmt/src/def.rs +++ b/compiler/fmt/src/def.rs @@ -2,7 +2,9 @@ use crate::annotation::{Formattable, Newlines, Parens}; use crate::pattern::fmt_pattern; use crate::spaces::{fmt_spaces, INDENT}; use crate::Buf; -use roc_parse::ast::{AbilityMember, Def, Expr, ExtractSpaces, Pattern, TypeHeader}; +use roc_parse::ast::{ + AbilityMember, Def, Expr, ExtractSpaces, Pattern, TypeAnnotation, TypeHeader, +}; use roc_region::all::Loc; /// A Located formattable value is also formattable @@ -51,10 +53,6 @@ impl<'a> Formattable for Def<'a> { Alias { header: TypeHeader { name, vars }, ann, - } - | Opaque { - header: TypeHeader { name, vars }, - typ: ann, } => { buf.indent(indent); buf.push_str(name.value); @@ -64,15 +62,64 @@ impl<'a> Formattable for Def<'a> { fmt_pattern(buf, &var.value, indent, Parens::NotNeeded); } - buf.push_str(match def { - Alias { .. } => " :", - Opaque { .. } => " :=", - _ => unreachable!(), - }); + buf.push_str(" :"); buf.spaces(1); ann.format(buf, indent + INDENT) } + Opaque { + header: TypeHeader { name, vars }, + typ: ann, + derived, + } => { + buf.indent(indent); + buf.push_str(name.value); + + for var in *vars { + buf.spaces(1); + fmt_pattern(buf, &var.value, indent, Parens::NotNeeded); + } + + buf.push_str(" :="); + buf.spaces(1); + + let ann_is_where_clause = + matches!(ann.extract_spaces().item, TypeAnnotation::Where(..)); + + let ann_has_spaces_before = + matches!(&ann.value, TypeAnnotation::SpaceBefore(..)); + + // Always put the has-derived clause on a newline if it is itself multiline, or + // the annotation has a where-has clause. + let derived_multiline = if let Some(derived) = derived { + !derived.value.is_empty() && (derived.is_multiline() || ann_is_where_clause) + } else { + false + }; + + let make_multiline = ann.is_multiline() || derived_multiline; + + // If the annotation has spaces before, a newline will already be printed. + if make_multiline && !ann_has_spaces_before { + buf.newline(); + buf.indent(indent + INDENT); + } + + ann.format(buf, indent + INDENT); + + if let Some(derived) = derived { + if !make_multiline { + buf.spaces(1); + } + + derived.format_with_options( + buf, + Parens::NotNeeded, + Newlines::from_bool(make_multiline), + indent + INDENT, + ); + } + } Ability { header: TypeHeader { name, vars }, loc_has: _, @@ -103,14 +150,50 @@ impl<'a> Formattable for Def<'a> { Value(def) => match def { Annotation(loc_pattern, loc_annotation) => { loc_pattern.format(buf, indent); + if loc_annotation.is_multiline() { buf.push_str(" :"); - loc_annotation.format_with_options( - buf, - Parens::NotNeeded, - Newlines::Yes, - indent + INDENT, - ); + + let should_outdent = match loc_annotation.value { + TypeAnnotation::SpaceBefore(sub_def, spaces) => match sub_def { + TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => { + let is_only_newlines = spaces.iter().all(|s| s.is_newline()); + is_only_newlines && sub_def.is_multiline() + } + _ => false, + }, + TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => true, + _ => false, + }; + + if should_outdent { + buf.spaces(1); + match loc_annotation.value { + TypeAnnotation::SpaceBefore(sub_def, _) => { + sub_def.format_with_options( + buf, + Parens::NotNeeded, + Newlines::No, + indent, + ); + } + _ => { + loc_annotation.format_with_options( + buf, + Parens::NotNeeded, + Newlines::No, + indent, + ); + } + } + } else { + loc_annotation.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + indent + INDENT, + ); + } } else { buf.spaces(1); buf.push_str(":"); @@ -134,15 +217,41 @@ impl<'a> Formattable for Def<'a> { body_pattern, body_expr, } => { + let is_type_multiline = ann_type.is_multiline(); + let is_type_function = matches!( + ann_type.value, + TypeAnnotation::Function(..) + | TypeAnnotation::SpaceBefore(TypeAnnotation::Function(..), ..) + | TypeAnnotation::SpaceAfter(TypeAnnotation::Function(..), ..) + ); + + let next_indent = if is_type_multiline { + indent + INDENT + } else { + indent + }; + ann_pattern.format(buf, indent); buf.push_str(" :"); - buf.spaces(1); - ann_type.format(buf, indent); + + if is_type_multiline && is_type_function { + ann_type.format_with_options( + buf, + Parens::NotNeeded, + Newlines::Yes, + next_indent, + ); + } else { + buf.spaces(1); + ann_type.format(buf, indent); + } + if let Some(comment_str) = comment { buf.push_str(" #"); buf.spaces(1); buf.push_str(comment_str.trim()); } + buf.newline(); fmt_body(buf, &body_pattern.value, &body_expr.value, indent); } @@ -230,7 +339,9 @@ impl<'a> Formattable for AbilityMember<'a> { fn format<'buf>(&self, buf: &mut Buf<'buf>, indent: u16) { buf.push_str(self.name.value.extract_spaces().item); - buf.push_str(" : "); + buf.spaces(1); + buf.push(':'); + buf.spaces(1); self.typ.value.format(buf, indent + INDENT); } } diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index 7e038bc148..1fd51ac0b2 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -611,6 +611,24 @@ fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool { } } +fn is_when_patterns_multiline(when_branch: &WhenBranch) -> bool { + let patterns = when_branch.patterns; + let (first_pattern, rest) = patterns.split_first().unwrap(); + + let is_multiline_patterns = if let Some((last_pattern, inner_patterns)) = rest.split_last() { + !first_pattern.value.extract_spaces().after.is_empty() + || !last_pattern.value.extract_spaces().before.is_empty() + || inner_patterns.iter().any(|p| { + let spaces = p.value.extract_spaces(); + !spaces.before.is_empty() || !spaces.after.is_empty() + }) + } else { + false + }; + + is_multiline_patterns +} + fn fmt_when<'a, 'buf>( buf: &mut Buf<'buf>, loc_condition: &'a Loc>, @@ -668,34 +686,23 @@ fn fmt_when<'a, 'buf>( let mut it = branches.iter().peekable(); while let Some(branch) = it.next() { - let patterns = &branch.patterns; let expr = &branch.value; - let (first_pattern, rest) = patterns.split_first().unwrap(); - let is_multiline = if let Some((last_pattern, inner_patterns)) = rest.split_last() { - !first_pattern.value.extract_spaces().after.is_empty() - || !last_pattern.value.extract_spaces().before.is_empty() - || inner_patterns.iter().any(|p| { - let spaces = p.value.extract_spaces(); - !spaces.before.is_empty() || !spaces.after.is_empty() - }) - } else { - false - }; + let patterns = &branch.patterns; + let is_multiline_expr = expr.is_multiline(); + let is_multiline_patterns = is_when_patterns_multiline(branch); - fmt_pattern( - buf, - &first_pattern.value, - indent + INDENT, - Parens::NotNeeded, - ); - for when_pattern in rest { - if is_multiline { - buf.newline(); - buf.indent(indent + INDENT); + for (index, pattern) in patterns.iter().enumerate() { + if index != 0 { + if is_multiline_patterns { + buf.newline(); + buf.indent(indent + INDENT); + } + + buf.push_str(" |"); + buf.spaces(1); } - buf.push_str(" |"); - buf.spaces(1); - fmt_pattern(buf, &when_pattern.value, indent + INDENT, Parens::NotNeeded); + + fmt_pattern(buf, &pattern.value, indent + INDENT, Parens::NotNeeded); } if let Some(guard_expr) = &branch.guard { @@ -705,7 +712,12 @@ fn fmt_when<'a, 'buf>( } buf.push_str(" ->"); - buf.newline(); + + if is_multiline_expr { + buf.newline(); + } else { + buf.spaces(1); + } match expr.value { Expr::SpaceBefore(nested, spaces) => { @@ -729,7 +741,6 @@ fn fmt_when<'a, 'buf>( if it.peek().is_some() { buf.newline(); - buf.newline(); } } } @@ -855,7 +866,9 @@ fn fmt_if<'a, 'buf>( } } _ => { - loc_condition.format(buf, return_indent); + buf.newline(); + loc_then.format(buf, return_indent); + buf.newline(); } } } else { diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index e4f53fc47c..4df623dd5c 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -85,20 +85,22 @@ impl<'a> Formattable for Pattern<'a> { RecordDestructure(loc_patterns) => { buf.indent(indent); buf.push_str("{"); - buf.spaces(1); - let mut it = loc_patterns.iter().peekable(); + if !loc_patterns.is_empty() { + buf.spaces(1); + let mut it = loc_patterns.iter().peekable(); + while let Some(loc_pattern) = it.next() { + loc_pattern.format(buf, indent); - while let Some(loc_pattern) = it.next() { - loc_pattern.format(buf, indent); - - if it.peek().is_some() { - buf.push_str(","); - buf.spaces(1); + if it.peek().is_some() { + buf.push_str(","); + buf.spaces(1); + } } + buf.spaces(1); } - buf.push_str(" }"); + buf.push_str("}"); } RequiredField(name, loc_pattern) => { diff --git a/compiler/fmt/src/spaces.rs b/compiler/fmt/src/spaces.rs index 116ed718d4..e0b387957c 100644 --- a/compiler/fmt/src/spaces.rs +++ b/compiler/fmt/src/spaces.rs @@ -3,9 +3,9 @@ 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, + AbilityMember, AssignedField, Collection, CommentOrNewline, Def, Derived, Expr, Has, + HasClause, Module, Pattern, Spaced, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, + TypeHeader, ValueDef, WhenBranch, }, header::{ AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, ModuleName, @@ -159,10 +159,10 @@ where fn fmt_docs<'buf>(buf: &mut Buf<'buf>, docs: &str) { buf.push_str("##"); - if !docs.starts_with(' ') { + if !docs.is_empty() { buf.spaces(1); } - buf.push_str(docs); + buf.push_str(docs.trim_end()); } /// RemoveSpaces normalizes the ast to something that we _expect_ to be invariant under formatting. @@ -438,12 +438,14 @@ impl<'a> RemoveSpaces<'a> for TypeDef<'a> { Opaque { header: TypeHeader { name, vars }, typ, + derived, } => Opaque { header: TypeHeader { name: name.remove_spaces(arena), vars: vars.remove_spaces(arena), }, typ: typ.remove_spaces(arena), + derived: derived.remove_spaces(arena), }, Ability { header: TypeHeader { name, vars }, @@ -690,9 +692,14 @@ impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> { ), 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::As(a, _, TypeHeader { name, vars }) => TypeAnnotation::As( + arena.alloc(a.remove_spaces(arena)), + &[], + TypeHeader { + name: name.remove_spaces(arena), + vars: vars.remove_spaces(arena), + }, + ), TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record { fields: fields.remove_spaces(arena), ext: ext.remove_spaces(arena), @@ -736,3 +743,14 @@ impl<'a> RemoveSpaces<'a> for Tag<'a> { } } } + +impl<'a> RemoveSpaces<'a> for Derived<'a> { + fn remove_spaces(&self, arena: &'a Bump) -> Self { + match *self { + Derived::Has(derived) => Derived::Has(derived.remove_spaces(arena)), + Derived::SpaceBefore(derived, _) | Derived::SpaceAfter(derived, _) => { + derived.remove_spaces(arena) + } + } + } +} diff --git a/compiler/fmt/tests/test_fmt.rs b/compiler/fmt/tests/test_fmt.rs index 985d4cbc28..b840443b65 100644 --- a/compiler/fmt/tests/test_fmt.rs +++ b/compiler/fmt/tests/test_fmt.rs @@ -14,7 +14,7 @@ mod test_fmt { 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; + use roc_test_utils::{assert_multiline_str_eq, workspace_root}; // Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same fn expr_formats_to(input: &str, expected: &str) { @@ -289,12 +289,11 @@ mod test_fmt { fn type_annotation_allow_blank_line_before_and_after_comment() { expr_formats_same(indoc!( r#" - person : - { - firstName : Str, - # comment - lastName : Str, - } + person : { + firstName : Str, + # comment + lastName : Str, + } person "# @@ -302,13 +301,12 @@ mod test_fmt { expr_formats_same(indoc!( r#" - person : - { - firstName : Str, + person : { + firstName : Str, - # comment - lastName : Str, - } + # comment + lastName : Str, + } person "# @@ -316,13 +314,12 @@ mod test_fmt { expr_formats_same(indoc!( r#" - person : - { - firstName : Str, - # comment + person : { + firstName : Str, + # comment - lastName : Str, - } + lastName : Str, + } person "# @@ -330,14 +327,13 @@ mod test_fmt { expr_formats_same(indoc!( r#" - person : - { - firstName : Str, + person : { + firstName : Str, - # comment + # comment - lastName : Str, - } + lastName : Str, + } person "# @@ -345,17 +341,16 @@ mod test_fmt { expr_formats_same(indoc!( r#" - person : - { - firstName : Str, + person : { + firstName : Str, - # comment 1 + # comment 1 - lastName : Str, + lastName : Str, - # comment 2 - # comment 3 - } + # comment 2 + # comment 3 + } person "# @@ -363,16 +358,15 @@ mod test_fmt { expr_formats_same(indoc!( r#" - person : - { - firstName : Str, + person : { + firstName : Str, - # comment 1 + # comment 1 - lastName : Str, - # comment 2 - # comment 3 - } + lastName : Str, + # comment 2 + # comment 3 + } person "# @@ -381,27 +375,25 @@ mod test_fmt { expr_formats_to( indoc!( r#" - person : - { + person : { - # comment + # comment - firstName : Str, - lastName : Str, - } + firstName : Str, + lastName : Str, + } person "# ), indoc!( r#" - person : - { - # comment + person : { + # comment - firstName : Str, - lastName : Str, - } + firstName : Str, + lastName : Str, + } person "# @@ -411,27 +403,25 @@ mod test_fmt { expr_formats_to( indoc!( r#" - person : - { - firstName : Str, - lastName : Str, + person : { + firstName : Str, + lastName : Str, - # comment + # comment - } + } person "# ), indoc!( r#" - person : - { - firstName : Str, - lastName : Str, + person : { + firstName : Str, + lastName : Str, - # comment - } + # comment + } person "# @@ -441,27 +431,25 @@ mod test_fmt { expr_formats_to( indoc!( r#" - person : - { - firstName : Str, + person : { + firstName : Str, - # comment - lastName : Str, - } + # comment + lastName : Str, + } person "# ), indoc!( r#" - person : - { - firstName : Str, + person : { + firstName : Str, - # comment - lastName : Str, - } + # comment + lastName : Str, + } person "# @@ -471,27 +459,25 @@ mod test_fmt { expr_formats_to( indoc!( r#" - person : - { - firstName : Str, - # comment + person : { + firstName : Str, + # comment - lastName : Str, - } + lastName : Str, + } person "# ), indoc!( r#" - person : - { - firstName : Str, - # comment + person : { + firstName : Str, + # comment - lastName : Str, - } + lastName : Str, + } person "# @@ -501,30 +487,28 @@ mod test_fmt { expr_formats_to( indoc!( r#" - person : - { - firstName : Str, + person : { + firstName : Str, - # comment + # comment - lastName : Str, - } + lastName : Str, + } person "# ), indoc!( r#" - person : - { - firstName : Str, + person : { + firstName : Str, - # comment + # comment - lastName : Str, - } + lastName : Str, + } person "# @@ -1724,14 +1708,14 @@ mod test_fmt { fn multiline_record_func_arg() { expr_formats_same(indoc!( r#" - result = func arg { - x: 1, - y: 2, - z: 3, - } + result = func arg { + x: 1, + y: 2, + z: 3, + } - result - "# + result + "# )); expr_formats_to( @@ -2046,11 +2030,10 @@ mod test_fmt { ), indoc!( r#" - f : - { - y : Int *, - x : Int *, - } + f : { + y : Int *, + x : Int *, + } f"# ), @@ -2061,14 +2044,38 @@ mod test_fmt { fn trailing_comma_in_record_annotation_same() { expr_formats_same(indoc!( r#" - f : - { + f : { + y : Int *, + x : Int *, + } + + f + "# + )); + + expr_formats_to( + indoc!( + r#" + f : + { + y : Int *, + x : Int *, + } + + f + "# + ), + indoc!( + r#" + f : { y : Int *, x : Int *, } - f"# - )); + f + "# + ), + ); } #[test] @@ -2089,7 +2096,18 @@ mod test_fmt { f : {} - f"# + f + "# + )); + + expr_formats_same(indoc!( + r#" + f : + { + } + + f + "# )); } @@ -2128,10 +2146,9 @@ mod test_fmt { ), indoc!( r#" - f : - { - # comment - } + f : { + # comment + } f"# ), @@ -2139,15 +2156,46 @@ mod test_fmt { } #[test] - #[ignore] - fn multiline_inside_empty_record_annotation() { + fn multiline_curly_brace_type() { expr_formats_same(indoc!( r#" - f : - { - } + x : { + a : Int, + } - f"# + x + "# + )); + + expr_formats_same(indoc!( + r#" + x : + { a : Int } + + x + "# + )); + } + + #[test] + fn multiline_brace_type() { + expr_formats_same(indoc!( + r#" + x : [ + Int, + ] + + x + "# + )); + + expr_formats_same(indoc!( + r#" + x : + [ Int ] + + x + "# )); } @@ -2272,12 +2320,11 @@ mod test_fmt { ), indoc!( r#" - f : - { - x : Int *, - # comment 1 - # comment 2 - } + f : { + x : Int *, + # comment 1 + # comment 2 + } f"# ), @@ -2727,6 +2774,61 @@ mod test_fmt { #[test] fn empty_record() { expr_formats_same("{}"); + expr_formats_to("{ }", "{}"); + } + + #[test] + fn empty_record_patterns() { + expr_formats_to( + indoc!( + r#" + f = \{ } -> "Hello World" + + f + "# + ), + indoc!( + r#" + f = \{} -> "Hello World" + + f + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + f = \a, b -> { } + + f + "# + ), + indoc!( + r#" + f = \a, b -> {} + + f + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + { } <- f a b + + {} + "# + ), + indoc!( + r#" + {} <- f a b + + {} + "# + ), + ); } #[test] @@ -3213,7 +3315,6 @@ mod test_fmt { when b is 1 -> 1 - _ -> 2 "# @@ -3243,7 +3344,6 @@ mod test_fmt { when year is 1999 -> 1 - _ -> 0 "# @@ -3260,7 +3360,6 @@ mod test_fmt { 1 -> # when 1 1 - # important # fall through _ -> @@ -3294,7 +3393,6 @@ mod test_fmt { when c is 6 | 7 -> 8 - 3 | 4 -> 5 "# @@ -3368,25 +3466,16 @@ mod test_fmt { | 2 | 3 -> 4 - 5 | 6 | 7 -> 8 - 9 - | 10 -> - 11 - + | 10 -> 11 12 | 13 -> when c is - 14 | 15 -> - 16 - + 14 | 15 -> 16 17 - | 18 -> - 19 - - 20 -> - 21 + | 18 -> 19 + 20 -> 21 "# ), ); @@ -3405,12 +3494,9 @@ mod test_fmt { indoc!( r#" when b is - 3 -> - 4 - + 3 -> 4 9 - | 8 -> - 9 + | 8 -> 9 "# ), ); @@ -3435,7 +3521,6 @@ mod test_fmt { when b is 1 -> 1 - # when 1 # fall through _ -> @@ -3454,7 +3539,6 @@ mod test_fmt { is 1 -> Nothing - _ -> Just True "# @@ -3472,7 +3556,6 @@ mod test_fmt { is Complex x y -> simplify x y - Simple z -> z "# @@ -3507,7 +3590,6 @@ mod test_fmt { is 2 -> x - _ -> y "# @@ -3544,7 +3626,6 @@ mod test_fmt { is 4 -> x - _ -> y "# @@ -3552,6 +3633,64 @@ mod test_fmt { ); } + #[test] + fn single_line_when_patterns() { + expr_formats_same(indoc!( + r#" + when x is + Foo -> 1 + Bar -> 2 + "# + )); + + expr_formats_same(indoc!( + r#" + when x is + Foo -> 1 + Bar -> + 2 + "# + )); + + expr_formats_to( + indoc!( + r#" + when x is + Foo -> 1 + + Bar -> + 2 + "# + ), + indoc!( + r#" + when x is + Foo -> 1 + Bar -> + 2 + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + when x is + Foo -> 1 + + Bar -> 2 + "# + ), + indoc!( + r#" + when x is + Foo -> 1 + Bar -> 2 + "# + ), + ); + } + // NEWLINES #[test] @@ -3634,7 +3773,6 @@ mod test_fmt { when maybeScore is Just score if score > 21 -> win - _ -> nextRound "# @@ -3648,10 +3786,8 @@ mod test_fmt { when authenticationResponse is Ok user if hasPermission user -> loadPage route user - Ok user -> PageNotFound - Err _ -> ErrorPage "# @@ -3757,7 +3893,6 @@ mod test_fmt { when f x == g y == h z is True -> Ok 1 - False -> Err 2 "# @@ -4247,15 +4382,70 @@ mod test_fmt { fn multiline_tag_union_annotation() { expr_formats_same(indoc!( r#" - b : - [ - True, - False, - ] + b : [ + True, + False, + ] b "# )); + + expr_formats_same(indoc!( + r#" + b : + [ True, False ] + + b + "# + )); + + expr_formats_to( + indoc!( + r#" + b : + [ + True, + False, + ] + + b + "# + ), + indoc!( + r#" + b : [ + True, + False, + ] + + b + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + b : [ + True, + False, + ] + + b + "# + ), + indoc!( + r#" + b : [ + True, + False, + ] + + b + "# + ), + ); } #[test] @@ -4292,14 +4482,13 @@ mod test_fmt { ), indoc!( r#" - b : - [ - True, - # comment 1 - False, - # comment 2 - # comment 3 - ] + b : [ + True, + # comment 1 + False, + # comment 2 + # comment 3 + ] b "# @@ -4350,7 +4539,6 @@ mod test_fmt { when list is Nil -> Nothing - Cons first _ -> Just first @@ -4487,6 +4675,19 @@ mod test_fmt { "# )); + expr_formats_same(indoc!( + r#" + foo : + (Str -> Bool), + Str + -> Bool + foo = \bar, baz -> + 42 + + 42 + "# + )); + expr_formats_to( indoc!( r#" @@ -4506,6 +4707,96 @@ mod test_fmt { "# ), ); + + expr_formats_to( + indoc!( + r#" + foo : + (Str -> Bool), Str -> Bool + foo = \bar, baz -> + 42 + + 42 + "# + ), + indoc!( + r#" + foo : + (Str -> Bool), + Str + -> Bool + foo = \bar, baz -> + 42 + + 42 + "# + ), + ); + + expr_formats_to( + indoc!( + r#" + foo : + (Str -> Bool), Str -> Bool # comment + foo = \bar, baz -> + 42 + + 42 + "# + ), + indoc!( + r#" + foo : + (Str -> Bool), + Str + -> Bool # comment + foo = \bar, baz -> + 42 + + 42 + "# + ), + ); + } + + #[test] + fn opaque_has_clause() { + expr_formats_same(indoc!( + r#" + A := U8 has [ Eq, Hash ] + + 0 + "# + )); + + expr_formats_same(indoc!( + r#" + A := + U8 + has [ Eq, Hash ] + + 0 + "# + )); + + expr_formats_to( + indoc!( + r#" + A := a | a has Hash has [ Eq, Hash ] + + 0 + "# + ), + indoc!( + r#" + A := + a | a has Hash + has [ Eq, Hash ] + + 0 + "# + ), + ); } #[test] @@ -4515,13 +4806,7 @@ mod test_fmt { /// `cargo run -- format $(find examples -name \*.roc)` fn test_fmt_examples() { let mut count = 0; - let mut root = std::env::current_dir() - .unwrap() - .parent() - .unwrap() - .parent() - .unwrap() - .to_owned(); + let mut root = workspace_root(); root.push("examples"); for entry in walkdir::WalkDir::new(&root) { let entry = entry.unwrap(); @@ -4540,6 +4825,32 @@ mod test_fmt { ); } + #[test] + /// Test that builtins are formatted correctly + /// If this test fails on your diff, it probably means you need to re-format a builtin. + /// Try this: + /// `cargo run -- format $(find compiler/builtins/roc -name \*.roc)` + fn test_fmt_builtins() { + let mut count = 0; + let mut root = workspace_root(); + root.push("compiler/builtins/roc"); + for entry in walkdir::WalkDir::new(&root) { + let entry = entry.unwrap(); + let path = entry.path(); + if path.extension() == Some(std::ffi::OsStr::new("roc")) { + count += 1; + let src = std::fs::read_to_string(path).unwrap(); + println!("Now trying to format {}", path.display()); + module_formats_same(&src); + } + } + assert!( + count > 0, + "Expecting to find at least 1 .roc file to format under {}", + root.display() + ); + } + // this is a parse error atm // #[test] // fn multiline_apply() { diff --git a/compiler/gen_dev/Cargo.toml b/compiler/gen_dev/Cargo.toml index 00b08bd467..ef8f6d1dbd 100644 --- a/compiler/gen_dev/Cargo.toml +++ b/compiler/gen_dev/Cargo.toml @@ -4,7 +4,7 @@ description = "The development backend for the Roc compiler" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/gen_dev/src/generic64/mod.rs b/compiler/gen_dev/src/generic64/mod.rs index ed1aca5277..64dd060cb5 100644 --- a/compiler/gen_dev/src/generic64/mod.rs +++ b/compiler/gen_dev/src/generic64/mod.rs @@ -963,7 +963,7 @@ impl< self.load_literal( &Symbol::DEV_TMP, u32_layout, - &Literal::Int(list_alignment as i128), + &Literal::Int((list_alignment as i128).to_ne_bytes()), ); // Have to pass the input element by pointer, so put it on the stack and load it's address. @@ -982,7 +982,7 @@ impl< self.load_literal( &Symbol::DEV_TMP3, u64_layout, - &Literal::Int(elem_stack_size as i128), + &Literal::Int((elem_stack_size as i128).to_ne_bytes()), ); // Setup the return location. @@ -1141,7 +1141,7 @@ impl< ) => { let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym); let val = *x; - ASM::mov_reg64_imm64(&mut self.buf, reg, val as i64); + ASM::mov_reg64_imm64(&mut self.buf, reg, i128::from_ne_bytes(val) as i64); } (Literal::Float(x), Layout::Builtin(Builtin::Float(FloatWidth::F64))) => { let reg = self.storage_manager.claim_float_reg(&mut self.buf, sym); diff --git a/compiler/gen_dev/src/lib.rs b/compiler/gen_dev/src/lib.rs index cd655880ac..bdc9aaf6b2 100644 --- a/compiler/gen_dev/src/lib.rs +++ b/compiler/gen_dev/src/lib.rs @@ -640,7 +640,11 @@ trait Backend<'a> { "NumIsZero: expected to have return layout of type Bool" ); - self.load_literal(&Symbol::DEV_TMP, &arg_layouts[0], &Literal::Int(0)); + self.load_literal( + &Symbol::DEV_TMP, + &arg_layouts[0], + &Literal::Int(0i128.to_ne_bytes()), + ); self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]); self.free_symbol(&Symbol::DEV_TMP) } diff --git a/compiler/gen_llvm/Cargo.toml b/compiler/gen_llvm/Cargo.toml index 5d020a5507..8093e436ed 100644 --- a/compiler/gen_llvm/Cargo.toml +++ b/compiler/gen_llvm/Cargo.toml @@ -4,7 +4,7 @@ description = "The LLVM backend for the Roc compiler" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_alias_analysis = { path = "../alias_analysis" } diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 18a39ab97e..e464f947cb 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -1,6 +1,3 @@ -use std::convert::{TryFrom, TryInto}; -use std::path::Path; - use crate::llvm::bitcode::{ call_bitcode_fn, call_bitcode_fn_fixing_for_convention, call_list_bitcode_fn, call_str_bitcode_fn, call_void_bitcode_fn, @@ -56,7 +53,9 @@ use morphic_lib::{ use roc_builtins::bitcode::{self, FloatWidth, IntWidth, IntrinsicName}; use roc_builtins::{float_intrinsic, llvm_int_intrinsic}; use roc_collections::all::{ImMap, MutMap, MutSet}; -use roc_debug_flags::{dbg_do, ROC_PRINT_LLVM_FN_VERIFICATION}; +use roc_debug_flags::dbg_do; +#[cfg(debug_assertions)] +use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION; use roc_error_macros::internal_error; use roc_module::low_level::LowLevel; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -65,7 +64,10 @@ use roc_mono::ir::{ ModifyRc, OptLevel, ProcLayout, }; use roc_mono::layout::{Builtin, LambdaSet, Layout, LayoutIds, TagIdIntType, UnionLayout}; +use roc_std::RocDec; use roc_target::{PtrWidth, TargetInfo}; +use std::convert::{TryFrom, TryInto}; +use std::path::Path; use target_lexicon::{Architecture, OperatingSystem, Triple}; use super::convert::zig_with_overflow_roc_dec; @@ -780,17 +782,19 @@ pub fn build_exp_literal<'a, 'ctx, 'env>( use roc_mono::ir::Literal::*; match literal { - Int(int) => match layout { - Layout::Builtin(Builtin::Bool) => { - env.context.bool_type().const_int(*int as u64, false).into() - } + Int(bytes) => match layout { + Layout::Builtin(Builtin::Bool) => env + .context + .bool_type() + .const_int(i128::from_ne_bytes(*bytes) as u64, false) + .into(), Layout::Builtin(Builtin::Int(int_width)) => { - int_with_precision(env, *int, *int_width).into() + int_with_precision(env, i128::from_ne_bytes(*bytes), *int_width).into() } _ => panic!("Invalid layout for int literal = {:?}", layout), }, - U128(int) => const_u128(env, *int).into(), + U128(bytes) => const_u128(env, u128::from_ne_bytes(*bytes)).into(), Float(float) => match layout { Layout::Builtin(Builtin::Float(float_width)) => { @@ -799,8 +803,8 @@ pub fn build_exp_literal<'a, 'ctx, 'env>( _ => panic!("Invalid layout for float literal = {:?}", layout), }, - Decimal(int) => { - let (upper_bits, lower_bits) = int.as_bits(); + Decimal(bytes) => { + let (upper_bits, lower_bits) = RocDec::from_ne_bytes(*bytes).as_bits(); env.context .i128_type() .const_int_arbitrary_precision(&[lower_bits, upper_bits as u64]) diff --git a/compiler/gen_llvm/src/run_roc.rs b/compiler/gen_llvm/src/run_roc.rs index 812fbdede6..34f893f68e 100644 --- a/compiler/gen_llvm/src/run_roc.rs +++ b/compiler/gen_llvm/src/run_roc.rs @@ -56,11 +56,11 @@ macro_rules! run_jit_function { } unsafe { - let main: libloading::Symbol) -> ()> = - $lib.get($main_fn_name.as_bytes()) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) - .expect("errored"); + let main: libloading::Symbol)> = $lib + .get($main_fn_name.as_bytes()) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", $main_fn_name)) + .expect("errored"); #[repr(C)] struct Failures { diff --git a/compiler/gen_wasm/Cargo.toml b/compiler/gen_wasm/Cargo.toml index ccdb6b2768..5989fb6aa2 100644 --- a/compiler/gen_wasm/Cargo.toml +++ b/compiler/gen_wasm/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "roc_gen_wasm" version = "0.1.0" -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index d52ac14008..423df2363a 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -4,6 +4,7 @@ use std::fmt::Write; use code_builder::Align; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::all::MutMap; +use roc_error_macros::internal_error; use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, Symbol}; use roc_mono::code_gen_help::{CodeGenHelp, HelperOp, REFCOUNT_MAX}; @@ -11,9 +12,8 @@ use roc_mono::ir::{ BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, ModifyRc, Param, Proc, ProcLayout, Stmt, }; - -use roc_error_macros::internal_error; use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout}; +use roc_std::RocDec; use crate::layout::{CallConv, ReturnMethod, WasmLayout}; use crate::low_level::{call_higher_order_lowlevel, LowLevelCall}; @@ -833,8 +833,12 @@ impl<'a> WasmBackend<'a> { match (lit, value_type) { (Literal::Float(x), ValueType::F64) => self.code_builder.f64_const(*x as f64), (Literal::Float(x), ValueType::F32) => self.code_builder.f32_const(*x as f32), - (Literal::Int(x), ValueType::I64) => self.code_builder.i64_const(*x as i64), - (Literal::Int(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), + (Literal::Int(x), ValueType::I64) => { + self.code_builder.i64_const(i128::from_ne_bytes(*x) as i64) + } + (Literal::Int(x), ValueType::I32) => { + self.code_builder.i32_const(i128::from_ne_bytes(*x) as i32) + } (Literal::Bool(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), (Literal::Byte(x), ValueType::I32) => self.code_builder.i32_const(*x as i32), _ => invalid_error(), @@ -856,13 +860,13 @@ impl<'a> WasmBackend<'a> { }; match lit { - Literal::Decimal(decimal) => { - let (upper_bits, lower_bits) = decimal.as_bits(); + Literal::Decimal(bytes) => { + let (upper_bits, lower_bits) = RocDec::from_ne_bytes(*bytes).as_bits(); write128(lower_bits as i64, upper_bits); } Literal::Int(x) => { - let lower_bits = (*x & 0xffff_ffff_ffff_ffff) as i64; - let upper_bits = (*x >> 64) as i64; + let lower_bits = (i128::from_ne_bytes(*x) & 0xffff_ffff_ffff_ffff) as i64; + let upper_bits = (i128::from_ne_bytes(*x) >> 64) as i64; write128(lower_bits, upper_bits); } Literal::Float(_) => { diff --git a/compiler/ident/Cargo.toml b/compiler/ident/Cargo.toml index d272f4818f..70987df20e 100644 --- a/compiler/ident/Cargo.toml +++ b/compiler/ident/Cargo.toml @@ -3,4 +3,4 @@ name = "roc_ident" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" diff --git a/compiler/load/Cargo.toml b/compiler/load/Cargo.toml index f3d1d4c30f..ec3fae6f05 100644 --- a/compiler/load/Cargo.toml +++ b/compiler/load/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_load" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_load_internal = { path = "../load_internal" } diff --git a/compiler/load/build.rs b/compiler/load/build.rs index 5f2754723e..0ec18fa578 100644 --- a/compiler/load/build.rs +++ b/compiler/load/build.rs @@ -13,6 +13,8 @@ const MODULES: &[(ModuleId, &str)] = &[ (ModuleId::DICT, "Dict.roc"), (ModuleId::SET, "Set.roc"), (ModuleId::BOX, "Box.roc"), + (ModuleId::ENCODE, "Encode.roc"), + (ModuleId::JSON, "Json.roc"), ]; fn main() { diff --git a/compiler/load_internal/Cargo.toml b/compiler/load_internal/Cargo.toml index 798c462551..1e171f226b 100644 --- a/compiler/load_internal/Cargo.toml +++ b/compiler/load_internal/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_load_internal" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/load_internal/src/docs.rs b/compiler/load_internal/src/docs.rs index 55cdca9328..ba2d8ff928 100644 --- a/compiler/load_internal/src/docs.rs +++ b/compiler/load_internal/src/docs.rs @@ -2,10 +2,9 @@ use crate::docs::DocEntry::DetachedDoc; use crate::docs::TypeAnnotation::{Apply, BoundVariable, Function, NoTypeAnn, Record, TagUnion}; use crate::file::LoadedModule; use roc_can::scope::Scope; -use roc_error_macros::todo_abilities; use roc_module::ident::ModuleName; use roc_module::symbol::IdentIds; -use roc_parse::ast::{self, TypeHeader}; +use roc_parse::ast::{self, ExtractSpaces, TypeHeader}; use roc_parse::ast::{AssignedField, Def}; use roc_parse::ast::{CommentOrNewline, TypeDef, ValueDef}; use roc_region::all::Loc; @@ -62,9 +61,13 @@ pub enum TypeAnnotation { fields: Vec, extension: Box, }, + Ability { + members: Vec, + }, Wildcard, NoTypeAnn, } + #[derive(Debug, Clone)] pub enum RecordField { RecordField { @@ -80,6 +83,14 @@ pub enum RecordField { }, } +#[derive(Debug, Clone)] +pub struct AbilityMember { + pub name: String, + pub type_annotation: TypeAnnotation, + pub able_variables: Vec<(String, TypeAnnotation)>, + pub docs: Option, +} + #[derive(Debug, Clone)] pub struct Tag { pub name: String, @@ -264,7 +275,45 @@ fn generate_entry_doc<'a>( (acc, None) } - TypeDef::Ability { .. } => todo_abilities!(), + TypeDef::Ability { + header: TypeHeader { name, vars }, + members, + .. + } => { + let mut type_vars = Vec::new(); + + for var in vars.iter() { + if let Pattern::Identifier(ident_name) = var.value { + type_vars.push(ident_name.to_string()); + } + } + + let members = members + .iter() + .map(|mem| { + let extracted = mem.name.value.extract_spaces(); + let (type_annotation, able_variables) = + ability_member_type_to_docs(mem.typ.value); + + AbilityMember { + name: extracted.item.to_string(), + type_annotation, + able_variables, + docs: comments_or_new_lines_to_docs(extracted.before), + } + }) + .collect(); + + let doc_def = DocDef { + name: name.value.to_string(), + type_annotation: TypeAnnotation::Ability { members }, + type_vars, + docs: before_comments_or_new_lines.and_then(comments_or_new_lines_to_docs), + }; + acc.push(DocEntry::DocDef(doc_def)); + + (acc, None) + } }, Def::NotYetImplemented(s) => todo!("{}", s), @@ -352,6 +401,29 @@ fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) -> } } +fn ability_member_type_to_docs( + type_annotation: ast::TypeAnnotation, +) -> (TypeAnnotation, Vec<(String, TypeAnnotation)>) { + match type_annotation { + ast::TypeAnnotation::Where(ta, has_clauses) => { + let ta = type_to_docs(false, ta.value); + let has_clauses = has_clauses + .iter() + .map(|hc| { + let ast::HasClause { var, ability } = hc.value; + ( + var.value.extract_spaces().item.to_string(), + type_to_docs(false, ability.value), + ) + }) + .collect(); + + (ta, has_clauses) + } + _ => (type_to_docs(false, type_annotation), vec![]), + } +} + fn record_field_to_doc( in_func_ann: bool, field: ast::AssignedField<'_, ast::TypeAnnotation>, diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index 7414108065..4fb42dba3f 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -6,18 +6,21 @@ use crossbeam::thread; use parking_lot::Mutex; use roc_builtins::roc::module_source; use roc_builtins::std::borrow_stdlib; -use roc_can::abilities::AbilitiesStore; +use roc_can::abilities::{AbilitiesStore, SolvedSpecializations}; use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; use roc_can::expr::Declarations; +use roc_can::expr::PendingDerives; use roc_can::module::{canonicalize_module_defs, Module}; -use roc_collections::{default_hasher, BumpMap, MutMap, MutSet, VecSet}; +use roc_collections::{default_hasher, BumpMap, MutMap, MutSet, VecMap, VecSet}; use roc_constrain::module::{ constrain_builtin_imports, constrain_module, ExposedByModule, ExposedForModule, ExposedModuleTypes, }; +use roc_debug_flags::dbg_do; +#[cfg(debug_assertions)] use roc_debug_flags::{ - dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, - ROC_PRINT_IR_AFTER_SPECIALIZATION, ROC_PRINT_LOAD_LOG, + ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, ROC_PRINT_IR_AFTER_SPECIALIZATION, + ROC_PRINT_LOAD_LOG, }; use roc_error_macros::internal_error; use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; @@ -125,6 +128,7 @@ struct ModuleCache<'a> { headers: MutMap>, parsed: MutMap>, aliases: MutMap>, + abilities: MutMap, constrained: MutMap, typechecked: MutMap>, found_specializations: MutMap>, @@ -136,7 +140,6 @@ struct ModuleCache<'a> { documentation: MutMap, can_problems: MutMap>, type_problems: MutMap>, - mono_problems: MutMap>, sources: MutMap, } @@ -145,51 +148,34 @@ impl Default for ModuleCache<'_> { fn default() -> Self { let mut module_names = MutMap::default(); - module_names.insert( - ModuleId::RESULT, - PQModuleName::Unqualified(ModuleName::from(ModuleName::RESULT)), - ); + macro_rules! insert_builtins { + ($($name:ident,)*) => {$( + module_names.insert( + ModuleId::$name, + PQModuleName::Unqualified(ModuleName::from(ModuleName::$name)), + ); + )*} + } - module_names.insert( - ModuleId::LIST, - PQModuleName::Unqualified(ModuleName::from(ModuleName::LIST)), - ); - - module_names.insert( - ModuleId::STR, - PQModuleName::Unqualified(ModuleName::from(ModuleName::STR)), - ); - - module_names.insert( - ModuleId::DICT, - PQModuleName::Unqualified(ModuleName::from(ModuleName::DICT)), - ); - - module_names.insert( - ModuleId::SET, - PQModuleName::Unqualified(ModuleName::from(ModuleName::SET)), - ); - - module_names.insert( - ModuleId::BOOL, - PQModuleName::Unqualified(ModuleName::from(ModuleName::BOOL)), - ); - - module_names.insert( - ModuleId::NUM, - PQModuleName::Unqualified(ModuleName::from(ModuleName::NUM)), - ); - - module_names.insert( - ModuleId::BOX, - PQModuleName::Unqualified(ModuleName::from(ModuleName::BOX)), - ); + insert_builtins! { + RESULT, + LIST, + STR, + DICT, + SET, + BOOL, + NUM, + BOX, + ENCODE, + JSON, + } Self { module_names, headers: Default::default(), parsed: Default::default(), aliases: Default::default(), + abilities: Default::default(), constrained: Default::default(), typechecked: Default::default(), found_specializations: Default::default(), @@ -199,7 +185,6 @@ impl Default for ModuleCache<'_> { documentation: Default::default(), can_problems: Default::default(), type_problems: Default::default(), - mono_problems: Default::default(), sources: Default::default(), } } @@ -305,10 +290,12 @@ fn start_phase<'a>( let exposed_symbols = state .exposed_symbols_by_module - .remove(&module_id) - .expect("Could not find listener ID in exposed_symbols_by_module"); + .get(&module_id) + .expect("Could not find listener ID in exposed_symbols_by_module") + .clone(); let mut aliases = MutMap::default(); + let mut abilities_store = AbilitiesStore::default(); for imported in parsed.imported_modules.keys() { match state.module_cache.aliases.get(imported) { @@ -327,6 +314,27 @@ fn start_phase<'a>( })); } } + + match state.module_cache.abilities.get(imported) { + None => unreachable!( + r"imported module {:?} did not register its abilities, so {:?} cannot use them", + imported, parsed.module_id, + ), + Some(import_store) => { + let exposed_symbols = state + .exposed_symbols_by_module + .get(imported) + .unwrap_or_else(|| { + internal_error!( + "Could not find exposed symbols of imported {:?}", + imported + ) + }); + + abilities_store + .union(import_store.closure_from_imported(exposed_symbols)); + } + } } let skip_constraint_gen = { @@ -341,6 +349,7 @@ fn start_phase<'a>( exposed_symbols, module_ids, aliases, + abilities_store, skip_constraint_gen, } } @@ -358,6 +367,7 @@ fn start_phase<'a>( imported_modules, declarations, dep_idents, + pending_derives, .. } = constrained; @@ -367,9 +377,10 @@ fn start_phase<'a>( module_timing, constraints, constraint, + pending_derives, var_store, imported_modules, - &mut state.exposed_types, + &state.exposed_types, dep_idents, declarations, state.cached_subs.clone(), @@ -528,6 +539,9 @@ struct ConstrainedModule { var_store: VarStore, dep_idents: IdentIdsByModule, module_timing: ModuleTiming, + // Rather than adding pending derives as constraints, hand them directly to solve because they + // must be solved at the end of a module. + pending_derives: PendingDerives, } #[derive(Debug)] @@ -639,7 +653,6 @@ enum Msg<'a> { ident_ids: IdentIds, layout_cache: LayoutCache<'a>, procs_base: ProcsBase<'a>, - problems: Vec, solved_subs: Solved, module_timing: ModuleTiming, abilities_store: AbilitiesStore, @@ -650,7 +663,6 @@ enum Msg<'a> { layout_cache: LayoutCache<'a>, external_specializations_requested: BumpMap, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, - problems: Vec, update_mode_ids: UpdateModeIds, module_timing: ModuleTiming, subs: Subs, @@ -868,6 +880,7 @@ enum BuildTask<'a> { dep_idents: IdentIdsByModule, exposed_symbols: VecSet, aliases: MutMap, + abilities_store: AbilitiesStore, skip_constraint_gen: bool, }, Solve { @@ -878,6 +891,7 @@ enum BuildTask<'a> { module_timing: ModuleTiming, constraints: Constraints, constraint: ConstraintSoa, + pending_derives: PendingDerives, var_store: VarStore, declarations: Declarations, dep_idents: IdentIdsByModule, @@ -2030,6 +2044,11 @@ fn update<'a>( .aliases .insert(module_id, constrained_module.module.aliases.clone()); + state + .module_cache + .abilities + .insert(module_id, constrained_module.module.abilities_store.clone()); + state .module_cache .constrained @@ -2075,7 +2094,13 @@ fn update<'a>( solved_module .exposed_vars_by_symbol .iter() - .map(|(k, v)| (*k, *v)), + .filter_map(|(k, v)| { + if abilities_store.is_specialization_name(*k) { + None + } else { + Some((*k, *v)) + } + }), ); state @@ -2118,9 +2143,10 @@ fn update<'a>( } else { state.exposed_types.insert( module_id, - ExposedModuleTypes::Valid { + ExposedModuleTypes { stored_vars_by_symbol: solved_module.stored_vars_by_symbol, storage_subs: solved_module.storage_subs, + solved_specializations: solved_module.solved_specializations, }, ); @@ -2160,14 +2186,11 @@ fn update<'a>( solved_subs, ident_ids, layout_cache, - problems, module_timing, abilities_store, } => { log!("found specializations for {:?}", module_id); - debug_assert!(problems.is_empty()); - let subs = solved_subs.into_inner(); state @@ -2207,7 +2230,6 @@ fn update<'a>( subs, procedures, external_specializations_requested, - problems, module_timing, layout_cache, .. @@ -2217,8 +2239,6 @@ fn update<'a>( // in the future, layouts will be in SoA form and we'll want to hold on to this data let _ = layout_cache; - state.module_cache.mono_problems.insert(module_id, problems); - state.procedures.extend(procedures); state.timings.insert(module_id, module_timing); @@ -2652,90 +2672,37 @@ fn load_module<'a>( module_timing.read_roc_file = Default::default(); module_timing.parse_header = parse_header_duration; - match module_name.as_inner().as_str() { - "Result" => { - return Ok(load_builtin_module( - arena, - module_ids, - ident_ids_by_module, - module_timing, - ModuleId::RESULT, - "Result.roc", - )); - } - "List" => { - return Ok(load_builtin_module( - arena, - module_ids, - ident_ids_by_module, - module_timing, - ModuleId::LIST, - "List.roc", - )); - } - "Str" => { - return Ok(load_builtin_module( - arena, - module_ids, - ident_ids_by_module, - module_timing, - ModuleId::STR, - "Str.roc", - )); - } - "Dict" => { - return Ok(load_builtin_module( - arena, - module_ids, - ident_ids_by_module, - module_timing, - ModuleId::DICT, - "Dict.roc", - )); - } - "Set" => { - return Ok(load_builtin_module( - arena, - module_ids, - ident_ids_by_module, - module_timing, - ModuleId::SET, - "Set.roc", - )); - } - "Num" => { - return Ok(load_builtin_module( - arena, - module_ids, - ident_ids_by_module, - module_timing, - ModuleId::NUM, - "Num.roc", - )); - } - "Bool" => { - return Ok(load_builtin_module( - arena, - module_ids, - ident_ids_by_module, - module_timing, - ModuleId::BOOL, - "Bool.roc", - )); - } - "Box" => { - return Ok(load_builtin_module( - arena, - module_ids, - ident_ids_by_module, - module_timing, - ModuleId::BOX, - "Box.roc", - )); - } - _ => { - // fall through - } + + macro_rules! load_builtins { + ($($name:literal, $module_id:path)*) => { + match module_name.as_inner().as_str() { + $( + $name => { + return Ok(load_builtin_module( + arena, + module_ids, + ident_ids_by_module, + module_timing, + $module_id, + concat!($name, ".roc") + )); + } + )* + _ => { /* fall through */ } + }} + } + + load_builtins! { + "Result", ModuleId::RESULT + "List", ModuleId::LIST + "Str", ModuleId::STR + "Dict", ModuleId::DICT + "Set", ModuleId::SET + "Num", ModuleId::NUM + "Bool", ModuleId::BOOL + "Box", ModuleId::BOX + "Encode", ModuleId::ENCODE + "Json", ModuleId::JSON } let (filename, opt_shorthand) = module_name_to_path(src_dir, module_name, arc_shorthands); @@ -3535,19 +3502,36 @@ impl<'a> BuildTask<'a> { // TODO trim down these arguments - possibly by moving Constraint into Module #[allow(clippy::too_many_arguments)] fn solve_module( - module: Module, + mut module: Module, ident_ids: IdentIds, module_timing: ModuleTiming, constraints: Constraints, constraint: ConstraintSoa, + pending_derives: PendingDerives, var_store: VarStore, imported_modules: MutMap, - exposed_types: &mut ExposedByModule, + exposed_types: &ExposedByModule, dep_idents: IdentIdsByModule, declarations: Declarations, cached_subs: CachedSubs, ) -> Self { let exposed_by_module = exposed_types.retain_modules(imported_modules.keys()); + + let abilities_store = &mut module.abilities_store; + + for module in imported_modules.keys() { + let exposed = exposed_by_module + .get(module) + .unwrap_or_else(|| internal_error!("No exposed types for {:?}", module)); + let ExposedModuleTypes { + solved_specializations, + .. + } = exposed; + for ((member, typ), specialization) in solved_specializations.iter() { + abilities_store.register_specialization_for_type(*member, *typ, *specialization); + } + } + let exposed_for_module = ExposedForModule::new(module.referenced_values.iter(), exposed_by_module); @@ -3566,6 +3550,7 @@ impl<'a> BuildTask<'a> { exposed_for_module, constraints, constraint, + pending_derives, var_store, declarations, dep_idents, @@ -3577,6 +3562,7 @@ impl<'a> BuildTask<'a> { fn add_imports( subs: &mut Subs, + abilities_store: &mut AbilitiesStore, mut exposed_for_module: ExposedForModule, def_types: &mut Vec<(Symbol, Loc)>, rigid_vars: &mut Vec, @@ -3586,51 +3572,44 @@ fn add_imports( for symbol in exposed_for_module.imported_values { let module_id = symbol.module_id(); match exposed_for_module.exposed_by_module.get_mut(&module_id) { - Some(t) => match t { - ExposedModuleTypes::Invalid => { - // make the type a flex var, so it unifies with anything - // this way the error is only reported in the module it originates in - let variable = subs.fresh_unnamed_flex_var(); + Some(ExposedModuleTypes { + stored_vars_by_symbol, + storage_subs, + solved_specializations: _, + }) => { + let variable = match stored_vars_by_symbol.iter().find(|(s, _)| *s == symbol) { + None => { + // Today we define builtins in each module that uses them + // so even though they have a different module name from + // the surrounding module, they are not technically imported + debug_assert!(symbol.is_builtin()); + continue; + } + Some((_, x)) => *x, + }; - def_types.push(( - symbol, - Loc::at_zero(roc_types::types::Type::Variable(variable)), - )); + let copied_import = storage_subs.export_variable_to(subs, variable); + + def_types.push(( + symbol, + Loc::at_zero(roc_types::types::Type::Variable(copied_import.variable)), + )); + + // not a typo; rigids are turned into flex during type inference, but when imported we must + // consider them rigid variables + rigid_vars.extend(copied_import.rigid); + rigid_vars.extend(copied_import.flex); + + // Rigid vars bound to abilities are also treated like rigids. + rigid_vars.extend(copied_import.rigid_able); + rigid_vars.extend(copied_import.flex_able); + + import_variables.extend(copied_import.registered); + + if abilities_store.is_ability_member_name(symbol) { + abilities_store.resolved_imported_member_var(symbol, copied_import.variable); } - ExposedModuleTypes::Valid { - stored_vars_by_symbol, - storage_subs, - } => { - let variable = match stored_vars_by_symbol.iter().find(|(s, _)| *s == symbol) { - None => { - // Today we define builtins in each module that uses them - // so even though they have a different module name from - // the surrounding module, they are not technically imported - debug_assert!(symbol.is_builtin()); - continue; - } - Some((_, x)) => *x, - }; - - let copied_import = storage_subs.export_variable_to(subs, variable); - - // not a typo; rigids are turned into flex during type inference, but when imported we must - // consider them rigid variables - rigid_vars.extend(copied_import.rigid); - rigid_vars.extend(copied_import.flex); - - // Rigid vars bound to abilities are also treated like rigids. - rigid_vars.extend(copied_import.rigid_able); - rigid_vars.extend(copied_import.flex_able); - - import_variables.extend(copied_import.registered); - - def_types.push(( - symbol, - Loc::at_zero(roc_types::types::Type::Variable(copied_import.variable)), - )); - } - }, + } None => { internal_error!("Imported module {:?} is not available", module_id) } @@ -3646,10 +3625,12 @@ fn run_solve_solve( exposed_for_module: ExposedForModule, mut constraints: Constraints, constraint: ConstraintSoa, + pending_derives: PendingDerives, mut var_store: VarStore, module: Module, ) -> ( Solved, + SolvedSpecializations, Vec<(Symbol, Variable)>, Vec, AbilitiesStore, @@ -3658,7 +3639,7 @@ fn run_solve_solve( exposed_symbols, aliases, rigid_variables, - abilities_store, + mut abilities_store, .. } = module; @@ -3669,6 +3650,7 @@ fn run_solve_solve( let import_variables = add_imports( &mut subs, + &mut abilities_store, exposed_for_module, &mut def_types, &mut rigid_vars, @@ -3683,7 +3665,7 @@ fn run_solve_solve( solve_aliases.insert(*name, alias.clone()); } - let (solved_subs, exposed_vars_by_symbol, problems, abilities_store) = { + let (solved_subs, solved_specializations, exposed_vars_by_symbol, problems, abilities_store) = { let (solved_subs, solved_env, problems, abilities_store) = roc_solve::module::run_solve( &constraints, actual_constraint, @@ -3691,15 +3673,33 @@ fn run_solve_solve( subs, solve_aliases, abilities_store, + pending_derives, ); + let module_id = module.module_id; + // Figure out what specializations belong to this module + let solved_specializations: SolvedSpecializations = abilities_store + .iter_specializations() + .filter(|((member, typ), _)| { + // This module solved this specialization if either the member or the type comes from the + // module. + member.module_id() == module_id || typ.module_id() == module_id + }) + .collect(); + + let is_specialization_symbol = + |sym| solved_specializations.values().any(|ms| ms.symbol == sym); + + // Expose anything that is explicitly exposed by the header, or is a specialization of an + // ability. let exposed_vars_by_symbol: Vec<_> = solved_env .vars_by_symbol() - .filter(|(k, _)| exposed_symbols.contains(k)) + .filter(|(k, _)| exposed_symbols.contains(k) || is_specialization_symbol(*k)) .collect(); ( solved_subs, + solved_specializations, exposed_vars_by_symbol, problems, abilities_store, @@ -3708,6 +3708,7 @@ fn run_solve_solve( ( solved_subs, + solved_specializations, exposed_vars_by_symbol, problems, abilities_store, @@ -3723,6 +3724,7 @@ fn run_solve<'a>( exposed_for_module: ExposedForModule, constraints: Constraints, constraint: ConstraintSoa, + pending_derives: PendingDerives, var_store: VarStore, decls: Declarations, dep_idents: IdentIdsByModule, @@ -3735,7 +3737,7 @@ fn run_solve<'a>( // TODO remove when we write builtins in roc let aliases = module.aliases.clone(); - let (solved_subs, exposed_vars_by_symbol, problems, abilities_store) = { + let (solved_subs, solved_specializations, exposed_vars_by_symbol, problems, abilities_store) = { if module_id.is_builtin() { match cached_subs.lock().remove(&module_id) { None => run_solve_solve( @@ -3743,15 +3745,18 @@ fn run_solve<'a>( exposed_for_module, constraints, constraint, + pending_derives, var_store, module, ), Some((subs, exposed_vars_by_symbol)) => { ( Solved(subs), + // TODO(abilities) cache abilities for builtins + VecMap::default(), exposed_vars_by_symbol.to_vec(), vec![], - // TODO(abilities) replace when we have abilities for builtins + // TODO(abilities) cache abilities for builtins AbilitiesStore::default(), ) } @@ -3762,6 +3767,7 @@ fn run_solve<'a>( exposed_for_module, constraints, constraint, + pending_derives, var_store, module, ) @@ -3777,6 +3783,7 @@ fn run_solve<'a>( problems, aliases, stored_vars_by_symbol, + solved_specializations, storage_subs, }; @@ -3855,6 +3862,7 @@ fn canonicalize_and_constrain<'a>( dep_idents: IdentIdsByModule, exposed_symbols: VecSet, aliases: MutMap, + imported_abilities_state: AbilitiesStore, parsed: ParsedModule<'a>, skip_constraint_gen: bool, ) -> CanAndCon { @@ -3873,7 +3881,8 @@ fn canonicalize_and_constrain<'a>( .. } = parsed; - let before = roc_types::types::get_type_clone_count(); + // _before has an underscore because it's unused in --release builds + let _before = roc_types::types::get_type_clone_count(); let mut var_store = VarStore::default(); let module_output = canonicalize_module_defs( @@ -3885,20 +3894,22 @@ fn canonicalize_and_constrain<'a>( exposed_ident_ids, &dep_idents, aliases, + imported_abilities_state, exposed_imports, &exposed_symbols, &symbols_from_requires, &mut var_store, ); - let after = roc_types::types::get_type_clone_count(); + // _after has an underscore because it's unused in --release builds + let _after = roc_types::types::get_type_clone_count(); log!( "canonicalize of {:?} cloned Type {} times ({} -> {})", module_id, - after - before, - before, - after + _after - _before, + _before, + _after ); let canonicalize_end = SystemTime::now(); @@ -3921,7 +3932,8 @@ fn canonicalize_and_constrain<'a>( } }; - let before = roc_types::types::get_type_clone_count(); + // _before has an underscore because it's unused in --release builds + let _before = roc_types::types::get_type_clone_count(); let mut constraints = Constraints::new(); @@ -3937,14 +3949,15 @@ fn canonicalize_and_constrain<'a>( ) }; - let after = roc_types::types::get_type_clone_count(); + // _after has an underscore because it's unused in --release builds + let _after = roc_types::types::get_type_clone_count(); log!( "constraint gen of {:?} cloned Type {} times ({} -> {})", module_id, - after - before, - before, - after + _after - _before, + _before, + _after ); // scope has imported aliases, but misses aliases from inner scopes @@ -3960,7 +3973,7 @@ fn canonicalize_and_constrain<'a>( // do nothing } Vacant(vacant) => { - if !name.is_builtin() { + if !name.is_builtin() || name.module_id() == ModuleId::ENCODE { vacant.insert((false, alias)); } } @@ -3988,6 +4001,7 @@ fn canonicalize_and_constrain<'a>( ident_ids: module_output.scope.locals.ident_ids, dep_idents, module_timing, + pending_derives: module_output.pending_derives, }; CanAndCon { @@ -4110,12 +4124,10 @@ fn make_specializations<'a>( mut abilities_store: AbilitiesStore, ) -> Msg<'a> { let make_specializations_start = SystemTime::now(); - let mut mono_problems = Vec::new(); let mut update_mode_ids = UpdateModeIds::new(); // do the thing let mut mono_env = roc_mono::ir::Env { arena, - problems: &mut mono_problems, subs: &mut subs, home, ident_ids: &mut ident_ids, @@ -4163,7 +4175,6 @@ fn make_specializations<'a>( ident_ids, layout_cache, procedures, - problems: mono_problems, update_mode_ids, subs, external_specializations_requested, @@ -4208,12 +4219,10 @@ fn build_pending_specializations<'a>( imported_module_thunks, }; - let mut mono_problems = std::vec::Vec::new(); let mut update_mode_ids = UpdateModeIds::new(); let mut subs = solved_subs.into_inner(); let mut mono_env = roc_mono::ir::Env { arena, - problems: &mut mono_problems, subs: &mut subs, home, ident_ids: &mut ident_ids, @@ -4440,8 +4449,6 @@ fn build_pending_specializations<'a>( procs_base.module_thunks = module_thunks.into_bump_slice(); - let problems = mono_env.problems.to_vec(); - let find_specializations_end = SystemTime::now(); module_timing.find_specializations = find_specializations_end .duration_since(find_specializations_start) @@ -4453,7 +4460,6 @@ fn build_pending_specializations<'a>( ident_ids, layout_cache, procs_base, - problems, module_timing, abilities_store, } @@ -4490,6 +4496,7 @@ fn run_task<'a>( dep_idents, exposed_symbols, aliases, + abilities_store, skip_constraint_gen, } => { let can_and_con = canonicalize_and_constrain( @@ -4498,6 +4505,7 @@ fn run_task<'a>( dep_idents, exposed_symbols, aliases, + abilities_store, parsed, skip_constraint_gen, ); @@ -4511,6 +4519,7 @@ fn run_task<'a>( exposed_for_module, constraints, constraint, + pending_derives, var_store, ident_ids, declarations, @@ -4524,6 +4533,7 @@ fn run_task<'a>( exposed_for_module, constraints, constraint, + pending_derives, var_store, declarations, dep_idents, diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index 01913ff8a1..b1f397fef3 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -856,7 +856,7 @@ mod test_load { " ── UNRECOGNIZED NAME ────────── tmp/issue_2863_module_type_does_not_exist/Main ─ - I cannot find a `DoesNotExist` value + Nothing is named `DoesNotExist` in this scope. 5│ main : DoesNotExist ^^^^^^^^^^^^ diff --git a/compiler/module/Cargo.toml b/compiler/module/Cargo.toml index 9b8c272d54..26db73eeb1 100644 --- a/compiler/module/Cargo.toml +++ b/compiler/module/Cargo.toml @@ -2,7 +2,7 @@ name = "roc_module" version = "0.1.0" authors = ["The Roc Contributors"] -edition = "2018" +edition = "2021" license = "UPL-1.0" [dependencies] diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 56adb96189..783af558cb 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -89,6 +89,8 @@ impl ModuleName { pub const SET: &'static str = "Set"; pub const RESULT: &'static str = "Result"; pub const BOX: &'static str = "Box"; + pub const ENCODE: &'static str = "Encode"; + pub const JSON: &'static str = "Json"; pub fn as_str(&self) -> &str { self.0.as_str() diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 8a3516f632..6e642b41bd 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -48,6 +48,8 @@ const SYMBOL_HAS_NICHE: () = #[cfg(debug_assertions)] const PRETTY_PRINT_DEBUG_SYMBOLS: bool = true; +pub const BUILTIN_ABILITIES: &[Symbol] = &[Symbol::ENCODE_ENCODING]; + /// In Debug builds only, Symbol has a name() method that lets /// you look up its name in a global intern table. This table is /// behind a mutex, so it is neither populated nor available in release builds. @@ -85,6 +87,10 @@ impl Symbol { self.module_id().is_builtin() } + pub fn is_builtin_ability(self) -> bool { + BUILTIN_ABILITIES.contains(&self) + } + pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a ModuleName { interns .module_ids @@ -1309,8 +1315,36 @@ define_builtins! { 0 BOX_BOX_TYPE: "Box" imported // the Box.Box opaque type 1 BOX_BOX_FUNCTION: "box" // Box.box 2 BOX_UNBOX: "unbox" - + } + 9 ENCODE: "Encode" => { + 0 ENCODE_ENCODER: "Encoder" + 1 ENCODE_ENCODING: "Encoding" + 2 ENCODE_TO_ENCODER: "toEncoder" + 3 ENCODE_ENCODERFORMATTING: "EncoderFormatting" + 4 ENCODE_U8: "u8" + 5 ENCODE_U16: "u16" + 6 ENCODE_U32: "u32" + 7 ENCODE_U64: "u64" + 8 ENCODE_U128: "u128" + 9 ENCODE_I8: "i8" + 10 ENCODE_I16: "i16" + 11 ENCODE_I32: "i32" + 12 ENCODE_I64: "i64" + 13 ENCODE_I128: "i128" + 14 ENCODE_F32: "f32" + 15 ENCODE_F64: "f64" + 16 ENCODE_DEC: "dec" + 17 ENCODE_BOOL: "bool" + 18 ENCODE_STRING: "string" + 19 ENCODE_LIST: "list" + 20 ENCODE_CUSTOM: "custom" + 21 ENCODE_APPEND_WITH: "appendWith" + 22 ENCODE_APPEND: "append" + 23 ENCODE_TO_BYTES: "toBytes" + } + 10 JSON: "Json" => { + 0 JSON_JSON: "Json" } - num_modules: 9 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro) + num_modules: 11 // Keep this count up to date by hand! (TODO: see the mut_map! macro for how we could determine this count correctly in the macro) } diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index ff32e8e1e9..39ef08944c 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_mono" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/mono/src/code_gen_help/equality.rs b/compiler/mono/src/code_gen_help/equality.rs index c11f8c421a..374daeb6d2 100644 --- a/compiler/mono/src/code_gen_help/equality.rs +++ b/compiler/mono/src/code_gen_help/equality.rs @@ -45,11 +45,11 @@ pub fn eq_generic<'a>( Stmt::Let( Symbol::BOOL_TRUE, - Expr::Literal(Literal::Int(1)), + Expr::Literal(Literal::Int(1i128.to_ne_bytes())), LAYOUT_BOOL, root.arena.alloc(Stmt::Let( Symbol::BOOL_FALSE, - Expr::Literal(Literal::Int(0)), + Expr::Literal(Literal::Int(0i128.to_ne_bytes())), LAYOUT_BOOL, root.arena.alloc(main_body), )), @@ -601,7 +601,7 @@ fn eq_list<'a>( // let size = literal int let size = root.create_symbol(ident_ids, "size"); let size_expr = Expr::Literal(Literal::Int( - elem_layout.stack_size(root.target_info) as i128 + (elem_layout.stack_size(root.target_info) as i128).to_ne_bytes(), )); let size_stmt = |next| Stmt::Let(size, size_expr, layout_isize, next); diff --git a/compiler/mono/src/code_gen_help/refcount.rs b/compiler/mono/src/code_gen_help/refcount.rs index 176f875d41..e3a6282ce0 100644 --- a/compiler/mono/src/code_gen_help/refcount.rs +++ b/compiler/mono/src/code_gen_help/refcount.rs @@ -35,7 +35,7 @@ pub fn refcount_stmt<'a>( // Define a constant for the amount to increment let amount_sym = root.create_symbol(ident_ids, "amount"); - let amount_expr = Expr::Literal(Literal::Int(*amount as i128)); + let amount_expr = Expr::Literal(Literal::Int((*amount as i128).to_ne_bytes())); let amount_stmt = |next| Stmt::Let(amount_sym, amount_expr, layout_isize, next); // Call helper proc, passing the Roc structure and constant amount @@ -230,7 +230,7 @@ pub fn refcount_reset_proc_body<'a>( // Zero let zero = root.create_symbol(ident_ids, "zero"); - let zero_expr = Expr::Literal(Literal::Int(0)); + let zero_expr = Expr::Literal(Literal::Int(0i128.to_ne_bytes())); let zero_stmt = |next| Stmt::Let(zero, zero_expr, root.layout_isize, next); // Null pointer with union layout @@ -274,7 +274,8 @@ pub fn refcount_reset_proc_body<'a>( let refcount_1_encoded = match root.target_info.ptr_width() { PtrWidth::Bytes4 => i32::MIN as i128, PtrWidth::Bytes8 => i64::MIN as i128, - }; + } + .to_ne_bytes(); let refcount_1_expr = Expr::Literal(Literal::Int(refcount_1_encoded)); let refcount_1_stmt = Stmt::Let( refcount_1, @@ -381,6 +382,8 @@ pub fn rc_ptr_from_data_ptr<'a>( mask_lower_bits: bool, following: &'a Stmt<'a>, ) -> Stmt<'a> { + use std::ops::Neg; + // Typecast the structure pointer to an integer // Backends expect a number Layout to choose the right "subtract" instruction let addr_sym = root.create_symbol(ident_ids, "addr"); @@ -395,7 +398,9 @@ pub fn rc_ptr_from_data_ptr<'a>( // Mask for lower bits (for tag union id) let mask_sym = root.create_symbol(ident_ids, "mask"); - let mask_expr = Expr::Literal(Literal::Int(-(root.target_info.ptr_width() as i128))); + let mask_expr = Expr::Literal(Literal::Int( + (root.target_info.ptr_width() as i128).neg().to_ne_bytes(), + )); let mask_stmt = |next| Stmt::Let(mask_sym, mask_expr, root.layout_isize, next); let masked_sym = root.create_symbol(ident_ids, "masked"); @@ -410,7 +415,9 @@ pub fn rc_ptr_from_data_ptr<'a>( // Pointer size constant let ptr_size_sym = root.create_symbol(ident_ids, "ptr_size"); - let ptr_size_expr = Expr::Literal(Literal::Int(root.target_info.ptr_width() as i128)); + let ptr_size_expr = Expr::Literal(Literal::Int( + (root.target_info.ptr_width() as i128).to_ne_bytes(), + )); let ptr_size_stmt = |next| Stmt::Let(ptr_size_sym, ptr_size_expr, root.layout_isize, next); // Refcount address @@ -502,7 +509,7 @@ fn modify_refcount<'a>( HelperOp::Dec | HelperOp::DecRef(_) => { let alignment_sym = root.create_symbol(ident_ids, "alignment"); - let alignment_expr = Expr::Literal(Literal::Int(alignment as i128)); + let alignment_expr = Expr::Literal(Literal::Int((alignment as i128).to_ne_bytes())); let alignment_stmt = |next| Stmt::Let(alignment_sym, alignment_expr, LAYOUT_U32, next); let zig_call_expr = Expr::Call(Call { @@ -545,7 +552,7 @@ fn refcount_str<'a>( // Zero let zero = root.create_symbol(ident_ids, "zero"); - let zero_expr = Expr::Literal(Literal::Int(0)); + let zero_expr = Expr::Literal(Literal::Int(0i128.to_ne_bytes())); let zero_stmt = |next| Stmt::Let(zero, zero_expr, layout_isize, next); // is_big_str = (last_word >= 0); @@ -647,7 +654,7 @@ fn refcount_list<'a>( // Zero let zero = root.create_symbol(ident_ids, "zero"); - let zero_expr = Expr::Literal(Literal::Int(0)); + let zero_expr = Expr::Literal(Literal::Int(0i128.to_ne_bytes())); let zero_stmt = |next| Stmt::Let(zero, zero_expr, layout_isize, next); // let is_empty = lowlevel Eq len zero @@ -774,7 +781,7 @@ fn refcount_list_elems<'a>( // let size = literal int let elem_size = root.create_symbol(ident_ids, "elem_size"); let elem_size_expr = Expr::Literal(Literal::Int( - elem_layout.stack_size(root.target_info) as i128 + (elem_layout.stack_size(root.target_info) as i128).to_ne_bytes(), )); let elem_size_stmt = |next| Stmt::Let(elem_size, elem_size_expr, layout_isize, next); @@ -1337,7 +1344,7 @@ fn refcount_union_tailrec<'a>( (filtered.into_bump_slice(), tail_stmt.unwrap()) } else { let zero = root.create_symbol(ident_ids, "zero"); - let zero_expr = Expr::Literal(Literal::Int(0)); + let zero_expr = Expr::Literal(Literal::Int(0i128.to_ne_bytes())); let zero_stmt = |next| Stmt::Let(zero, zero_expr, root.layout_isize, next); let null = root.create_symbol(ident_ids, "null"); diff --git a/compiler/mono/src/copy.rs b/compiler/mono/src/copy.rs index 35c94df620..0a53657554 100644 --- a/compiler/mono/src/copy.rs +++ b/compiler/mono/src/copy.rs @@ -366,6 +366,8 @@ pub fn deep_copy_type_vars_into_expr<'a>( Expect(e1, e2) => Expect(Box::new(e1.map(go_help)), Box::new(e2.map(go_help))), + TypedHole(v) => TypedHole(sub!(*v)), + RuntimeError(err) => RuntimeError(err.clone()), } } @@ -389,14 +391,14 @@ fn deep_copy_type_vars<'a>( // in one go (without looking at the UnificationTable) and clear the copy field let mut result = Vec::with_capacity_in(copied.len(), arena); for var in copied { - let descriptor = subs.get_ref_mut(var); - - if let Some(copy) = descriptor.copy.into_variable() { - result.push((var, copy)); - descriptor.copy = OptVariable::NONE; - } else { - debug_assert!(false, "{:?} marked as copied but it wasn't", var); - } + subs.modify(var, |descriptor| { + if let Some(copy) = descriptor.copy.into_variable() { + result.push((var, copy)); + descriptor.copy = OptVariable::NONE; + } else { + debug_assert!(false, "{:?} marked as copied but it wasn't", var); + } + }) } debug_assert!(result.contains(&(var, cloned_var))); @@ -411,7 +413,7 @@ fn deep_copy_type_vars<'a>( // Always deal with the root, so that unified variables are treated the same. let var = subs.get_root_key_without_compacting(var); - let desc = subs.get_ref_mut(var); + let desc = subs.get(var); // Unlike `deep_copy_var` in solve, here we are cloning *all* flex and rigid vars. // So we only want to short-circuit if we've already done the cloning work for a particular @@ -430,7 +432,7 @@ fn deep_copy_type_vars<'a>( }; let copy = subs.fresh(copy_descriptor); - subs.get_ref_mut(var).copy = copy.into(); + subs.set_copy(var, copy.into()); visited.push(var); @@ -454,7 +456,7 @@ fn deep_copy_type_vars<'a>( let new_arguments = VariableSubsSlice::reserve_into_subs(subs, $slice.len()); for (target_index, var_index) in (new_arguments.indices()).zip($slice) { let var = subs[var_index]; - let copy_var = subs.get_ref(var).copy.into_variable().unwrap_or(var); + let copy_var = subs.get_copy(var).into_variable().unwrap_or(var); subs.variables[target_index] = copy_var; } new_arguments @@ -609,15 +611,10 @@ fn deep_copy_type_vars<'a>( }) } - RangedNumber(typ, range_vars) => { + RangedNumber(typ, range) => { let new_typ = descend_var!(typ); - descend_slice!(range_vars); - perform_clone!({ - let new_range_vars = clone_var_slice!(range_vars); - - RangedNumber(new_typ, new_range_vars) - }) + perform_clone!(RangedNumber(new_typ, range)) } Error => Error, }; diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index 8e3f3816b8..d1023a3758 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -8,7 +8,6 @@ use roc_exhaustive::{Ctor, CtorName, RenderAs, TagId, Union}; use roc_module::ident::TagName; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; -use roc_std::RocDec; /// COMPILE CASES @@ -86,10 +85,9 @@ enum Test<'a> { union: roc_exhaustive::Union, arguments: Vec<(Pattern<'a>, Layout<'a>)>, }, - IsInt(i128, IntWidth), - IsU128(u128), + IsInt([u8; 16], IntWidth), IsFloat(u64, FloatWidth), - IsDecimal(RocDec), + IsDecimal([u8; 16]), IsStr(Box), IsBit(bool), IsByte { @@ -137,10 +135,6 @@ impl<'a> Hash for Test<'a> { state.write_u8(6); v.hash(state); } - IsU128(v) => { - state.write_u8(7); - v.hash(state); - } } } } @@ -316,7 +310,6 @@ fn tests_are_complete_help(last_test: &Test, number_of_tests: usize) -> bool { Test::IsByte { num_alts, .. } => number_of_tests == *num_alts, Test::IsBit(_) => number_of_tests == 2, Test::IsInt(_, _) => false, - Test::IsU128(_) => false, Test::IsFloat(_, _) => false, Test::IsDecimal(_) => false, Test::IsStr(_) => false, @@ -591,7 +584,6 @@ fn test_at_path<'a>( num_alts: union.alternatives.len(), }, IntLiteral(v, precision) => IsInt(*v, *precision), - U128Literal(v) => IsU128(*v), FloatLiteral(v, precision) => IsFloat(*v, *precision), DecimalLiteral(v) => IsDecimal(*v), StrLiteral(v) => IsStr(v.clone()), @@ -881,18 +873,6 @@ fn to_relevant_branch_help<'a>( _ => None, }, - U128Literal(int) => match test { - IsU128(is_int) if int == *is_int => { - start.extend(end); - Some(Branch { - goal: branch.goal, - guard: branch.guard.clone(), - patterns: start, - }) - } - _ => None, - }, - FloatLiteral(float, p1) => match test { IsFloat(test_float, p2) if float == *test_float => { debug_assert_eq!(p1, *p2); @@ -1005,7 +985,6 @@ fn needs_tests(pattern: &Pattern) -> bool { | BitLiteral { .. } | EnumLiteral { .. } | IntLiteral(_, _) - | U128Literal(_) | FloatLiteral(_, _) | DecimalLiteral(_) | StrLiteral(_) => true, @@ -1338,7 +1317,7 @@ fn test_to_equality<'a>( match test_layout { Layout::Union(union_layout) => { - let lhs = Expr::Literal(Literal::Int(tag_id as i128)); + let lhs = Expr::Literal(Literal::Int((tag_id as i128).to_ne_bytes())); let rhs = Expr::GetTagId { structure: path_symbol, @@ -1370,22 +1349,14 @@ fn test_to_equality<'a>( Test::IsInt(test_int, precision) => { // TODO don't downcast i128 here - debug_assert!(test_int <= i64::MAX as i128); - let lhs = Expr::Literal(Literal::Int(test_int as i128)); + debug_assert!(i128::from_ne_bytes(test_int) <= i64::MAX as i128); + let lhs = Expr::Literal(Literal::Int(test_int)); let lhs_symbol = env.unique_symbol(); stores.push((lhs_symbol, Layout::int_width(precision), lhs)); (stores, lhs_symbol, rhs_symbol, None) } - Test::IsU128(test_int) => { - let lhs = Expr::Literal(Literal::U128(test_int)); - let lhs_symbol = env.unique_symbol(); - stores.push((lhs_symbol, Layout::int_width(IntWidth::U128), lhs)); - - (stores, lhs_symbol, rhs_symbol, None) - } - Test::IsFloat(test_int, precision) => { // TODO maybe we can actually use i64 comparison here? let test_float = f64::from_bits(test_int as u64); @@ -1835,7 +1806,7 @@ fn decide_to_branching<'a>( ); let tag = match test { - Test::IsInt(v, _) => v as u64, + Test::IsInt(v, _) => i128::from_ne_bytes(v) as u64, Test::IsFloat(v, _) => v as u64, Test::IsBit(v) => v as u64, Test::IsByte { tag_id, .. } => tag_id as u64, diff --git a/compiler/mono/src/exhaustive.rs b/compiler/mono/src/exhaustive.rs index 79b54d446e..3c5516bc23 100644 --- a/compiler/mono/src/exhaustive.rs +++ b/compiler/mono/src/exhaustive.rs @@ -13,7 +13,6 @@ fn simplify(pattern: &crate::ir::Pattern) -> Pattern { match pattern { IntLiteral(v, _) => Literal(Literal::Int(*v)), - U128Literal(v) => Literal(Literal::U128(*v)), FloatLiteral(v, _) => Literal(Literal::Float(*v)), DecimalLiteral(v) => Literal(Literal::Decimal(*v)), StrLiteral(v) => Literal(Literal::Str(v.clone())), diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index a8f5383c63..5b0d6be96f 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -11,17 +11,19 @@ use roc_can::abilities::{AbilitiesStore, SpecializationId}; use roc_can::expr::{AnnotatedMark, ClosureData, IntValue}; use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap}; use roc_collections::{MutSet, VecMap}; +use roc_debug_flags::dbg_do; +#[cfg(debug_assertions)] use roc_debug_flags::{ - dbg_do, ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, - ROC_PRINT_IR_AFTER_SPECIALIZATION, + ROC_PRINT_IR_AFTER_REFCOUNT, ROC_PRINT_IR_AFTER_RESET_REUSE, ROC_PRINT_IR_AFTER_SPECIALIZATION, }; +use roc_error_macros::todo_abilities; use roc_exhaustive::{Ctor, CtorName, Guard, RenderAs, TagId}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName}; use roc_module::low_level::LowLevel; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_problem::can::{RuntimeError, ShadowKind}; use roc_region::all::{Loc, Region}; -use roc_solve::ability::resolve_ability_specialization; +use roc_solve::ability::{resolve_ability_specialization, Resolved}; use roc_std::RocDec; use roc_target::TargetInfo; use roc_types::subs::{ @@ -50,14 +52,6 @@ pub fn pretty_print_ir_symbols() -> bool { // please change it to the lower number. // if it went up, maybe check that the change is really required -// i128 alignment is different on arm -roc_error_macros::assert_sizeof_aarch64!(Literal, 4 * 8); -roc_error_macros::assert_sizeof_aarch64!(Expr, 10 * 8); -roc_error_macros::assert_sizeof_aarch64!(Stmt, 20 * 8); -roc_error_macros::assert_sizeof_aarch64!(ProcLayout, 6 * 8); -roc_error_macros::assert_sizeof_aarch64!(Call, 7 * 8); -roc_error_macros::assert_sizeof_aarch64!(CallType, 5 * 8); - roc_error_macros::assert_sizeof_wasm!(Literal, 24); roc_error_macros::assert_sizeof_wasm!(Expr, 48); roc_error_macros::assert_sizeof_wasm!(Stmt, 120); @@ -65,12 +59,12 @@ roc_error_macros::assert_sizeof_wasm!(ProcLayout, 32); roc_error_macros::assert_sizeof_wasm!(Call, 36); roc_error_macros::assert_sizeof_wasm!(CallType, 28); -roc_error_macros::assert_sizeof_default!(Literal, 3 * 8); -roc_error_macros::assert_sizeof_default!(Expr, 10 * 8); -roc_error_macros::assert_sizeof_default!(Stmt, 19 * 8); -roc_error_macros::assert_sizeof_default!(ProcLayout, 6 * 8); -roc_error_macros::assert_sizeof_default!(Call, 7 * 8); -roc_error_macros::assert_sizeof_default!(CallType, 5 * 8); +roc_error_macros::assert_sizeof_non_wasm!(Literal, 3 * 8); +roc_error_macros::assert_sizeof_non_wasm!(Expr, 10 * 8); +roc_error_macros::assert_sizeof_non_wasm!(Stmt, 19 * 8); +roc_error_macros::assert_sizeof_non_wasm!(ProcLayout, 6 * 8); +roc_error_macros::assert_sizeof_non_wasm!(Call, 7 * 8); +roc_error_macros::assert_sizeof_non_wasm!(CallType, 5 * 8); macro_rules! return_on_layout_error { ($env:expr, $layout_result:expr) => { @@ -110,11 +104,6 @@ pub enum OptLevel { Optimize, } -#[derive(Clone, Debug, PartialEq)] -pub enum MonoProblem { - PatternProblem(roc_exhaustive::Error), -} - #[derive(Debug, Clone, Copy)] pub struct EntryPoint<'a> { pub symbol: Symbol, @@ -1247,7 +1236,6 @@ impl<'a> Specializations<'a> { pub struct Env<'a, 'i> { pub arena: &'a Bump, pub subs: &'i mut Subs, - pub problems: &'i mut std::vec::Vec, pub home: ModuleId, pub ident_ids: &'i mut IdentIds, pub target_info: TargetInfo, @@ -1456,10 +1444,13 @@ impl ModifyRc { #[derive(Clone, Copy, Debug, PartialEq)] pub enum Literal<'a> { // Literals - Int(i128), - U128(u128), + /// stored as raw bytes rather than a number to avoid an alignment bump + Int([u8; 16]), + /// stored as raw bytes rather than a number to avoid an alignment bump + U128([u8; 16]), Float(f64), - Decimal(RocDec), + /// stored as raw bytes rather than a number to avoid an alignment bump + Decimal([u8; 16]), Str(&'a str), /// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool, /// so they can (at least potentially) be emitted as 1-bit machine bools. @@ -1711,10 +1702,10 @@ impl<'a> Literal<'a> { use Literal::*; match self { - Int(lit) => alloc.text(format!("{}i64", lit)), - U128(lit) => alloc.text(format!("{}u128", lit)), + Int(bytes) => alloc.text(format!("{}i64", i128::from_ne_bytes(*bytes))), + U128(bytes) => alloc.text(format!("{}u128", u128::from_ne_bytes(*bytes))), Float(lit) => alloc.text(format!("{}f64", lit)), - Decimal(lit) => alloc.text(format!("{}dec", lit)), + Decimal(bytes) => alloc.text(format!("{}dec", RocDec::from_ne_bytes(*bytes))), Bool(lit) => alloc.text(format!("{}", lit)), Byte(lit) => alloc.text(format!("{}u8", lit)), Str(lit) => alloc.text(format!("{:?}", lit)), @@ -2836,6 +2827,13 @@ fn resolve_abilities_in_specialized_body<'a>( ) .expect("Ability specialization is unknown - code generation cannot proceed!"); + let specialization = match specialization { + Resolved::Specialization(symbol) => symbol, + Resolved::NeedsGenerated => { + todo_abilities!("Generate impls for structural types") + } + }; + self.abilities_store .insert_resolved(*specialization_id, specialization); @@ -3686,7 +3684,7 @@ fn try_make_literal<'a>( ), }; - Some(Literal::Decimal(dec)) + Some(Literal::Decimal(dec.to_ne_bytes())) } _ => unreachable!("unexpected float precision for integer"), } @@ -3702,8 +3700,8 @@ fn try_make_literal<'a>( IntValue::U128(n) => Literal::U128(n), }), IntOrFloat::Float(_) => Some(match *num { - IntValue::I128(n) => Literal::Float(n as f64), - IntValue::U128(n) => Literal::Float(n as f64), + IntValue::I128(n) => Literal::Float(i128::from_ne_bytes(n) as f64), + IntValue::U128(n) => Literal::Float(u128::from_ne_bytes(n) as f64), }), IntOrFloat::DecimalFloatType => { let dec = match RocDec::from_str(num_str) { @@ -3714,7 +3712,7 @@ fn try_make_literal<'a>( ), }; - Some(Literal::Decimal(dec)) + Some(Literal::Decimal(dec.to_ne_bytes())) } } } @@ -3766,7 +3764,7 @@ pub fn with_hole<'a>( }; Stmt::Let( assigned, - Expr::Literal(Literal::Decimal(dec)), + Expr::Literal(Literal::Decimal(dec.to_ne_bytes())), Layout::Builtin(Builtin::Decimal), hole, ) @@ -3784,7 +3782,7 @@ pub fn with_hole<'a>( SingleQuote(character) => Stmt::Let( assigned, - Expr::Literal(Literal::Int(character as _)), + Expr::Literal(Literal::Int((character as i128).to_ne_bytes())), Layout::int_width(IntWidth::I32), hole, ), @@ -3804,8 +3802,8 @@ pub fn with_hole<'a>( IntOrFloat::Float(precision) => Stmt::Let( assigned, Expr::Literal(match num { - IntValue::I128(n) => Literal::Float(n as f64), - IntValue::U128(n) => Literal::Float(n as f64), + IntValue::I128(n) => Literal::Float(i128::from_ne_bytes(n) as f64), + IntValue::U128(n) => Literal::Float(u128::from_ne_bytes(n) as f64), }), Layout::float_width(precision), hole, @@ -3817,7 +3815,7 @@ pub fn with_hole<'a>( }; Stmt::Let( assigned, - Expr::Literal(Literal::Decimal(dec)), + Expr::Literal(Literal::Decimal(dec.to_ne_bytes())), Layout::Builtin(Builtin::Decimal), hole, ) @@ -4878,14 +4876,21 @@ pub fn with_hole<'a>( UnspecializedExpr(symbol) => { match procs.ability_member_aliases.get(symbol).unwrap() { &self::AbilityMember(member) => { - let proc_name = resolve_ability_specialization(env.subs, env.abilities_store, member, fn_var).expect("Recorded as an ability member, but it doesn't have a specialization"); + let resolved_proc = resolve_ability_specialization(env.subs, env.abilities_store, member, fn_var).expect("Recorded as an ability member, but it doesn't have a specialization"); + + let resolved_proc = match resolved_proc { + Resolved::Specialization(symbol) => symbol, + Resolved::NeedsGenerated => { + todo_abilities!("Generate impls for structural types") + } + }; // a call by a known name return call_by_name( env, procs, fn_var, - proc_name, + resolved_proc, loc_args, layout_cache, assigned, @@ -5234,6 +5239,7 @@ pub fn with_hole<'a>( } } } + TypedHole(_) => Stmt::RuntimeError("Hit a blank"), RuntimeError(e) => Stmt::RuntimeError(env.arena.alloc(format!("{:?}", e))), } } @@ -6577,7 +6583,6 @@ fn store_pattern_help<'a>( return StorePattern::NotProductive(stmt); } IntLiteral(_, _) - | U128Literal(_) | FloatLiteral(_, _) | DecimalLiteral(_) | EnumLiteral { .. } @@ -8078,10 +8083,9 @@ fn call_specialized_proc<'a>( pub enum Pattern<'a> { Identifier(Symbol), Underscore, - U128Literal(u128), - IntLiteral(i128, IntWidth), + IntLiteral([u8; 16], IntWidth), FloatLiteral(u64, FloatWidth), - DecimalLiteral(RocDec), + DecimalLiteral([u8; 16]), BitLiteral { value: bool, tag_name: TagName, @@ -8167,13 +8171,9 @@ fn from_can_pattern_help<'a>( AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)), IntLiteral(_, precision_var, _, int, _bound) => { match num_argument_to_int_or_float(env.subs, env.target_info, *precision_var, false) { - IntOrFloat::Int(precision) => { - let int = match *int { - IntValue::I128(n) => Pattern::IntLiteral(n, precision), - IntValue::U128(n) => Pattern::U128Literal(n), - }; - Ok(int) - } + IntOrFloat::Int(precision) => match *int { + IntValue::I128(n) | IntValue::U128(n) => Ok(Pattern::IntLiteral(n, precision)), + }, other => { panic!( "Invalid precision for int pattern: {:?} has {:?}", @@ -8199,12 +8199,15 @@ fn from_can_pattern_help<'a>( float_str ), }; - Ok(Pattern::DecimalLiteral(dec)) + Ok(Pattern::DecimalLiteral(dec.to_ne_bytes())) } } } StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())), - SingleQuote(c) => Ok(Pattern::IntLiteral(*c as _, IntWidth::I32)), + SingleQuote(c) => Ok(Pattern::IntLiteral( + (*c as i128).to_ne_bytes(), + IntWidth::I32, + )), Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing { original_region: *region, shadow: ident.clone(), @@ -8222,14 +8225,15 @@ fn from_can_pattern_help<'a>( NumLiteral(var, num_str, num, _bound) => { match num_argument_to_int_or_float(env.subs, env.target_info, *var, false) { IntOrFloat::Int(precision) => Ok(match num { - IntValue::I128(num) => Pattern::IntLiteral(*num, precision), - IntValue::U128(num) => Pattern::U128Literal(*num), + IntValue::I128(num) | IntValue::U128(num) => { + Pattern::IntLiteral(*num, precision) + } }), IntOrFloat::Float(precision) => { // TODO: this may be lossy let num = match *num { - IntValue::I128(n) => f64::to_bits(n as f64), - IntValue::U128(n) => f64::to_bits(n as f64), + IntValue::I128(n) => f64::to_bits(i128::from_ne_bytes(n) as f64), + IntValue::U128(n) => f64::to_bits(u128::from_ne_bytes(n) as f64), }; Ok(Pattern::FloatLiteral(num, precision)) } @@ -8238,7 +8242,7 @@ fn from_can_pattern_help<'a>( Some(d) => d, None => panic!("Invalid decimal for float literal = {}. TODO: Make this a nice, user-friendly error message", num_str), }; - Ok(Pattern::DecimalLiteral(dec)) + Ok(Pattern::DecimalLiteral(dec.to_ne_bytes())) } } } diff --git a/compiler/mono/src/layout_soa.rs b/compiler/mono/src/layout_soa.rs index f5477a5976..accb3c929f 100644 --- a/compiler/mono/src/layout_soa.rs +++ b/compiler/mono/src/layout_soa.rs @@ -124,7 +124,7 @@ impl FunctionLayout { subs: &Subs, var: Variable, ) -> Result { - let content = &subs.get_ref(var).content; + let content = &subs.get_content_without_compacting(var); Self::from_content(layouts, subs, var, content) } @@ -232,7 +232,7 @@ impl LambdaSet { subs: &Subs, var: Variable, ) -> Result { - let content = &subs.get_ref(var).content; + let content = &subs.get_content_without_compacting(var); Self::from_content(layouts, subs, var, content) } @@ -598,7 +598,7 @@ impl Layout { subs: &Subs, var: Variable, ) -> Result { - let content = &subs.get_ref(var).content; + let content = &subs.get_content_without_compacting(var); Self::from_content(layouts, subs, var, content) } @@ -612,7 +612,7 @@ impl Layout { subs: &Subs, var: Variable, ) -> Result { - let content = &subs.get_ref(var).content; + let content = &subs.get_content_without_compacting(var); match content { Content::FlexVar(_) | Content::RigidVar(_) => Ok(Layout::VOID), diff --git a/compiler/parse/Cargo.toml b/compiler/parse/Cargo.toml index 33954d8d95..00e9ce63c9 100644 --- a/compiler/parse/Cargo.toml +++ b/compiler/parse/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_parse" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [features] "parse_debug_trace" = [] diff --git a/compiler/parse/fuzz/Cargo.toml b/compiler/parse/fuzz/Cargo.toml index 2d1bd145cf..54889585a9 100644 --- a/compiler/parse/fuzz/Cargo.toml +++ b/compiler/parse/fuzz/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_parse-fuzz" version = "0.0.0" authors = ["Automatically generated"] publish = false -edition = "2018" +edition = "2021" [package.metadata] cargo-fuzz = true diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index a47f2da392..55b3325423 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -297,6 +297,7 @@ pub enum TypeDef<'a> { Opaque { header: TypeHeader<'a>, typ: Loc>, + derived: Option>>, }, /// An ability definition. E.g. @@ -380,11 +381,41 @@ impl<'a> From> for Def<'a> { } } +/// Should always be a zero-argument `Apply`; we'll check this in canonicalization +pub type AbilityName<'a> = Loc>; + #[derive(Debug, Copy, Clone, PartialEq)] pub struct HasClause<'a> { pub var: Loc>, - // Should always be a zero-argument `Apply`; we'll check this in canonicalization - pub ability: Loc>, + pub ability: AbilityName<'a>, +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum Derived<'a> { + /// `has [ Eq, Hash ]` + Has(Collection<'a, AbilityName<'a>>), + + // We preserve this for the formatter; canonicalization ignores it. + SpaceBefore(&'a Derived<'a>, &'a [CommentOrNewline<'a>]), + SpaceAfter(&'a Derived<'a>, &'a [CommentOrNewline<'a>]), +} + +impl Derived<'_> { + pub fn collection(&self) -> &Collection { + let mut it = self; + loop { + match it { + Self::SpaceBefore(inner, _) | Self::SpaceAfter(inner, _) => { + it = inner; + } + Self::Has(collection) => return collection, + } + } + } + + pub fn is_empty(&self) -> bool { + self.collection().is_empty() + } } #[derive(Debug, Copy, Clone, PartialEq)] @@ -901,6 +932,15 @@ impl<'a> Spaceable<'a> for Has<'a> { } } +impl<'a> Spaceable<'a> for Derived<'a> { + fn before(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self { + Derived::SpaceBefore(self, spaces) + } + fn after(&'a self, spaces: &'a [CommentOrNewline<'a>]) -> Self { + Derived::SpaceAfter(self, spaces) + } +} + impl<'a> Expr<'a> { pub fn loc_ref(&'a self, region: Region) -> Loc<&'a Self> { Loc { @@ -983,6 +1023,7 @@ impl_extract_spaces!(Expr); impl_extract_spaces!(Pattern); impl_extract_spaces!(Tag); impl_extract_spaces!(AssignedField); +impl_extract_spaces!(TypeAnnotation); impl<'a, T: Copy> ExtractSpaces<'a> for Spaced<'a, T> { type Item = T; diff --git a/compiler/parse/src/blankspace.rs b/compiler/parse/src/blankspace.rs index 1b85af03a0..78f4f41fab 100644 --- a/compiler/parse/src/blankspace.rs +++ b/compiler/parse/src/blankspace.rs @@ -180,56 +180,143 @@ fn spaces_help_help<'a, E>( where E: 'a + SpaceProblem, { - use SpaceState::*; + move |arena, state: State<'a>| match fast_eat_spaces(&state) { + FastSpaceState::HasTab(position) => Err(( + MadeProgress, + E::space_problem(BadInputError::HasTab, position), + state, + )), + FastSpaceState::Good { + newlines, + consumed, + column, + } => { + if consumed == 0 { + Ok((NoProgress, &[] as &[_], state)) + } else if column < min_indent { + Err((MadeProgress, indent_problem(state.pos()), state)) + } else { + let comments_and_newlines = Vec::with_capacity_in(newlines, arena); + let mut spaces = eat_spaces(state, false, comments_and_newlines); - move |arena, state: State<'a>| { - let comments_and_newlines = Vec::new_in(arena); - match eat_spaces(state.clone(), false, comments_and_newlines) { - HasTab(state) => Err(( - MadeProgress, - E::space_problem(BadInputError::HasTab, state.pos()), - state, - )), - Good { - state: mut new_state, - multiline, - comments_and_newlines, - } => { - if new_state.bytes() == state.bytes() { - Ok((NoProgress, &[] as &[_], state)) - } else if multiline { - // we parsed at least one newline - - new_state.indent_column = new_state.column(); - - if new_state.column() >= min_indent { - Ok(( - MadeProgress, - comments_and_newlines.into_bump_slice(), - new_state, - )) - } else { - Err((MadeProgress, indent_problem(state.pos()), state)) - } - } else { - Ok(( - MadeProgress, - comments_and_newlines.into_bump_slice(), - new_state, - )) + if spaces.multiline { + spaces.state.indent_column = spaces.state.column(); } + + Ok(( + MadeProgress, + spaces.comments_and_newlines.into_bump_slice(), + spaces.state, + )) } } } } -enum SpaceState<'a> { +enum FastSpaceState { Good { - state: State<'a>, - multiline: bool, - comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, + newlines: usize, + consumed: usize, + column: u32, }, - HasTab(State<'a>), + HasTab(Position), +} + +fn fast_eat_spaces(state: &State) -> FastSpaceState { + use FastSpaceState::*; + + let mut newlines = 0; + let mut line_start = state.line_start.offset as usize; + let base_offset = state.pos().offset as usize; + + let mut index = base_offset; + let bytes = state.original_bytes(); + let length = bytes.len(); + + 'outer: while index < length { + match bytes[index] { + b' ' => { + index += 1; + } + b'\n' => { + newlines += 1; + index += 1; + line_start = index; + } + b'\r' => { + index += 1; + line_start = index; + } + b'\t' => { + return HasTab(Position::new(index as u32)); + } + b'#' => { + index += 1; + + // try to use SIMD instructions explicitly + #[cfg(target_arch = "x86_64")] + { + use std::arch::x86_64::*; + + // a bytestring with the three characters we're looking for (the rest is ignored) + let needle = b"\r\n\t============="; + let needle = unsafe { _mm_loadu_si128(needle.as_ptr() as *const _) }; + + while index < length { + let remaining = length - index; + let length = if remaining < 16 { remaining as i32 } else { 16 }; + + // the source bytes we'll be looking at + let haystack = + unsafe { _mm_loadu_si128(bytes.as_ptr().add(index) as *const _) }; + + // use first 3 characters of needle, first `length` characters of haystack + // finds the first index where one of the `needle` characters occurs + // or 16 when none of the needle characters occur + let first_special_char = unsafe { + _mm_cmpestri(needle, 3, haystack, length, _SIDD_CMP_EQUAL_ANY) + }; + + // we've made `first_special_char` characters of progress + index += usize::min(first_special_char as usize, remaining); + + // if we found a special char, let the outer loop handle it + if first_special_char != 16 { + continue 'outer; + } + } + } + + #[cfg(not(target_arch = "x86_64"))] + { + while index < length { + match bytes[index] { + b'\n' | b'\t' | b'\r' => { + continue 'outer; + } + + _ => { + index += 1; + } + } + } + } + } + _ => break, + } + } + + Good { + newlines, + consumed: index - base_offset, + column: (index - line_start) as u32, + } +} + +struct SpaceState<'a> { + state: State<'a>, + multiline: bool, + comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, } fn eat_spaces<'a>( @@ -237,8 +324,6 @@ fn eat_spaces<'a>( mut multiline: bool, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { - use SpaceState::*; - for c in state.bytes() { match c { b' ' => { @@ -252,9 +337,8 @@ fn eat_spaces<'a>( b'\r' => { state = state.advance_newline(); } - b'\t' => { - return HasTab(state); - } + b'\t' => unreachable!(), + b'#' => { state = state.advance(1); return eat_line_comment(state, multiline, comments_and_newlines); @@ -263,7 +347,7 @@ fn eat_spaces<'a>( } } - Good { + SpaceState { state, multiline, comments_and_newlines, @@ -275,81 +359,238 @@ fn eat_line_comment<'a>( mut multiline: bool, mut comments_and_newlines: Vec<'a, CommentOrNewline<'a>>, ) -> SpaceState<'a> { - use SpaceState::*; + let mut index = state.pos().offset as usize; + let bytes = state.original_bytes(); + let length = bytes.len(); - let is_doc_comment = if let Some(b'#') = state.bytes().get(0) { - match state.bytes().get(1) { - Some(b' ') => { - state = state.advance(2); + 'outer: loop { + let is_doc_comment = if let Some(b'#') = bytes.get(index) { + match bytes.get(index + 1) { + Some(b' ') => { + state = state.advance(2); + index += 2; - true - } - Some(b'\n') => { - // consume the second # and the \n - state = state.advance(1); - state = state.advance_newline(); - - comments_and_newlines.push(CommentOrNewline::DocComment("")); - multiline = true; - return eat_spaces(state, multiline, comments_and_newlines); - } - None => { - // consume the second # - state = state.advance(1); - - return Good { - state, - multiline, - comments_and_newlines, - }; - } - - _ => false, - } - } else { - false - }; - - let initial = state.bytes(); - - for c in state.bytes() { - match c { - b'\t' => return HasTab(state), - b'\n' => { - let delta = initial.len() - state.bytes().len(); - let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) }; - - if is_doc_comment { - comments_and_newlines.push(CommentOrNewline::DocComment(comment)); - } else { - comments_and_newlines.push(CommentOrNewline::LineComment(comment)); + true } - state = state.advance_newline(); - multiline = true; - return eat_spaces(state, multiline, comments_and_newlines); + Some(b'\n') => { + // consume the second # and the \n + state = state.advance(1); + state = state.advance_newline(); + index += 2; + + comments_and_newlines.push(CommentOrNewline::DocComment("")); + multiline = true; + + for c in state.bytes() { + match c { + b' ' => { + state = state.advance(1); + } + b'\n' => { + state = state.advance_newline(); + multiline = true; + comments_and_newlines.push(CommentOrNewline::Newline); + } + b'\r' => { + state = state.advance_newline(); + } + b'\t' => unreachable!(), + b'#' => { + state = state.advance(1); + index += 1; + continue 'outer; + } + _ => break, + } + + index += 1; + } + + return SpaceState { + state, + multiline, + comments_and_newlines, + }; + } + None => { + // consume the second # + state = state.advance(1); + + return SpaceState { + state, + multiline, + comments_and_newlines, + }; + } + + Some(_) => false, } - b'\r' => { - state = state.advance_newline(); - } - _ => { - state = state.advance(1); + } else { + false + }; + + let loop_start = index; + + #[cfg(target_arch = "x86_64")] + { + use std::arch::x86_64::*; + + // a bytestring with the three characters we're looking for (the rest is ignored) + let needle = b"\r\n\t============="; + let needle = unsafe { _mm_loadu_si128(needle.as_ptr() as *const _) }; + + while index < length { + let remaining = length - index; + let chunk = if remaining < 16 { remaining as i32 } else { 16 }; + + // the source bytes we'll be looking at + let haystack = unsafe { _mm_loadu_si128(bytes.as_ptr().add(index) as *const _) }; + + // use first 3 characters of needle, first chunk` characters of haystack + // finds the first index where one of the `needle` characters occurs + // or 16 when none of the needle characters occur + let first_special_char = + unsafe { _mm_cmpestri(needle, 3, haystack, chunk, _SIDD_CMP_EQUAL_ANY) }; + + // we've made `first_special_char` characters of progress + let progress = usize::min(first_special_char as usize, remaining); + index += progress; + state = state.advance(progress); + + if first_special_char != 16 { + match bytes[index] { + b'\t' => unreachable!(), + b'\n' => { + let comment = + unsafe { std::str::from_utf8_unchecked(&bytes[loop_start..index]) }; + + if is_doc_comment { + comments_and_newlines.push(CommentOrNewline::DocComment(comment)); + } else { + comments_and_newlines.push(CommentOrNewline::LineComment(comment)); + } + state = state.advance_newline(); + multiline = true; + + index += 1; + while index < length { + match bytes[index] { + b' ' => { + state = state.advance(1); + } + b'\n' => { + state = state.advance_newline(); + multiline = true; + comments_and_newlines.push(CommentOrNewline::Newline); + } + b'\r' => { + state = state.advance_newline(); + } + b'\t' => unreachable!(), + b'#' => { + state = state.advance(1); + index += 1; + continue 'outer; + } + _ => break, + } + + index += 1; + } + + return SpaceState { + state, + multiline, + comments_and_newlines, + }; + } + b'\r' => { + state = state.advance_newline(); + index += 1; + } + odd_character => { + unreachable!( + "unexpected_character {} {}", + odd_character, odd_character as char + ) + } + } + } } } - } - // We made it to the end of the bytes. This means there's a comment without a trailing newline. - let delta = initial.len() - state.bytes().len(); - let comment = unsafe { std::str::from_utf8_unchecked(&initial[..delta]) }; + #[cfg(not(target_arch = "x86_64"))] + while index < length { + match bytes[index] { + b'\t' => unreachable!(), + b'\n' => { + let comment = + unsafe { std::str::from_utf8_unchecked(&bytes[loop_start..index]) }; - if is_doc_comment { - comments_and_newlines.push(CommentOrNewline::DocComment(comment)); - } else { - comments_and_newlines.push(CommentOrNewline::LineComment(comment)); - } + if is_doc_comment { + comments_and_newlines.push(CommentOrNewline::DocComment(comment)); + } else { + comments_and_newlines.push(CommentOrNewline::LineComment(comment)); + } + state = state.advance_newline(); + multiline = true; - Good { - state, - multiline, - comments_and_newlines, + index += 1; + while index < length { + match bytes[index] { + b' ' => { + state = state.advance(1); + } + b'\n' => { + state = state.advance_newline(); + multiline = true; + comments_and_newlines.push(CommentOrNewline::Newline); + } + b'\r' => { + state = state.advance_newline(); + } + b'\t' => unreachable!(), + b'#' => { + state = state.advance(1); + index += 1; + continue 'outer; + } + _ => break, + } + + index += 1; + } + + return SpaceState { + state, + multiline, + comments_and_newlines, + }; + } + b'\r' => { + state = state.advance_newline(); + } + _ => { + state = state.advance(1); + } + } + + index += 1; + } + + // We made it to the end of the bytes. This means there's a comment without a trailing newline. + let comment = unsafe { std::str::from_utf8_unchecked(&bytes[loop_start..index]) }; + + if is_doc_comment { + comments_and_newlines.push(CommentOrNewline::DocComment(comment)); + } else { + comments_and_newlines.push(CommentOrNewline::LineComment(comment)); + } + + return SpaceState { + state, + multiline, + comments_and_newlines, + }; } } diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 2ede367506..2bc2d16e97 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -1,6 +1,6 @@ use crate::ast::{ - AssignedField, Collection, CommentOrNewline, Def, Expr, ExtractSpaces, Has, Pattern, Spaceable, - TypeAnnotation, TypeDef, TypeHeader, ValueDef, + AssignedField, Collection, CommentOrNewline, Def, Derived, Expr, ExtractSpaces, Has, Pattern, + Spaceable, TypeAnnotation, TypeDef, TypeHeader, ValueDef, }; use crate::blankspace::{ space0_after_e, space0_around_ee, space0_before_e, space0_before_optional_after, space0_e, @@ -426,21 +426,21 @@ impl<'a> ExprState<'a> { mut self, arena: &'a Bump, loc_op: Loc, - kind: TypeKind, + kind: AliasOrOpaque, ) -> Result<(Loc>, Vec<'a, &'a Loc>>), EExpr<'a>> { debug_assert_eq!( loc_op.value, match kind { - TypeKind::Alias => BinOp::IsAliasType, - TypeKind::Opaque => BinOp::IsOpaqueType, + AliasOrOpaque::Alias => BinOp::IsAliasType, + AliasOrOpaque::Opaque => BinOp::IsOpaqueType, } ); if !self.operators.is_empty() { // this `:`/`:=` likely occurred inline; treat it as an invalid operator let op = match kind { - TypeKind::Alias => ":", - TypeKind::Opaque => ":=", + AliasOrOpaque::Alias => ":", + AliasOrOpaque::Opaque => ":=", }; let fail = EExpr::BadOperator(op, loc_op.region.start()); @@ -690,7 +690,10 @@ fn append_annotation_definition<'a>( spaces: &'a [CommentOrNewline<'a>], loc_pattern: Loc>, loc_ann: Loc>, - kind: TypeKind, + + // If this turns out to be an alias + kind: AliasOrOpaque, + opt_derived: Option>>, ) { let region = Region::span_across(&loc_pattern.region, &loc_ann.region); @@ -702,7 +705,7 @@ fn append_annotation_definition<'a>( .. }, alias_arguments, - ) => append_type_definition( + ) => append_alias_or_opaque_definition( arena, defs, region, @@ -711,8 +714,9 @@ fn append_annotation_definition<'a>( alias_arguments, loc_ann, kind, + opt_derived, ), - Pattern::Tag(name) => append_type_definition( + Pattern::Tag(name) => append_alias_or_opaque_definition( arena, defs, region, @@ -721,6 +725,7 @@ fn append_annotation_definition<'a>( &[], loc_ann, kind, + opt_derived, ), _ => { let mut loc_def = Loc::at( @@ -762,7 +767,7 @@ fn append_expect_definition<'a>( } #[allow(clippy::too_many_arguments)] -fn append_type_definition<'a>( +fn append_alias_or_opaque_definition<'a>( arena: &'a Bump, defs: &mut Vec<'a, &'a Loc>>, region: Region, @@ -770,23 +775,26 @@ fn append_type_definition<'a>( name: Loc<&'a str>, pattern_arguments: &'a [Loc>], loc_ann: Loc>, - kind: TypeKind, + kind: AliasOrOpaque, + opt_derived: Option>>, ) { let header = TypeHeader { name, vars: pattern_arguments, }; - let def = match kind { - TypeKind::Alias => TypeDef::Alias { + + let type_def = match kind { + AliasOrOpaque::Alias => TypeDef::Alias { header, ann: loc_ann, }, - TypeKind::Opaque => TypeDef::Opaque { + AliasOrOpaque::Opaque => TypeDef::Opaque { header, typ: loc_ann, + derived: opt_derived, }, }; - let mut loc_def = Loc::at(region, Def::Type(def)); + let mut loc_def = Loc::at(region, Def::Type(type_def)); if !spaces.is_empty() { loc_def = arena @@ -891,6 +899,7 @@ fn parse_defs_end<'a>( Loc::at(name_region, name), args, loc_has, + def_state.spaces_after, arena, state, )?; @@ -922,16 +931,9 @@ fn parse_defs_end<'a>( parse_defs_end(options, column, def_state, arena, state) } - Ok((_, op @ (BinOp::IsAliasType | BinOp::IsOpaqueType), state)) => { - let (_, ann_type, state) = specialize( - EExpr::Type, - space0_before_e( - type_annotation::located_help(min_indent + 1, false), - min_indent + 1, - EType::TIndentStart, - ), - ) - .parse(arena, state)?; + Ok((_, BinOp::IsAliasType, state)) => { + let (_, ann_type, state) = + alias_signature_with_space_before(min_indent + 1).parse(arena, state)?; append_annotation_definition( arena, @@ -939,11 +941,24 @@ fn parse_defs_end<'a>( def_state.spaces_after, loc_pattern, ann_type, - match op { - BinOp::IsAliasType => TypeKind::Alias, - BinOp::IsOpaqueType => TypeKind::Opaque, - _ => unreachable!(), - }, + AliasOrOpaque::Alias, + None, + ); + + parse_defs_end(options, column, def_state, arena, state) + } + Ok((_, BinOp::IsOpaqueType, state)) => { + let (_, (signature, derived), state) = + opaque_signature_with_space_before(min_indent + 1).parse(arena, state)?; + + append_annotation_definition( + arena, + &mut def_state.defs, + def_state.spaces_after, + loc_pattern, + signature, + AliasOrOpaque::Opaque, + derived, ); parse_defs_end(options, column, def_state, arena, state) @@ -994,8 +1009,44 @@ fn parse_defs_expr<'a>( } } +fn alias_signature_with_space_before<'a>( + min_indent: u32, +) -> impl Parser<'a, Loc>, EExpr<'a>> { + specialize( + EExpr::Type, + space0_before_e( + type_annotation::located(min_indent + 1, false), + min_indent + 1, + EType::TIndentStart, + ), + ) +} + +fn opaque_signature_with_space_before<'a>( + min_indent: u32, +) -> impl Parser<'a, (Loc>, Option>>), EExpr<'a>> { + and!( + specialize( + EExpr::Type, + space0_before_e( + type_annotation::located_opaque_signature(min_indent, true), + min_indent, + EType::TIndentStart, + ), + ), + optional(specialize( + EExpr::Type, + space0_before_e( + type_annotation::has_derived(min_indent), + min_indent, + EType::TIndentStart, + ), + )) + ) +} + #[derive(Copy, Clone, PartialEq, Eq)] -enum TypeKind { +enum AliasOrOpaque { Alias, Opaque, } @@ -1010,7 +1061,7 @@ fn finish_parsing_alias_or_opaque<'a>( arena: &'a Bump, state: State<'a>, spaces_after_operator: &'a [CommentOrNewline<'a>], - kind: TypeKind, + kind: AliasOrOpaque, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { let expr_region = expr_state.expr.region; let indented_more = start_column + 1; @@ -1032,34 +1083,48 @@ fn finish_parsing_alias_or_opaque<'a>( } } - let (_, ann_type, state) = specialize( - EExpr::Type, - space0_before_e( - type_annotation::located_help(indented_more, true), - min_indent, - EType::TIndentStart, - ), - ) - .parse(arena, state)?; + let (loc_def, state) = match kind { + AliasOrOpaque::Alias => { + let (_, signature, state) = + alias_signature_with_space_before(indented_more).parse(arena, state)?; - let def_region = Region::span_across(&expr.region, &ann_type.region); + let def_region = Region::span_across(&expr.region, &signature.region); - let header = TypeHeader { - name: Loc::at(expr.region, name), - vars: type_arguments.into_bump_slice(), - }; - let type_def = match kind { - TypeKind::Alias => TypeDef::Alias { - header, - ann: ann_type, - }, - TypeKind::Opaque => TypeDef::Opaque { - header, - typ: ann_type, - }, + let header = TypeHeader { + name: Loc::at(expr.region, name), + vars: type_arguments.into_bump_slice(), + }; + + let def = TypeDef::Alias { + header, + ann: signature, + }; + + (Loc::at(def_region, Def::Type(def)), state) + } + + AliasOrOpaque::Opaque => { + let (_, (signature, derived), state) = + opaque_signature_with_space_before(indented_more).parse(arena, state)?; + + let def_region = Region::span_across(&expr.region, &signature.region); + + let header = TypeHeader { + name: Loc::at(expr.region, name), + vars: type_arguments.into_bump_slice(), + }; + + let def = TypeDef::Opaque { + header, + typ: signature, + derived, + }; + + (Loc::at(def_region, Def::Type(def)), state) + } }; - (&*arena.alloc(Loc::at(def_region, type_def.into())), state) + (&*arena.alloc(loc_def), state) } _ => { @@ -1070,7 +1135,7 @@ fn finish_parsing_alias_or_opaque<'a>( let parser = specialize( EExpr::Type, space0_before_e( - type_annotation::located_help(indented_more, false), + type_annotation::located(indented_more, false), min_indent, EType::TIndentStart, ), @@ -1097,8 +1162,8 @@ fn finish_parsing_alias_or_opaque<'a>( Err(_) => { // this `:`/`:=` likely occurred inline; treat it as an invalid operator let op = match kind { - TypeKind::Alias => ":", - TypeKind::Opaque => ":=", + AliasOrOpaque::Alias => ":", + AliasOrOpaque::Opaque => ":=", }; let fail = EExpr::BadOperator(op, loc_op.region.start()); @@ -1139,7 +1204,7 @@ mod ability { specialize( EAbility::Type, // Require the type to be more indented than the name - type_annotation::located_help(start_column + 1, true) + type_annotation::located(start_column + 1, true) ) ) ), @@ -1240,6 +1305,7 @@ fn finish_parsing_ability_def<'a>( name: Loc<&'a str>, args: &'a [Loc>], loc_has: Loc>, + spaces_before: &'a [CommentOrNewline<'a>], arena: &'a Bump, state: State<'a>, ) -> ParseResult<'a, &'a Loc>, EExpr<'a>> { @@ -1281,13 +1347,21 @@ fn finish_parsing_ability_def<'a>( } let def_region = Region::span_across(&name.region, &demands.last().unwrap().typ.region); - let def = TypeDef::Ability { + let def = Def::Type(TypeDef::Ability { header: TypeHeader { name, vars: args }, loc_has, members: demands.into_bump_slice(), - } - .into(); - let loc_def = &*(arena.alloc(Loc::at(def_region, def))); + }); + + let loc_def = &*(if spaces_before.is_empty() { + arena.alloc(Loc::at(def_region, def)) + } else { + arena.alloc( + arena + .alloc(def) + .with_spaces_before(spaces_before, def_region), + ) + }); Ok((MadeProgress, loc_def, state)) } @@ -1302,7 +1376,7 @@ fn finish_parsing_ability<'a>( state: State<'a>, ) -> ParseResult<'a, Expr<'a>, EExpr<'a>> { let (_, loc_def, state) = - finish_parsing_ability_def(start_column, name, args, loc_has, arena, state)?; + finish_parsing_ability_def(start_column, name, args, loc_has, &[], arena, state)?; let def_state = DefState { defs: bumpalo::vec![in arena; loc_def], @@ -1463,8 +1537,8 @@ fn parse_expr_operator<'a>( state, spaces_after_operator, match op { - BinOp::IsAliasType => TypeKind::Alias, - BinOp::IsOpaqueType => TypeKind::Opaque, + BinOp::IsAliasType => AliasOrOpaque::Alias, + BinOp::IsOpaqueType => AliasOrOpaque::Opaque, _ => unreachable!(), }, ), @@ -2705,6 +2779,21 @@ fn number_literal_help<'a>() -> impl Parser<'a, Expr<'a>, ENumber> { const BINOP_CHAR_SET: &[u8] = b"+-/*=.<>:&|^?%!"; +const BINOP_CHAR_MASK: [bool; 125] = { + let mut result = [false; 125]; + + let mut i = 0; + while i < BINOP_CHAR_SET.len() { + let index = BINOP_CHAR_SET[i] as usize; + + result[index] = true; + + i += 1; + } + + result +}; + fn operator<'a>() -> impl Parser<'a, BinOp, EExpr<'a>> { |_, state| operator_help(EExpr::Start, EExpr::BadOperator, state) } @@ -2774,10 +2863,11 @@ fn chomp_ops(bytes: &[u8]) -> &str { let mut chomped = 0; for c in bytes.iter() { - if !BINOP_CHAR_SET.contains(c) { + if let Some(true) = BINOP_CHAR_MASK.get(*c as usize) { + chomped += 1; + } else { break; } - chomped += 1; } unsafe { diff --git a/compiler/parse/src/module.rs b/compiler/parse/src/module.rs index b1e43773f5..e84412514b 100644 --- a/compiler/parse/src/module.rs +++ b/compiler/parse/src/module.rs @@ -816,7 +816,7 @@ fn typed_ident<'a>() -> impl Parser<'a, Spaced<'a, TypedIdent<'a>>, ETypedIdent< space0_before_e( specialize( ETypedIdent::Type, - type_annotation::located_help(min_indent, true) + type_annotation::located(min_indent, true) ), min_indent, ETypedIdent::IndentType, diff --git a/compiler/parse/src/state.rs b/compiler/parse/src/state.rs index b21bccc122..4e936d7589 100644 --- a/compiler/parse/src/state.rs +++ b/compiler/parse/src/state.rs @@ -13,11 +13,11 @@ pub struct State<'a> { offset: usize, /// Position of the start of the current line - line_start: Position, + pub(crate) line_start: Position, /// Current indentation level, in columns /// (so no indent is col 1 - this saves an arithmetic operation.) - pub indent_column: u32, + pub(crate) indent_column: u32, } impl<'a> State<'a> { @@ -43,18 +43,18 @@ impl<'a> State<'a> { } #[must_use] - pub(crate) fn advance(&self, offset: usize) -> State<'a> { - let mut state = self.clone(); - state.offset += offset; - state + #[inline(always)] + pub(crate) const fn advance(mut self, offset: usize) -> State<'a> { + self.offset += offset; + self } #[must_use] - pub(crate) fn advance_newline(&self) -> State<'a> { - let mut state = self.clone(); - state.offset += 1; - state.line_start = state.pos(); - state + #[inline(always)] + pub(crate) const fn advance_newline(mut self) -> State<'a> { + self.offset += 1; + self.line_start = self.pos(); + self } /// Returns the current position diff --git a/compiler/parse/src/type_annotation.rs b/compiler/parse/src/type_annotation.rs index f7e90ac1e0..bf2f5bca49 100644 --- a/compiler/parse/src/type_annotation.rs +++ b/compiler/parse/src/type_annotation.rs @@ -1,5 +1,6 @@ use crate::ast::{ - AssignedField, CommentOrNewline, HasClause, Pattern, Spaced, Tag, TypeAnnotation, TypeHeader, + AssignedField, CommentOrNewline, Derived, HasClause, Pattern, Spaced, Tag, TypeAnnotation, + TypeHeader, }; use crate::blankspace::{space0_around_ee, space0_before_e, space0_e}; use crate::ident::lowercase_ident; @@ -15,19 +16,29 @@ use bumpalo::collections::vec::Vec; use bumpalo::Bump; use roc_region::all::{Loc, Position, Region}; -pub fn located_help<'a>( +pub fn located<'a>( min_indent: u32, is_trailing_comma_valid: bool, ) -> impl Parser<'a, Loc>, EType<'a>> { - expression(min_indent, is_trailing_comma_valid) + expression(min_indent, is_trailing_comma_valid, false) +} + +pub fn located_opaque_signature<'a>( + min_indent: u32, + is_trailing_comma_valid: bool, +) -> impl Parser<'a, Loc>, EType<'a>> { + expression(min_indent, is_trailing_comma_valid, true) } #[inline(always)] -fn tag_union_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, ETypeTagUnion<'a>> { +fn tag_union_type<'a>( + min_indent: u32, + stop_at_surface_has: bool, +) -> impl Parser<'a, TypeAnnotation<'a>, ETypeTagUnion<'a>> { move |arena, state| { let (_, tags, state) = collection_trailing_sep_e!( word1(b'[', ETypeTagUnion::Open), - loc!(tag_type(min_indent)), + loc!(tag_type(min_indent, false)), word1(b',', ETypeTagUnion::End), word1(b']', ETypeTagUnion::End), min_indent, @@ -40,7 +51,7 @@ fn tag_union_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, ET // This could be an open tag union, e.g. `[ Foo, Bar ]a` let (_, ext, state) = optional(allocated(specialize_ref( ETypeTagUnion::Type, - term(min_indent), + term(min_indent, stop_at_surface_has), ))) .parse(arena, state)?; @@ -90,7 +101,7 @@ fn check_type_alias( fn parse_type_alias_after_as<'a>(min_indent: u32) -> impl Parser<'a, TypeHeader<'a>, EType<'a>> { move |arena, state| { - space0_before_e(term(min_indent), min_indent, EType::TAsIndentStart) + space0_before_e(term(min_indent, false), min_indent, EType::TAsIndentStart) .parse(arena, state) .and_then(|(p, annot, state)| { specialize(EType::TInlineAlias, check_type_alias(p, annot)).parse(arena, state) @@ -102,17 +113,26 @@ fn fail_type_start<'a, T: 'a>() -> impl Parser<'a, T, EType<'a>> { |_arena, state: State<'a>| Err((NoProgress, EType::TStart(state.pos()), state)) } -fn term<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EType<'a>> { +fn term<'a>( + min_indent: u32, + stop_at_surface_has: bool, +) -> impl Parser<'a, Loc>, EType<'a>> { map_with_arena!( and!( one_of!( loc_wildcard(), loc_inferred(), specialize(EType::TInParens, loc_type_in_parens(min_indent)), - loc!(specialize(EType::TRecord, record_type(min_indent))), - loc!(specialize(EType::TTagUnion, tag_union_type(min_indent))), - loc!(applied_type(min_indent)), - loc!(parse_type_variable), + loc!(specialize( + EType::TRecord, + record_type(min_indent, stop_at_surface_has) + )), + loc!(specialize( + EType::TTagUnion, + tag_union_type(min_indent, stop_at_surface_has) + )), + loc!(applied_type(min_indent, stop_at_surface_has)), + loc!(parse_type_variable(stop_at_surface_has)), fail_type_start(), ), // Inline alias notation, e.g. [ Nil, Cons a (List a) ] as List a @@ -163,7 +183,10 @@ fn loc_inferred<'a>() -> impl Parser<'a, Loc>, EType<'a>> { }) } -fn loc_applied_arg<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EType<'a>> { +fn loc_applied_arg<'a>( + min_indent: u32, + stop_at_surface_has: bool, +) -> impl Parser<'a, Loc>, EType<'a>> { use crate::ast::Spaceable; map_with_arena!( @@ -173,10 +196,16 @@ fn loc_applied_arg<'a>(min_indent: u32) -> impl Parser<'a, Loc>)| { @@ -196,8 +225,11 @@ fn loc_type_in_parens<'a>( between!( word1(b'(', ETypeInParens::Open), space0_around_ee( - move |arena, state| specialize_ref(ETypeInParens::Type, expression(min_indent, true)) - .parse(arena, state), + move |arena, state| specialize_ref( + ETypeInParens::Type, + expression(min_indent, true, false) + ) + .parse(arena, state), min_indent, ETypeInParens::IndentOpen, ETypeInParens::IndentEnd, @@ -207,12 +239,18 @@ fn loc_type_in_parens<'a>( } #[inline(always)] -fn tag_type<'a>(min_indent: u32) -> impl Parser<'a, Tag<'a>, ETypeTagUnion<'a>> { +fn tag_type<'a>( + min_indent: u32, + stop_at_surface_has: bool, +) -> impl Parser<'a, Tag<'a>, ETypeTagUnion<'a>> { move |arena, state: State<'a>| { let (_, name, state) = loc!(parse_tag_name(ETypeTagUnion::End)).parse(arena, state)?; - let (_, args, state) = specialize_ref(ETypeTagUnion::Type, loc_applied_args_e(min_indent)) - .parse(arena, state)?; + let (_, args, state) = specialize_ref( + ETypeTagUnion::Type, + loc_applied_args_e(min_indent, stop_at_surface_has), + ) + .parse(arena, state)?; let result = Tag::Apply { name, @@ -262,7 +300,7 @@ fn record_type_field<'a>( )) .parse(arena, state)?; - let val_parser = specialize_ref(ETypeRecord::Type, expression(min_indent, true)); + let val_parser = specialize_ref(ETypeRecord::Type, expression(min_indent, true, false)); match opt_loc_val { Some(First(_)) => { @@ -304,7 +342,10 @@ fn record_type_field<'a>( } #[inline(always)] -fn record_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> { +fn record_type<'a>( + min_indent: u32, + stop_at_surface_has: bool, +) -> impl Parser<'a, TypeAnnotation<'a>, ETypeRecord<'a>> { use crate::type_annotation::TypeAnnotation::*; (move |arena, state| { @@ -322,7 +363,7 @@ fn record_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, EType ) .parse(arena, state)?; - let field_term = specialize_ref(ETypeRecord::Type, term(min_indent)); + let field_term = specialize_ref(ETypeRecord::Type, term(min_indent, stop_at_surface_has)); let (_, ext, state) = optional(allocated(field_term)).parse(arena, state)?; let result = Record { fields, ext }; @@ -332,13 +373,16 @@ fn record_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, EType .trace("type_annotation:record_type") } -fn applied_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> { +fn applied_type<'a>( + min_indent: u32, + stop_at_surface_has: bool, +) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> { map!( and!( specialize(EType::TApply, parse_concrete_type), // Optionally parse space-separated arguments for the constructor, // e.g. `Str Float` in `Map Str Float` - loc_applied_args_e(min_indent) + loc_applied_args_e(min_indent, stop_at_surface_has) ), |(ctor, args): (TypeAnnotation<'a>, Vec<'a, Loc>>)| { match &ctor { @@ -360,8 +404,9 @@ fn applied_type<'a>(min_indent: u32) -> impl Parser<'a, TypeAnnotation<'a>, ETyp fn loc_applied_args_e<'a>( min_indent: u32, + stop_at_surface_has: bool, ) -> impl Parser<'a, Vec<'a, Loc>>, EType<'a>> { - zero_or_more!(loc_applied_arg(min_indent)) + zero_or_more!(loc_applied_arg(min_indent, stop_at_surface_has)) } fn has_clause<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EType<'a>> { @@ -433,20 +478,51 @@ fn has_clause_chain<'a>( } } +/// Parse a has-derived clause, e.g. `has [ Eq, Hash ]`. +pub fn has_derived<'a>(min_indent: u32) -> impl Parser<'a, Loc>, EType<'a>> { + skip_first!( + // Parse "has"; we don't care about this keyword + word3(b'h', b'a', b's', EType::THasClause), + // Parse "Hash"; this may be qualified from another module like "Hash.Hash" + space0_before_e( + loc!(map!( + collection_trailing_sep_e!( + word1(b'[', EType::TStart), + specialize(EType::TApply, loc!(parse_concrete_type)), + word1(b',', EType::TEnd), + word1(b']', EType::TEnd), + min_indent + 1, + EType::TStart, + EType::TIndentEnd, + TypeAnnotation::SpaceBefore + ), + Derived::Has + )), + min_indent + 1, + EType::TIndentEnd + ) + ) +} + fn expression<'a>( min_indent: u32, is_trailing_comma_valid: bool, + stop_at_surface_has: bool, ) -> impl Parser<'a, Loc>, EType<'a>> { (move |arena, state: State<'a>| { - let (p1, first, state) = space0_before_e(term(min_indent), min_indent, EType::TIndentStart) - .parse(arena, state)?; + let (p1, first, state) = space0_before_e( + term(min_indent, stop_at_surface_has), + min_indent, + EType::TIndentStart, + ) + .parse(arena, state)?; let result = and![ zero_or_more!(skip_first!( word1(b',', EType::TFunctionArgument), one_of![ space0_around_ee( - term(min_indent), + term(min_indent, stop_at_surface_has), min_indent, EType::TIndentStart, EType::TIndentEnd @@ -471,9 +547,12 @@ fn expression<'a>( let (progress, annot, state) = match result { Ok((p2, (rest, _dropped_spaces), state)) => { - let (p3, return_type, state) = - space0_before_e(term(min_indent), min_indent, EType::TIndentStart) - .parse(arena, state)?; + let (p3, return_type, state) = space0_before_e( + term(min_indent, stop_at_surface_has), + min_indent, + EType::TIndentStart, + ) + .parse(arena, state)?; let region = Region::span_across(&first.region, &return_type.region); @@ -590,14 +669,17 @@ fn parse_concrete_type<'a>( } fn parse_type_variable<'a>( - arena: &'a Bump, - state: State<'a>, -) -> ParseResult<'a, TypeAnnotation<'a>, EType<'a>> { - match crate::ident::lowercase_ident().parse(arena, state) { + stop_at_surface_has: bool, +) -> impl Parser<'a, TypeAnnotation<'a>, EType<'a>> { + move |arena, state: State<'a>| match crate::ident::lowercase_ident().parse(arena, state) { Ok((_, name, state)) => { - let answer = TypeAnnotation::BoundVariable(name); + if name == "has" && stop_at_surface_has { + Err((NoProgress, EType::TEnd(state.pos()), state)) + } else { + let answer = TypeAnnotation::BoundVariable(name); - Ok((MadeProgress, answer, state)) + Ok((MadeProgress, answer, state)) + } } Err((progress, _, state)) => Err((progress, EType::TBadTypeVariable(state.pos()), state)), } diff --git a/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast b/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast index 05f8825676..dc6057152e 100644 --- a/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast +++ b/compiler/parse/tests/snapshots/pass/ability_two_in_a_row.expr.result-ast @@ -37,42 +37,48 @@ Defs( ], }, ), - @35-68 Type( - Ability { - header: TypeHeader { - name: @35-38 "Ab2", - vars: [], - }, - loc_has: @39-42 Has, - members: [ - AbilityMember { - name: @43-46 "ab2", - typ: @49-68 Where( - @49-56 Function( - [ - @49-50 BoundVariable( - "a", - ), - ], - @54-56 Record { - fields: [], - ext: None, - }, - ), - [ - @59-68 HasClause { - var: @59-60 "a", - ability: @65-68 Apply( - "", - "Ab2", - [], - ), - }, - ], - ), + @35-68 SpaceBefore( + Type( + Ability { + header: TypeHeader { + name: @35-38 "Ab2", + vars: [], }, - ], - }, + loc_has: @39-42 Has, + members: [ + AbilityMember { + name: @43-46 "ab2", + typ: @49-68 Where( + @49-56 Function( + [ + @49-50 BoundVariable( + "a", + ), + ], + @54-56 Record { + fields: [], + ext: None, + }, + ), + [ + @59-68 HasClause { + var: @59-60 "a", + ability: @65-68 Apply( + "", + "Ab2", + [], + ), + }, + ], + ), + }, + ], + }, + ), + [ + Newline, + Newline, + ], ), ], @70-71 SpaceBefore( diff --git a/compiler/parse/tests/snapshots/pass/opaque_has_abilities.expr.result-ast b/compiler/parse/tests/snapshots/pass/opaque_has_abilities.expr.result-ast new file mode 100644 index 0000000000..da465ced6d --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_has_abilities.expr.result-ast @@ -0,0 +1,137 @@ +Defs( + [ + @0-7 Type( + Opaque { + header: TypeHeader { + name: @0-1 "A", + vars: [], + }, + typ: @5-7 Apply( + "", + "U8", + [], + ), + derived: Some( + @12-22 Has( + [ + @13-15 Apply( + "", + "Eq", + [], + ), + @17-21 Apply( + "", + "Hash", + [], + ), + ], + ), + ), + }, + ), + @24-44 SpaceBefore( + Type( + Opaque { + header: TypeHeader { + name: @24-25 "A", + vars: [], + }, + typ: @29-44 Where( + @29-30 BoundVariable( + "a", + ), + [ + @33-44 HasClause { + var: @33-34 "a", + ability: @39-44 Apply( + "", + "Other", + [], + ), + }, + ], + ), + derived: Some( + @49-59 Has( + [ + @50-52 Apply( + "", + "Eq", + [], + ), + @54-58 Apply( + "", + "Hash", + [], + ), + ], + ), + ), + }, + ), + [ + Newline, + Newline, + ], + ), + @61-81 SpaceBefore( + Type( + Opaque { + header: TypeHeader { + name: @61-62 "A", + vars: [], + }, + typ: @66-81 Where( + @66-67 BoundVariable( + "a", + ), + [ + @70-81 HasClause { + var: @70-71 "a", + ability: @76-81 Apply( + "", + "Other", + [], + ), + }, + ], + ), + derived: Some( + @91-101 SpaceBefore( + Has( + [ + @92-94 Apply( + "", + "Eq", + [], + ), + @96-100 Apply( + "", + "Hash", + [], + ), + ], + ), + [ + Newline, + ], + ), + ), + }, + ), + [ + Newline, + Newline, + ], + ), + ], + @103-104 SpaceBefore( + Num( + "0", + ), + [ + Newline, + Newline, + ], + ), +) diff --git a/compiler/parse/tests/snapshots/pass/opaque_has_abilities.expr.roc b/compiler/parse/tests/snapshots/pass/opaque_has_abilities.expr.roc new file mode 100644 index 0000000000..605ae5a29c --- /dev/null +++ b/compiler/parse/tests/snapshots/pass/opaque_has_abilities.expr.roc @@ -0,0 +1,8 @@ +A := U8 has [Eq, Hash] + +A := a | a has Other has [Eq, Hash] + +A := a | a has Other + has [Eq, Hash] + +0 diff --git a/compiler/parse/tests/snapshots/pass/opaque_simple.module.result-ast b/compiler/parse/tests/snapshots/pass/opaque_simple.module.result-ast index 297911616f..3c304ff7c7 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_simple.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/opaque_simple.module.result-ast @@ -12,6 +12,7 @@ "U8", [], ), + derived: None, }, ), [], diff --git a/compiler/parse/tests/snapshots/pass/opaque_with_type_arguments.module.result-ast b/compiler/parse/tests/snapshots/pass/opaque_with_type_arguments.module.result-ast index c541386e23..8a1efafec4 100644 --- a/compiler/parse/tests/snapshots/pass/opaque_with_type_arguments.module.result-ast +++ b/compiler/parse/tests/snapshots/pass/opaque_with_type_arguments.module.result-ast @@ -41,6 +41,7 @@ ], ext: None, }, + derived: None, }, ), [], diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 63a9e5c685..35ab5b9043 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -211,6 +211,7 @@ mod test_parse { pass/one_minus_two.expr, pass/one_plus_two.expr, pass/one_spaced_def.expr, + pass/opaque_has_abilities.expr, pass/opaque_simple.module, pass/opaque_with_type_arguments.module, pass/opaque_reference_expr.expr, diff --git a/compiler/problem/Cargo.toml b/compiler/problem/Cargo.toml index af0f1108a0..9aa102ff5f 100644 --- a/compiler/problem/Cargo.toml +++ b/compiler/problem/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_problem" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/problem/src/can.rs b/compiler/problem/src/can.rs index f8913b1b64..3d740c9fac 100644 --- a/compiler/problem/src/can.rs +++ b/compiler/problem/src/can.rs @@ -130,6 +130,7 @@ pub enum Problem { }, AbilityUsedAsType(Lowercase, Symbol, Region), NestedSpecialization(Symbol, Region), + IllegalDerive(Region), } #[derive(Clone, Debug, PartialEq)] diff --git a/compiler/region/Cargo.toml b/compiler/region/Cargo.toml index ecea4be54a..d80e07b9a0 100644 --- a/compiler/region/Cargo.toml +++ b/compiler/region/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_region" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] static_assertions = "1.1.0" diff --git a/compiler/roc_target/Cargo.toml b/compiler/roc_target/Cargo.toml index 5ef3aba2d9..6e3bffe5df 100644 --- a/compiler/roc_target/Cargo.toml +++ b/compiler/roc_target/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_target" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] target-lexicon = "0.12.3" diff --git a/compiler/roc_target/src/lib.rs b/compiler/roc_target/src/lib.rs index be98e06621..3235d212c3 100644 --- a/compiler/roc_target/src/lib.rs +++ b/compiler/roc_target/src/lib.rs @@ -12,6 +12,13 @@ impl TargetInfo { self.architecture.ptr_width() } + pub const fn ptr_size(&self) -> usize { + match self.ptr_width() { + PtrWidth::Bytes4 => 4, + PtrWidth::Bytes8 => 8, + } + } + pub const fn ptr_alignment_bytes(&self) -> usize { self.architecture.ptr_alignment_bytes() } @@ -44,7 +51,7 @@ impl From<&target_lexicon::Triple> for TargetInfo { } #[repr(u8)] -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PtrWidth { Bytes4 = 4, Bytes8 = 8, diff --git a/compiler/solve/Cargo.toml b/compiler/solve/Cargo.toml index 8f06caef58..3044589259 100644 --- a/compiler/solve/Cargo.toml +++ b/compiler/solve/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_solve" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/solve/src/ability.rs b/compiler/solve/src/ability.rs index 0f8527f620..2b33657de3 100644 --- a/compiler/solve/src/ability.rs +++ b/compiler/solve/src/ability.rs @@ -1,14 +1,16 @@ use roc_can::abilities::AbilitiesStore; +use roc_can::expr::PendingDerives; +use roc_collections::VecMap; +use roc_error_macros::internal_error; use roc_module::symbol::Symbol; use roc_region::all::{Loc, Region}; -use roc_types::subs::Subs; -use roc_types::subs::Variable; -use roc_types::types::{Category, PatternCategory}; -use roc_unify::unify::MustImplementAbility; +use roc_types::subs::{Content, FlatType, GetSubsSlice, Rank, Subs, Variable}; +use roc_types::types::{AliasKind, Category, ErrorType, PatternCategory}; use roc_unify::unify::MustImplementConstraints; +use roc_unify::unify::{MustImplementAbility, Obligated}; -use crate::solve::instantiate_rigids; -use crate::solve::{IncompleteAbilityImplementation, TypeError}; +use crate::solve::{instantiate_rigids, type_to_var}; +use crate::solve::{Aliases, Pools, TypeError}; #[derive(Debug, Clone)] pub enum AbilityImplError { @@ -20,33 +22,178 @@ pub enum AbilityImplError { BadPattern(Region, PatternCategory, Variable), } -#[derive(Default)] -pub struct DeferredMustImplementAbility(Vec<(MustImplementConstraints, AbilityImplError)>); +#[derive(PartialEq, Debug, Clone)] +pub enum UnderivableReason { + NotABuiltin, + /// The surface type is not derivable + SurfaceNotDerivable, + /// A nested type is not derivable + NestedNotDerivable(ErrorType), +} -impl DeferredMustImplementAbility { - pub fn add(&mut self, must_implement: MustImplementConstraints, on_error: AbilityImplError) { - self.0.push((must_implement, on_error)); +#[derive(PartialEq, Debug, Clone)] +pub enum Unfulfilled { + /// Incomplete custom implementation for an ability by an opaque type. + Incomplete { + typ: Symbol, + ability: Symbol, + missing_members: Vec>, + }, + /// Cannot derive implementation of an ability for a structural type. + AdhocUnderivable { + typ: ErrorType, + ability: Symbol, + reason: UnderivableReason, + }, + /// Cannot derive implementation of an ability for an opaque type. + OpaqueUnderivable { + typ: ErrorType, + ability: Symbol, + opaque: Symbol, + derive_region: Region, + reason: UnderivableReason, + }, +} + +/// Indexes a deriving of an ability for an opaque type. +#[derive(Debug, PartialEq, Clone, Copy)] +pub struct DeriveKey { + pub opaque: Symbol, + pub ability: Symbol, +} + +/// Indexes a custom implementation of an ability for an opaque type. +#[derive(Debug, PartialEq, Clone, Copy)] +struct ImplKey { + opaque: Symbol, + ability: Symbol, +} + +#[derive(Debug)] +pub struct PendingDerivesTable( + /// derive key -> (opaque type var to use for checking, derive region) + VecMap, +); + +impl PendingDerivesTable { + pub fn new(subs: &mut Subs, aliases: &mut Aliases, pending_derives: PendingDerives) -> Self { + let mut table = VecMap::with_capacity(pending_derives.len()); + + for (opaque, (typ, derives)) in pending_derives.into_iter() { + for Loc { + value: ability, + region, + } in derives + { + debug_assert!( + ability.is_builtin_ability(), + "Not a builtin - should have been caught during can" + ); + let derive_key = DeriveKey { opaque, ability }; + + // Neither rank nor pools should matter here. + let opaque_var = + type_to_var(subs, Rank::toplevel(), &mut Pools::default(), aliases, &typ); + let real_var = match subs.get_content_without_compacting(opaque_var) { + Content::Alias(_, _, real_var, AliasKind::Opaque) => real_var, + _ => internal_error!("Non-opaque in derives table"), + }; + + let old = table.insert(derive_key, (*real_var, region)); + debug_assert!(old.is_none()); + } + } + + Self(table) + } +} + +#[derive(Debug)] +pub struct DeferredObligations { + /// Obligations, to be filled in during solving of a module. + obligations: Vec<(MustImplementConstraints, AbilityImplError)>, + /// Derives that module-defined opaques claim to have. + pending_derives: PendingDerivesTable, + /// Derives that are claimed, but have also been determined to have + /// specializations. Maps to the first member specialization of the same + /// ability. + dominated_derives: VecMap, +} + +impl DeferredObligations { + pub fn new(pending_derives: PendingDerivesTable) -> Self { + Self { + obligations: Default::default(), + pending_derives, + dominated_derives: Default::default(), + } } - pub fn check(self, subs: &mut Subs, abilities_store: &AbilitiesStore) -> Vec { - let mut good = vec![]; - let mut bad = vec![]; + pub fn add(&mut self, must_implement: MustImplementConstraints, on_error: AbilityImplError) { + self.obligations.push((must_implement, on_error)); + } - macro_rules! is_good { - ($e:expr) => { - good.contains($e) - }; - } - macro_rules! get_bad { - ($e:expr) => { - bad.iter() - .find(|(cand, _)| $e == *cand) - .map(|(_, incomplete)| incomplete) - }; + pub fn dominate(&mut self, key: DeriveKey, impl_region: Region) { + // Only builtin abilities can be derived, and hence dominated. + if self.pending_derives.0.contains_key(&key) && !self.dominated_derives.contains_key(&key) { + self.dominated_derives.insert(key, impl_region); } + } + // Rules for checking ability implementations: + // - Ad-hoc derives for structural types are checked on-the-fly + // - Opaque derives are registered as "pending" when we check a module + // - Opaque derives are always checked and registered at the end to make sure opaque + // specializations are found first + // - If an opaque O both derives and specializes an ability A + // - The specialization is recorded in the abilities store (this is done in solve/solve) + // - The derive is checked, but will not be recorded in the abilities store (this is done here) + // - Obligations for O to implement A will defer to whether the specialization is complete + pub fn check_all( + self, + subs: &mut Subs, + abilities_store: &AbilitiesStore, + ) -> (Vec, Vec) { let mut problems = vec![]; + let Self { + obligations, + pending_derives, + dominated_derives, + } = self; + + let mut obligation_cache = ObligationCache { + abilities_store, + pending_derives: &pending_derives, + dominated_derives: &dominated_derives, + + impl_cache: VecMap::with_capacity(obligations.len()), + derive_cache: VecMap::with_capacity(pending_derives.0.len()), + }; + + let mut legal_derives = Vec::with_capacity(pending_derives.0.len()); + + // First, check all derives. + for (&derive_key, &(opaque_real_var, derive_region)) in pending_derives.0.iter() { + obligation_cache.check_derive(subs, derive_key, opaque_real_var, derive_region); + let result = obligation_cache.derive_cache.get(&derive_key).unwrap(); + match result { + Ok(()) => legal_derives.push(derive_key), + Err(problem) => problems.push(TypeError::UnfulfilledAbility(problem.clone())), + } + } + + for (derive_key, impl_region) in dominated_derives.iter() { + let derive_region = pending_derives.0.get(derive_key).unwrap().1; + + problems.push(TypeError::DominatedDerive { + opaque: derive_key.opaque, + ability: derive_key.ability, + derive_region, + impl_region: *impl_region, + }); + } + // Keep track of which types that have an incomplete ability were reported as part of // another type error (from an expression or pattern). If we reported an error for a type // that doesn't implement an ability in that context, we don't want to repeat the error @@ -54,47 +201,22 @@ impl DeferredMustImplementAbility { let mut reported_in_context = vec![]; let mut incomplete_not_in_context = vec![]; - for (constraints, on_error) in self.0.into_iter() { + for (constraints, on_error) in obligations.into_iter() { let must_implement = constraints.get_unique(); - // First off, make sure we populate information about which of the "must implement" - // constraints are met, and which aren't. - for &mia @ MustImplementAbility { typ, ability } in must_implement.iter() { - if is_good!(&mia) || get_bad!(mia).is_some() { - continue; - } + let mut get_unfulfilled = |must_implement: &[MustImplementAbility]| { + must_implement + .iter() + .filter_map(|mia| { + obligation_cache + .check_one(subs, *mia) + .as_ref() + .err() + .cloned() + }) + .collect::>() + }; - let members_of_ability = abilities_store.members_of_ability(ability).unwrap(); - let mut specialized_members = Vec::with_capacity(members_of_ability.len()); - let mut missing_members = Vec::with_capacity(members_of_ability.len()); - for &member in members_of_ability { - match abilities_store.get_specialization(member, typ) { - None => { - let root_data = abilities_store.member_def(member).unwrap(); - missing_members.push(Loc::at(root_data.region, member)); - } - Some(specialization) => { - specialized_members.push(Loc::at(specialization.region, member)); - } - } - } - - if missing_members.is_empty() { - good.push(mia); - } else { - bad.push(( - mia, - IncompleteAbilityImplementation { - typ, - ability, - specialized_members, - missing_members, - }, - )); - } - } - - // Now, figure out what errors we need to report. use AbilityImplError::*; match on_error { IncompleteAbility => { @@ -106,12 +228,9 @@ impl DeferredMustImplementAbility { incomplete_not_in_context.extend(must_implement); } BadExpr(region, category, var) => { - let incomplete_types = must_implement - .iter() - .filter_map(|e| get_bad!(*e)) - .cloned() - .collect::>(); - if !incomplete_types.is_empty() { + let unfulfilled = get_unfulfilled(&must_implement); + + if !unfulfilled.is_empty() { // Demote the bad variable that exposed this problem to an error, both so // that we have an ErrorType to report and so that codegen knows to deal // with the error later. @@ -120,18 +239,15 @@ impl DeferredMustImplementAbility { region, category, error_type, - incomplete_types, + unfulfilled, )); reported_in_context.extend(must_implement); } } BadPattern(region, category, var) => { - let incomplete_types = must_implement - .iter() - .filter_map(|e| get_bad!(*e)) - .cloned() - .collect::>(); - if !incomplete_types.is_empty() { + let unfulfilled = get_unfulfilled(&must_implement); + + if !unfulfilled.is_empty() { // Demote the bad variable that exposed this problem to an error, both so // that we have an ErrorType to report and so that codegen knows to deal // with the error later. @@ -140,7 +256,7 @@ impl DeferredMustImplementAbility { region, category, error_type, - incomplete_types, + unfulfilled, )); reported_in_context.extend(must_implement); } @@ -151,25 +267,324 @@ impl DeferredMustImplementAbility { // Go through and attach generic "type does not implement ability" errors, if they were not // part of a larger context. for mia in incomplete_not_in_context.into_iter() { - if let Some(must_implement) = get_bad!(mia) { + if let Err(unfulfilled) = obligation_cache.check_one(subs, mia) { if !reported_in_context.contains(&mia) { - problems.push(TypeError::IncompleteAbilityImplementation( - must_implement.clone(), - )); + problems.push(TypeError::UnfulfilledAbility(unfulfilled.clone())); } } } - problems + (problems, legal_derives) + } +} + +type ObligationResult = Result<(), Unfulfilled>; + +struct ObligationCache<'a> { + abilities_store: &'a AbilitiesStore, + dominated_derives: &'a VecMap, + pending_derives: &'a PendingDerivesTable, + + impl_cache: VecMap, + derive_cache: VecMap, +} + +enum ReadCache { + Impl, + Derive, +} + +impl ObligationCache<'_> { + fn check_one(&mut self, subs: &mut Subs, mia: MustImplementAbility) -> ObligationResult { + let MustImplementAbility { typ, ability } = mia; + + match typ { + Obligated::Adhoc(var) => self.check_adhoc(subs, var, ability), + Obligated::Opaque(opaque) => self.check_opaque_and_read(subs, opaque, ability).clone(), + } + } + + fn check_adhoc(&mut self, subs: &mut Subs, var: Variable, ability: Symbol) -> ObligationResult { + // Not worth caching ad-hoc checks because variables are unlikely to be the same between + // independent queries. + + let opt_can_derive_builtin = match ability { + Symbol::ENCODE_ENCODING => Some(self.can_derive_encoding(subs, var)), + _ => None, + }; + + let opt_underivable = match opt_can_derive_builtin { + Some(Ok(())) => { + // can derive! + None + } + Some(Err(failure_var)) => Some(if failure_var == var { + UnderivableReason::SurfaceNotDerivable + } else { + let (error_type, _skeletons) = subs.var_to_error_type(failure_var); + UnderivableReason::NestedNotDerivable(error_type) + }), + None => Some(UnderivableReason::NotABuiltin), + }; + + if let Some(underivable_reason) = opt_underivable { + let (error_type, _skeletons) = subs.var_to_error_type(var); + + Err(Unfulfilled::AdhocUnderivable { + typ: error_type, + ability, + reason: underivable_reason, + }) + } else { + Ok(()) + } + } + + fn check_opaque(&mut self, subs: &mut Subs, opaque: Symbol, ability: Symbol) -> ReadCache { + let impl_key = ImplKey { opaque, ability }; + let derive_key = DeriveKey { opaque, ability }; + + match self.pending_derives.0.get(&derive_key) { + Some(&(opaque_real_var, derive_region)) => { + if self.dominated_derives.contains_key(&derive_key) { + // We have a derive, but also a custom implementation. The custom + // implementation takes priority because we'll use that for codegen. + // We'll report an error for the conflict, and whether the derive is + // legal will be checked out-of-band. + self.check_impl(impl_key); + ReadCache::Impl + } else { + // Only a derive + self.check_derive(subs, derive_key, opaque_real_var, derive_region); + ReadCache::Derive + } + } + // Only an impl + None => { + self.check_impl(impl_key); + ReadCache::Impl + } + } + } + + fn check_opaque_and_read( + &mut self, + subs: &mut Subs, + opaque: Symbol, + ability: Symbol, + ) -> &ObligationResult { + match self.check_opaque(subs, opaque, ability) { + ReadCache::Impl => self.impl_cache.get(&ImplKey { opaque, ability }).unwrap(), + ReadCache::Derive => self + .derive_cache + .get(&DeriveKey { opaque, ability }) + .unwrap(), + } + } + + fn check_impl(&mut self, impl_key: ImplKey) { + if self.impl_cache.get(&impl_key).is_some() { + return; + } + + let ImplKey { opaque, ability } = impl_key; + + let members_of_ability = self.abilities_store.members_of_ability(ability).unwrap(); + let mut missing_members = Vec::new(); + for &member in members_of_ability { + if self + .abilities_store + .get_specialization(member, opaque) + .is_none() + { + let root_data = self.abilities_store.member_def(member).unwrap(); + missing_members.push(Loc::at(root_data.region, member)); + } + } + + let obligation_result = if !missing_members.is_empty() { + Err(Unfulfilled::Incomplete { + typ: opaque, + ability, + missing_members, + }) + } else { + Ok(()) + }; + + self.impl_cache.insert(impl_key, obligation_result); + } + + fn check_derive( + &mut self, + subs: &mut Subs, + derive_key: DeriveKey, + opaque_real_var: Variable, + derive_region: Region, + ) { + if self.derive_cache.get(&derive_key).is_some() { + return; + } + + // The opaque may be recursive, so make sure we stop if we see it again. + // We need to enforce that on both the impl and derive cache. + let fake_fulfilled = Ok(()); + // If this opaque both derives and specializes, we may already know whether the + // specialization fulfills or not. Since specializations take priority over derives, we + // want to keep that result around. + let impl_key = ImplKey { + opaque: derive_key.opaque, + ability: derive_key.ability, + }; + let opt_specialization_result = self.impl_cache.insert(impl_key, fake_fulfilled.clone()); + let is_dominated = self.dominated_derives.contains_key(&derive_key); + debug_assert!( + opt_specialization_result.is_none() || is_dominated, + "This derive also has a specialization but it's not marked as dominated!" + ); + + let old_deriving = self.derive_cache.insert(derive_key, fake_fulfilled.clone()); + debug_assert!( + old_deriving.is_none(), + "Already knew deriving result, but here anyway" + ); + + // Now we check whether the structural type behind the opaque is derivable, since that's + // what we'll need to generate an implementation for during codegen. + let real_var_result = self.check_adhoc(subs, opaque_real_var, derive_key.ability); + + let root_result = real_var_result.map_err(|err| match err { + // Promote the failure, which should be related to a structural type not being + // derivable for the ability, to a failure regarding the opaque in particular. + Unfulfilled::AdhocUnderivable { + typ, + ability, + reason, + } => Unfulfilled::OpaqueUnderivable { + typ, + ability, + reason, + opaque: derive_key.opaque, + derive_region, + }, + _ => internal_error!("unexpected underivable result"), + }); + + // Remove the derive result because the specialization check should take priority. + let check_has_fake = self.impl_cache.remove(&impl_key); + debug_assert_eq!(check_has_fake.map(|(_, b)| b), Some(fake_fulfilled.clone())); + + if let Some(specialization_result) = opt_specialization_result { + self.impl_cache.insert(impl_key, specialization_result); + } + + // Make sure we fix-up with the correct result of the check. + let check_has_fake = self.derive_cache.insert(derive_key, root_result); + debug_assert_eq!(check_has_fake, Some(fake_fulfilled)); + } + + // If we have a lot of these, consider using a visitor. + // It will be very similar for most types (can't derive functions, can't derive unbound type + // variables, can only derive opaques if they have an impl, etc). + fn can_derive_encoding(&mut self, subs: &mut Subs, var: Variable) -> Result<(), Variable> { + let mut stack = vec![var]; + let mut seen_recursion_vars = vec![]; + + macro_rules! push_var_slice { + ($slice:expr) => { + stack.extend(subs.get_subs_slice($slice)) + }; + } + + while let Some(var) = stack.pop() { + if seen_recursion_vars.contains(&var) { + continue; + } + + let content = subs.get_content_without_compacting(var); + + use Content::*; + use FlatType::*; + match content { + FlexVar(_) | RigidVar(_) => return Err(var), + FlexAbleVar(_, ability) | RigidAbleVar(_, ability) => { + if *ability != Symbol::ENCODE_ENCODING { + return Err(var); + } + // Any concrete type this variables is instantiated with will also gain a "does + // implement" check so this is okay. + } + RecursionVar { + structure, + opt_name: _, + } => { + seen_recursion_vars.push(var); + stack.push(*structure); + } + Structure(flat_type) => match flat_type { + Apply( + Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR, + vars, + ) => push_var_slice!(*vars), + Apply(..) => return Err(var), + Func(..) => { + return Err(var); + } + Record(fields, var) => { + push_var_slice!(fields.variables()); + stack.push(*var); + } + TagUnion(tags, ext_var) => { + for i in tags.variables() { + push_var_slice!(subs[i]); + } + stack.push(*ext_var); + } + FunctionOrTagUnion(_, _, var) => stack.push(*var), + RecursiveTagUnion(rec_var, tags, ext_var) => { + seen_recursion_vars.push(*rec_var); + for i in tags.variables() { + push_var_slice!(subs[i]); + } + stack.push(*ext_var); + } + EmptyRecord | EmptyTagUnion => { + // yes + } + Erroneous(_) => return Err(var), + }, + Alias(name, _, _, AliasKind::Opaque) => { + let opaque = *name; + if self + .check_opaque_and_read(subs, opaque, Symbol::ENCODE_ENCODING) + .is_err() + { + return Err(var); + } + } + Alias(_, arguments, real_type_var, _) => { + push_var_slice!(arguments.all_variables()); + stack.push(*real_type_var); + } + RangedNumber(..) => { + // yes, all numbers can + } + Error => { + return Err(var); + } + } + } + + Ok(()) } } /// Determines what type implements an ability member of a specialized signature, given the /// [MustImplementAbility] constraints of the signature. -pub fn type_implementing_member( +pub fn type_implementing_specialization( specialization_must_implement_constraints: &MustImplementConstraints, ability: Symbol, -) -> Option { +) -> Option { debug_assert!({ specialization_must_implement_constraints .clone() @@ -188,12 +603,21 @@ pub fn type_implementing_member( .map(|mia| mia.typ) } +/// Result of trying to resolve an ability specialization. +#[derive(Clone, Copy)] +pub enum Resolved { + /// A user-defined specialization should be used. + Specialization(Symbol), + /// A specialization must be generated. + NeedsGenerated, +} + pub fn resolve_ability_specialization( subs: &mut Subs, abilities_store: &AbilitiesStore, ability_member: Symbol, specialization_var: Variable, -) -> Option { +) -> Option { use roc_unify::unify::{unify, Mode}; let member_def = abilities_store @@ -201,18 +625,33 @@ pub fn resolve_ability_specialization( .expect("Not an ability member symbol"); let snapshot = subs.snapshot(); - instantiate_rigids(subs, member_def.signature_var); - let (_, must_implement_ability) = - unify(subs, specialization_var, member_def.signature_var, Mode::EQ).expect_success( + + let signature_var = member_def + .signature_var() + .unwrap_or_else(|| internal_error!("Signature var not resolved for {:?}", ability_member)); + + instantiate_rigids(subs, signature_var); + let (_, must_implement_ability) = unify(subs, specialization_var, signature_var, Mode::EQ) + .expect_success( "If resolving a specialization, the specialization must be known to typecheck.", ); subs.rollback_to(snapshot); - let specializing_type = - type_implementing_member(&must_implement_ability, member_def.parent_ability)?; + let obligated = + type_implementing_specialization(&must_implement_ability, member_def.parent_ability)?; - let specialization = abilities_store.get_specialization(ability_member, specializing_type)?; + let resolved = match obligated { + Obligated::Opaque(symbol) => { + let specialization = abilities_store.get_specialization(ability_member, symbol)?; - Some(specialization.symbol) + Resolved::Specialization(specialization.symbol) + } + Obligated::Adhoc(_) => { + // TODO: more rules need to be validated here, like is this a builtin ability? + Resolved::NeedsGenerated + } + }; + + Some(resolved) } diff --git a/compiler/solve/src/module.rs b/compiler/solve/src/module.rs index 93570bb62e..17da080633 100644 --- a/compiler/solve/src/module.rs +++ b/compiler/solve/src/module.rs @@ -1,6 +1,7 @@ use crate::solve::{self, Aliases}; -use roc_can::abilities::AbilitiesStore; +use roc_can::abilities::{AbilitiesStore, SolvedSpecializations}; use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; +use roc_can::expr::PendingDerives; use roc_can::module::RigidVariables; use roc_collections::all::MutMap; use roc_module::symbol::Symbol; @@ -20,11 +21,15 @@ pub struct SolvedModule { /// to create the types for HostExposed. This /// has some overlap with the StorageSubs fields, /// so maybe we can get rid of this at some point + /// + /// Contains both variables of symbols that are explicitly exposed by the header, + /// and the variables of any solved ability specializations we have. pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>, /// Used when importing this module into another module pub stored_vars_by_symbol: Vec<(Symbol, Variable)>, pub storage_subs: StorageSubs, + pub solved_specializations: SolvedSpecializations, } pub fn run_solve( @@ -34,6 +39,7 @@ pub fn run_solve( mut subs: Subs, mut aliases: Aliases, mut abilities_store: AbilitiesStore, + pending_derives: PendingDerives, ) -> ( Solved, solve::Env, @@ -63,6 +69,7 @@ pub fn run_solve( subs, &mut aliases, &constraint, + pending_derives, &mut abilities_store, ); diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 374db1b1e8..22ff28a81f 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1,14 +1,17 @@ use crate::ability::{ - resolve_ability_specialization, type_implementing_member, AbilityImplError, - DeferredMustImplementAbility, + resolve_ability_specialization, type_implementing_specialization, AbilityImplError, + DeferredObligations, DeriveKey, PendingDerivesTable, Resolved, Unfulfilled, }; use bumpalo::Bump; use roc_can::abilities::{AbilitiesStore, MemberSpecialization}; use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::{Constraints, Cycle, LetConstraint, OpportunisticResolve}; use roc_can::expected::{Expected, PExpected}; +use roc_can::expr::PendingDerives; use roc_collections::all::MutMap; -use roc_debug_flags::{dbg_do, ROC_VERIFY_RIGID_LET_GENERALIZED}; +use roc_debug_flags::dbg_do; +#[cfg(debug_assertions)] +use roc_debug_flags::ROC_VERIFY_RIGID_LET_GENERALIZED; use roc_error_macros::internal_error; use roc_module::ident::TagName; use roc_module::symbol::Symbol; @@ -24,7 +27,7 @@ use roc_types::types::{ gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, ErrorType, OptAbleType, OptAbleVar, PatternCategory, Reason, TypeExtension, }; -use roc_unify::unify::{unify, Mode, Unified::*}; +use roc_unify::unify::{unify, Mode, Obligated, Unified::*}; // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // https://github.com/elm/compiler @@ -76,15 +79,6 @@ use roc_unify::unify::{unify, Mode, Unified::*}; // Ranks are used to limit the number of type variables considered for generalization. Only those inside // of the let (so those used in inferring the type of `\x -> x`) are considered. -#[derive(PartialEq, Debug, Clone)] -pub struct IncompleteAbilityImplementation { - // TODO(abilities): have general types here, not just opaques - pub typ: Symbol, - pub ability: Symbol, - pub specialized_members: Vec>, - pub missing_members: Vec>, -} - #[derive(Debug, Clone)] pub enum TypeError { BadExpr(Region, Category, ErrorType, Expected), @@ -93,20 +87,22 @@ pub enum TypeError { CircularDef(Vec), BadType(roc_types::types::Problem), UnexposedLookup(Symbol), - IncompleteAbilityImplementation(IncompleteAbilityImplementation), - BadExprMissingAbility( - Region, - Category, - ErrorType, - Vec, - ), - BadPatternMissingAbility( - Region, - PatternCategory, - ErrorType, - Vec, - ), + UnfulfilledAbility(Unfulfilled), + BadExprMissingAbility(Region, Category, ErrorType, Vec), + BadPatternMissingAbility(Region, PatternCategory, ErrorType, Vec), Exhaustive(roc_exhaustive::Error), + StructuralSpecialization { + region: Region, + typ: ErrorType, + ability: Symbol, + member: Symbol, + }, + DominatedDerive { + opaque: Symbol, + ability: Symbol, + derive_region: Region, + impl_region: Region, + }, } use roc_types::types::Alias; @@ -297,7 +293,7 @@ impl Aliases { arena: &bumpalo::Bump, symbol: Symbol, alias_variables: AliasVariables, - ) -> Result<(Variable, AliasKind), ()> { + ) -> (Variable, AliasKind) { // hardcoded instantiations for builtin aliases if let Some((var, kind)) = Self::instantiate_builtin_aliases_real_var( self, @@ -307,12 +303,15 @@ impl Aliases { symbol, alias_variables, ) { - return Ok((var, kind)); + return (var, kind); } let (typ, delayed_variables, &mut kind) = match self.aliases.iter_mut().find(|(s, _, _, _)| *s == symbol) { - None => return Err(()), + None => internal_error!( + "Alias not registered in delayed aliases! {:?}", + &self.aliases + ), Some((_, typ, delayed_variables, kind)) => (typ, delayed_variables, kind), }; @@ -382,7 +381,7 @@ impl Aliases { } } - Ok((alias_variable, kind)) + (alias_variable, kind) } } @@ -426,7 +425,7 @@ impl Env { const DEFAULT_POOLS: usize = 8; #[derive(Clone, Debug)] -struct Pools(Vec>); +pub(crate) struct Pools(Vec>); impl Default for Pools { fn default() -> Self { @@ -489,6 +488,7 @@ pub fn run( mut subs: Subs, aliases: &mut Aliases, constraint: &Constraint, + pending_derives: PendingDerives, abilities_store: &mut AbilitiesStore, ) -> (Solved, Env) { let env = run_in_place( @@ -497,6 +497,7 @@ pub fn run( &mut subs, aliases, constraint, + pending_derives, abilities_store, ); @@ -510,6 +511,7 @@ fn run_in_place( subs: &mut Subs, aliases: &mut Aliases, constraint: &Constraint, + pending_derives: PendingDerives, abilities_store: &mut AbilitiesStore, ) -> Env { let mut pools = Pools::default(); @@ -521,7 +523,8 @@ fn run_in_place( let rank = Rank::toplevel(); let arena = Bump::new(); - let mut deferred_must_implement_abilities = DeferredMustImplementAbility::default(); + let pending_derives = PendingDerivesTable::new(subs, aliases, pending_derives); + let mut deferred_obligations = DeferredObligations::new(pending_derives); let state = solve( &arena, @@ -534,12 +537,14 @@ fn run_in_place( subs, constraint, abilities_store, - &mut deferred_must_implement_abilities, + &mut deferred_obligations, ); // Now that the module has been solved, we can run through and check all - // types claimed to implement abilities. - problems.extend(deferred_must_implement_abilities.check(subs, abilities_store)); + // types claimed to implement abilities. This will also tell us what derives + // are legal, which we need to register. + let (obligation_problems, _derived) = deferred_obligations.check_all(subs, abilities_store); + problems.extend(obligation_problems); state.env } @@ -592,7 +597,7 @@ fn solve( subs: &mut Subs, constraint: &Constraint, abilities_store: &mut AbilitiesStore, - deferred_must_implement_abilities: &mut DeferredMustImplementAbility, + deferred_obligations: &mut DeferredObligations, ) -> State { let initial = Work::Constraint { env: &Env::default(), @@ -652,7 +657,7 @@ fn solve( rank, abilities_store, problems, - deferred_must_implement_abilities, + deferred_obligations, *symbol, *loc_var, ); @@ -757,7 +762,7 @@ fn solve( rank, abilities_store, problems, - deferred_must_implement_abilities, + deferred_obligations, *symbol, *loc_var, ); @@ -812,7 +817,7 @@ fn solve( } => { introduce(subs, rank, pools, &vars); if !must_implement_ability.is_empty() { - deferred_must_implement_abilities.add( + deferred_obligations.add( must_implement_ability, AbilityImplError::BadExpr(*region, category.clone(), actual), ); @@ -919,7 +924,7 @@ fn solve( } => { introduce(subs, rank, pools, &vars); if !must_implement_ability.is_empty() { - deferred_must_implement_abilities.add( + deferred_obligations.add( must_implement_ability, AbilityImplError::BadExpr( *region, @@ -996,7 +1001,7 @@ fn solve( } => { introduce(subs, rank, pools, &vars); if !must_implement_ability.is_empty() { - deferred_must_implement_abilities.add( + deferred_obligations.add( must_implement_ability, AbilityImplError::BadPattern(*region, category.clone(), actual), ); @@ -1158,7 +1163,7 @@ fn solve( } => { introduce(subs, rank, pools, &vars); if !must_implement_ability.is_empty() { - deferred_must_implement_abilities.add( + deferred_obligations.add( must_implement_ability, AbilityImplError::BadPattern( *region, @@ -1374,12 +1379,14 @@ fn solve( member, specialization_id, }) => { - if let Some(specialization) = resolve_ability_specialization( - subs, - abilities_store, - member, - specialization_variable, - ) { + if let Some(Resolved::Specialization(specialization)) = + resolve_ability_specialization( + subs, + abilities_store, + member, + specialization_variable, + ) + { abilities_store.insert_resolved(specialization_id, specialization); // We must now refine the current type state to account for this specialization. @@ -1449,14 +1456,13 @@ fn open_tag_union(subs: &mut Subs, var: Variable) { while let Some(var) = stack.pop() { use {Content::*, FlatType::*}; - let mut desc = subs.get(var); + let desc = subs.get(var); if let Structure(TagUnion(tags, ext)) = desc.content { if let Structure(EmptyTagUnion) = subs.get_content_without_compacting(ext) { let new_ext = subs.fresh_unnamed_flex_var(); subs.set_rank(new_ext, desc.rank); let new_union = Structure(TagUnion(tags, new_ext)); - desc.content = new_union; - subs.set(var, desc); + subs.set_content(var, new_union); } // Also open up all nested tag unions. @@ -1485,7 +1491,7 @@ fn check_ability_specialization( rank: Rank, abilities_store: &mut AbilitiesStore, problems: &mut Vec, - deferred_must_implement_abilities: &mut DeferredMustImplementAbility, + deferred_obligations: &mut DeferredObligations, symbol: Symbol, symbol_loc_var: Loc, ) { @@ -1493,7 +1499,10 @@ fn check_ability_specialization( // inferred type for the specialization actually aligns with the expected // implementation. if let Some((root_symbol, root_data)) = abilities_store.root_name_and_def(symbol) { - let root_signature_var = root_data.signature_var; + let root_signature_var = root_data + .signature_var() + .unwrap_or_else(|| internal_error!("Signature var not resolved for {:?}", root_symbol)); + let parent_ability = root_data.parent_ability; // Check if they unify - if they don't, then the claimed specialization isn't really one, // and that's a type error! @@ -1508,63 +1517,90 @@ fn check_ability_specialization( let unified = unify(subs, symbol_loc_var.value, root_signature_var, Mode::EQ); match unified { - Success { - vars: _, - must_implement_ability, - } if must_implement_ability.is_empty() => { - // This can happen when every ability constriant on a type variable went - // through only another type variable. That means this def is not specialized - // for one type - for now, we won't admit this. - - // Rollback the snapshot so we unlink the root signature with the specialization, - // so we can have two separate error types. - subs.rollback_to(snapshot); - - let (expected_type, _problems) = subs.var_to_error_type(root_signature_var); - let (actual_type, _problems) = subs.var_to_error_type(symbol_loc_var.value); - - let reason = Reason::GeneralizedAbilityMemberSpecialization { - member_name: root_symbol, - def_region: root_data.region, - }; - - let problem = TypeError::BadExpr( - symbol_loc_var.region, - Category::AbilityMemberSpecialization(root_symbol), - actual_type, - Expected::ForReason(reason, expected_type, symbol_loc_var.region), - ); - - problems.push(problem); - } - Success { vars, must_implement_ability, } => { - subs.commit_snapshot(snapshot); - introduce(subs, rank, pools, &vars); - - // First, figure out and register for what type does this symbol specialize - // the ability member. let specialization_type = - type_implementing_member(&must_implement_ability, root_data.parent_ability) - .expect("checked in previous branch"); - let specialization = MemberSpecialization { - symbol, - region: symbol_loc_var.region, - }; - abilities_store.register_specialization_for_type( - root_symbol, - specialization_type, - specialization, - ); + type_implementing_specialization(&must_implement_ability, parent_ability); - // Store the checks for what abilities must be implemented to be checked after the - // whole module is complete. - deferred_must_implement_abilities - .add(must_implement_ability, AbilityImplError::IncompleteAbility); + match specialization_type { + Some(Obligated::Opaque(opaque)) => { + // This is a specialization for an opaque - that's allowed. + + subs.commit_snapshot(snapshot); + introduce(subs, rank, pools, &vars); + + let specialization_region = symbol_loc_var.region; + let specialization = MemberSpecialization { + symbol, + region: specialization_region, + }; + abilities_store.register_specialization_for_type( + root_symbol, + opaque, + specialization, + ); + + // Make sure we check that the opaque has specialized all members of the + // ability, after we finish solving the module. + deferred_obligations + .add(must_implement_ability, AbilityImplError::IncompleteAbility); + // This specialization dominates any derives that might be present. + deferred_obligations.dominate( + DeriveKey { + opaque, + ability: parent_ability, + }, + specialization_region, + ); + } + Some(Obligated::Adhoc(var)) => { + // This is a specialization of a structural type - never allowed. + + // Commit so that `var` persists in subs. + subs.commit_snapshot(snapshot); + + let (typ, _problems) = subs.var_to_error_type(var); + + let problem = TypeError::StructuralSpecialization { + region: symbol_loc_var.region, + typ, + ability: parent_ability, + member: root_symbol, + }; + + problems.push(problem); + } + None => { + // This can happen when every ability constriant on a type variable went + // through only another type variable. That means this def is not specialized + // for one concrete type - we won't admit this. + + // Rollback the snapshot so we unlink the root signature with the specialization, + // so we can have two separate error types. + subs.rollback_to(snapshot); + + let (expected_type, _problems) = subs.var_to_error_type(root_signature_var); + let (actual_type, _problems) = subs.var_to_error_type(symbol_loc_var.value); + + let reason = Reason::GeneralizedAbilityMemberSpecialization { + member_name: root_symbol, + def_region: root_data.region, + }; + + let problem = TypeError::BadExpr( + symbol_loc_var.region, + Category::AbilityMemberSpecialization(root_symbol), + actual_type, + Expected::ForReason(reason, expected_type, symbol_loc_var.region), + ); + + problems.push(problem); + } + } } + Failure(vars, actual_type, expected_type, unimplemented_abilities) => { subs.commit_snapshot(snapshot); introduce(subs, rank, pools, &vars); @@ -1686,7 +1722,7 @@ fn either_type_index_to_var( } } -fn type_to_var( +pub(crate) fn type_to_var( subs: &mut Subs, rank: Rank, pools: &mut Pools, @@ -1818,10 +1854,9 @@ fn type_to_variable<'a>( Variable(_) | EmptyRec | EmptyTagUnion => { unreachable!("This variant should never be deferred!") } - RangedNumber(typ, vars) => { + RangedNumber(typ, range) => { let ty_var = helper!(typ); - let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); - let content = Content::RangedNumber(ty_var, vars); + let content = Content::RangedNumber(ty_var, *range); register_with_known_var(subs, destination, rank, pools, content) } @@ -1998,7 +2033,7 @@ fn type_to_variable<'a>( } }; - let instantiated = aliases.instantiate_real_var( + let (alias_variable, kind) = aliases.instantiate_real_var( subs, rank, pools, @@ -2007,9 +2042,6 @@ fn type_to_variable<'a>( alias_variables, ); - let (alias_variable, kind) = instantiated - .unwrap_or_else(|_| unreachable!("Alias {:?} is not available", symbol)); - let content = Content::Alias(*symbol, alias_variables, alias_variable, kind); register_with_known_var(subs, destination, rank, pools, content) @@ -2483,11 +2515,9 @@ fn check_for_infinite_type( let var = loc_var.value; while let Err((recursive, _chain)) = subs.occurs(var) { - let description = subs.get(recursive); - // try to make a tag union recursive, see if that helps - match description.content { - Content::Structure(FlatType::TagUnion(tags, ext_var)) => { + match subs.get_content_without_compacting(recursive) { + &Content::Structure(FlatType::TagUnion(tags, ext_var)) => { subs.mark_tag_union_recursive(recursive, tags, ext_var); } @@ -2574,8 +2604,10 @@ fn pool_to_rank_table( // the vast majority of young variables have young_rank let mut i = 0; while i < young_vars.len() { - let var = young_vars[i]; - let rank = subs.get_rank_set_mark(var, young_mark); + let var = subs.get_root_key(young_vars[i]); + + subs.set_mark_unchecked(var, young_mark); + let rank = subs.get_rank_unchecked(var); if rank != young_rank { debug_assert!(rank.into_usize() < young_rank.into_usize() + 1); @@ -2603,27 +2635,24 @@ fn adjust_rank( group_rank: Rank, var: Variable, ) -> Rank { - let desc = subs.get_ref_mut(var); + let var = subs.get_root_key(var); - let desc_rank = desc.rank; - let desc_mark = desc.mark; + let desc_rank = subs.get_rank_unchecked(var); + let desc_mark = subs.get_mark_unchecked(var); if desc_mark == young_mark { - // SAFETY: in this function (and functions it calls, we ONLY modify rank and mark, never content! - // hence, we can have an immutable reference to it even though we also have a mutable - // reference to the Subs as a whole. This prevents a clone of the content, which turns out - // to be quite expensive. let content = { - let ptr = &desc.content as *const _; + let ptr = subs.get_content_unchecked(var) as *const _; unsafe { &*ptr } }; // Mark the variable as visited before adjusting content, as it may be cyclic. - desc.mark = visit_mark; + subs.set_mark_unchecked(var, visit_mark); let max_rank = adjust_rank_content(subs, young_mark, visit_mark, group_rank, content); - subs.set_rank_mark(var, max_rank, visit_mark); + subs.set_rank_unchecked(var, max_rank); + subs.set_mark_unchecked(var, visit_mark); max_rank } else if desc_mark == visit_mark { @@ -2634,8 +2663,8 @@ fn adjust_rank( let min_rank = group_rank.min(desc_rank); // TODO from elm-compiler: how can min_rank ever be group_rank? - desc.rank = min_rank; - desc.mark = visit_mark; + subs.set_rank_unchecked(var, min_rank); + subs.set_mark_unchecked(var, visit_mark); min_rank } @@ -2873,19 +2902,20 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { while let Some(var) = stack.pop() { visited.push(var); - let desc = subs.get_ref_mut(var); - if desc.copy.is_some() { + if subs.get_copy(var).is_some() { continue; } - desc.rank = Rank::NONE; - desc.mark = Mark::NONE; - desc.copy = OptVariable::from(var); + subs.modify(var, |desc| { + desc.rank = Rank::NONE; + desc.mark = Mark::NONE; + desc.copy = OptVariable::from(var); + }); use Content::*; use FlatType::*; - match &desc.content { + match subs.get_content_without_compacting(var) { RigidVar(name) => { // what it's all about: convert the rigid var into a flex var let name = *name; @@ -2893,17 +2923,25 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { // NOTE: we must write to the mutually borrowed `desc` value here // using `subs.set` does not work (unclear why, really) // but get_ref_mut approach saves a lookup, so the weirdness is worth it - desc.content = FlexVar(Some(name)); - desc.rank = max_rank; - desc.mark = Mark::NONE; - desc.copy = OptVariable::NONE; + subs.modify(var, |d| { + *d = Descriptor { + content: FlexVar(Some(name)), + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + } + }) } &RigidAbleVar(name, ability) => { // Same as `RigidVar` above - desc.content = FlexAbleVar(Some(name), ability); - desc.rank = max_rank; - desc.mark = Mark::NONE; - desc.copy = OptVariable::NONE; + subs.modify(var, |d| { + *d = Descriptor { + content: FlexAbleVar(Some(name), ability), + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + } + }) } FlexVar(_) | FlexAbleVar(_, _) | Error => (), @@ -2976,10 +3014,8 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { stack.push(var); } - &RangedNumber(typ, vars) => { + &RangedNumber(typ, _) => { stack.push(typ); - - stack.extend(var_slice!(vars)); } } } @@ -2987,13 +3023,13 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) { // we have tracked all visited variables, and can now traverse them // in one go (without looking at the UnificationTable) and clear the copy field for var in visited { - let descriptor = subs.get_ref_mut(var); - - if descriptor.copy.is_some() { - descriptor.rank = Rank::NONE; - descriptor.mark = Mark::NONE; - descriptor.copy = OptVariable::NONE; - } + subs.modify(var, |descriptor| { + if descriptor.copy.is_some() { + descriptor.rank = Rank::NONE; + descriptor.mark = Mark::NONE; + descriptor.copy = OptVariable::NONE; + } + }); } } @@ -3006,27 +3042,35 @@ fn deep_copy_var_in( ) -> Variable { let mut visited = bumpalo::collections::Vec::with_capacity_in(256, arena); - let copy = deep_copy_var_help(subs, rank, pools, &mut visited, var); + let pool = pools.get_mut(rank); + let copy = deep_copy_var_help(subs, rank, pool, &mut visited, var); // we have tracked all visited variables, and can now traverse them // in one go (without looking at the UnificationTable) and clear the copy field for var in visited { - let descriptor = subs.get_ref_mut(var); - - if descriptor.copy.is_some() { - descriptor.rank = Rank::NONE; - descriptor.mark = Mark::NONE; - descriptor.copy = OptVariable::NONE; - } + subs.set_copy_unchecked(var, OptVariable::NONE); } copy } +#[inline] +fn has_trivial_copy(subs: &Subs, root_var: Variable) -> Option { + let existing_copy = subs.get_copy_unchecked(root_var); + + if let Some(copy) = existing_copy.into_variable() { + Some(copy) + } else if subs.get_rank_unchecked(root_var) != Rank::NONE { + Some(root_var) + } else { + None + } +} + fn deep_copy_var_help( subs: &mut Subs, max_rank: Rank, - pools: &mut Pools, + pool: &mut Vec, visited: &mut bumpalo::collections::Vec<'_, Variable>, var: Variable, ) -> Variable { @@ -3034,43 +3078,54 @@ fn deep_copy_var_help( use roc_types::subs::FlatType::*; let subs_len = subs.len(); - let desc = subs.get_ref_mut(var); + let var = subs.get_root_key(var); - if let Some(copy) = desc.copy.into_variable() { + // either this variable has been copied before, or does not have NONE rank + if let Some(copy) = has_trivial_copy(subs, var) { return copy; - } else if desc.rank != Rank::NONE { - return var; } - visited.push(var); - - let make_descriptor = |content| Descriptor { - content, - rank: max_rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }; - - let content = desc.content; - // Safety: Here we make a variable that is 1 position out of bounds. // The reason is that we can now keep the mutable reference to `desc` // Below, we actually push a new variable onto subs meaning the `copy` // variable is in-bounds before it is ever used. let copy = unsafe { Variable::from_index(subs_len as u32) }; - pools.get_mut(max_rank).push(copy); + visited.push(var); + pool.push(copy); // Link the original variable to the new variable. This lets us // avoid making multiple copies of the variable we are instantiating. // // Need to do this before recursively copying to avoid looping. - desc.mark = Mark::NONE; - desc.copy = copy.into(); + subs.set_mark_unchecked(var, Mark::NONE); + subs.set_copy_unchecked(var, copy.into()); - let actual_copy = subs.fresh(make_descriptor(content)); + let content = *subs.get_content_unchecked(var); + + let copy_descriptor = Descriptor { + content, + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + let actual_copy = subs.fresh(copy_descriptor); debug_assert_eq!(copy, actual_copy); + macro_rules! copy_sequence { + ($length:expr, $variables:expr) => {{ + let new_variables = SubsSlice::reserve_into_subs(subs, $length as _); + for (target_index, var_index) in (new_variables.indices()).zip($variables) { + let var = subs[var_index]; + let copy_var = deep_copy_var_help(subs, max_rank, pool, visited, var); + subs.variables[target_index] = copy_var; + } + + new_variables + }}; + } + // Now we recursively copy the content of the variable. // We have already marked the variable as copied, so we // will not repeat this work or crawl this variable again. @@ -3078,27 +3133,17 @@ fn deep_copy_var_help( Structure(flat_type) => { let new_flat_type = match flat_type { Apply(symbol, arguments) => { - let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { - let var = subs[var_index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var); - subs.variables[target_index] = copy_var; - } + let new_arguments = copy_sequence!(arguments.len(), arguments); Apply(symbol, new_arguments) } Func(arguments, closure_var, ret_var) => { - let new_ret_var = deep_copy_var_help(subs, max_rank, pools, visited, ret_var); + let new_ret_var = deep_copy_var_help(subs, max_rank, pool, visited, ret_var); let new_closure_var = - deep_copy_var_help(subs, max_rank, pools, visited, closure_var); + deep_copy_var_help(subs, max_rank, pool, visited, closure_var); - let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { - let var = subs[var_index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var); - subs.variables[target_index] = copy_var; - } + let new_arguments = copy_sequence!(arguments.len(), arguments); Func(new_arguments, new_closure_var, new_ret_var) } @@ -3107,15 +3152,7 @@ fn deep_copy_var_help( Record(fields, ext_var) => { let record_fields = { - let new_variables = - VariableSubsSlice::reserve_into_subs(subs, fields.len()); - - let it = (new_variables.indices()).zip(fields.iter_variables()); - for (target_index, var_index) in it { - let var = subs[var_index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var); - subs.variables[target_index] = copy_var; - } + let new_variables = copy_sequence!(fields.len(), fields.iter_variables()); RecordFields { length: fields.length, @@ -3127,7 +3164,7 @@ fn deep_copy_var_help( Record( record_fields, - deep_copy_var_help(subs, max_rank, pools, visited, ext_var), + deep_copy_var_help(subs, max_rank, pool, visited, ext_var), ) } @@ -3138,27 +3175,20 @@ fn deep_copy_var_help( for (target_index, index) in it { let slice = subs[index]; - let new_variables = VariableSubsSlice::reserve_into_subs(subs, slice.len()); - let it = (new_variables.indices()).zip(slice); - for (target_index, var_index) in it { - let var = subs[var_index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var); - subs.variables[target_index] = copy_var; - } - + let new_variables = copy_sequence!(slice.len(), slice); subs.variable_slices[target_index] = new_variables; } let union_tags = UnionTags::from_slices(tags.tag_names(), new_variable_slices); - let new_ext = deep_copy_var_help(subs, max_rank, pools, visited, ext_var); + let new_ext = deep_copy_var_help(subs, max_rank, pool, visited, ext_var); TagUnion(union_tags, new_ext) } FunctionOrTagUnion(tag_name, symbol, ext_var) => FunctionOrTagUnion( tag_name, symbol, - deep_copy_var_help(subs, max_rank, pools, visited, ext_var), + deep_copy_var_help(subs, max_rank, pool, visited, ext_var), ), RecursiveTagUnion(rec_var, tags, ext_var) => { @@ -3168,27 +3198,20 @@ fn deep_copy_var_help( for (target_index, index) in it { let slice = subs[index]; - let new_variables = VariableSubsSlice::reserve_into_subs(subs, slice.len()); - let it = (new_variables.indices()).zip(slice); - for (target_index, var_index) in it { - let var = subs[var_index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var); - subs.variables[target_index] = copy_var; - } - + let new_variables = copy_sequence!(slice.len(), slice); subs.variable_slices[target_index] = new_variables; } let union_tags = UnionTags::from_slices(tags.tag_names(), new_variable_slices); - let new_ext = deep_copy_var_help(subs, max_rank, pools, visited, ext_var); - let new_rec_var = deep_copy_var_help(subs, max_rank, pools, visited, rec_var); + let new_ext = deep_copy_var_help(subs, max_rank, pool, visited, ext_var); + let new_rec_var = deep_copy_var_help(subs, max_rank, pool, visited, rec_var); RecursiveTagUnion(new_rec_var, union_tags, new_ext) } }; - subs.set(copy, make_descriptor(Structure(new_flat_type))); + subs.set_content_unchecked(copy, Structure(new_flat_type)); copy } @@ -3199,41 +3222,33 @@ fn deep_copy_var_help( opt_name, structure, } => { - let new_structure = deep_copy_var_help(subs, max_rank, pools, visited, structure); + let new_structure = deep_copy_var_help(subs, max_rank, pool, visited, structure); - subs.set( - copy, - make_descriptor(RecursionVar { - opt_name, - structure: new_structure, - }), - ); + let content = RecursionVar { + opt_name, + structure: new_structure, + }; + + subs.set_content_unchecked(copy, content); copy } RigidVar(name) => { - subs.set(copy, make_descriptor(FlexVar(Some(name)))); + subs.set_content_unchecked(copy, FlexVar(Some(name))); copy } RigidAbleVar(name, ability) => { - subs.set(copy, make_descriptor(FlexAbleVar(Some(name), ability))); + subs.set_content_unchecked(copy, FlexAbleVar(Some(name), ability)); copy } Alias(symbol, arguments, real_type_var, kind) => { let new_variables = - SubsSlice::reserve_into_subs(subs, arguments.all_variables_len as _); - for (target_index, var_index) in - (new_variables.indices()).zip(arguments.all_variables()) - { - let var = subs[var_index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var); - subs.variables[target_index] = copy_var; - } + copy_sequence!(arguments.all_variables_len, arguments.all_variables()); let new_arguments = AliasVariables { variables_start: new_variables.start, @@ -3241,27 +3256,20 @@ fn deep_copy_var_help( }; let new_real_type_var = - deep_copy_var_help(subs, max_rank, pools, visited, real_type_var); + deep_copy_var_help(subs, max_rank, pool, visited, real_type_var); let new_content = Alias(symbol, new_arguments, new_real_type_var, kind); - subs.set(copy, make_descriptor(new_content)); + subs.set_content_unchecked(copy, new_content); copy } - RangedNumber(typ, range_vars) => { - let new_type_var = deep_copy_var_help(subs, max_rank, pools, visited, typ); + RangedNumber(typ, range) => { + let new_type_var = deep_copy_var_help(subs, max_rank, pool, visited, typ); - let new_vars = SubsSlice::reserve_into_subs(subs, range_vars.len()); - for (target_index, var_index) in (new_vars.indices()).zip(range_vars) { - let var = subs[var_index]; - let copy_var = deep_copy_var_help(subs, max_rank, pools, visited, var); - subs.variables[target_index] = copy_var; - } + let new_content = RangedNumber(new_type_var, range); - let new_content = RangedNumber(new_type_var, new_vars); - - subs.set(copy, make_descriptor(new_content)); + subs.set_content_unchecked(copy, new_content); copy } diff --git a/compiler/solve/tests/helpers/mod.rs b/compiler/solve/tests/helpers/mod.rs index ef782e17a1..9d7f565100 100644 --- a/compiler/solve/tests/helpers/mod.rs +++ b/compiler/solve/tests/helpers/mod.rs @@ -33,7 +33,7 @@ where #[inline(always)] pub fn with_larger_debug_stack(run_test: F) where - F: FnOnce() -> (), + F: FnOnce(), F: Send, F: 'static, { diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 9ce54e72b9..add24ab15a 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -154,7 +154,8 @@ mod solve_expr { mut type_problems, interns, mut solved, - exposed_to_host, + mut exposed_to_host, + abilities_store, .. }, src, @@ -172,6 +173,8 @@ mod solve_expr { let subs = solved.inner_mut(); + exposed_to_host.retain(|s, _| !abilities_store.is_specialization_name(*s)); + debug_assert!(exposed_to_host.len() == 1); let (_symbol, variable) = exposed_to_host.into_iter().next().unwrap(); let actual_str = name_and_print_var(variable, subs, home, &interns); @@ -6426,4 +6429,67 @@ mod solve_expr { &["A#default : {} -> A"], ) } + + #[test] + fn stdlib_encode_json() { + infer_eq_without_problem( + indoc!( + r#" + app "test" + imports [ Encode.{ toEncoder }, Json ] + provides [ main ] to "./platform" + + HelloWorld := {} + + toEncoder = \@HelloWorld {} -> + Encode.custom \bytes, fmt -> + bytes + |> Encode.appendWith (Encode.string "Hello, World!\n") fmt + + main = + when Str.fromUtf8 (Encode.toBytes (@HelloWorld {}) Json.format) is + Ok s -> s + _ -> "" + "# + ), + "Str", + ) + } + + #[test] + fn encode_record() { + infer_queries( + indoc!( + r#" + app "test" + imports [ Encode.{ toEncoder } ] + provides [ main ] to "./platform" + + main = toEncoder { a: "" } + # ^^^^^^^^^ + "# + ), + &["Encoding#toEncoder : { a : Str } -> Encoder fmt | fmt has EncoderFormatting"], + ) + } + + #[test] + fn encode_record_with_nested_custom_impl() { + infer_queries( + indoc!( + r#" + app "test" + imports [ Encode.{ toEncoder, Encoding, custom } ] + provides [ main ] to "./platform" + + A := {} + toEncoder = \@A _ -> custom \b, _ -> b + + main = toEncoder { a: @A {} } + # ^^^^^^^^^ + "# + ), + &["Encoding#toEncoder : { a : A } -> Encoder fmt | fmt has EncoderFormatting"], + ) + } } diff --git a/compiler/str/Cargo.toml b/compiler/str/Cargo.toml index 68e5cfde2e..a59efae3c4 100644 --- a/compiler/str/Cargo.toml +++ b/compiler/str/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_str" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/test_gen/Cargo.toml b/compiler/test_gen/Cargo.toml index ff2e79a7b5..2322900e08 100644 --- a/compiler/test_gen/Cargo.toml +++ b/compiler/test_gen/Cargo.toml @@ -3,7 +3,7 @@ name = "test_gen" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [[test]] name = "test_gen" diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index c88520023e..923e3e3ed7 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -3331,6 +3331,20 @@ fn dec_float_suffix() { ); } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn dec_no_decimal() { + assert_evals_to!( + indoc!( + r#" + 3dec + "# + ), + RocDec::from_str_to_i128_unsafe("3.0"), + i128 + ); +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn ceiling_to_u32() { diff --git a/compiler/test_gen/src/helpers/mod.rs b/compiler/test_gen/src/helpers/mod.rs index 64697024d1..7285adf7df 100644 --- a/compiler/test_gen/src/helpers/mod.rs +++ b/compiler/test_gen/src/helpers/mod.rs @@ -49,7 +49,7 @@ where #[inline(always)] pub fn with_larger_debug_stack(run_test: F) where - F: FnOnce() -> (), + F: FnOnce(), F: Send, F: 'static, { diff --git a/compiler/test_gen/src/tests.rs b/compiler/test_gen/src/tests.rs index eb730c7e96..b5ab6f3633 100644 --- a/compiler/test_gen/src/tests.rs +++ b/compiler/test_gen/src/tests.rs @@ -60,7 +60,6 @@ pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { use roc_gen_llvm::llvm::build::PanicTagId; - use std::convert::TryFrom; use std::ffi::CStr; use std::os::raw::c_char; diff --git a/compiler/test_mono/Cargo.toml b/compiler/test_mono/Cargo.toml index fb06a403be..eb87b5efa8 100644 --- a/compiler/test_mono/Cargo.toml +++ b/compiler/test_mono/Cargo.toml @@ -3,7 +3,7 @@ name = "test_mono" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [[test]] name = "test_mono" diff --git a/compiler/test_mono/src/tests.rs b/compiler/test_mono/src/tests.rs index abdcc5b7b3..39f6f74155 100644 --- a/compiler/test_mono/src/tests.rs +++ b/compiler/test_mono/src/tests.rs @@ -51,7 +51,7 @@ where #[inline(always)] pub fn with_larger_debug_stack(run_test: F) where - F: FnOnce() -> (), + F: FnOnce(), F: Send, F: 'static, { diff --git a/compiler/test_mono_macros/Cargo.toml b/compiler/test_mono_macros/Cargo.toml index 8ddad8c85e..c5889844c5 100644 --- a/compiler/test_mono_macros/Cargo.toml +++ b/compiler/test_mono_macros/Cargo.toml @@ -3,7 +3,7 @@ name = "test_mono_macros" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [lib] proc-macro = true diff --git a/compiler/test_mono_macros/src/lib.rs b/compiler/test_mono_macros/src/lib.rs index 00f023125a..2be3ab4ceb 100644 --- a/compiler/test_mono_macros/src/lib.rs +++ b/compiler/test_mono_macros/src/lib.rs @@ -19,7 +19,7 @@ pub fn mono_test(_args: TokenStream, item: TokenStream) -> TokenStream { let result = quote! { #[test] #(#attributes)* - #visibility fn #name(#args) -> () { + #visibility fn #name(#args) { compiles_to_ir(#name_str, #body); } diff --git a/compiler/types/Cargo.toml b/compiler/types/Cargo.toml index e00c368701..a27e2ad4c9 100644 --- a/compiler/types/Cargo.toml +++ b/compiler/types/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_types" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../collections" } diff --git a/compiler/types/src/lib.rs b/compiler/types/src/lib.rs index 780bed8621..0846e058aa 100644 --- a/compiler/types/src/lib.rs +++ b/compiler/types/src/lib.rs @@ -2,7 +2,9 @@ // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant)] pub mod builtin_aliases; +pub mod num; pub mod pretty_print; pub mod solved_types; pub mod subs; pub mod types; +mod unification_table; diff --git a/compiler/types/src/num.rs b/compiler/types/src/num.rs new file mode 100644 index 0000000000..725ff00fbe --- /dev/null +++ b/compiler/types/src/num.rs @@ -0,0 +1,327 @@ +use crate::subs::Variable; +use roc_module::symbol::Symbol; + +/// A bound placed on a number because of its literal value. +/// e.g. `-5` cannot be unsigned, and 300 does not fit in a U8 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum NumericRange { + IntAtLeastSigned(IntWidth), + IntAtLeastEitherSign(IntWidth), + NumAtLeastSigned(IntWidth), + NumAtLeastEitherSign(IntWidth), +} + +impl NumericRange { + pub fn contains_symbol(&self, symbol: Symbol) -> Option { + let contains = match symbol { + Symbol::NUM_I8 => self.contains_int_width(IntWidth::I8), + Symbol::NUM_U8 => self.contains_int_width(IntWidth::U8), + Symbol::NUM_I16 => self.contains_int_width(IntWidth::I16), + Symbol::NUM_U16 => self.contains_int_width(IntWidth::U16), + Symbol::NUM_I32 => self.contains_int_width(IntWidth::I32), + Symbol::NUM_U32 => self.contains_int_width(IntWidth::U32), + Symbol::NUM_I64 => self.contains_int_width(IntWidth::I64), + Symbol::NUM_NAT => self.contains_int_width(IntWidth::Nat), + Symbol::NUM_U64 => self.contains_int_width(IntWidth::U64), + Symbol::NUM_I128 => self.contains_int_width(IntWidth::I128), + Symbol::NUM_U128 => self.contains_int_width(IntWidth::U128), + + Symbol::NUM_DEC => self.contains_float_width(FloatWidth::Dec), + Symbol::NUM_F32 => self.contains_float_width(FloatWidth::F32), + Symbol::NUM_F64 => self.contains_float_width(FloatWidth::F64), + + Symbol::NUM_NUM | Symbol::NUM_INT | Symbol::NUM_FRAC => { + // these satisfy any range that they are given + true + } + + _ => { + return None; + } + }; + + Some(contains) + } + + fn contains_float_width(&self, _width: FloatWidth) -> bool { + // we don't currently check the float width + true + } + + fn contains_int_width(&self, width: IntWidth) -> bool { + use NumericRange::*; + + let (range_signedness, at_least_width) = match self { + IntAtLeastSigned(width) => (SignDemand::Signed, width), + IntAtLeastEitherSign(width) => (SignDemand::NoDemand, width), + NumAtLeastSigned(width) => (SignDemand::Signed, width), + NumAtLeastEitherSign(width) => (SignDemand::NoDemand, width), + }; + + let (actual_signedness, _) = width.signedness_and_width(); + + if let (IntSignedness::Unsigned, SignDemand::Signed) = (actual_signedness, range_signedness) + { + return false; + } + + width.signedness_and_width().1 >= at_least_width.signedness_and_width().1 + } + + pub fn variable_slice(&self) -> &'static [Variable] { + use NumericRange::*; + + match self { + IntAtLeastSigned(width) => { + let target = int_width_to_variable(*width); + let start = SIGNED_VARIABLES.iter().position(|v| *v == target).unwrap(); + let end = SIGNED_VARIABLES.len() - 3; + + &SIGNED_VARIABLES[start..end] + } + IntAtLeastEitherSign(width) => { + let target = int_width_to_variable(*width); + let start = ALL_VARIABLES.iter().position(|v| *v == target).unwrap(); + let end = ALL_VARIABLES.len() - 3; + + &ALL_VARIABLES[start..end] + } + NumAtLeastSigned(width) => { + let target = int_width_to_variable(*width); + let start = SIGNED_VARIABLES.iter().position(|v| *v == target).unwrap(); + + &SIGNED_VARIABLES[start..] + } + NumAtLeastEitherSign(width) => { + let target = int_width_to_variable(*width); + let start = ALL_VARIABLES.iter().position(|v| *v == target).unwrap(); + + &ALL_VARIABLES[start..] + } + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +enum IntSignedness { + Unsigned, + Signed, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum IntWidth { + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + Nat, +} + +impl IntWidth { + /// Returns the `IntSignedness` and bit width of a variant. + fn signedness_and_width(&self) -> (IntSignedness, u32) { + use IntSignedness::*; + use IntWidth::*; + match self { + U8 => (Unsigned, 8), + U16 => (Unsigned, 16), + U32 => (Unsigned, 32), + U64 => (Unsigned, 64), + U128 => (Unsigned, 128), + I8 => (Signed, 8), + I16 => (Signed, 16), + I32 => (Signed, 32), + I64 => (Signed, 64), + I128 => (Signed, 128), + // TODO: this is platform specific! + Nat => (Unsigned, 64), + } + } + + pub fn type_str(&self) -> &'static str { + use IntWidth::*; + match self { + U8 => "U8", + U16 => "U16", + U32 => "U32", + U64 => "U64", + U128 => "U128", + I8 => "I8", + I16 => "I16", + I32 => "I32", + I64 => "I64", + I128 => "I128", + Nat => "Nat", + } + } + + pub fn max_value(&self) -> u128 { + use IntWidth::*; + match self { + U8 => u8::MAX as u128, + U16 => u16::MAX as u128, + U32 => u32::MAX as u128, + U64 => u64::MAX as u128, + U128 => u128::MAX, + I8 => i8::MAX as u128, + I16 => i16::MAX as u128, + I32 => i32::MAX as u128, + I64 => i64::MAX as u128, + I128 => i128::MAX as u128, + // TODO: this is platform specific! + Nat => u64::MAX as u128, + } + } + + pub fn min_value(&self) -> i128 { + use IntWidth::*; + match self { + U8 | U16 | U32 | U64 | U128 | Nat => 0, + I8 => i8::MIN as i128, + I16 => i16::MIN as i128, + I32 => i32::MIN as i128, + I64 => i64::MIN as i128, + I128 => i128::MIN, + } + } + + /// Checks if `self` represents superset of integers that `lower_bound` represents, on a particular + /// side of the integers relative to 0. + /// + /// If `is_negative` is true, the negative side is checked; otherwise the positive side is checked. + pub fn is_superset(&self, lower_bound: &Self, is_negative: bool) -> bool { + use IntSignedness::*; + + if is_negative { + match ( + self.signedness_and_width(), + lower_bound.signedness_and_width(), + ) { + ((Signed, us), (Signed, lower_bound)) => us >= lower_bound, + // Unsigned ints can never represent negative numbers; signed (non-zero width) + // ints always can. + ((Unsigned, _), (Signed, _)) => false, + ((Signed, _), (Unsigned, _)) => true, + // Trivially true; both can only express 0. + ((Unsigned, _), (Unsigned, _)) => true, + } + } else { + match ( + self.signedness_and_width(), + lower_bound.signedness_and_width(), + ) { + ((Signed, us), (Signed, lower_bound)) + | ((Unsigned, us), (Unsigned, lower_bound)) => us >= lower_bound, + + // Unsigned ints with the same bit width as their unsigned counterparts can always + // express 2x more integers on the positive side as unsigned ints. + ((Unsigned, us), (Signed, lower_bound)) => us >= lower_bound, + + // ...but that means signed int widths can represent less than their unsigned + // counterparts, so the below is true iff the bit width is strictly greater. E.g. + // i16 is a superset of u8, but i16 is not a superset of u16. + ((Signed, us), (Unsigned, lower_bound)) => us > lower_bound, + } + } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum FloatWidth { + Dec, + F32, + F64, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum SignDemand { + /// Can be signed or unsigned. + NoDemand, + /// Must be signed. + Signed, +} + +/// Describes a bound on the width of an integer. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum IntBound { + /// There is no bound on the width. + None, + /// Must have an exact width. + Exact(IntWidth), + /// Must have a certain sign and a minimum width. + AtLeast { sign: SignDemand, width: IntWidth }, +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum FloatBound { + None, + Exact(FloatWidth), +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum NumBound { + None, + /// Must be an integer of a certain size, or any float. + AtLeastIntOrFloat { + sign: SignDemand, + width: IntWidth, + }, +} + +pub const fn int_width_to_variable(w: IntWidth) -> Variable { + match w { + IntWidth::U8 => Variable::U8, + IntWidth::U16 => Variable::U16, + IntWidth::U32 => Variable::U32, + IntWidth::U64 => Variable::U64, + IntWidth::U128 => Variable::U128, + IntWidth::I8 => Variable::I8, + IntWidth::I16 => Variable::I16, + IntWidth::I32 => Variable::I32, + IntWidth::I64 => Variable::I64, + IntWidth::I128 => Variable::I128, + IntWidth::Nat => Variable::NAT, + } +} + +pub const fn float_width_to_variable(w: FloatWidth) -> Variable { + match w { + FloatWidth::Dec => Variable::DEC, + FloatWidth::F32 => Variable::F32, + FloatWidth::F64 => Variable::F64, + } +} + +const ALL_VARIABLES: &[Variable] = &[ + Variable::I8, + Variable::U8, + Variable::I16, + Variable::U16, + Variable::I32, + Variable::U32, + Variable::I64, + Variable::NAT, // FIXME: Nat's order here depends on the platfor, + Variable::U64, + Variable::I128, + Variable::U128, + Variable::F32, + Variable::F64, + Variable::DEC, +]; + +const SIGNED_VARIABLES: &[Variable] = &[ + Variable::I8, + Variable::I16, + Variable::I32, + Variable::I64, + Variable::I128, + Variable::F32, + Variable::F64, + Variable::DEC, +]; diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 6500a1b4ce..ad764009da 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -220,12 +220,8 @@ fn find_names_needed( // TODO should we also look in the actual variable? // find_names_needed(_actual, subs, roots, root_appearances, names_taken); } - &RangedNumber(typ, vars) => { + &RangedNumber(typ, _) => { find_names_needed(typ, subs, roots, root_appearances, names_taken); - for var_index in vars { - let var = subs[var_index]; - find_names_needed(var, subs, roots, root_appearances, names_taken); - } } Error | Structure(Erroneous(_)) | Structure(EmptyRecord) | Structure(EmptyTagUnion) => { // Errors and empty records don't need names. diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 374b5aa3c9..5baee3d4fc 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -8,7 +8,9 @@ use roc_module::ident::{Lowercase, TagName, Uppercase}; use roc_module::symbol::Symbol; use std::fmt; use std::iter::{once, Iterator, Map}; -use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey}; +use ven_ena::unify::UnifyKey; + +use crate::unification_table::{Snapshot, UnificationTable}; // if your changes cause this number to go down, great! // please change it to the lower number. @@ -130,7 +132,7 @@ impl Subs { written += header.len(); writer.write_all(&header)?; - written = Self::serialize_unification_table(&self.utable, writer, written)?; + written = self.utable.serialize(writer, written)?; written = Self::serialize_slice(&self.variables, writer, written)?; written = Self::serialize_tag_names(&self.tag_names, writer, written)?; @@ -142,37 +144,6 @@ impl Subs { Ok(written) } - fn serialize_unification_table( - utable: &UnificationTable>, - writer: &mut impl std::io::Write, - mut written: usize, - ) -> std::io::Result { - for i in 0..utable.len() { - let var = unsafe { Variable::from_index(i as u32) }; - - let desc = if utable.is_redirect(var) { - let root = utable.get_root_key_without_compacting(var); - - // our strategy for a redirect; rank is max, mark is max, copy stores the var - Descriptor { - content: Content::Error, - rank: Rank(u32::MAX), - mark: Mark(i32::MAX), - copy: root.into(), - } - } else { - utable.probe_value_without_compacting(var) - }; - - let bytes: [u8; std::mem::size_of::()] = - unsafe { std::mem::transmute(desc) }; - written += bytes.len(); - writer.write_all(&bytes)?; - } - - Ok(written) - } - /// Lowercase can be heap-allocated fn serialize_field_names( lowercases: &[Lowercase], @@ -222,7 +193,7 @@ impl Subs { Self::serialize_slice(&buf, writer, written) } - fn serialize_slice( + pub(crate) fn serialize_slice( slice: &[T], writer: &mut impl std::io::Write, written: usize, @@ -241,15 +212,12 @@ impl Subs { } pub fn deserialize(bytes: &[u8]) -> (Self, &[(Symbol, Variable)]) { - use std::convert::TryInto; - let mut offset = 0; let header_slice = &bytes[..std::mem::size_of::()]; offset += header_slice.len(); let header = SubsHeader::from_array(header_slice.try_into().unwrap()); - let (utable, offset) = - Self::deserialize_unification_table(bytes, header.utable as usize, offset); + let (utable, offset) = UnificationTable::deserialize(bytes, header.utable as usize, offset); let (variables, offset) = Self::deserialize_slice(bytes, header.variables as usize, offset); let (tag_names, offset) = @@ -278,44 +246,6 @@ impl Subs { ) } - fn deserialize_unification_table( - bytes: &[u8], - length: usize, - offset: usize, - ) -> (UnificationTable>, usize) { - let alignment = std::mem::align_of::(); - let size = std::mem::size_of::(); - debug_assert_eq!(offset, round_to_multiple_of(offset, alignment)); - - let mut utable = UnificationTable::default(); - utable.reserve(length); - - let byte_length = length * size; - let byte_slice = &bytes[offset..][..byte_length]; - - let slice = - unsafe { std::slice::from_raw_parts(byte_slice.as_ptr() as *const Descriptor, length) }; - - let mut roots = Vec::new(); - - for desc in slice { - let var = utable.new_key(*desc); - - if desc.rank == Rank(u32::MAX) && desc.mark == Mark(i32::MAX) { - let root = desc.copy.into_variable().unwrap(); - - roots.push((var, root)); - } - } - - for (var, root) in roots { - let desc = utable.probe_value_without_compacting(root); - utable.unify_roots(var, root, desc) - } - - (utable, offset + byte_length) - } - fn deserialize_field_names( bytes: &[u8], length: usize, @@ -362,7 +292,11 @@ impl Subs { (tag_names, offset) } - fn deserialize_slice(bytes: &[u8], length: usize, mut offset: usize) -> (&[T], usize) { + pub(crate) fn deserialize_slice( + bytes: &[u8], + length: usize, + mut offset: usize, + ) -> (&[T], usize) { let alignment = std::mem::align_of::(); let size = std::mem::size_of::(); @@ -379,7 +313,7 @@ impl Subs { #[derive(Clone)] pub struct Subs { - utable: UnificationTable>, + utable: UnificationTable, pub variables: Vec, pub tag_names: Vec, pub field_names: Vec, @@ -779,8 +713,7 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt: ) } Content::RangedNumber(typ, range) => { - let slice = subs.get_subs_slice(*range); - write!(f, "RangedNumber({:?}, {:?})", typ, slice) + write!(f, "RangedNumber({:?}, {:?})", typ, range) } Content::Error => write!(f, "Error"), } @@ -926,14 +859,17 @@ pub struct OptVariable(u32); impl OptVariable { pub const NONE: OptVariable = OptVariable(Variable::NULL.0); + #[inline(always)] pub const fn is_none(self) -> bool { self.0 == Self::NONE.0 } + #[inline(always)] pub const fn is_some(self) -> bool { self.0 != Self::NONE.0 } + #[inline(always)] pub const fn into_variable(self) -> Option { if self.is_none() { None @@ -1599,16 +1535,8 @@ impl Subs { problems: Vec::new(), }; - // NOTE the utable does not (currently) have a with_capacity; using this as the next-best thing subs.utable.reserve(capacity); - // TODO There are at least these opportunities for performance optimization here: - // * Making the default flex_var_descriptor be all 0s, so no init step is needed. - - for _ in 0..capacity { - subs.utable.new_key(flex_var_descriptor()); - } - define_integer_types(&mut subs); define_float_types(&mut subs); @@ -1656,14 +1584,14 @@ impl Subs { pub fn extend_by(&mut self, entries: usize) { self.utable.reserve(entries); - for _ in 0..entries { - self.utable.new_key(flex_var_descriptor()); - } } #[inline(always)] pub fn fresh(&mut self, value: Descriptor) -> Variable { - self.utable.new_key(value) + // self.utable.new_key(value) + + self.utable + .push(value.content, value.rank, value.mark, value.copy) } #[inline(always)] @@ -1699,39 +1627,48 @@ impl Subs { } pub fn get(&mut self, key: Variable) -> Descriptor { - self.utable.probe_value(key) - } - - pub fn get_ref(&self, key: Variable) -> &Descriptor { - &self.utable.probe_value_ref(key).value - } - - #[inline(always)] - pub fn get_ref_mut(&mut self, key: Variable) -> &mut Descriptor { - &mut self.utable.probe_value_ref_mut(key).value + self.utable.get_descriptor(key) } pub fn get_rank(&self, key: Variable) -> Rank { - self.utable.probe_value_ref(key).value.rank + self.utable.get_rank(key) + } + + pub fn get_copy(&self, key: Variable) -> OptVariable { + self.utable.get_copy(key) } pub fn get_mark(&self, key: Variable) -> Mark { - self.utable.probe_value_ref(key).value.mark + self.utable.get_mark(key) } pub fn get_rank_mark(&self, key: Variable) -> (Rank, Mark) { - let desc = &self.utable.probe_value_ref(key).value; + (self.utable.get_rank(key), self.utable.get_mark(key)) + } - (desc.rank, desc.mark) + pub fn get_mark_unchecked(&self, key: Variable) -> Mark { + self.utable.get_mark_unchecked(key) + } + + pub fn get_content_unchecked(&self, key: Variable) -> &Content { + self.utable.get_content_unchecked(key) + } + + pub fn get_rank_unchecked(&self, key: Variable) -> Rank { + self.utable.get_rank_unchecked(key) + } + + pub fn get_copy_unchecked(&self, key: Variable) -> OptVariable { + self.utable.get_copy_unchecked(key) } #[inline(always)] pub fn get_without_compacting(&self, key: Variable) -> Descriptor { - self.utable.probe_value_without_compacting(key) + self.utable.get_descriptor(key) } pub fn get_content_without_compacting(&self, key: Variable) -> &Content { - &self.utable.probe_value_ref(key).value.content + self.utable.get_content(key) } #[inline(always)] @@ -1741,68 +1678,64 @@ impl Subs { #[inline(always)] pub fn get_root_key_without_compacting(&self, key: Variable) -> Variable { - self.utable.get_root_key_without_compacting(key) + self.utable.root_key_without_compacting(key) } #[inline(always)] pub fn set(&mut self, key: Variable, r_value: Descriptor) { let l_key = self.utable.inlined_get_root_key(key); - self.utable.update_value(l_key, |node| node.value = r_value); + // self.utable.update_value(l_key, |node| node.value = r_value); + self.utable.set_descriptor(l_key, r_value) } pub fn set_rank(&mut self, key: Variable, rank: Rank) { - let l_key = self.utable.inlined_get_root_key(key); - - self.utable.update_value(l_key, |node| { - node.value.rank = rank; - }); + self.utable.set_rank(key, rank) } pub fn set_mark(&mut self, key: Variable, mark: Mark) { - let l_key = self.utable.inlined_get_root_key(key); + self.utable.set_mark(key, mark) + } - self.utable.update_value(l_key, |node| { - node.value.mark = mark; - }); + pub fn set_rank_unchecked(&mut self, key: Variable, rank: Rank) { + self.utable.set_rank_unchecked(key, rank) + } + + pub fn set_mark_unchecked(&mut self, key: Variable, mark: Mark) { + self.utable.set_mark_unchecked(key, mark) + } + + pub fn set_copy_unchecked(&mut self, key: Variable, copy: OptVariable) { + self.utable.set_copy_unchecked(key, copy) + } + + pub fn set_copy(&mut self, key: Variable, copy: OptVariable) { + self.utable.set_copy(key, copy) } pub fn set_rank_mark(&mut self, key: Variable, rank: Rank, mark: Mark) { - let l_key = self.utable.inlined_get_root_key(key); - - self.utable.update_value(l_key, |node| { - node.value.rank = rank; - node.value.mark = mark; - }); + self.utable.set_rank(key, rank); + self.utable.set_mark(key, mark); } pub fn set_content(&mut self, key: Variable, content: Content) { - let l_key = self.utable.inlined_get_root_key(key); - - self.utable.update_value(l_key, |node| { - node.value.content = content; - }); + self.utable.set_content(key, content); } - pub fn modify(&mut self, key: Variable, mapper: F) + pub fn set_content_unchecked(&mut self, key: Variable, content: Content) { + self.utable.set_content_unchecked(key, content); + } + + pub fn modify(&mut self, key: Variable, mapper: F) -> T where - F: Fn(&mut Descriptor), + F: FnOnce(&mut Descriptor) -> T, { - mapper(self.get_ref_mut(key)); + self.utable.modify(key, mapper) } #[inline(always)] pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank { - let l_key = self.utable.inlined_get_root_key(key); - - let mut rank = Rank::NONE; - - self.utable.update_value(l_key, |node| { - node.value.mark = mark; - rank = node.value.rank; - }); - - rank + self.utable.get_rank_set_mark(key, mark) } pub fn equivalent(&mut self, left: Variable, right: Variable) -> bool { @@ -1922,38 +1855,21 @@ impl Subs { (var.index() as usize) < self.len() } - pub fn snapshot(&mut self) -> Snapshot> { + pub fn snapshot(&mut self) -> Snapshot { self.utable.snapshot() } - pub fn rollback_to(&mut self, snapshot: Snapshot>) { + pub fn rollback_to(&mut self, snapshot: Snapshot) { self.utable.rollback_to(snapshot) } - pub fn commit_snapshot(&mut self, snapshot: Snapshot>) { - self.utable.commit(snapshot) + pub fn commit_snapshot(&mut self, _snapshot: Snapshot) { + // self.utable.commit(snapshot) } - pub fn vars_since_snapshot( - &mut self, - snapshot: &Snapshot>, - ) -> core::ops::Range { + pub fn vars_since_snapshot(&mut self, snapshot: &Snapshot) -> core::ops::Range { self.utable.vars_since_snapshot(snapshot) } - - /// Checks whether the content of `var`, or any nested content, satisfies the `predicate`. - pub fn var_contains_content

(&self, var: Variable, predicate: P) -> bool - where - P: Fn(&Content) -> bool + Copy, - { - let mut seen_recursion_vars = MutSet::default(); - var_contains_content_help(self, var, predicate, &mut seen_recursion_vars) - } -} - -#[inline(always)] -fn flex_var_descriptor() -> Descriptor { - Descriptor::from(unnamed_flex_var()) } #[inline(always)] @@ -2092,7 +2008,7 @@ pub enum Content { }, Structure(FlatType), Alias(Symbol, AliasVariables, Variable, AliasKind), - RangedNumber(Variable, VariableSubsSlice), + RangedNumber(Variable, crate::num::NumericRange), Error, } @@ -3108,16 +3024,10 @@ fn explicit_substitute( in_var } - RangedNumber(typ, vars) => { - for index in vars.into_iter() { - let var = subs[index]; - let new_var = explicit_substitute(subs, from, to, var, seen); - subs[index] = new_var; - } - + RangedNumber(typ, range) => { let new_typ = explicit_substitute(subs, from, to, typ, seen); - subs.set_content(in_var, RangedNumber(new_typ, vars)); + subs.set_content(in_var, RangedNumber(new_typ, range)); in_var } @@ -3177,12 +3087,7 @@ fn get_var_names( get_var_names(subs, subs[arg_var], answer) }), - RangedNumber(typ, vars) => { - let taken_names = get_var_names(subs, typ, taken_names); - vars.into_iter().fold(taken_names, |answer, var| { - get_var_names(subs, subs[var], answer) - }) - } + RangedNumber(typ, _) => get_var_names(subs, typ, taken_names), Structure(flat_type) => match flat_type { FlatType::Apply(_, args) => { @@ -3423,12 +3328,12 @@ fn content_to_err_type( RangedNumber(typ, range) => { let err_type = var_to_err_type(subs, state, typ); - if state.context == ErrorTypeContext::ExpandRanges { - let mut types = Vec::with_capacity(range.len()); - for var_index in range { - let var = subs[var_index]; + dbg!(range); - types.push(var_to_err_type(subs, state, var)); + if state.context == ErrorTypeContext::ExpandRanges { + let mut types = Vec::new(); + for var in range.variable_slice() { + types.push(var_to_err_type(subs, state, *var)); } ErrorType::Range(Box::new(err_type), types) } else { @@ -3656,77 +3561,80 @@ fn restore_help(subs: &mut Subs, initial: Variable) { |variable_subs_slice: VariableSubsSlice| &variables[variable_subs_slice.indices()]; while let Some(var) = stack.pop() { - let desc = &mut subs.utable.probe_value_ref_mut(var).value; + // let desc = &mut subs.utable.probe_value_ref_mut(var).value; - if desc.copy.is_some() { - desc.rank = Rank::NONE; - desc.mark = Mark::NONE; - desc.copy = OptVariable::NONE; + let copy = subs.utable.get_copy(var); - use Content::*; - use FlatType::*; + if copy.is_none() { + continue; + } - match &desc.content { - FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) | Error => (), + subs.utable.set_rank(var, Rank::NONE); + subs.utable.set_mark(var, Mark::NONE); + subs.utable.set_copy(var, OptVariable::NONE); - RecursionVar { structure, .. } => { - stack.push(*structure); + use Content::*; + use FlatType::*; + + match subs.utable.get_content(var) { + FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) | Error => (), + + RecursionVar { structure, .. } => { + stack.push(*structure); + } + + Structure(flat_type) => match flat_type { + Apply(_, args) => { + stack.extend(var_slice(*args)); } - Structure(flat_type) => match flat_type { - Apply(_, args) => { - stack.extend(var_slice(*args)); - } + Func(arg_vars, closure_var, ret_var) => { + stack.extend(var_slice(*arg_vars)); - Func(arg_vars, closure_var, ret_var) => { - stack.extend(var_slice(*arg_vars)); - - stack.push(*ret_var); - stack.push(*closure_var); - } - - EmptyRecord => (), - EmptyTagUnion => (), - - Record(fields, ext_var) => { - stack.extend(var_slice(fields.variables())); - - stack.push(*ext_var); - } - TagUnion(tags, ext_var) => { - for slice_index in tags.variables() { - let slice = variable_slices[slice_index.index as usize]; - stack.extend(var_slice(slice)); - } - - stack.push(*ext_var); - } - FunctionOrTagUnion(_, _, ext_var) => { - stack.push(*ext_var); - } - - RecursiveTagUnion(rec_var, tags, ext_var) => { - for slice_index in tags.variables() { - let slice = variable_slices[slice_index.index as usize]; - stack.extend(var_slice(slice)); - } - - stack.push(*ext_var); - stack.push(*rec_var); - } - - Erroneous(_) => (), - }, - Alias(_, args, var, _) => { - stack.extend(var_slice(args.all_variables())); - - stack.push(*var); + stack.push(*ret_var); + stack.push(*closure_var); } - RangedNumber(typ, vars) => { - stack.push(*typ); - stack.extend(var_slice(*vars)); + EmptyRecord => (), + EmptyTagUnion => (), + + Record(fields, ext_var) => { + stack.extend(var_slice(fields.variables())); + + stack.push(*ext_var); } + TagUnion(tags, ext_var) => { + for slice_index in tags.variables() { + let slice = variable_slices[slice_index.index as usize]; + stack.extend(var_slice(slice)); + } + + stack.push(*ext_var); + } + FunctionOrTagUnion(_, _, ext_var) => { + stack.push(*ext_var); + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + for slice_index in tags.variables() { + let slice = variable_slices[slice_index.index as usize]; + stack.extend(var_slice(slice)); + } + + stack.push(*ext_var); + stack.push(*rec_var); + } + + Erroneous(_) => (), + }, + Alias(_, args, var, _) => { + stack.extend(var_slice(args.all_variables())); + + stack.push(*var); + } + + RangedNumber(typ, _vars) => { + stack.push(*typ); } } } @@ -3803,7 +3711,7 @@ impl StorageSubs { for i in range { let variable = Variable(i as u32); - let descriptor = self.subs.get_ref(variable); + let descriptor = self.subs.utable.get_descriptor(variable); debug_assert!(descriptor.copy.is_none()); let new_content = Self::offset_content(&offsets, &descriptor.content); @@ -3912,10 +3820,7 @@ impl StorageSubs { Self::offset_variable(offsets, *actual), *kind, ), - RangedNumber(typ, vars) => RangedNumber( - Self::offset_variable(offsets, *typ), - Self::offset_variable_slice(offsets, *vars), - ), + RangedNumber(typ, range) => RangedNumber(Self::offset_variable(offsets, *typ), *range), Error => Content::Error, } } @@ -4024,13 +3929,13 @@ pub fn deep_copy_var_to( // we have tracked all visited variables, and can now traverse them // in one go (without looking at the UnificationTable) and clear the copy field for var in env.visited { - let descriptor = env.source.get_ref_mut(var); - - if descriptor.copy.is_some() { - descriptor.rank = Rank::NONE; - descriptor.mark = Mark::NONE; - descriptor.copy = OptVariable::NONE; - } + env.source.modify(var, |descriptor| { + if descriptor.copy.is_some() { + descriptor.rank = Rank::NONE; + descriptor.mark = Mark::NONE; + descriptor.copy = OptVariable::NONE; + } + }); } copy @@ -4341,18 +4246,10 @@ fn deep_copy_var_to_help(env: &mut DeepCopyVarToEnv<'_>, var: Variable) -> Varia copy } - RangedNumber(typ, vars) => { + RangedNumber(typ, range) => { let new_typ = deep_copy_var_to_help(env, typ); - let new_vars = SubsSlice::reserve_into_subs(env.target, vars.len()); - - for (target_index, var_index) in (new_vars.indices()).zip(vars) { - let var = env.source[var_index]; - let copy_var = deep_copy_var_to_help(env, var); - env.target.variables[target_index] = copy_var; - } - - let new_content = RangedNumber(new_typ, new_vars); + let new_content = RangedNumber(new_typ, range); env.target.set(copy, make_descriptor(new_content)); copy @@ -4435,13 +4332,13 @@ pub fn copy_import_to( // in one go (without looking at the UnificationTable) and clear the copy field for var in visited { - let descriptor = source.get_ref_mut(var); - - if descriptor.copy.is_some() { - descriptor.rank = Rank::NONE; - descriptor.mark = Mark::NONE; - descriptor.copy = OptVariable::NONE; - } + source.modify(var, |descriptor| { + if descriptor.copy.is_some() { + descriptor.rank = Rank::NONE; + descriptor.mark = Mark::NONE; + descriptor.copy = OptVariable::NONE; + } + }); } CopiedImport { @@ -4810,101 +4707,13 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl copy } - RangedNumber(typ, vars) => { + RangedNumber(typ, range) => { let new_typ = copy_import_to_help(env, max_rank, typ); - let new_vars = SubsSlice::reserve_into_subs(env.target, vars.len()); - - for (target_index, var_index) in (new_vars.indices()).zip(vars) { - let var = env.source[var_index]; - let copy_var = copy_import_to_help(env, max_rank, var); - env.target.variables[target_index] = copy_var; - } - - let new_content = RangedNumber(new_typ, new_vars); + let new_content = RangedNumber(new_typ, range); env.target.set(copy, make_descriptor(new_content)); copy } } } - -fn var_contains_content_help

( - subs: &Subs, - var: Variable, - predicate: P, - seen_recursion_vars: &mut MutSet, -) -> bool -where - P: Fn(&Content) -> bool + Copy, -{ - let mut stack = vec![var]; - - macro_rules! push_var_slice { - ($slice:expr) => { - stack.extend(subs.get_subs_slice($slice)) - }; - } - - while let Some(var) = stack.pop() { - if seen_recursion_vars.contains(&var) { - continue; - } - - let content = subs.get_content_without_compacting(var); - - if predicate(content) { - return true; - } - - use Content::*; - use FlatType::*; - match content { - FlexVar(_) | RigidVar(_) | FlexAbleVar(_, _) | RigidAbleVar(_, _) => {} - RecursionVar { - structure, - opt_name: _, - } => { - seen_recursion_vars.insert(var); - stack.push(*structure); - } - Structure(flat_type) => match flat_type { - Apply(_, vars) => push_var_slice!(*vars), - Func(args, clos, ret) => { - push_var_slice!(*args); - stack.push(*clos); - stack.push(*ret); - } - Record(fields, var) => { - push_var_slice!(fields.variables()); - stack.push(*var); - } - TagUnion(tags, ext_var) => { - for i in tags.variables() { - push_var_slice!(subs[i]); - } - stack.push(*ext_var); - } - FunctionOrTagUnion(_, _, var) => stack.push(*var), - RecursiveTagUnion(rec_var, tags, ext_var) => { - seen_recursion_vars.insert(*rec_var); - for i in tags.variables() { - push_var_slice!(subs[i]); - } - stack.push(*ext_var); - } - Erroneous(_) | EmptyRecord | EmptyTagUnion => {} - }, - Alias(_, arguments, real_type_var, _) => { - push_var_slice!(arguments.all_variables()); - stack.push(*real_type_var); - } - RangedNumber(typ, vars) => { - stack.push(*typ); - push_var_slice!(*vars); - } - Error => {} - } - } - false -} diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 0ba04ba799..fcb0631b2a 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -1,3 +1,4 @@ +use crate::num::NumericRange; use crate::pretty_print::Parens; use crate::subs::{ GetSubsSlice, RecordFields, Subs, UnionTags, VarStore, Variable, VariableSubsSlice, @@ -254,7 +255,7 @@ pub enum Type { /// Applying a type to some arguments (e.g. Dict.Dict String Int) Apply(Symbol, Vec, Region), Variable(Variable), - RangedNumber(Box, Vec), + RangedNumber(Box, NumericRange), /// A type error, which will code gen to a runtime error Erroneous(Problem), } @@ -324,7 +325,7 @@ impl Clone for Type { } Self::Apply(arg0, arg1, arg2) => Self::Apply(*arg0, arg1.clone(), *arg2), Self::Variable(arg0) => Self::Variable(*arg0), - Self::RangedNumber(arg0, arg1) => Self::RangedNumber(arg0.clone(), arg1.clone()), + Self::RangedNumber(arg0, arg1) => Self::RangedNumber(arg0.clone(), *arg1), Self::Erroneous(arg0) => Self::Erroneous(arg0.clone()), } } @@ -1089,9 +1090,7 @@ impl Type { } => actual_type.contains_variable(rep_variable), HostExposedAlias { actual, .. } => actual.contains_variable(rep_variable), Apply(_, args, _) => args.iter().any(|arg| arg.contains_variable(rep_variable)), - RangedNumber(typ, vars) => { - typ.contains_variable(rep_variable) || vars.iter().any(|&v| v == rep_variable) - } + RangedNumber(typ, _) => typ.contains_variable(rep_variable), EmptyRec | EmptyTagUnion | Erroneous(_) => false, } } @@ -1422,7 +1421,7 @@ impl Type { pub fn expect_variable(&self, reason: &'static str) -> Variable { match self { Type::Variable(v) => *v, - _ => internal_error!(reason), + _ => internal_error!("{}", reason), } } } @@ -1594,9 +1593,8 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { } variables_help(actual, accum); } - RangedNumber(typ, vars) => { + RangedNumber(typ, _) => { variables_help(typ, accum); - accum.extend(vars.iter().copied()); } Apply(_, args, _) => { for x in args { @@ -1730,9 +1728,8 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) { } variables_help_detailed(actual, accum); } - RangedNumber(typ, vars) => { + RangedNumber(typ, _) => { variables_help_detailed(typ, accum); - accum.type_variables.extend(vars); } Apply(_, args, _) => { for x in args { diff --git a/compiler/types/src/unification_table.rs b/compiler/types/src/unification_table.rs new file mode 100644 index 0000000000..9f2f95a223 --- /dev/null +++ b/compiler/types/src/unification_table.rs @@ -0,0 +1,344 @@ +use crate::subs::{Content, Descriptor, Mark, OptVariable, Rank, Variable, VariableSubsSlice}; + +#[derive(Clone, Default)] +pub struct UnificationTable { + contents: Vec, + ranks: Vec, + marks: Vec, + copies: Vec, + redirects: Vec, +} + +pub struct Snapshot(UnificationTable); + +impl UnificationTable { + #[allow(unused)] + pub fn with_capacity(cap: usize) -> Self { + Self { + contents: Vec::with_capacity(cap), // vec![Content::Error; cap], + ranks: Vec::with_capacity(cap), // vec![Rank::NONE; cap], + marks: Vec::with_capacity(cap), // vec![Mark::NONE; cap], + copies: Vec::with_capacity(cap), // vec![OptVariable::NONE; cap], + redirects: Vec::with_capacity(cap), // vec![OptVariable::NONE; cap], + } + } + + pub fn len(&self) -> usize { + self.contents.len() + } + + pub fn is_empty(&self) -> bool { + self.contents.is_empty() + } + + pub fn reserve(&mut self, extra_length: usize) -> VariableSubsSlice { + use std::iter::repeat; + + let start = self.contents.len(); + + self.contents + .extend(repeat(Content::FlexVar(None)).take(extra_length)); + self.ranks.extend(repeat(Rank::NONE).take(extra_length)); + self.marks.extend(repeat(Mark::NONE).take(extra_length)); + self.copies + .extend(repeat(OptVariable::NONE).take(extra_length)); + self.redirects + .extend(repeat(OptVariable::NONE).take(extra_length)); + + VariableSubsSlice::new(start as _, extra_length as _) + } + + pub fn push( + &mut self, + content: Content, + rank: Rank, + mark: Mark, + copy: OptVariable, + ) -> Variable { + let variable = unsafe { Variable::from_index(self.len() as _) }; + + self.contents.push(content); + self.ranks.push(rank); + self.marks.push(mark); + self.copies.push(copy); + self.redirects.push(OptVariable::NONE); + + variable + } + + #[allow(unused)] + pub fn set( + &mut self, + key: Variable, + content: Content, + rank: Rank, + mark: Mark, + copy: OptVariable, + ) { + let index = self.root_key(key).index() as usize; + + self.contents[index] = content; + self.ranks[index] = rank; + self.marks[index] = mark; + self.copies[index] = copy; + } + + pub fn modify(&mut self, key: Variable, mapper: F) -> T + where + F: FnOnce(&mut Descriptor) -> T, + { + let index = self.root_key(key).index() as usize; + + let mut desc = Descriptor { + content: self.contents[index], + rank: self.ranks[index], + mark: self.marks[index], + copy: self.copies[index], + }; + + let result = mapper(&mut desc); + + self.contents[index] = desc.content; + self.ranks[index] = desc.rank; + self.marks[index] = desc.mark; + self.copies[index] = desc.copy; + + result + } + + // GET UNCHECKED + + #[inline(always)] + pub fn get_rank_unchecked(&self, key: Variable) -> Rank { + self.ranks[key.index() as usize] + } + + #[inline(always)] + pub fn get_mark_unchecked(&self, key: Variable) -> Mark { + self.marks[key.index() as usize] + } + + #[allow(unused)] + #[inline(always)] + pub fn get_copy_unchecked(&self, key: Variable) -> OptVariable { + self.copies[key.index() as usize] + } + + #[inline(always)] + pub fn get_content_unchecked(&self, key: Variable) -> &Content { + &self.contents[key.index() as usize] + } + + // GET CHECKED + + #[inline(always)] + pub fn get_rank(&self, key: Variable) -> Rank { + self.ranks[self.root_key_without_compacting(key).index() as usize] + } + + #[inline(always)] + pub fn get_mark(&self, key: Variable) -> Mark { + self.marks[self.root_key_without_compacting(key).index() as usize] + } + + #[inline(always)] + pub fn get_copy(&self, key: Variable) -> OptVariable { + let index = self.root_key_without_compacting(key).index() as usize; + self.copies[index] + } + + #[inline(always)] + pub fn get_content(&self, key: Variable) -> &Content { + &self.contents[self.root_key_without_compacting(key).index() as usize] + } + + // SET UNCHECKED + + #[inline(always)] + pub fn set_rank_unchecked(&mut self, key: Variable, value: Rank) { + self.ranks[key.index() as usize] = value; + } + + #[inline(always)] + pub fn set_mark_unchecked(&mut self, key: Variable, value: Mark) { + self.marks[key.index() as usize] = value; + } + + #[allow(unused)] + #[inline(always)] + pub fn set_copy_unchecked(&mut self, key: Variable, value: OptVariable) { + self.copies[key.index() as usize] = value; + } + + #[allow(unused)] + #[inline(always)] + pub fn set_content_unchecked(&mut self, key: Variable, value: Content) { + self.contents[key.index() as usize] = value; + } + + // SET CHECKED + + #[inline(always)] + pub fn set_rank(&mut self, key: Variable, value: Rank) { + let index = self.root_key(key).index() as usize; + self.ranks[index] = value; + } + + #[inline(always)] + pub fn set_mark(&mut self, key: Variable, value: Mark) { + let index = self.root_key(key).index() as usize; + self.marks[index] = value; + } + + #[inline(always)] + pub fn set_copy(&mut self, key: Variable, value: OptVariable) { + let index = self.root_key(key).index() as usize; + self.copies[index] = value; + } + + #[inline(always)] + pub fn set_content(&mut self, key: Variable, value: Content) { + let index = self.root_key(key).index() as usize; + self.contents[index] = value; + } + + // ROOT KEY + + #[inline(always)] + pub fn root_key(&mut self, mut key: Variable) -> Variable { + let index = key.index() as usize; + + while let Some(redirect) = self.redirects[key.index() as usize].into_variable() { + key = redirect; + } + + if index != key.index() as usize { + self.redirects[index] = OptVariable::from(key); + } + + key + } + + #[inline(always)] + pub fn root_key_without_compacting(&self, mut key: Variable) -> Variable { + while let Some(redirect) = self.redirects[key.index() as usize].into_variable() { + key = redirect; + } + + key + } + + pub fn snapshot(&self) -> Snapshot { + Snapshot(self.clone()) + } + + pub fn rollback_to(&mut self, snapshot: Snapshot) { + *self = snapshot.0; + } + + pub fn vars_since_snapshot(&self, snapshot: &Snapshot) -> std::ops::Range { + unsafe { + let start = Variable::from_index(snapshot.0.len() as u32); + let end = Variable::from_index(self.len() as u32); + + start..end + } + } + + pub fn is_redirect(&self, key: Variable) -> bool { + self.redirects[key.index() as usize].is_some() + } + + pub fn unioned(&mut self, a: Variable, b: Variable) -> bool { + self.root_key(a) == self.root_key(b) + } + + // custom very specific helpers + #[inline(always)] + pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank { + let index = self.root_key(key).index() as usize; + + self.marks[index] = mark; + + self.ranks[index] + } + + // TODO remove + #[inline(always)] + pub fn inlined_get_root_key(&mut self, key: Variable) -> Variable { + self.root_key(key) + } + + /// NOTE: assumes variables are root + pub fn unify_roots(&mut self, to: Variable, from: Variable, desc: Descriptor) { + let from_index = from.index() as usize; + let to_index = to.index() as usize; + + // redirect from -> to + if from_index != to_index { + self.redirects[from_index] = OptVariable::from(to); + } + + // update to's Descriptor + self.contents[to_index] = desc.content; + self.ranks[to_index] = desc.rank; + self.marks[to_index] = desc.mark; + self.copies[to_index] = desc.copy; + } + + pub fn get_descriptor(&self, key: Variable) -> Descriptor { + let index = self.root_key_without_compacting(key).index() as usize; + + Descriptor { + content: self.contents[index], + rank: self.ranks[index], + mark: self.marks[index], + copy: self.copies[index], + } + } + + pub fn set_descriptor(&mut self, key: Variable, desc: Descriptor) { + let index = self.root_key(key).index() as usize; + + self.contents[index] = desc.content; + self.ranks[index] = desc.rank; + self.marks[index] = desc.mark; + self.copies[index] = desc.copy; + } + + pub(crate) fn serialize( + &self, + writer: &mut impl std::io::Write, + mut written: usize, + ) -> std::io::Result { + use crate::subs::Subs; + + written = Subs::serialize_slice(&self.contents, writer, written)?; + written = Subs::serialize_slice(&self.ranks, writer, written)?; + written = Subs::serialize_slice(&self.marks, writer, written)?; + written = Subs::serialize_slice(&self.copies, writer, written)?; + written = Subs::serialize_slice(&self.redirects, writer, written)?; + + Ok(written) + } + + pub(crate) fn deserialize(bytes: &[u8], length: usize, offset: usize) -> (Self, usize) { + use crate::subs::Subs; + + let (contents, offset) = Subs::deserialize_slice::(bytes, length, offset); + let (ranks, offset) = Subs::deserialize_slice::(bytes, length, offset); + let (marks, offset) = Subs::deserialize_slice::(bytes, length, offset); + let (copies, offset) = Subs::deserialize_slice::(bytes, length, offset); + let (redirects, offset) = Subs::deserialize_slice::(bytes, length, offset); + + let this = Self { + contents: contents.to_vec(), + ranks: ranks.to_vec(), + marks: marks.to_vec(), + copies: copies.to_vec(), + redirects: redirects.to_vec(), + }; + + (this, offset) + } +} diff --git a/compiler/unify/Cargo.toml b/compiler/unify/Cargo.toml index e92e2aa3e3..3cb709c120 100644 --- a/compiler/unify/Cargo.toml +++ b/compiler/unify/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["The Roc Contributors"] -edition = "2018" +edition = "2021" license = "UPL-1.0" name = "roc_unify" version = "0.1.0" diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 4afbb0abe6..055cd24383 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1,8 +1,11 @@ use bitflags::bitflags; -use roc_debug_flags::{dbg_do, ROC_PRINT_MISMATCHES, ROC_PRINT_UNIFICATIONS}; +use roc_debug_flags::dbg_do; +#[cfg(debug_assertions)] +use roc_debug_flags::{ROC_PRINT_MISMATCHES, ROC_PRINT_UNIFICATIONS}; use roc_error_macros::internal_error; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; +use roc_types::num::NumericRange; use roc_types::subs::Content::{self, *}; use roc_types::subs::{ AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, Mark, OptVariable, @@ -167,12 +170,22 @@ impl Unified { } } +/// Type obligated to implement an ability. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum Obligated { + /// Opaque types can either define custom implementations for an ability, or ask the compiler + /// to generate an implementation of a builtin ability for them. In any case they have unique + /// obligation rules for abilities. + Opaque(Symbol), + /// A structural type for which the compiler can at most generate an adhoc implementation of + /// a builtin ability. + Adhoc(Variable), +} + /// Specifies that `type` must implement the ability `ability`. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct MustImplementAbility { - // This only points to opaque type names currently. - // TODO(abilities) support structural types in general - pub typ: Symbol, + pub typ: Obligated, pub ability: Symbol, } @@ -347,6 +360,8 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome { #[cfg(debug_assertions)] debug_print_unified_types(subs, &ctx, None); + // This #[allow] is needed in release builds, where `result` is no longer used. + #[allow(clippy::let_and_return)] let result = match &ctx.first_desc.content { FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, None, &ctx.second_desc.content), FlexAbleVar(opt_name, ability) => unify_flex( @@ -399,7 +414,7 @@ fn unify_ranged_number( pool: &mut Pool, ctx: &Context, real_var: Variable, - range_vars: VariableSubsSlice, + range_vars: NumericRange, ) -> Outcome { let other_content = &ctx.second_desc.content; @@ -417,7 +432,7 @@ fn unify_ranged_number( &RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, real_var, other_real_var, ctx.mode); if outcome.mismatches.is_empty() { - check_valid_range(subs, pool, ctx.first, other_range_vars, ctx.mode) + check_valid_range(subs, ctx.first, other_range_vars) } else { outcome } @@ -430,41 +445,41 @@ fn unify_ranged_number( return outcome; } - check_valid_range(subs, pool, ctx.second, range_vars, ctx.mode) + check_valid_range(subs, ctx.second, range_vars) } -fn check_valid_range( - subs: &mut Subs, - pool: &mut Pool, - var: Variable, - range: VariableSubsSlice, - mode: Mode, -) -> Outcome { - let slice = subs.get_subs_slice(range).to_vec(); +fn check_valid_range(subs: &mut Subs, var: Variable, range: NumericRange) -> Outcome { + let content = subs.get_content_without_compacting(var); - let mut it = slice.iter().peekable(); - while let Some(&possible_var) = it.next() { - let snapshot = subs.snapshot(); - let old_pool = pool.clone(); - let outcome = unify_pool(subs, pool, var, possible_var, mode | Mode::RIGID_AS_FLEX); - if outcome.mismatches.is_empty() { - // Okay, we matched some type in the range. - subs.rollback_to(snapshot); - *pool = old_pool; - return Outcome::default(); - } else if it.peek().is_some() { - // We failed to match something in the range, but there are still things we can try. - subs.rollback_to(snapshot); - *pool = old_pool; - } else { - subs.commit_snapshot(snapshot); + match content { + &Content::Alias(symbol, _, actual, _) => { + match range.contains_symbol(symbol) { + None => { + // symbol not recognized; go into the alias + return check_valid_range(subs, actual, range); + } + Some(false) => { + let outcome = Outcome { + mismatches: vec![Mismatch::TypeNotInRange], + must_implement_ability: Default::default(), + }; + + return outcome; + } + Some(true) => { /* fall through */ } + } + } + + Content::RangedNumber(_, _) => { + // these ranges always intersect, we need more information before we can say more + } + + _ => { + // anything else is definitely a type error, and will be reported elsewhere } } - Outcome { - mismatches: vec![Mismatch::TypeNotInRange], - ..Outcome::default() - } + Outcome::default() } #[inline(always)] @@ -473,7 +488,8 @@ fn unify_two_aliases( subs: &mut Subs, pool: &mut Pool, ctx: &Context, - symbol: Symbol, + // _symbol has an underscore because it's unused in --release builds + _symbol: Symbol, args: AliasVariables, real_var: Variable, other_args: AliasVariables, @@ -487,7 +503,7 @@ fn unify_two_aliases( .into_iter() .zip(other_args.all_variables().into_iter()); - let args_unification_snapshot = subs.snapshot(); + let length_before = subs.len(); for (l, r) in it { let l_var = subs[l]; @@ -499,10 +515,9 @@ fn unify_two_aliases( outcome.union(merge(subs, ctx, *other_content)); } - let args_unification_had_changes = !subs - .vars_since_snapshot(&args_unification_snapshot) - .is_empty(); - subs.commit_snapshot(args_unification_snapshot); + let length_after = subs.len(); + + let args_unification_had_changes = length_after != length_before; if !args.is_empty() && args_unification_had_changes && outcome.mismatches.is_empty() { // We need to unify the real vars because unification of type variables @@ -513,7 +528,7 @@ fn unify_two_aliases( outcome } else { dbg!(args.len(), other_args.len()); - mismatch!("{:?}", symbol) + mismatch!("{:?}", _symbol) } } @@ -562,7 +577,7 @@ fn unify_alias( RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, real_var, *other_real_var, ctx.mode); if outcome.mismatches.is_empty() { - check_valid_range(subs, pool, real_var, *other_range_vars, ctx.mode) + check_valid_range(subs, real_var, *other_range_vars) } else { outcome } @@ -589,12 +604,11 @@ fn unify_opaque( // Alias wins merge(subs, ctx, Alias(symbol, args, real_var, kind)) } - // RigidVar(_) | RigidAbleVar(..) => unify_pool(subs, pool, real_var, ctx.second, ctx.mode), FlexAbleVar(_, ability) if args.is_empty() => { // Opaque type wins let mut outcome = merge(subs, ctx, Alias(symbol, args, real_var, kind)); outcome.must_implement_ability.push(MustImplementAbility { - typ: symbol, + typ: Obligated::Opaque(symbol), ability: *ability, }); outcome @@ -624,14 +638,15 @@ fn unify_opaque( // This opaque might be a number, check if it unifies with the target ranged number var. let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); if outcome.mismatches.is_empty() { - check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode) + check_valid_range(subs, ctx.first, *other_range_vars) } else { outcome } } - other => { + // _other has an underscore because it's unused in --release builds + _other => { // The type on the left is an opaque, but the one on the right is not! - mismatch!("Cannot unify opaque {:?} with {:?}", symbol, other) + mismatch!("Cannot unify opaque {:?} with {:?}", symbol, _other) } } } @@ -668,9 +683,31 @@ fn unify_structure( } outcome } - RigidVar(name) => { + FlexAbleVar(_, ability) => { + let mut outcome = merge(subs, ctx, Structure(*flat_type)); + let must_implement_ability = MustImplementAbility { + typ: Obligated::Adhoc(ctx.first), + ability: *ability, + }; + outcome.must_implement_ability.push(must_implement_ability); + outcome + } + // _name has an underscore because it's unused in --release builds + RigidVar(_name) => { // Type mismatch! Rigid can only unify with flex. - mismatch!("trying to unify {:?} with rigid var {:?}", &flat_type, name) + mismatch!( + "trying to unify {:?} with rigid var {:?}", + &flat_type, + _name + ) + } + RigidAbleVar(_, _ability) => { + mismatch!( + %not_able, ctx.first, *_ability, + "trying to unify {:?} with RigidAble {:?}", + &flat_type, + &other + ) } RecursionVar { structure, .. } => match flat_type { FlatType::TagUnion(_, _) => { @@ -714,7 +751,8 @@ fn unify_structure( // Unify the two flat types unify_flat_type(subs, pool, ctx, flat_type, other_flat_type) } - Alias(sym, _, real_var, kind) => match kind { + // _sym has an underscore because it's unused in --release builds + Alias(_sym, _, real_var, kind) => match kind { AliasKind::Structural => { // NB: not treating this as a presence constraint seems pivotal! I // can't quite figure out why, but it doesn't seem to impact other types. @@ -724,37 +762,19 @@ fn unify_structure( mismatch!( "Cannot unify structure {:?} with opaque {:?}", &flat_type, - sym + _sym ) } }, RangedNumber(other_real_var, other_range_vars) => { let outcome = unify_pool(subs, pool, ctx.first, *other_real_var, ctx.mode); if outcome.mismatches.is_empty() { - check_valid_range(subs, pool, ctx.first, *other_range_vars, ctx.mode) + check_valid_range(subs, ctx.first, *other_range_vars) } else { outcome } } Error => merge(subs, ctx, Error), - - FlexAbleVar(_, ability) => { - // TODO(abilities) support structural types in ability bounds - mismatch!( - %not_able, ctx.first, *ability, - "trying to unify {:?} with FlexAble {:?}", - &flat_type, - &other - ) - } - RigidAbleVar(_, ability) => { - mismatch!( - %not_able, ctx.first, *ability, - "trying to unify {:?} with RigidAble {:?}", - &flat_type, - &other - ) - } } } @@ -1693,12 +1713,13 @@ fn unify_flat_type( unify_tag_union_new(subs, pool, ctx, tags1, *ext1, *tags2, *ext2, rec) } - (other1, other2) => { + // these have underscores because they're unused in --release builds + (_other1, _other2) => { // any other combination is a mismatch mismatch!( "Trying to unify two flat types that are incompatible: {:?} ~ {:?}", - roc_types::subs::SubsFmtFlatType(other1, subs), - roc_types::subs::SubsFmtFlatType(other2, subs) + roc_types::subs::SubsFmtFlatType(_other1, subs), + roc_types::subs::SubsFmtFlatType(_other2, subs) ) } } @@ -1778,19 +1799,21 @@ fn unify_rigid( { let mut output = merge(subs, ctx, *other); let must_implement_ability = MustImplementAbility { - typ: *opaque_name, + typ: Obligated::Opaque(*opaque_name), ability, }; output.must_implement_ability.push(must_implement_ability); output } - (Some(ability), other) => { + + // these have underscores because they're unused in --release builds + (Some(_ability), _other) => { // For now, only allow opaque types with no type variables to implement abilities. mismatch!( - %not_able, ctx.second, ability, + %not_able, ctx.second, _ability, "RigidAble {:?} with non-opaque or opaque with type variables {:?}", ctx.first, - &other + &_other ) } } @@ -1923,11 +1946,12 @@ fn unify_recursion( }, ), - Alias(opaque, _, _, AliasKind::Opaque) => { + // _opaque has an underscore because it's unused in --release builds + Alias(_opaque, _, _, AliasKind::Opaque) => { mismatch!( "RecursionVar {:?} cannot be equal to opaque {:?}", ctx.first, - opaque + _opaque ) } diff --git a/docs/Cargo.toml b/docs/Cargo.toml index 39e39bb68f..9f41af3a46 100644 --- a/docs/Cargo.toml +++ b/docs/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_docs" version = "0.1.0" license = "UPL-1.0" authors = ["The Roc Contributors"] -edition = "2018" +edition = "2021" [dependencies] pulldown-cmark = { version = "0.8.0", default-features = false } diff --git a/docs/src/lib.rs b/docs/src/lib.rs index 9e91906b25..18bdf0e933 100644 --- a/docs/src/lib.rs +++ b/docs/src/lib.rs @@ -636,6 +636,9 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ type_annotation_to_html(next_indent_level, buf, output); } + TypeAnnotation::Ability { members: _ } => { + // TODO(abilities): fill me in + } TypeAnnotation::ObscuredTagUnion => { buf.push_str("[ @.. ]"); } @@ -712,6 +715,7 @@ fn should_be_multiline(type_ann: &TypeAnnotation) -> bool { is_multiline } + TypeAnnotation::Ability { .. } => true, TypeAnnotation::Wildcard => false, TypeAnnotation::NoTypeAnn => false, } @@ -734,7 +738,7 @@ fn doc_url<'a>( if module_name.is_empty() { // This is an unqualified lookup, so look for the ident // in scope! - match scope.lookup(&ident.into(), Region::zero()) { + match scope.lookup_str(ident, Region::zero()) { Ok(symbol) => { // Get the exact module_name from scope. It could be the // current module's name, but it also could be a different diff --git a/docs_cli/Cargo.toml b/docs_cli/Cargo.toml index 5cf22bf54c..96600f1f17 100644 --- a/docs_cli/Cargo.toml +++ b/docs_cli/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_docs_cli" version = "0.1.0" license = "UPL-1.0" authors = ["The Roc Contributors"] -edition = "2018" +edition = "2021" # This binary is only used on static build servers, e.g. Netlify. # Having its own (extremely minimal) CLI means docs can be generated diff --git a/editor/Cargo.toml b/editor/Cargo.toml index 82128eabef..ef6058bff5 100644 --- a/editor/Cargo.toml +++ b/editor/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_editor" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" description = "An editor for Roc" [package.metadata.cargo-udeps.ignore] diff --git a/error_macros/Cargo.toml b/error_macros/Cargo.toml index d5c7276898..83744ab39d 100644 --- a/error_macros/Cargo.toml +++ b/error_macros/Cargo.toml @@ -3,6 +3,6 @@ name = "roc_error_macros" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] diff --git a/error_macros/src/lib.rs b/error_macros/src/lib.rs index 41ec7b6172..679af370b9 100644 --- a/error_macros/src/lib.rs +++ b/error_macros/src/lib.rs @@ -66,6 +66,15 @@ macro_rules! assert_sizeof_all { }; } +/// Assert that a type has the expected size on all targets except wasm +#[macro_export] +macro_rules! assert_sizeof_non_wasm { + ($t: ty, $expected_size: expr) => { + #[cfg(not(target_family = "wasm"))] + static_assertions::assert_eq_size!($t, [u8; $expected_size]); + }; +} + /// Assert that a type has `Copy` #[macro_export] macro_rules! assert_copyable { diff --git a/examples/algorithms/quicksort.roc b/examples/algorithms/quicksort.roc index d7cc023e2d..cae9f04572 100644 --- a/examples/algorithms/quicksort.roc +++ b/examples/algorithms/quicksort.roc @@ -26,7 +26,6 @@ partition = \low, high, initialList -> when partitionHelp low low initialList high pivot is Pair newI newList -> Pair newI (swap newI high newList) - Err _ -> Pair low initialList @@ -39,7 +38,6 @@ partitionHelp = \i, j, list, high, pivot -> partitionHelp (i + 1) (j + 1) (swap i j list) high pivot else partitionHelp i (j + 1) list high pivot - Err _ -> Pair i list else @@ -52,7 +50,6 @@ swap = \i, j, list -> list |> List.set i atJ |> List.set j atI - _ -> # to prevent a decrement on list # turns out this is very important for optimizations diff --git a/examples/benchmarks/AStar.roc b/examples/benchmarks/AStar.roc index d6aec65d8f..d9658c62f1 100644 --- a/examples/benchmarks/AStar.roc +++ b/examples/benchmarks/AStar.roc @@ -28,7 +28,6 @@ cheapestOpen = \costFn, model -> when Dict.get model.costs position is Err _ -> Err {} - Ok cost -> Ok { cost: cost + costFn position, position } ) @@ -42,7 +41,6 @@ reconstructPath = \cameFrom, goal -> when Dict.get cameFrom goal is Err _ -> [] - Ok next -> List.append (reconstructPath cameFrom next) goal @@ -68,7 +66,6 @@ updateCost = \current, neighbor, model -> when Dict.get model.costs neighbor is Err _ -> newModel - Ok previousDistance -> if distanceTo < previousDistance then newModel @@ -78,9 +75,8 @@ updateCost = \current, neighbor, model -> astar : (position, position -> F64), (position -> Set position), position, Model position -> Result (List position) {} astar = \costFn, moveFn, goal, model -> when cheapestOpen (\source -> costFn source goal) model is - Err { } -> + Err {} -> Err {} - Ok current -> if current == goal then Ok (reconstructPath model.cameFrom goal) diff --git a/examples/benchmarks/Base64.roc b/examples/benchmarks/Base64.roc index f22f0b8644..c738decde6 100644 --- a/examples/benchmarks/Base64.roc +++ b/examples/benchmarks/Base64.roc @@ -6,7 +6,6 @@ fromBytes = \bytes -> when Base64.Decode.fromBytes bytes is Ok v -> Ok v - Err _ -> Err InvalidInput @@ -27,9 +26,7 @@ toStr = \str -> when Str.fromUtf8 bytes is Ok v -> Ok v - Err _ -> Err InvalidInput - Err _ -> Err InvalidInput diff --git a/examples/benchmarks/Base64/Decode.roc b/examples/benchmarks/Base64/Decode.roc index ad5c92ef17..381da6681d 100644 --- a/examples/benchmarks/Base64/Decode.roc +++ b/examples/benchmarks/Base64/Decode.roc @@ -50,7 +50,6 @@ bitsToChars = \bits, missing -> when Str.fromUtf8 (bitsToCharsHelp bits missing) is Ok str -> str - Err _ -> "" @@ -90,13 +89,10 @@ bitsToCharsHelp = \bits, missing -> when missing is 0 -> [ p, q, r, s ] - 1 -> [ p, q, r, equals ] - 2 -> [ p, q, equals, equals ] - _ -> # unreachable [] @@ -119,11 +115,9 @@ unsafeToChar = \n -> 62 -> # '+' 43 - 63 -> # '/' 47 - _ -> # anything else is invalid '\u{0000}' 0 diff --git a/examples/benchmarks/Base64/Encode.roc b/examples/benchmarks/Base64/Encode.roc index 2806ddf4b5..12c16bf4d9 100644 --- a/examples/benchmarks/Base64/Encode.roc +++ b/examples/benchmarks/Base64/Encode.roc @@ -26,16 +26,12 @@ folder = \{ output, accum }, char -> when accum is Unreachable n -> coerce n { output, accum: Unreachable n } - None -> { output, accum: One char } - One a -> { output, accum: Two a char } - Two a b -> { output, accum: Three a b char } - Three a b c -> when encodeCharacters a b c char is Ok encoder -> @@ -43,7 +39,6 @@ folder = \{ output, accum }, char -> output: List.append output encoder, accum: None, } - Err _ -> { output, accum: None } @@ -53,26 +48,20 @@ encodeResidual = \{ output, accum } -> when accum is Unreachable _ -> output - None -> output - One _ -> output - Two a b -> when encodeCharacters a b equals equals is Ok encoder -> List.append output encoder - Err _ -> output - Three a b c -> when encodeCharacters a b c equals is Ok encoder -> List.append output encoder - Err _ -> output @@ -157,11 +146,9 @@ isValidChar = \c -> 43 -> # '+' True - 47 -> # '/' True - _ -> False @@ -187,10 +174,8 @@ unsafeConvertChar = \key -> 43 -> # '+' 62 - 47 -> # '/' 63 - _ -> 0 diff --git a/examples/benchmarks/Bytes/Decode.roc b/examples/benchmarks/Bytes/Decode.roc index 6a5df2c8f3..3d476dc6a2 100644 --- a/examples/benchmarks/Bytes/Decode.roc +++ b/examples/benchmarks/Bytes/Decode.roc @@ -11,7 +11,6 @@ decode = \bytes, @Decoder decoder -> when decoder { bytes, cursor: 0 } is Good _ value -> Ok value - Bad e -> Err e @@ -25,7 +24,6 @@ map = \@Decoder decoder, transform -> when decoder state is Good state1 value -> Good state1 (transform value) - Bad e -> Bad e @@ -38,10 +36,8 @@ map2 = \@Decoder decoder1, @Decoder decoder2, transform -> when decoder2 state2 is Good state3 b -> Good state3 (transform a b) - Bad e -> Bad e - Bad e -> Bad e @@ -56,13 +52,10 @@ map3 = \@Decoder decoder1, @Decoder decoder2, @Decoder decoder3, transform -> when decoder3 state3 is Good state4 c -> Good state4 (transform a b c) - Bad e -> Bad e - Bad e -> Bad e - Bad e -> Bad e @@ -75,7 +68,6 @@ after = \@Decoder decoder, transform -> (@Decoder decoder1) = transform value decoder1 state1 - Bad e -> Bad e @@ -85,7 +77,6 @@ u8 = @Decoder when List.get state.bytes state.cursor is Ok b -> Good { state & cursor: state.cursor + 1 } b - Err _ -> Bad OutOfBytes @@ -103,9 +94,7 @@ loopHelp = \stepper, accum, state -> when stepper1 state is Good newState (Done value) -> Good newState value - Good newState (Loop newAccum) -> loopHelp stepper newAccum newState - Bad e -> Bad e diff --git a/examples/benchmarks/Bytes/Encode.roc b/examples/benchmarks/Bytes/Encode.roc index 195ccd2407..53c659195f 100644 --- a/examples/benchmarks/Bytes/Encode.roc +++ b/examples/benchmarks/Bytes/Encode.roc @@ -29,16 +29,12 @@ getWidth = \encoder -> when encoder is Signed8 _ -> 1 - Unsigned8 _ -> 1 - Signed16 _ _ -> 2 - Unsigned16 _ _ -> 2 - # Signed32 _ -> 4 # Unsigned32 _ -> 4 # Signed64 _ -> 8 @@ -47,7 +43,6 @@ getWidth = \encoder -> # Unsigned128 _ -> 16 Sequence w _ -> w - Bytes bs -> List.len bs @@ -70,7 +65,6 @@ encodeHelp = \encoder, offset, output -> output: List.set output offset value, offset: offset + 1, } - Signed8 value -> cast : U8 cast = Num.intCast value @@ -79,7 +73,6 @@ encodeHelp = \encoder, offset, output -> output: List.set output offset cast, offset: offset + 1, } - Unsigned16 endianness value -> a : U8 a = Num.intCast (Num.shiftRightBy 8 value) @@ -93,7 +86,6 @@ encodeHelp = \encoder, offset, output -> output |> List.set (offset + 0) a |> List.set (offset + 1) b - LE -> output |> List.set (offset + 0) b @@ -103,7 +95,6 @@ encodeHelp = \encoder, offset, output -> output: newOutput, offset: offset + 2, } - Signed16 endianness value -> a : U8 a = Num.intCast (Num.shiftRightBy 8 value) @@ -117,7 +108,6 @@ encodeHelp = \encoder, offset, output -> output |> List.set (offset + 0) a |> List.set (offset + 1) b - LE -> output |> List.set (offset + 0) b @@ -127,7 +117,6 @@ encodeHelp = \encoder, offset, output -> output: newOutput, offset: offset + 1, } - Bytes bs -> List.walk bs @@ -136,7 +125,6 @@ encodeHelp = \encoder, offset, output -> offset: accum.offset + 1, output: List.set accum.output offset byte, } - Sequence _ encoders -> List.walk encoders diff --git a/examples/benchmarks/CFold.roc b/examples/benchmarks/CFold.roc index 96dd438a04..f84b27bf8f 100644 --- a/examples/benchmarks/CFold.roc +++ b/examples/benchmarks/CFold.roc @@ -31,7 +31,6 @@ mkExpr = \n, v -> when n is 0 -> if v == 0 then Var 1 else Val v - _ -> Add (mkExpr (n - 1) (v + 1)) (mkExpr (n - 1) (max (v - 1) 0)) @@ -43,7 +42,6 @@ appendAdd = \e1, e2 -> when e1 is Add a1 a2 -> Add a1 (appendAdd a2 e2) - _ -> Add e1 e2 @@ -52,7 +50,6 @@ appendMul = \e1, e2 -> when e1 is Mul a1 a2 -> Mul a1 (appendMul a2 e2) - _ -> Mul e1 e2 @@ -61,13 +58,10 @@ eval = \e -> when e is Var _ -> 0 - Val v -> v - Add l r -> eval l + eval r - Mul l r -> eval l * eval r @@ -79,13 +73,11 @@ reassoc = \e -> x2 = reassoc e2 appendAdd x1 x2 - Mul e1 e2 -> x1 = reassoc e1 x2 = reassoc e2 appendMul x1 x2 - _ -> e @@ -99,16 +91,12 @@ constFolding = \e -> when Pair x1 x2 is Pair (Val a) (Val b) -> Val (a + b) - Pair (Val a) (Add (Val b) x) -> Add (Val (a + b)) x - Pair (Val a) (Add x (Val b)) -> Add (Val (a + b)) x - Pair y1 y2 -> Add y1 y2 - Mul e1 e2 -> x1 = constFolding e1 x2 = constFolding e2 @@ -116,15 +104,11 @@ constFolding = \e -> when Pair x1 x2 is Pair (Val a) (Val b) -> Val (a * b) - Pair (Val a) (Mul (Val b) x) -> Mul (Val (a * b)) x - Pair (Val a) (Mul x (Val b)) -> Mul (Val (a * b)) x - Pair y1 y2 -> Add y1 y2 - _ -> e diff --git a/examples/benchmarks/Deriv.roc b/examples/benchmarks/Deriv.roc index c26e46d8a5..a40ef82b9b 100644 --- a/examples/benchmarks/Deriv.roc +++ b/examples/benchmarks/Deriv.roc @@ -31,7 +31,6 @@ nestHelp = \{ s, f, m, x } -> when m is 0 -> Task.succeed (Done x) - _ -> w <- Task.after (f (s - m) x) @@ -44,7 +43,6 @@ divmod = \l, r -> when Pair (Num.divTruncChecked l r) (Num.remChecked l r) is Pair (Ok div) (Ok mod) -> Ok { div, mod } - _ -> Err DivByZero @@ -53,17 +51,14 @@ pown = \a, n -> when n is 0 -> 1 - 1 -> a - _ -> when divmod n 2 is Ok { div, mod } -> b = pown a div b * b * (if mod == 0 then 1 else a) - Err DivByZero -> -1 @@ -72,25 +67,18 @@ add = \a, b -> when Pair a b is Pair (Val n) (Val m) -> Val (n + m) - Pair (Val 0) f -> f - Pair f (Val 0) -> f - Pair f (Val n) -> add (Val n) f - Pair (Val n) (Add (Val m) f) -> add (Val (n + m)) f - Pair f (Add (Val n) g) -> add (Val n) (add f g) - Pair (Add f g) h -> add f (add g h) - Pair f g -> Add f g @@ -99,31 +87,22 @@ mul = \a, b -> when Pair a b is Pair (Val n) (Val m) -> Val (n * m) - Pair (Val 0) _ -> Val 0 - Pair _ (Val 0) -> Val 0 - Pair (Val 1) f -> f - Pair f (Val 1) -> f - Pair f (Val n) -> mul (Val n) f - Pair (Val n) (Mul (Val m) f) -> mul (Val (n * m)) f - Pair f (Mul (Val n) g) -> mul (Val n) (mul f g) - Pair (Mul f g) h -> mul f (mul g h) - Pair f g -> Mul f g @@ -132,16 +111,12 @@ pow = \a, b -> when Pair a b is Pair (Val m) (Val n) -> Val (pown m n) - Pair _ (Val 0) -> Val 1 - Pair f (Val 1) -> f - Pair (Val 0) _ -> Val 0 - Pair f g -> Pow f g @@ -150,7 +125,6 @@ ln = \f -> when f is Val 1 -> Val 0 - _ -> Ln f @@ -159,19 +133,14 @@ d = \x, expr -> when expr is Val _ -> Val 0 - Var y -> if x == y then Val 1 else Val 0 - Add f g -> add (d x f) (d x g) - Mul f g -> add (mul f (d x g)) (mul g (d x f)) - Pow f g -> mul (pow f g) (add (mul (mul g (d x f)) (pow f (Val (-1)))) (mul (ln f) (d x g))) - Ln f -> mul (d x f) (pow f (Val (-1))) @@ -180,19 +149,14 @@ count = \expr -> when expr is Val _ -> 1 - Var _ -> 1 - Add f g -> count f + count g - Mul f g -> count f + count g - Pow f g -> count f + count g - Ln f -> count f diff --git a/examples/benchmarks/NQueens.roc b/examples/benchmarks/NQueens.roc index 0cde720b36..ba4efe6b79 100644 --- a/examples/benchmarks/NQueens.roc +++ b/examples/benchmarks/NQueens.roc @@ -25,7 +25,6 @@ lengthHelp = \foobar, acc -> when foobar is Cons _ lrest -> lengthHelp lrest (1 + acc) - Nil -> acc @@ -34,7 +33,6 @@ safe = \queen, diagonal, xs -> when xs is Nil -> True - Cons q t -> queen != q && queen != q + diagonal && queen != q - diagonal && safe queen (diagonal + 1) t @@ -51,7 +49,6 @@ extend = \n, acc, solutions -> when solutions is Nil -> acc - Cons soln rest -> extend n (appendSafe n soln acc) rest diff --git a/examples/benchmarks/Quicksort.roc b/examples/benchmarks/Quicksort.roc index c5fb45acc2..56db520970 100644 --- a/examples/benchmarks/Quicksort.roc +++ b/examples/benchmarks/Quicksort.roc @@ -42,7 +42,6 @@ partition = \low, high, initialList, order -> when partitionHelp low low initialList order high pivot is Pair newI newList -> Pair newI (swap newI high newList) - Err _ -> Pair low initialList @@ -54,10 +53,8 @@ partitionHelp = \i, j, list, order, high, pivot -> when order value pivot is LT | EQ -> partitionHelp (i + 1) (j + 1) (swap i j list) order high pivot - GT -> partitionHelp i (j + 1) list order high pivot - Err _ -> Pair i list else @@ -70,6 +67,5 @@ swap = \i, j, list -> list |> List.set i atJ |> List.set j atI - _ -> [] diff --git a/examples/benchmarks/RBTreeCk.roc b/examples/benchmarks/RBTreeCk.roc index 90c6fb3d4f..3ef6ff7ba2 100644 --- a/examples/benchmarks/RBTreeCk.roc +++ b/examples/benchmarks/RBTreeCk.roc @@ -20,7 +20,6 @@ makeMapHelp = \freq, n, m, acc -> when n is 0 -> Cons m acc - _ -> powerOf10 = n % 10 == 0 @@ -39,7 +38,6 @@ fold = \f, tree, b -> when tree is Leaf -> b - Node _ l k v r -> fold f r (f k v (fold f l b)) @@ -59,7 +57,6 @@ main = val |> Num.toStr |> Task.putLine - Nil -> Task.putLine "fail" @@ -71,7 +68,6 @@ setBlack = \tree -> when tree is Node _ l k v r -> Node Black l k v r - _ -> tree @@ -80,7 +76,6 @@ isRed = \tree -> when tree is Node Red _ _ _ _ -> True - _ -> False @@ -91,7 +86,6 @@ ins = \tree, kx, vx -> when tree is Leaf -> Node Red Leaf kx vx Leaf - Node Red a ky vy b -> if lt kx ky then Node Red (ins a kx vx) ky vy b @@ -99,7 +93,6 @@ ins = \tree, kx, vx -> Node Red a ky vy (ins b kx vx) else Node Red a ky vy (ins b kx vx) - Node Black a ky vy b -> if lt kx ky then (if isRed a then balance1 (Node Black Leaf ky vy b) (ins a kx vx) else Node Black (ins a kx vx) ky vy b) @@ -113,18 +106,14 @@ balance1 = \tree1, tree2 -> when tree1 is Leaf -> Leaf - Node _ _ kv vv t -> when tree2 is Node _ (Node Red l kx vx r1) ky vy r2 -> Node Red (Node Black l kx vx r1) ky vy (Node Black r2 kv vv t) - Node _ l1 ky vy (Node Red l2 kx vx r) -> Node Red (Node Black l1 ky vy l2) kx vx (Node Black r kv vv t) - Node _ l ky vy r -> Node Black (Node Red l ky vy r) kv vv t - Leaf -> Leaf @@ -133,17 +122,13 @@ balance2 = \tree1, tree2 -> when tree1 is Leaf -> Leaf - Node _ t kv vv _ -> when tree2 is Node _ (Node Red l kx1 vx1 r1) ky vy r2 -> Node Red (Node Black t kv vv l) kx1 vx1 (Node Black r1 ky vy r2) - Node _ l1 ky vy (Node Red l2 kx2 vx2 r2) -> Node Red (Node Black t kv vv l1) ky vy (Node Black l2 kx2 vx2 r2) - Node _ l ky vy r -> Node Black t kv vv (Node Red l ky vy r) - Leaf -> Leaf diff --git a/examples/benchmarks/RBTreeDel.roc b/examples/benchmarks/RBTreeDel.roc index 43fd309e5d..eb19e834db 100644 --- a/examples/benchmarks/RBTreeDel.roc +++ b/examples/benchmarks/RBTreeDel.roc @@ -35,7 +35,6 @@ makeMapHelp = \total, n, m -> when n is 0 -> m - _ -> n1 = n - 1 @@ -57,7 +56,6 @@ fold = \f, tree, b -> when tree is Leaf -> b - Node _ l k v r -> fold f r (f k v (fold f l b)) @@ -66,7 +64,6 @@ depth = \tree -> when tree is Leaf -> 1 - Node _ l _ _ r -> 1 + depth l + depth r @@ -75,7 +72,6 @@ resultWithDefault = \res, default -> when res is Ok v -> v - Err _ -> default @@ -87,7 +83,6 @@ setBlack = \tree -> when tree is Node _ l k v r -> Node Black l k v r - _ -> tree @@ -96,7 +91,6 @@ isRed = \tree -> when tree is Node Red _ _ _ _ -> True - _ -> False @@ -105,36 +99,28 @@ ins = \tree, kx, vx -> when tree is Leaf -> Node Red Leaf kx vx Leaf - Node Red a ky vy b -> when Num.compare kx ky is LT -> Node Red (ins a kx vx) ky vy b - GT -> Node Red a ky vy (ins b kx vx) - EQ -> Node Red a ky vy (ins b kx vx) - Node Black a ky vy b -> when Num.compare kx ky is LT -> when isRed a is True -> balanceLeft (ins a kx vx) ky vy b - False -> Node Black (ins a kx vx) ky vy b - GT -> when isRed b is True -> balanceRight a ky vy (ins b kx vx) - False -> Node Black a ky vy (ins b kx vx) - EQ -> Node Black a kx vx b @@ -143,13 +129,10 @@ balanceLeft = \l, k, v, r -> when l is Leaf -> Leaf - Node _ (Node Red lx kx vx rx) ky vy ry -> Node Red (Node Black lx kx vx rx) ky vy (Node Black ry k v r) - Node _ ly ky vy (Node Red lx kx vx rx) -> Node Red (Node Black ly ky vy lx) kx vx (Node Black rx k v r) - Node _ lx kx vx rx -> Node Black (Node Red lx kx vx rx) k v r @@ -158,13 +141,10 @@ balanceRight = \l, k, v, r -> when r is Leaf -> Leaf - Node _ (Node Red lx kx vx rx) ky vy ry -> Node Red (Node Black l k v lx) kx vx (Node Black rx ky vy ry) - Node _ lx kx vx (Node Red ly ky vy ry) -> Node Red (Node Black l k v lx) kx vx (Node Black ly ky vy ry) - Node _ lx kx vx rx -> Node Black l k v (Node Red lx kx vx rx) @@ -173,7 +153,6 @@ isBlack = \c -> when c is Black -> True - Red -> False @@ -184,7 +163,6 @@ setRed = \t -> when t is Node _ l k v r -> Node Red l k v r - _ -> t @@ -193,7 +171,6 @@ makeBlack = \t -> when t is Node Red l k v r -> Del (Node Black l k v r) False - _ -> Del t True @@ -201,10 +178,8 @@ rebalanceLeft = \c, l, k, v, r -> when l is Node Black _ _ _ _ -> Del (balanceLeft (setRed l) k v r) (isBlack c) - Node Red lx kx vx rx -> Del (Node Black lx kx vx (balanceLeft (setRed rx) k v r)) False - _ -> boom "unreachable" @@ -212,10 +187,8 @@ rebalanceRight = \c, l, k, v, r -> when r is Node Black _ _ _ _ -> Del (balanceRight l k v (setRed r)) (isBlack c) - Node Red lx kx vx rx -> Del (Node Black (balanceRight l k v (setRed lx)) kx vx rx) False - _ -> boom "unreachable" @@ -225,21 +198,16 @@ delMin = \t -> when r is Leaf -> Delmin (Del Leaf True) k v - _ -> Delmin (Del (setBlack r) False) k v - Node Red Leaf k v r -> Delmin (Del r False) k v - Node c l k v r -> when delMin l is Delmin (Del lx True) kx vx -> Delmin (rebalanceRight c lx k v r) kx vx - Delmin (Del lx False) kx vx -> Delmin (Del (Node c lx k v r) False) kx vx - Leaf -> Delmin (Del t False) 0 False @@ -254,31 +222,26 @@ del = \t, k -> when t is Leaf -> Del Leaf False - Node cx lx kx vx rx -> if (k < kx) then when del lx k is Del ly True -> rebalanceRight cx ly kx vx rx - Del ly False -> Del (Node cx ly kx vx rx) False else if (k > kx) then when del rx k is Del ry True -> rebalanceLeft cx lx kx vx ry - Del ry False -> Del (Node cx lx kx vx ry) False else when rx is Leaf -> if isBlack cx then makeBlack lx else Del lx False - Node _ _ _ _ _ -> when delMin rx is Delmin (Del ry True) ky vy -> rebalanceLeft cx lx ky vy ry - Delmin (Del ry False) ky vy -> Del (Node cx lx ky vy ry) False diff --git a/examples/benchmarks/RBTreeInsert.roc b/examples/benchmarks/RBTreeInsert.roc index 442bbdefff..461be1e6f5 100644 --- a/examples/benchmarks/RBTreeInsert.roc +++ b/examples/benchmarks/RBTreeInsert.roc @@ -13,14 +13,13 @@ main = |> Task.putLine show : RedBlackTree I64 {} -> Str -show = \tree -> showRBTree tree Num.toStr (\{ } -> "{}") +show = \tree -> showRBTree tree Num.toStr (\{} -> "{}") showRBTree : RedBlackTree k v, (k -> Str), (v -> Str) -> Str showRBTree = \tree, showKey, showValue -> when tree is Empty -> "Empty" - Node color key value left right -> sColor = showColor color sKey = showKey key @@ -35,7 +34,6 @@ nodeInParens = \tree, showKey, showValue -> when tree is Empty -> showRBTree tree showKey showValue - Node _ _ _ _ _ -> inner = showRBTree tree showKey showValue @@ -46,7 +44,6 @@ showColor = \color -> when color is Red -> "Red" - Black -> "Black" @@ -61,7 +58,6 @@ insert = \key, value, dict -> when insertHelp key value dict is Node Red k v l r -> Node Black k v l r - x -> x @@ -72,15 +68,12 @@ insertHelp = \key, value, dict -> # New nodes are always red. If it violates the rules, it will be fixed # when balancing. Node Red key value Empty Empty - Node nColor nKey nValue nLeft nRight -> when Num.compare key nKey is LT -> balance nColor nKey nValue (insertHelp key value nLeft) nRight - EQ -> Node nColor nKey value nLeft nRight - GT -> balance nColor nKey nValue nLeft (insertHelp key value nRight) @@ -96,10 +89,8 @@ balance = \color, key, value, left, right -> value (Node Black lK lV lLeft lRight) (Node Black rK rV rLeft rRight) - _ -> Node color rK rV (Node Red key value left rLeft) rRight - _ -> when left is Node Red lK lV (Node Red llK llV llLeft llRight) lRight -> @@ -109,6 +100,5 @@ balance = \color, key, value, left, right -> lV (Node Black llK llV llLeft llRight) (Node Black key value lRight right) - _ -> Node color key value left right diff --git a/examples/benchmarks/TestAStar.roc b/examples/benchmarks/TestAStar.roc index cd3e392f6a..b72a0c72e5 100644 --- a/examples/benchmarks/TestAStar.roc +++ b/examples/benchmarks/TestAStar.roc @@ -20,7 +20,6 @@ showBool = \b -> when b is True -> "True" - False -> "False" @@ -35,13 +34,10 @@ example1 = when n is 1 -> Set.fromList [ 2, 3 ] - 2 -> Set.fromList [ 4 ] - 3 -> Set.fromList [ 4 ] - _ -> Set.fromList [] @@ -51,6 +47,5 @@ example1 = when AStar.findPath cost step 1 4 is Ok path -> path - Err _ -> [] diff --git a/examples/benchmarks/TestBase64.roc b/examples/benchmarks/TestBase64.roc index e092b7ac07..bb4d305778 100644 --- a/examples/benchmarks/TestBase64.roc +++ b/examples/benchmarks/TestBase64.roc @@ -10,7 +10,6 @@ main = when Base64.fromBytes (Str.toUtf8 "Hello World") is Err _ -> Task.putLine "sadness" - Ok encoded -> Task.after (Task.putLine (Str.concat "encoded: " encoded)) @@ -18,6 +17,5 @@ main = when Base64.toStr encoded is Ok decoded -> Task.putLine (Str.concat "decoded: " decoded) - Err _ -> Task.putLine "sadness" diff --git a/examples/benchmarks/platform/Task.roc b/examples/benchmarks/platform/Task.roc index 1cc96bc6c5..d0e2b38cbb 100644 --- a/examples/benchmarks/platform/Task.roc +++ b/examples/benchmarks/platform/Task.roc @@ -6,14 +6,13 @@ Task ok err : Effect.Effect (Result ok err) forever : Task val err -> Task * err forever = \task -> - looper = \{ } -> + looper = \{} -> task |> Effect.map \res -> when res is Ok _ -> Step {} - Err e -> Done (Err e) @@ -28,10 +27,8 @@ loop = \state, step -> when res is Ok (Step newState) -> Step newState - Ok (Done result) -> Done (Ok result) - Err e -> Done (Err e) @@ -53,7 +50,6 @@ after = \effect, transform -> when result is Ok a -> transform a - Err err -> Task.fail err @@ -65,7 +61,6 @@ map = \effect, transform -> when result is Ok a -> Ok (transform a) - Err err -> Err err @@ -87,6 +82,5 @@ getInt = # # B -> Task.fail IOError # _ -> Task.succeed -1 - False -> Task.succeed value diff --git a/examples/breakout/breakout.roc b/examples/breakout/breakout.roc index 1625f9b411..b958d7cc1d 100644 --- a/examples/breakout/breakout.roc +++ b/examples/breakout/breakout.roc @@ -48,16 +48,12 @@ update = \model, event -> when event is Resize size -> { model & width: size.width, height: size.height } - KeyDown Left -> { model & paddleX: model.paddleX - paddleSpeed } - KeyDown Right -> { model & paddleX: model.paddleX + paddleSpeed } - Tick _ -> tick model - _ -> model diff --git a/examples/breakout/platform/Action.roc b/examples/breakout/platform/Action.roc index ad15ee728b..0c6834fdb8 100644 --- a/examples/breakout/platform/Action.roc +++ b/examples/breakout/platform/Action.roc @@ -15,6 +15,5 @@ map = \action, transform -> when action is None -> None - Update state -> Update (transform state) diff --git a/examples/breakout/platform/Cargo.toml b/examples/breakout/platform/Cargo.toml index 47281be89d..3e110e5aaf 100644 --- a/examples/breakout/platform/Cargo.toml +++ b/examples/breakout/platform/Cargo.toml @@ -3,12 +3,7 @@ name = "host" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" - -# Needed to be able to run on non-Windows systems for some reason. Without this, cargo panics with: -# -# error: DX12 API enabled on non-Windows OS. If your project is not using resolver="2" in Cargo.toml, it should. -resolver = "2" +edition = "2021" [lib] name = "host" diff --git a/examples/breakout/platform/Elem.roc b/examples/breakout/platform/Elem.roc index 519d007b99..1cf7e17188 100644 --- a/examples/breakout/platform/Elem.roc +++ b/examples/breakout/platform/Elem.roc @@ -54,7 +54,6 @@ lazy = \state, render -> # same as the cached one, then we can return exactly # what we had cached. cached - _ -> # Either the state changed or else we didn't have a # cached value to use. Either way, we need to render @@ -81,13 +80,10 @@ translate = \child, toChild, toParent -> when child is Text str -> Text str - Col elems -> Col (List.map elems \elem -> translate elem toChild toParent) - Row elems -> Row (List.map elems \elem -> translate elem toChild toParent) - Button config label -> onPress = \parentState, event -> toChild parentState @@ -95,7 +91,6 @@ translate = \child, toChild, toParent -> |> Action.map \c -> toParent parentState c Button { onPress } (translate label toChild toParent) - Lazy renderChild -> Lazy \parentState -> @@ -105,7 +100,6 @@ translate = \child, toChild, toParent -> elem: translate toChild toParent newChild, state: toParent parentState state, } - None -> None @@ -154,13 +148,10 @@ translateOrDrop = \child, toChild, toParent -> when child is Text str -> Text str - Col elems -> Col (List.map elems \elem -> translateOrDrop elem toChild toParent) - Row elems -> Row (List.map elems \elem -> translateOrDrop elem toChild toParent) - Button config label -> onPress = \parentState, event -> when toChild parentState is @@ -168,14 +159,12 @@ translateOrDrop = \child, toChild, toParent -> newChild |> config.onPress event |> Action.map \c -> toParent parentState c - Err _ -> # The child was removed from the list before this onPress handler resolved. # (For example, by a previous event handler that fired simultaneously.) Action.none Button { onPress } (translateOrDrop label toChild toParent) - Lazy childState renderChild -> Lazy (toParent childState) @@ -184,10 +173,8 @@ translateOrDrop = \child, toChild, toParent -> Ok newChild -> renderChild newChild |> translateOrDrop toChild toParent - Err _ -> None - # I don't think this should ever happen in practice. None -> None diff --git a/examples/breakout/platform/src/roc.rs b/examples/breakout/platform/src/roc.rs index a8aeb73d31..3c85aef60f 100644 --- a/examples/breakout/platform/src/roc.rs +++ b/examples/breakout/platform/src/roc.rs @@ -14,7 +14,7 @@ extern "C" { // program #[link_name = "roc__programForHost_1_exposed_generic"] - fn roc_program() -> (); + fn roc_program(); #[link_name = "roc__programForHost_size"] fn roc_program_size() -> i64; diff --git a/examples/false-interpreter/Context.roc b/examples/false-interpreter/Context.roc index e41529843b..de8ab8469a 100644 --- a/examples/false-interpreter/Context.roc +++ b/examples/false-interpreter/Context.roc @@ -26,7 +26,6 @@ popStack = \ctx -> poppedCtx = { ctx & stack: List.dropAt ctx.stack (List.len ctx.stack - 1) } Ok (T poppedCtx val) - Err ListWasEmpty -> Err EmptyStack @@ -35,10 +34,8 @@ toStrData = \data -> when data is Lambda _ -> "[]" - Number n -> Num.toStr (Num.intCast n) - Var v -> Variable.toStr v @@ -47,22 +44,16 @@ toStrState = \state -> when state is Executing -> "Executing" - InComment -> "InComment" - InString _ -> "InString" - InNumber _ -> "InNumber" - InLambda _ _ -> "InLambda" - InSpecialChar -> "InSpecialChar" - LoadChar -> "LoadChar" @@ -90,7 +81,6 @@ getChar = \ctx -> Ok scope -> (T val newScope) <- Task.await (getCharScope scope) Task.succeed (T val { ctx & scopes: List.set ctx.scopes (List.len ctx.scopes - 1) newScope }) - Err ListWasEmpty -> Task.fail NoScope @@ -99,7 +89,6 @@ getCharScope = \scope -> when List.get scope.buf scope.index is Ok val -> Task.succeed (T val { scope & index: scope.index + 1 }) - Err OutOfBounds -> when scope.data is Some h -> @@ -108,10 +97,8 @@ getCharScope = \scope -> Ok val -> # This starts at 1 because the first character is already being returned. Task.succeed (T val { scope & buf: bytes, index: 1 }) - Err ListWasEmpty -> Task.fail EndOfData - None -> Task.fail EndOfData @@ -120,6 +107,5 @@ inWhileScope = \ctx -> when List.last ctx.scopes is Ok scope -> scope.whileInfo != None - Err ListWasEmpty -> False diff --git a/examples/false-interpreter/False.roc b/examples/false-interpreter/False.roc index 537d794a18..adf6ab3ada 100644 --- a/examples/false-interpreter/False.roc +++ b/examples/false-interpreter/False.roc @@ -30,40 +30,28 @@ interpretFile = \filename -> when result is Ok _ -> Task.succeed {} - Err BadUtf8 -> Task.fail (StringErr "Failed to convert string from Utf8 bytes") - Err DivByZero -> Task.fail (StringErr "Division by zero") - Err EmptyStack -> Task.fail (StringErr "Tried to pop a value off of the stack when it was empty") - Err InvalidBooleanValue -> Task.fail (StringErr "Ran into an invalid boolean that was neither false (0) or true (-1)") - Err (InvalidChar char) -> Task.fail (StringErr "Ran into an invalid character with ascii code: \(char)") - Err MaxInputNumber -> Task.fail (StringErr "Like the original false compiler, the max input number is 320,000") - Err NoLambdaOnStack -> Task.fail (StringErr "Tried to run a lambda when no lambda was on the stack") - Err NoNumberOnStack -> Task.fail (StringErr "Tried to run a number when no number was on the stack") - Err NoVariableOnStack -> Task.fail (StringErr "Tried to load a variable when no variable was on the stack") - Err NoScope -> Task.fail (StringErr "Tried to run code when not in any scope") - Err OutOfBounds -> Task.fail (StringErr "Tried to load from an offset that was outside of the stack") - Err UnexpectedEndOfData -> Task.fail (StringErr "Hit end of data while still parsing something") @@ -113,22 +101,17 @@ interpretCtxLoop = \ctx -> newScope = { scope & whileInfo: Some { state: InBody, body, cond } } Task.succeed (Step { popCtx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: body, index: 0, whileInfo: None } }) - Err e -> Task.fail e - Some { state: InBody, body, cond } -> # Just rand the body. Run the condition again. newScope = { scope & whileInfo: Some { state: InCond, body, cond } } Task.succeed (Step { ctx & scopes: List.append (List.set ctx.scopes last newScope) { data: None, buf: cond, index: 0, whileInfo: None } }) - None -> Task.fail NoScope - Err OutOfBounds -> Task.fail NoScope - Executing -> # {} <- Task.await (Stdout.line (Context.toStr ctx)) result <- Task.attempt (Context.getChar ctx) @@ -136,10 +119,8 @@ interpretCtxLoop = \ctx -> Ok (T val newCtx) -> execCtx <- Task.await (stepExecCtx newCtx val) Task.succeed (Step execCtx) - Err NoScope -> Task.fail NoScope - Err EndOfData -> # Computation complete for this scope. # Drop a scope. @@ -150,7 +131,6 @@ interpretCtxLoop = \ctx -> Task.succeed (Done dropCtx) else Task.succeed (Step dropCtx) - InComment -> result <- Task.attempt (Context.getChar ctx) when result is @@ -160,13 +140,10 @@ interpretCtxLoop = \ctx -> Task.succeed (Step { newCtx & state: Executing }) else Task.succeed (Step { newCtx & state: InComment }) - Err NoScope -> Task.fail NoScope - Err EndOfData -> Task.fail UnexpectedEndOfData - InNumber accum -> result <- Task.attempt (Context.getChar ctx) when result is @@ -185,13 +162,10 @@ interpretCtxLoop = \ctx -> execCtx <- Task.await (stepExecCtx { pushCtx & state: Executing } val) Task.succeed (Step execCtx) - Err NoScope -> Task.fail NoScope - Err EndOfData -> Task.fail UnexpectedEndOfData - InString bytes -> result <- Task.attempt (Context.getChar ctx) when result is @@ -200,20 +174,16 @@ interpretCtxLoop = \ctx -> # `"` end of string when Str.fromUtf8 bytes is Ok str -> - { } <- Task.await (Stdout.raw str) + {} <- Task.await (Stdout.raw str) Task.succeed (Step { newCtx & state: Executing }) - Err _ -> Task.fail BadUtf8 else Task.succeed (Step { newCtx & state: InString (List.append bytes val) }) - Err NoScope -> Task.fail NoScope - Err EndOfData -> Task.fail UnexpectedEndOfData - InLambda depth bytes -> result <- Task.attempt (Context.getChar ctx) when result is @@ -231,13 +201,10 @@ interpretCtxLoop = \ctx -> Task.succeed (Step { newCtx & state: InLambda (depth - 1) (List.append bytes val) }) else Task.succeed (Step { newCtx & state: InLambda depth (List.append bytes val) }) - Err NoScope -> Task.fail NoScope - Err EndOfData -> Task.fail UnexpectedEndOfData - InSpecialChar -> result <- Task.attempt (Context.getChar { ctx & state: Executing }) when result is @@ -257,34 +224,26 @@ interpretCtxLoop = \ctx -> when result2 is Ok a -> Task.succeed (Step a) - Err e -> Task.fail e - Ok (T 0x9F newCtx) -> # This is supposed to flush io buffers. We don't buffer, so it does nothing Task.succeed (Step newCtx) - Ok (T x _) -> data = Num.toStr (Num.intCast x) Task.fail (InvalidChar data) - Err NoScope -> Task.fail NoScope - Err EndOfData -> Task.fail UnexpectedEndOfData - LoadChar -> result <- Task.attempt (Context.getChar { ctx & state: Executing }) when result is Ok (T x newCtx) -> Task.succeed (Step (Context.pushStack newCtx (Number (Num.intCast x)))) - Err NoScope -> Task.fail NoScope - Err EndOfData -> Task.fail UnexpectedEndOfData @@ -299,7 +258,6 @@ stepExecCtx = \ctx, char -> (T popCtx bytes) <- Result.after (popLambda ctx) Ok { popCtx & scopes: List.append popCtx.scopes { data: None, buf: bytes, index: 0, whileInfo: None } } ) - 0x3F -> # `?` if Task.fromResult @@ -311,7 +269,6 @@ stepExecCtx = \ctx, char -> else Ok { popCtx2 & scopes: List.append popCtx2.scopes { data: None, buf: bytes, index: 0, whileInfo: None } } ) - 0x23 -> # `#` while Task.fromResult @@ -327,11 +284,9 @@ stepExecCtx = \ctx, char -> # push a scope to execute the condition. Ok { popCtx2 & scopes: List.append scopes { data: None, buf: cond, index: 0, whileInfo: None } } - Err OutOfBounds -> Err NoScope ) - 0x24 -> # `$` dup # Switching this to List.last and changing the error to ListWasEmpty leads to a compiler bug. @@ -339,20 +294,16 @@ stepExecCtx = \ctx, char -> when List.get ctx.stack (List.len ctx.stack - 1) is Ok dupItem -> Task.succeed (Context.pushStack ctx dupItem) - Err OutOfBounds -> Task.fail EmptyStack - 0x25 -> # `%` drop when Context.popStack ctx is # Dropping with an empty stack, all results here are fine Ok (T popCtx _) -> Task.succeed popCtx - Err _ -> Task.succeed ctx - 0x5C -> # `\` swap result2 = @@ -363,11 +314,9 @@ stepExecCtx = \ctx, char -> when result2 is Ok a -> Task.succeed a - # Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack Err EmptyStack -> Task.fail EmptyStack - 0x40 -> # `@` rot result2 = @@ -379,17 +328,14 @@ stepExecCtx = \ctx, char -> when result2 is Ok a -> Task.succeed a - # Being explicit with error type is required to stop the need to propogate the error parameters to Context.popStack Err EmptyStack -> Task.fail EmptyStack - 0xC3 -> # `ø` pick or `ß` flush # these are actually 2 bytes, 0xC3 0xB8 or 0xC3 0x9F # requires special parsing Task.succeed { ctx & state: InSpecialChar } - 0x4F -> # `O` also treat this as pick for easier script writing Task.fromResult @@ -405,28 +351,22 @@ stepExecCtx = \ctx, char -> else Err OutOfBounds ) - 0x42 -> # `B` also treat this as flush for easier script writing # This is supposed to flush io buffers. We don't buffer, so it does nothing Task.succeed ctx - 0x27 -> # `'` load next char Task.succeed { ctx & state: LoadChar } - 0x2B -> # `+` add Task.fromResult (binaryOp ctx Num.addWrap) - 0x2D -> # `-` sub Task.fromResult (binaryOp ctx Num.subWrap) - 0x2A -> # `*` mul Task.fromResult (binaryOp ctx Num.mulWrap) - 0x2F -> # `/` div # Due to possible division by zero error, this must be handled specially. @@ -437,15 +377,12 @@ stepExecCtx = \ctx, char -> res <- Result.after (Num.divTruncChecked numL numR) Ok (Context.pushStack popCtx2 (Number res)) ) - 0x26 -> # `&` bitwise and Task.fromResult (binaryOp ctx Num.bitwiseAnd) - 0x7C -> # `|` bitwise or Task.fromResult (binaryOp ctx Num.bitwiseOr) - 0x3D -> # `=` equals Task.fromResult @@ -455,7 +392,6 @@ stepExecCtx = \ctx, char -> else 0 ) - 0x3E -> # `>` greater than Task.fromResult @@ -465,15 +401,12 @@ stepExecCtx = \ctx, char -> else 0 ) - 0x5F -> # `_` negate Task.fromResult (unaryOp ctx Num.neg) - 0x7E -> # `~` bitwise not Task.fromResult (unaryOp ctx (\x -> Num.bitwiseXor x -1)) - # xor with -1 should be bitwise not 0x2C -> # `,` write char @@ -481,25 +414,20 @@ stepExecCtx = \ctx, char -> Ok (T popCtx num) -> when Str.fromUtf8 [ Num.intCast num ] is Ok str -> - { } <- Task.await (Stdout.raw str) + {} <- Task.await (Stdout.raw str) Task.succeed popCtx - Err _ -> Task.fail BadUtf8 - Err e -> Task.fail e - 0x2E -> # `.` write int when popNumber ctx is Ok (T popCtx num) -> - { } <- Task.await (Stdout.raw (Num.toStr (Num.intCast num))) + {} <- Task.await (Stdout.raw (Num.toStr (Num.intCast num))) Task.succeed popCtx - Err e -> Task.fail e - 0x5E -> # `^` read char as int in <- Task.await Stdin.char @@ -508,7 +436,6 @@ stepExecCtx = \ctx, char -> Task.succeed (Context.pushStack ctx (Number -1)) else Task.succeed (Context.pushStack ctx (Number (Num.intCast in))) - 0x3A -> # `:` store to variable Task.fromResult @@ -518,7 +445,6 @@ stepExecCtx = \ctx, char -> (T popCtx2 n1) <- Result.after (Result.mapErr (Context.popStack popCtx1) (\EmptyStack -> EmptyStack)) Ok { popCtx2 & vars: List.set popCtx2.vars (Variable.toIndex var) n1 } ) - 0x3B -> # `;` load from variable Task.fromResult @@ -527,32 +453,25 @@ stepExecCtx = \ctx, char -> elem <- Result.after (List.get popCtx.vars (Variable.toIndex var)) Ok (Context.pushStack popCtx elem) ) - 0x22 -> # `"` string start Task.succeed { ctx & state: InString [] } - 0x5B -> # `"` string start Task.succeed { ctx & state: InLambda 0 [] } - 0x7B -> # `{` comment start Task.succeed { ctx & state: InComment } - x if isDigit x -> # number start Task.succeed { ctx & state: InNumber (Num.intCast (x - 0x30)) } - x if isWhitespace x -> Task.succeed ctx - x -> when Variable.fromUtf8 x is # letters are variable names Ok var -> Task.succeed (Context.pushStack ctx (Var var)) - Err _ -> data = Num.toStr (Num.intCast x) @@ -574,10 +493,8 @@ popNumber = \ctx -> when Context.popStack ctx is Ok (T popCtx (Number num)) -> Ok (T popCtx num) - Ok _ -> Err (NoNumberOnStack) - Err EmptyStack -> Err EmptyStack @@ -586,10 +503,8 @@ popLambda = \ctx -> when Context.popStack ctx is Ok (T popCtx (Lambda bytes)) -> Ok (T popCtx bytes) - Ok _ -> Err NoLambdaOnStack - Err EmptyStack -> Err EmptyStack @@ -598,9 +513,7 @@ popVariable = \ctx -> when Context.popStack ctx is Ok (T popCtx (Var var)) -> Ok (T popCtx var) - Ok _ -> Err NoVariableOnStack - Err EmptyStack -> Err EmptyStack diff --git a/examples/false-interpreter/Variable.roc b/examples/false-interpreter/Variable.roc index 866ec789b1..be0d78c26a 100644 --- a/examples/false-interpreter/Variable.roc +++ b/examples/false-interpreter/Variable.roc @@ -19,7 +19,6 @@ toStr = \@Variable char -> when Str.fromUtf8 [ char ] is Ok str -> str - _ -> "_" diff --git a/examples/false-interpreter/platform/Cargo.toml b/examples/false-interpreter/platform/Cargo.toml index eba1dfa680..37d5561b6b 100644 --- a/examples/false-interpreter/platform/Cargo.toml +++ b/examples/false-interpreter/platform/Cargo.toml @@ -3,7 +3,7 @@ name = "host" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" links = "app" diff --git a/examples/false-interpreter/platform/File.roc b/examples/false-interpreter/platform/File.roc index e64efe749a..7ad1f8c4aa 100644 --- a/examples/false-interpreter/platform/File.roc +++ b/examples/false-interpreter/platform/File.roc @@ -23,5 +23,5 @@ withOpen : Str, (Handle -> Task {} a) -> Task {} a withOpen = \path, callback -> handle <- Task.await (open path) result <- Task.attempt (callback handle) - { } <- Task.await (close handle) + {} <- Task.await (close handle) Task.fromResult result diff --git a/examples/false-interpreter/platform/Task.roc b/examples/false-interpreter/platform/Task.roc index 71c3ed4371..747efb510e 100644 --- a/examples/false-interpreter/platform/Task.roc +++ b/examples/false-interpreter/platform/Task.roc @@ -13,10 +13,8 @@ loop = \state, step -> when res is Ok (Step newState) -> Step newState - Ok (Done result) -> Done (Ok result) - Err e -> Done (Err e) @@ -35,7 +33,6 @@ fromResult = \result -> when result is Ok a -> succeed a - Err e -> fail e @@ -47,7 +44,6 @@ attempt = \effect, transform -> when result is Ok ok -> transform (Ok ok) - Err err -> transform (Err err) @@ -59,7 +55,6 @@ await = \effect, transform -> when result is Ok a -> transform a - Err err -> Task.fail err @@ -71,7 +66,6 @@ onFail = \effect, transform -> when result is Ok a -> Task.succeed a - Err err -> transform err @@ -83,6 +77,5 @@ map = \effect, transform -> when result is Ok a -> Task.succeed (transform a) - Err err -> Task.fail err diff --git a/examples/false-interpreter/platform/src/lib.rs b/examples/false-interpreter/platform/src/lib.rs index 7efee7307c..67c2dbe26d 100644 --- a/examples/false-interpreter/platform/src/lib.rs +++ b/examples/false-interpreter/platform/src/lib.rs @@ -19,7 +19,7 @@ extern "C" { fn roc_main_size() -> i64; #[link_name = "roc__mainForHost_1_Fx_caller"] - fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8) -> (); + fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8); #[allow(dead_code)] #[link_name = "roc__mainForHost_1_Fx_size"] diff --git a/examples/gui/platform/Action.roc b/examples/gui/platform/Action.roc index ad15ee728b..0c6834fdb8 100644 --- a/examples/gui/platform/Action.roc +++ b/examples/gui/platform/Action.roc @@ -15,6 +15,5 @@ map = \action, transform -> when action is None -> None - Update state -> Update (transform state) diff --git a/examples/gui/platform/Cargo.toml b/examples/gui/platform/Cargo.toml index b82712e562..083f3e369e 100644 --- a/examples/gui/platform/Cargo.toml +++ b/examples/gui/platform/Cargo.toml @@ -3,14 +3,9 @@ name = "host" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" links = "app" -# Needed to be able to run on non-Windows systems for some reason. Without this, cargo panics with: -# -# error: DX12 API enabled on non-Windows OS. If your project is not using resolver="2" in Cargo.toml, it should. -resolver = "2" - [lib] name = "host" path = "src/lib.rs" diff --git a/examples/gui/platform/Elem.roc b/examples/gui/platform/Elem.roc index 519d007b99..1cf7e17188 100644 --- a/examples/gui/platform/Elem.roc +++ b/examples/gui/platform/Elem.roc @@ -54,7 +54,6 @@ lazy = \state, render -> # same as the cached one, then we can return exactly # what we had cached. cached - _ -> # Either the state changed or else we didn't have a # cached value to use. Either way, we need to render @@ -81,13 +80,10 @@ translate = \child, toChild, toParent -> when child is Text str -> Text str - Col elems -> Col (List.map elems \elem -> translate elem toChild toParent) - Row elems -> Row (List.map elems \elem -> translate elem toChild toParent) - Button config label -> onPress = \parentState, event -> toChild parentState @@ -95,7 +91,6 @@ translate = \child, toChild, toParent -> |> Action.map \c -> toParent parentState c Button { onPress } (translate label toChild toParent) - Lazy renderChild -> Lazy \parentState -> @@ -105,7 +100,6 @@ translate = \child, toChild, toParent -> elem: translate toChild toParent newChild, state: toParent parentState state, } - None -> None @@ -154,13 +148,10 @@ translateOrDrop = \child, toChild, toParent -> when child is Text str -> Text str - Col elems -> Col (List.map elems \elem -> translateOrDrop elem toChild toParent) - Row elems -> Row (List.map elems \elem -> translateOrDrop elem toChild toParent) - Button config label -> onPress = \parentState, event -> when toChild parentState is @@ -168,14 +159,12 @@ translateOrDrop = \child, toChild, toParent -> newChild |> config.onPress event |> Action.map \c -> toParent parentState c - Err _ -> # The child was removed from the list before this onPress handler resolved. # (For example, by a previous event handler that fired simultaneously.) Action.none Button { onPress } (translateOrDrop label toChild toParent) - Lazy childState renderChild -> Lazy (toParent childState) @@ -184,10 +173,8 @@ translateOrDrop = \child, toChild, toParent -> Ok newChild -> renderChild newChild |> translateOrDrop toChild toParent - Err _ -> None - # I don't think this should ever happen in practice. None -> None diff --git a/examples/hello-world/rust-platform/Cargo.toml b/examples/hello-world/rust-platform/Cargo.toml index 72f534c88e..baad986559 100644 --- a/examples/hello-world/rust-platform/Cargo.toml +++ b/examples/hello-world/rust-platform/Cargo.toml @@ -3,7 +3,7 @@ name = "host" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" links = "app" [lib] diff --git a/examples/interactive/cli-platform/Cargo.toml b/examples/interactive/cli-platform/Cargo.toml index eba1dfa680..37d5561b6b 100644 --- a/examples/interactive/cli-platform/Cargo.toml +++ b/examples/interactive/cli-platform/Cargo.toml @@ -3,7 +3,7 @@ name = "host" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" links = "app" diff --git a/examples/interactive/cli-platform/Task.roc b/examples/interactive/cli-platform/Task.roc index 9aee250a24..474f4cb0bc 100644 --- a/examples/interactive/cli-platform/Task.roc +++ b/examples/interactive/cli-platform/Task.roc @@ -6,14 +6,13 @@ Task ok err : Effect.Effect (Result ok err) forever : Task val err -> Task * err forever = \task -> - looper = \{ } -> + looper = \{} -> task |> Effect.map \res -> when res is Ok _ -> Step {} - Err e -> Done (Err e) @@ -28,10 +27,8 @@ loop = \state, step -> when res is Ok (Step newState) -> Step newState - Ok (Done result) -> Done (Ok result) - Err e -> Done (Err e) @@ -53,7 +50,6 @@ attempt = \effect, transform -> when result is Ok ok -> transform (Ok ok) - Err err -> transform (Err err) @@ -65,7 +61,6 @@ await = \effect, transform -> when result is Ok a -> transform a - Err err -> Task.fail err @@ -77,7 +72,6 @@ onFail = \effect, transform -> when result is Ok a -> Task.succeed a - Err err -> transform err @@ -89,6 +83,5 @@ map = \effect, transform -> when result is Ok a -> Task.succeed (transform a) - Err err -> Task.fail err diff --git a/examples/interactive/cli-platform/src/lib.rs b/examples/interactive/cli-platform/src/lib.rs index b0b1783072..47e6e62424 100644 --- a/examples/interactive/cli-platform/src/lib.rs +++ b/examples/interactive/cli-platform/src/lib.rs @@ -10,13 +10,13 @@ use std::os::raw::c_char; extern "C" { #[link_name = "roc__mainForHost_1_exposed_generic"] - fn roc_main(output: *mut u8) -> (); + fn roc_main(output: *mut u8); #[link_name = "roc__mainForHost_size"] fn roc_main_size() -> i64; #[link_name = "roc__mainForHost_1_Fx_caller"] - fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8) -> (); + fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8); #[allow(dead_code)] #[link_name = "roc__mainForHost_1_Fx_size"] diff --git a/examples/interactive/effects.roc b/examples/interactive/effects.roc index a81c87f1ea..ff4762704d 100644 --- a/examples/interactive/effects.roc +++ b/examples/interactive/effects.roc @@ -10,8 +10,8 @@ main = \line -> Effect.after (Effect.putLine "You entered: \(line)") - \{ } -> + \{} -> Effect.after (Effect.putLine "It is known") - \{ } -> + \{} -> Effect.always {} diff --git a/examples/interactive/tui.roc b/examples/interactive/tui.roc index 3f09cbfdf5..fdfdbf69ee 100644 --- a/examples/interactive/tui.roc +++ b/examples/interactive/tui.roc @@ -7,7 +7,7 @@ Model : Str main : Program Model main = { - init: \{ } -> "Hello World", + init: \{} -> "Hello World", update: \model, new -> Str.concat model new, view: \model -> Str.concat model "!", } diff --git a/highlight/Cargo.toml b/highlight/Cargo.toml index aef00a2c33..4cd885c52f 100644 --- a/highlight/Cargo.toml +++ b/highlight/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_highlight" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" description = "For syntax highlighting, starts with a string and returns our markup nodes." [dependencies] diff --git a/linker/Cargo.toml b/linker/Cargo.toml index 497c8088a1..0952e7d924 100644 --- a/linker/Cargo.toml +++ b/linker/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" repository = "https://github.com/rtfeldman/roc" -edition = "2018" +edition = "2021" description = "A surgical linker for Roc" [lib] diff --git a/nightly_benches/Cargo.toml b/nightly_benches/Cargo.toml index 380cb7f454..f37a09f96a 100644 --- a/nightly_benches/Cargo.toml +++ b/nightly_benches/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "nightly_benches" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] diff --git a/nightly_benches/benches/events_bench.rs b/nightly_benches/benches/events_bench.rs index be889672f2..2e529f8c70 100644 --- a/nightly_benches/benches/events_bench.rs +++ b/nightly_benches/benches/events_bench.rs @@ -13,7 +13,7 @@ fn bench_group(c: &mut Criterion, hw_event_str: &str) { // calculate statistics based on a fixed(flat) 100 runs group.sampling_mode(SamplingMode::Flat); - let bench_funcs: Vec>) -> ()> = vec![ + let bench_funcs: Vec>)> = vec![ bench_nqueens, bench_cfold, bench_deriv, diff --git a/reporting/Cargo.toml b/reporting/Cargo.toml index 9ca5360dc6..3261d6ecd9 100644 --- a/reporting/Cargo.toml +++ b/reporting/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_reporting" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" [dependencies] roc_collections = { path = "../compiler/collections" } @@ -15,6 +15,7 @@ roc_problem = { path = "../compiler/problem" } roc_types = { path = "../compiler/types" } roc_can = { path = "../compiler/can" } roc_solve = { path = "../compiler/solve" } +roc_std = { path = "../roc_std" } ven_pretty = { path = "../vendor/pretty" } distance = "0.4.0" bumpalo = { version = "3.8.0", features = ["collections"] } diff --git a/reporting/src/error/canonicalize.rs b/reporting/src/error/canonicalize.rs index 957413f3d7..da17170cad 100644 --- a/reporting/src/error/canonicalize.rs +++ b/reporting/src/error/canonicalize.rs @@ -1,5 +1,6 @@ use roc_collections::all::MutSet; use roc_module::ident::{Ident, Lowercase, ModuleName}; +use roc_module::symbol::BUILTIN_ABILITIES; use roc_problem::can::PrecedenceProblem::BothNonAssociative; use roc_problem::can::{ BadPattern, ExtensionTypeKind, FloatErrorKind, IntErrorKind, Problem, RuntimeError, ShadowKind, @@ -46,6 +47,7 @@ const ABILITY_MEMBER_BINDS_MULTIPLE_VARIABLES: &str = "ABILITY MEMBER BINDS MULT const ABILITY_NOT_ON_TOPLEVEL: &str = "ABILITY NOT ON TOP-LEVEL"; const SPECIALIZATION_NOT_ON_TOPLEVEL: &str = "SPECIALIZATION NOT ON TOP-LEVEL"; const ABILITY_USED_AS_TYPE: &str = "ABILITY USED AS TYPE"; +const ILLEGAL_DERIVE: &str = "ILLEGAL DERIVE"; pub fn can_problem<'b>( alloc: &'b RocDocAllocator<'b>, @@ -736,6 +738,18 @@ pub fn can_problem<'b>( title = SPECIALIZATION_NOT_ON_TOPLEVEL.to_string(); severity = Severity::Warning; } + Problem::IllegalDerive(region) => { + doc = alloc.stack([ + alloc.reflow("This ability cannot be derived:"), + alloc.region(lines.convert_region(region)), + alloc.reflow("Only builtin abilities can be derived."), + alloc + .note("The builtin abilities are ") + .append(list_builtin_abilities(alloc)), + ]); + title = ILLEGAL_DERIVE.to_string(); + severity = Severity::Warning; + } }; Report { @@ -746,6 +760,12 @@ pub fn can_problem<'b>( } } +fn list_builtin_abilities<'a>(alloc: &'a RocDocAllocator<'a>) -> RocDocBuilder<'a> { + let doc = alloc.concat([alloc.symbol_qualified(BUILTIN_ABILITIES[0])]); + debug_assert!(BUILTIN_ABILITIES.len() == 1); + doc +} + fn to_invalid_optional_value_report<'b>( alloc: &'b RocDocAllocator<'b>, lines: &LineInfo, @@ -1166,14 +1186,7 @@ fn pretty_runtime_error<'b>( } RuntimeError::LookupNotInScope(loc_name, options) => { - doc = not_found( - alloc, - lines, - loc_name.region, - &loc_name.value, - "value", - options, - ); + doc = not_found(alloc, lines, loc_name.region, &loc_name.value, options); title = UNRECOGNIZED_NAME; } RuntimeError::CircularDef(entries) => { @@ -1766,7 +1779,6 @@ fn not_found<'b>( lines: &LineInfo, region: roc_region::all::Region, name: &Ident, - thing: &'b str, options: MutSet>, ) -> RocDocBuilder<'b> { let mut suggestions = suggest::sort( @@ -1800,10 +1812,9 @@ fn not_found<'b>( alloc.stack([ alloc.concat([ - alloc.reflow("I cannot find a `"), + alloc.reflow("Nothing is named `"), alloc.string(name.to_string()), - alloc.reflow("` "), - alloc.reflow(thing), + alloc.reflow("` in this scope."), ]), alloc.region(lines.convert_region(region)), to_details(default_no, default_yes), diff --git a/reporting/src/error/type.rs b/reporting/src/error/type.rs index 8eddfe1fa4..44c40c265d 100644 --- a/reporting/src/error/type.rs +++ b/reporting/src/error/type.rs @@ -7,7 +7,9 @@ use roc_module::called_via::{BinOp, CalledVia}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_region::all::{LineInfo, Loc, Region}; -use roc_solve::solve::{self, IncompleteAbilityImplementation}; +use roc_solve::ability::{UnderivableReason, Unfulfilled}; +use roc_solve::solve; +use roc_std::RocDec; use roc_types::pretty_print::{Parens, WILDCARD}; use roc_types::types::{ AliasKind, Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt, @@ -125,38 +127,26 @@ pub fn type_problem<'b>( other => panic!("unhandled bad type: {:?}", other), } } - IncompleteAbilityImplementation(incomplete) => { + UnfulfilledAbility(incomplete) => { let title = "INCOMPLETE ABILITY IMPLEMENTATION".to_string(); - let doc = report_incomplete_ability(alloc, lines, incomplete); + let doc = report_unfulfilled_ability(alloc, lines, incomplete); report(title, doc, filename) } - BadExprMissingAbility(region, category, found, incomplete) => { - let note = alloc.stack([ - alloc.reflow("The ways this expression is used requires that the following types implement the following abilities, which they do not:"), - alloc.type_block(alloc.stack(incomplete.iter().map(|incomplete| { - symbol_does_not_implement(alloc, incomplete.typ, incomplete.ability) - }))), - ]); + BadExprMissingAbility(region, _category, _found, incomplete) => { + let incomplete = incomplete + .into_iter() + .map(|unfulfilled| report_unfulfilled_ability(alloc, lines, unfulfilled)); + let note = alloc.stack(incomplete); let snippet = alloc.region(lines.convert_region(region)); - let mut stack = vec![ + let stack = [ alloc.text( "This expression has a type that does not implement the abilities it's expected to:", ), snippet, - lone_type( - alloc, - found.clone(), - found, - ExpectationContext::Arbitrary, - add_category(alloc, alloc.text("Right now it's"), &category), - note, - ), + note ]; - incomplete.into_iter().for_each(|incomplete| { - stack.push(report_incomplete_ability(alloc, lines, incomplete)) - }); let report = Report { title: "TYPE MISMATCH".to_string(), @@ -166,31 +156,19 @@ pub fn type_problem<'b>( }; Some(report) } - BadPatternMissingAbility(region, category, found, incomplete) => { - let note = alloc.stack([ - alloc.reflow("The ways this expression is used requires that the following types implement the following abilities, which they do not:"), - alloc.type_block(alloc.stack(incomplete.iter().map(|incomplete| { - symbol_does_not_implement(alloc, incomplete.typ, incomplete.ability) - }))), - ]); + BadPatternMissingAbility(region, _category, _found, incomplete) => { + let incomplete = incomplete + .into_iter() + .map(|unfulfilled| report_unfulfilled_ability(alloc, lines, unfulfilled)); + let note = alloc.stack(incomplete); let snippet = alloc.region(lines.convert_region(region)); - let mut stack = vec![ + let stack = [ alloc.text( "This expression has a type does not implement the abilities it's expected to:", ), snippet, - lone_type( - alloc, - found.clone(), - found, - ExpectationContext::Arbitrary, - add_pattern_category(alloc, alloc.text("Right now it's"), &category), - note, - ), + note, ]; - incomplete.into_iter().for_each(|incomplete| { - stack.push(report_incomplete_ability(alloc, lines, incomplete)) - }); let report = Report { title: "TYPE MISMATCH".to_string(), @@ -213,59 +191,240 @@ pub fn type_problem<'b>( severity, }) } + StructuralSpecialization { + region, + typ, + ability, + member, + } => { + let stack = [ + alloc.concat([ + alloc.reflow("This specialization of "), + alloc.symbol_unqualified(member), + alloc.reflow(" is for a non-opaque type:"), + ]), + alloc.region(lines.convert_region(region)), + alloc.reflow("It is specialized for"), + alloc.type_block(error_type_to_doc(alloc, typ)), + alloc.reflow("but structural types can never specialize abilities!"), + alloc.note("").append(alloc.concat([ + alloc.symbol_unqualified(member), + alloc.reflow(" is a member of "), + alloc.symbol_qualified(ability), + ])), + ]; + + Some(Report { + title: "ILLEGAL SPECIALIZATION".to_string(), + filename, + doc: alloc.stack(stack), + severity: Severity::RuntimeError, + }) + } + DominatedDerive { + opaque, + ability, + derive_region, + impl_region, + } => { + let stack = [ + alloc.concat([ + alloc.symbol_unqualified(opaque), + alloc.reflow(" both derives and custom-implements "), + alloc.symbol_qualified(ability), + alloc.reflow(". We found the derive here:"), + ]), + alloc.region(lines.convert_region(derive_region)), + alloc.concat([ + alloc.reflow("and one custom implementation of "), + alloc.symbol_qualified(ability), + alloc.reflow(" here:"), + ]), + alloc.region(lines.convert_region(impl_region)), + alloc.concat([ + alloc.reflow("Derived and custom implementations can conflict, so one of them needs to be removed!"), + ]), + alloc.note("").append(alloc.reflow("We'll try to compile your program using the custom implementation first, and fall-back on the derived implementation if needed. Make sure to disambiguate which one you want!")), + ]; + + Some(Report { + title: "CONFLICTING DERIVE AND IMPLEMENTATION".to_string(), + filename, + doc: alloc.stack(stack), + severity: Severity::Warning, + }) + } } } -fn report_incomplete_ability<'a>( +fn report_unfulfilled_ability<'a>( alloc: &'a RocDocAllocator<'a>, lines: &LineInfo, - incomplete: IncompleteAbilityImplementation, + unfulfilled: Unfulfilled, ) -> RocDocBuilder<'a> { - let IncompleteAbilityImplementation { - typ, - ability, - specialized_members, - missing_members, - } = incomplete; + match unfulfilled { + Unfulfilled::Incomplete { + typ, + ability, + missing_members, + } => { + debug_assert!(!missing_members.is_empty()); - debug_assert!(!missing_members.is_empty()); + let mut stack = vec![alloc.concat([ + alloc.reflow("The type "), + alloc.symbol_unqualified(typ), + alloc.reflow(" does not fully implement the ability "), + alloc.symbol_unqualified(ability), + alloc.reflow(". The following specializations are missing:"), + ])]; - let mut stack = vec![alloc.concat([ - alloc.reflow("The type "), - alloc.symbol_unqualified(typ), - alloc.reflow(" does not fully implement the ability "), - alloc.symbol_unqualified(ability), - alloc.reflow(". The following specializations are missing:"), - ])]; + for member in missing_members.into_iter() { + stack.push(alloc.concat([ + alloc.reflow("A specialization for "), + alloc.symbol_unqualified(member.value), + alloc.reflow(", which is defined here:"), + ])); + stack.push(alloc.region(lines.convert_region(member.region))); + } - for member in missing_members.into_iter() { - stack.push(alloc.concat([ - alloc.reflow("A specialization for "), - alloc.symbol_unqualified(member.value), - alloc.reflow(", which is defined here:"), - ])); - stack.push(alloc.region(lines.convert_region(member.region))); - } + alloc.stack(stack) + } + Unfulfilled::AdhocUnderivable { + typ, + ability, + reason, + } => { + let reason = report_underivable_reason(alloc, reason, ability, &typ); + let stack = [ + alloc.concat([ + alloc.reflow("Roc can't generate an implementation of the "), + alloc.symbol_qualified(ability), + alloc.reflow(" ability for"), + ]), + alloc.type_block(error_type_to_doc(alloc, typ)), + ] + .into_iter() + .chain(reason); - if !specialized_members.is_empty() { - stack.push(alloc.concat([ - alloc.note(""), - alloc.symbol_unqualified(typ), - alloc.reflow(" specializes the following members of "), - alloc.symbol_unqualified(ability), - alloc.reflow(":"), - ])); + alloc.stack(stack) + } + Unfulfilled::OpaqueUnderivable { + typ, + ability, + opaque, + derive_region, + reason, + } => { + let reason = report_underivable_reason(alloc, reason, ability, &typ); + let stack = [ + alloc.concat([ + alloc.reflow("Roc can't derive an implementation of the "), + alloc.symbol_qualified(ability), + alloc.reflow(" for "), + alloc.symbol_unqualified(opaque), + alloc.reflow(":"), + ]), + alloc.region(lines.convert_region(derive_region)), + ] + .into_iter() + .chain(reason) + .chain(std::iter::once(alloc.tip().append(alloc.concat([ + alloc.reflow("You can define a custom implementation of "), + alloc.symbol_qualified(ability), + alloc.reflow(" for "), + alloc.symbol_unqualified(opaque), + alloc.reflow("."), + ])))); - for spec in specialized_members { - stack.push(alloc.concat([ - alloc.symbol_unqualified(spec.value), - alloc.reflow(", specialized here:"), - ])); - stack.push(alloc.region(lines.convert_region(spec.region))); + alloc.stack(stack) } } +} - alloc.stack(stack) +fn report_underivable_reason<'a>( + alloc: &'a RocDocAllocator<'a>, + reason: UnderivableReason, + ability: Symbol, + typ: &ErrorType, +) -> Option> { + match reason { + UnderivableReason::NotABuiltin => { + Some(alloc.reflow("Only builtin abilities can have generated implementations!")) + } + UnderivableReason::SurfaceNotDerivable => underivable_hint(alloc, ability, typ), + UnderivableReason::NestedNotDerivable(nested_typ) => { + let hint = underivable_hint(alloc, ability, &nested_typ); + let reason = alloc.stack( + [ + alloc.reflow("In particular, an implementation for"), + alloc.type_block(error_type_to_doc(alloc, nested_typ)), + alloc.reflow("cannot be generated."), + ] + .into_iter() + .chain(hint), + ); + Some(reason) + } + } +} + +fn underivable_hint<'b>( + alloc: &'b RocDocAllocator<'b>, + ability: Symbol, + typ: &ErrorType, +) -> Option> { + match typ { + ErrorType::Function(..) => Some(alloc.note("").append(alloc.concat([ + alloc.symbol_unqualified(ability), + alloc.reflow(" cannot be generated for functions."), + ]))), + ErrorType::FlexVar(v) | ErrorType::RigidVar(v) => Some(alloc.tip().append(alloc.concat([ + alloc.reflow("This type variable is not bound to "), + alloc.symbol_unqualified(ability), + alloc.reflow(". Consider adding a "), + alloc.keyword("has"), + alloc.reflow(" clause to bind the type variable, like "), + alloc.inline_type_block(alloc.concat([ + alloc.string("| ".to_string()), + alloc.type_variable(v.clone()), + alloc.space(), + alloc.keyword("has"), + alloc.space(), + alloc.symbol_qualified(ability), + ])), + ]))), + ErrorType::Alias(symbol, _, _, AliasKind::Opaque) => { + Some(alloc.tip().append(alloc.concat([ + alloc.symbol_unqualified(*symbol), + alloc.reflow(" does not implement "), + alloc.symbol_unqualified(ability), + alloc.reflow("."), + if symbol.module_id() == alloc.home { + alloc.concat([ + alloc.reflow(" Consider adding a custom implementation"), + if ability.is_builtin() { + alloc.concat([ + alloc.reflow(" or "), + alloc.inline_type_block(alloc.concat([ + alloc.keyword("has"), + alloc.space(), + alloc.symbol_qualified(ability), + ])), + alloc.reflow(" to the definition of "), + alloc.symbol_unqualified(*symbol), + ]) + } else { + alloc.nil() + }, + alloc.reflow("."), + ]) + } else { + alloc.nil() + }, + ]))) + } + _ => None, + } } fn report_shadowing<'b>( @@ -775,9 +934,9 @@ fn to_expr_report<'b>( alloc.text(" branch has the type:"), ]), Some(alloc.concat([ - alloc.text("I need all branches in an "), + alloc.text("All branches in an "), alloc.keyword("if"), - alloc.text(" to have the same type!"), + alloc.text(" must have the same type!"), ])), ), _ => report_mismatch( @@ -799,9 +958,9 @@ fn to_expr_report<'b>( alloc.string(format!("The {} branch is", index.ordinal())), alloc.reflow("But all the previous branches have type:"), Some(alloc.concat([ - alloc.reflow("I need all branches in an "), + alloc.reflow("All branches in an "), alloc.keyword("if"), - alloc.reflow(" to have the same type!"), + alloc.reflow(" must have the same type!"), ])), ), }, @@ -828,9 +987,9 @@ fn to_expr_report<'b>( ]), alloc.reflow("But all the previous branches have type:"), Some(alloc.concat([ - alloc.reflow("I need all branches of a "), + alloc.reflow("All branches of a "), alloc.keyword("when"), - alloc.reflow(" to have the same type!"), + alloc.reflow(" must have the same type!"), ])), ), Reason::ElemInList { index } => { @@ -856,7 +1015,7 @@ fn to_expr_report<'b>( alloc.reflow("This list contains elements with different types:"), alloc.string(format!("Its {} element is", ith)), alloc.reflow(prev_elems_msg), - Some(alloc.reflow("I need every element in a list to have the same type!")), + Some(alloc.reflow("Every element in a list must have the same type!")), ) } Reason::RecordUpdateValue(field) => report_mismatch( @@ -1306,18 +1465,6 @@ fn does_not_implement<'a>( ]) } -fn symbol_does_not_implement<'a>( - alloc: &'a RocDocAllocator<'a>, - symbol: Symbol, - ability: Symbol, -) -> RocDocBuilder<'a> { - alloc.concat([ - alloc.symbol_unqualified(symbol), - alloc.reflow(" does not implement "), - alloc.symbol_unqualified(ability), - ]) -} - fn count_arguments(tipe: &ErrorType) -> usize { use ErrorType::*; @@ -2234,6 +2381,14 @@ fn type_with_able_vars<'b>( alloc.concat(doc) } +fn error_type_to_doc<'b>( + alloc: &'b RocDocAllocator<'b>, + error_type: ErrorType, +) -> RocDocBuilder<'b> { + let (typ, able_vars) = to_doc(alloc, Parens::Unnecessary, error_type); + type_with_able_vars(alloc, typ, able_vars) +} + fn to_diff<'b>( alloc: &'b RocDocAllocator<'b>, parens: Parens, @@ -3696,13 +3851,13 @@ fn pattern_to_doc_help<'b>( match pattern { Anything => alloc.text("_"), Literal(l) => match l { - Int(i) => alloc.text(i.to_string()), - U128(i) => alloc.text(i.to_string()), + Int(i) => alloc.text(i128::from_ne_bytes(i).to_string()), + U128(i) => alloc.text(u128::from_ne_bytes(i).to_string()), Bit(true) => alloc.text("True"), Bit(false) => alloc.text("False"), Byte(b) => alloc.text(b.to_string()), Float(f) => alloc.text(f.to_string()), - Decimal(d) => alloc.text(d.to_string()), + Decimal(d) => alloc.text(RocDec::from_ne_bytes(d).to_string()), Str(s) => alloc.string(s.into()), }, Ctor(union, tag_id, args) => { diff --git a/reporting/src/report.rs b/reporting/src/report.rs index b42c18b3de..9b3dd9a33a 100644 --- a/reporting/src/report.rs +++ b/reporting/src/report.rs @@ -479,7 +479,7 @@ impl<'a> RocDocAllocator<'a> { self.text(content.to_string()).annotate(Annotation::BinOp) } - /// Turns of backticks/colors in a block + /// Turns off backticks/colors in a block pub fn type_block( &'a self, content: DocBuilder<'a, Self, Annotation>, @@ -487,6 +487,14 @@ impl<'a> RocDocAllocator<'a> { content.annotate(Annotation::TypeBlock).indent(4) } + /// Turns off backticks/colors in a block + pub fn inline_type_block( + &'a self, + content: DocBuilder<'a, Self, Annotation>, + ) -> DocBuilder<'a, Self, Annotation> { + content.annotate(Annotation::InlineTypeBlock) + } + pub fn tip(&'a self) -> DocBuilder<'a, Self, Annotation> { self.text("Tip") .annotate(Annotation::Tip) @@ -804,6 +812,7 @@ pub enum Annotation { PlainText, CodeBlock, TypeBlock, + InlineTypeBlock, Module, Typo, TypoSuggestion, @@ -873,6 +882,11 @@ where TypeBlock => { self.in_type_block = true; } + InlineTypeBlock => { + debug_assert!(!self.in_type_block); + self.write_str("`")?; + self.in_type_block = true; + } CodeBlock => { self.in_code_block = true; } @@ -903,6 +917,11 @@ where TypeBlock => { self.in_type_block = false; } + InlineTypeBlock => { + debug_assert!(self.in_type_block); + self.write_str("`")?; + self.in_type_block = false; + } CodeBlock => { self.in_code_block = false; } @@ -1004,7 +1023,7 @@ where ParserSuggestion => { self.write_str(self.palette.parser_suggestion)?; } - TypeBlock | Tag | RecordField => { /* nothing yet */ } + TypeBlock | InlineTypeBlock | Tag | RecordField => { /* nothing yet */ } } self.style_stack.push(*annotation); Ok(()) @@ -1022,7 +1041,7 @@ where self.write_str(self.palette.reset)?; } - TypeBlock | Tag | Opaque | RecordField => { /* nothing yet */ } + TypeBlock | InlineTypeBlock | Tag | Opaque | RecordField => { /* nothing yet */ } }, } Ok(()) diff --git a/reporting/tests/helpers/mod.rs b/reporting/tests/helpers/mod.rs index 3582a4903a..cf87f0d8a6 100644 --- a/reporting/tests/helpers/mod.rs +++ b/reporting/tests/helpers/mod.rs @@ -5,7 +5,7 @@ use roc_can::abilities::AbilitiesStore; use roc_can::constraint::{Constraint, Constraints}; use roc_can::env::Env; use roc_can::expected::Expected; -use roc_can::expr::{canonicalize_expr, Expr, Output}; +use roc_can::expr::{canonicalize_expr, Expr, Output, PendingDerives}; use roc_can::operator; use roc_can::scope::Scope; use roc_collections::all::{ImMap, MutMap, SendSet}; @@ -26,11 +26,13 @@ pub fn test_home() -> ModuleId { } #[allow(dead_code)] +#[allow(clippy::too_many_arguments)] pub fn infer_expr( subs: Subs, problems: &mut Vec, constraints: &Constraints, constraint: &Constraint, + pending_derives: PendingDerives, aliases: &mut Aliases, abilities_store: &mut AbilitiesStore, expr_var: Variable, @@ -41,6 +43,7 @@ pub fn infer_expr( subs, aliases, constraint, + pending_derives, abilities_store, ); @@ -82,7 +85,7 @@ where #[inline(always)] pub fn with_larger_debug_stack(run_test: F) where - F: FnOnce() -> (), + F: FnOnce(), F: Send, F: 'static, { @@ -149,7 +152,7 @@ pub fn can_expr_with<'a>( // rules multiple times unnecessarily. let loc_expr = operator::desugar_expr(arena, &loc_expr); - let mut scope = Scope::new(home, IdentIds::default()); + let mut scope = Scope::new(home, IdentIds::default(), Default::default()); // to skip loading other modules, we populate the scope with the builtin aliases // that makes the reporting tests much faster diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 043922bded..a9d253cda9 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -12,6 +12,7 @@ mod test_reporting { use bumpalo::Bump; use indoc::indoc; use roc_can::abilities::AbilitiesStore; + use roc_can::expr::PendingDerives; use roc_load::{self, LoadedModule, LoadingProblem, Threading}; use roc_module::symbol::{Interns, ModuleId}; use roc_region::all::LineInfo; @@ -229,6 +230,9 @@ mod test_reporting { &mut unify_problems, &constraints, &constraint, + // Use `new_report_problem_as` in order to get proper derives. + // TODO: remove the non-new reporting test infra. + PendingDerives::default(), &mut solve_aliases, &mut abilities_store, var, @@ -670,7 +674,7 @@ mod test_reporting { r#" ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - I cannot find a `bar` value + Nothing is named `bar` in this scope. 8│ 4 -> bar baz "yay" ^^^ @@ -698,7 +702,7 @@ mod test_reporting { r#" ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - I cannot find a `true` value + Nothing is named `true` in this scope. 1│ if true then 1 else 2 ^^^^ @@ -863,7 +867,7 @@ mod test_reporting { r#" ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - I cannot find a `theAdmin` value + Nothing is named `theAdmin` in this scope. 3 theAdmin ^^^^^^^^ @@ -1009,7 +1013,7 @@ mod test_reporting { Num a - I need all branches in an `if` to have the same type! + All branches in an `if` must have the same type! "# ), ) @@ -1040,7 +1044,7 @@ mod test_reporting { Num a - I need all branches in an `if` to have the same type! + All branches in an `if` must have the same type! "# ), ) @@ -1076,7 +1080,7 @@ mod test_reporting { Str - I need all branches of a `when` to have the same type! + All branches of a `when` must have the same type! "# ), ) @@ -1107,7 +1111,7 @@ mod test_reporting { Num a - I need every element in a list to have the same type! + Every element in a list must have the same type! "# ), ) @@ -1221,9 +1225,9 @@ mod test_reporting { r#" ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `f`: + I'm inferring a weird self-referential type for `g`: - 1│ f = \x -> g x + 2│ g = \x -> f [x] ^ Here is my best effort at writing down the type. You will see ∞ for @@ -1234,9 +1238,9 @@ mod test_reporting { ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `g`: + I'm inferring a weird self-referential type for `f`: - 2│ g = \x -> f [x] + 1│ f = \x -> g x ^ Here is my best effort at writing down the type. You will see ∞ for @@ -1775,7 +1779,7 @@ mod test_reporting { r#" ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - I cannot find a `foo` value + Nothing is named `foo` in this scope. 2│ { foo: _ } -> foo ^^^ @@ -2232,7 +2236,7 @@ mod test_reporting { r#" ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - I cannot find a `ok` value + Nothing is named `ok` in this scope. 2│ f = \_ -> ok 4 ^^ @@ -5807,7 +5811,7 @@ mod test_reporting { Num a - I need all branches in an `if` to have the same type! + All branches in an `if` must have the same type! "# ), ) @@ -5836,7 +5840,7 @@ but the `then` branch has the type: Str -I need all branches in an `if` to have the same type! +All branches in an `if` must have the same type! "#, $op, "^".repeat($op.len()) ), @@ -6071,7 +6075,7 @@ I need all branches in an `if` to have the same type! r#" ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - I cannot find a `bar` value + Nothing is named `bar` in this scope. 1│ [ "foo", bar("") ] ^^^ @@ -8762,7 +8766,7 @@ I need all branches in an `if` to have the same type! r#" ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - I cannot find a `UnknownType` value + Nothing is named `UnknownType` in this scope. 1│ Type : [ Constructor UnknownType ] ^^^^^^^^^^^ @@ -8776,7 +8780,7 @@ I need all branches in an `if` to have the same type! ── UNRECOGNIZED NAME ───────────────────────────────────── /code/proj/Main.roc ─ - I cannot find a `UnknownType` value + Nothing is named `UnknownType` in this scope. 3│ insertHelper : UnknownType, Type -> Type ^^^^^^^^^^^ @@ -9296,25 +9300,20 @@ I need all branches in an `if` to have the same type! ), indoc!( r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + ── ILLEGAL SPECIALIZATION ──────────────────────────────── /code/proj/Main.roc ─ - Something is off with this specialization of `hash`: + This specialization of `hash` is for a non-opaque type: 5│ hash = \{} -> 0u64 ^^^^ - This value is a declared specialization of type: + It is specialized for - {}a -> U64 + {}a - But the type annotation on `hash` says it must match: + but structural types can never specialize abilities! - a -> U64 | a has Hash - - Note: Some types in this specialization don't implement the abilities - they are expected to. I found the following missing implementations: - - {}a does not implement Hash + Note: `hash` is a member of `#UserApp.Hash` "# ), ) @@ -9384,13 +9383,6 @@ I need all branches in an `if` to have the same type! 5│ le : a, a -> Bool | a has Eq ^^ - - Note: `Id` specializes the following members of `Eq`: - - `eq`, specialized here: - - 9│ eq = \@Id m, @Id n -> m == n - ^^ "# ), ) @@ -9563,18 +9555,16 @@ I need all branches in an `if` to have the same type! r#" ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - The 1st argument to `hash` is not what I expect: + This expression has a type that does not implement the abilities it's expected to: 15│ notYet: hash (A 1), ^^^ - This `A` tag application has the type: + Roc can't generate an implementation of the `#UserApp.Hash` ability for [ A (Num a) ]b - But `hash` needs the 1st argument to be: - - a | a has Hash + Only builtin abilities can have generated implementations! ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ @@ -9583,15 +9573,6 @@ I need all branches in an `if` to have the same type! 14│ nope: hash (@User {}), ^^^^^^^^ - This User opaque wrapping has the type: - - User - - The ways this expression is used requires that the following types - implement the following abilities, which they do not: - - User does not implement Hash - The type `User` does not fully implement the ability `Hash`. The following specializations are missing: @@ -10001,19 +9982,6 @@ I need all branches in an `if` to have the same type! ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `model`: - - 6│ go = \goal, model -> - ^^^^^ - - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. - - S (Set ∞) - - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `goal`: 6│ go = \goal, model -> @@ -10067,6 +10035,114 @@ I need all branches in an `if` to have the same type! ) } + #[test] + fn function_does_not_implement_encoding() { + new_report_problem_as( + "function_does_not_implement_encoding", + indoc!( + r#" + app "test" imports [ Encode ] provides [ main ] to "./platform" + + main = Encode.toEncoder \x -> x + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 3│ main = Encode.toEncoder \x -> x + ^^^^^^^ + + Roc can't generate an implementation of the `Encode.Encoding` ability + for + + a -> a + + Note: `Encoding` cannot be generated for functions. + "# + ), + ) + } + + #[test] + fn unbound_type_in_record_does_not_implement_encoding() { + new_report_problem_as( + "cycle_through_non_function", + indoc!( + r#" + app "test" imports [ Encode ] provides [ main ] to "./platform" + + main = \x -> Encode.toEncoder { x: x } + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 3│ main = \x -> Encode.toEncoder { x: x } + ^^^^^^^^ + + Roc can't generate an implementation of the `Encode.Encoding` ability + for + + { x : a } + + In particular, an implementation for + + a + + cannot be generated. + + Tip: This type variable is not bound to `Encoding`. Consider adding a + `has` clause to bind the type variable, like `| a has Encode.Encoding` + "# + ), + ) + } + + #[test] + fn nested_opaque_does_not_implement_encoding() { + new_report_problem_as( + "cycle_through_non_function", + indoc!( + r#" + app "test" imports [ Encode ] provides [ main ] to "./platform" + + A := {} + main = Encode.toEncoder { x: @A {} } + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 4│ main = Encode.toEncoder { x: @A {} } + ^^^^^^^^^^^^ + + Roc can't generate an implementation of the `Encode.Encoding` ability + for + + { x : A } + + In particular, an implementation for + + A + + cannot be generated. + + Tip: `A` does not implement `Encoding`. Consider adding a custom + implementation or `has Encode.Encoding` to the definition of `A`. + "# + ), + ) + } + #[test] fn cycle_through_non_function_top_level() { new_report_problem_as( @@ -10102,6 +10178,124 @@ I need all branches in an `if` to have the same type! └─────┘ "# ), + ) + } + + fn derive_non_builtin_ability() { + new_report_problem_as( + "derive_non_builtin_ability", + app "test" provides [ A ] to "./platform" + + Ab has ab : a -> a | a has Ab + + A := {} has [ Ab ] + "# + ), + indoc!( + r#" + ── ILLEGAL DERIVE ──────────────────────────────────────── /code/proj/Main.roc ─ + + This ability cannot be derived: + + 5│ A := {} has [ Ab ] + ^^ + + Only builtin abilities can be derived. + + Note: The builtin abilities are `Encode.Encoding` + "# + ), + ) + } + + #[test] + fn has_encoding_for_function() { + new_report_problem_as( + "has_encoding_for_function", + indoc!( + r#" + app "test" imports [ Encode ] provides [ A ] to "./platform" + + A a := a -> a has [ Encode.Encoding ] + "# + ), + indoc!( + r#" + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + + Roc can't derive an implementation of the `Encode.Encoding` for `A`: + + 3│ A a := a -> a has [ Encode.Encoding ] + ^^^^^^^^^^^^^^^ + + Note: `Encoding` cannot be generated for functions. + + Tip: You can define a custom implementation of `Encode.Encoding` for `A`. + "# + ), + ) + } + + #[test] + fn has_encoding_for_non_encoding_alias() { + new_report_problem_as( + "has_encoding_for_non_encoding_alias", + indoc!( + r#" + app "test" imports [ Encode ] provides [ A ] to "./platform" + + A := B has [ Encode.Encoding ] + + B := {} + "# + ), + indoc!( + r#" + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + + Roc can't derive an implementation of the `Encode.Encoding` for `A`: + + 3│ A := B has [ Encode.Encoding ] + ^^^^^^^^^^^^^^^ + + Tip: `B` does not implement `Encoding`. Consider adding a custom + implementation or `has Encode.Encoding` to the definition of `B`. + + Tip: You can define a custom implementation of `Encode.Encoding` for `A`. + "# + ), + ) + } + + #[test] + fn has_encoding_for_other_has_encoding() { + new_report_problem_as( + "has_encoding_for_other_has_encoding", + indoc!( + r#" + app "test" imports [ Encode ] provides [ A ] to "./platform" + + A := B has [ Encode.Encoding ] + + B := {} has [ Encode.Encoding ] + "# + ), + indoc!(""), // no error + ) + } + + #[test] + fn has_encoding_for_recursive_deriving() { + new_report_problem_as( + "has_encoding_for_recursive_deriving", + indoc!( + r#" + app "test" imports [ Encode ] provides [ MyNat ] to "./platform" + + MyNat := [ S MyNat, Z ] has [ Encode.Encoding ] + "# + ), + indoc!(""), // no error ) } @@ -10138,4 +10332,41 @@ I need all branches in an `if` to have the same type! ), ) } + + fn has_encoding_dominated_by_custom() { + new_report_problem_as( + "has_encoding_dominated_by_custom", + indoc!( + r#" + app "test" imports [ Encode.{ Encoding, toEncoder, custom } ] provides [ A ] to "./platform" + + A := {} has [ Encode.Encoding ] + + toEncoder = \@A {} -> custom \l, _ -> l + "# + ), indoc!( + r#" + ── CONFLICTING DERIVE AND IMPLEMENTATION ───────────────── /code/proj/Main.roc ─ + + `A` both derives and custom-implements `Encode.Encoding`. We found the + derive here: + + 3│ A := {} has [ Encode.Encoding ] + ^^^^^^^^^^^^^^^ + + and one custom implementation of `Encode.Encoding` here: + + 5│ toEncoder = \@A {} -> custom \l, _ -> l + ^^^^^^^^^ + + Derived and custom implementations can conflict, so one of them needs + to be removed! + + Note: We'll try to compile your program using the custom + implementation first, and fall-back on the derived implementation if + needed. Make sure to disambiguate which one you want! + "# + ), + ) + } } diff --git a/roc_std/Cargo.toml b/roc_std/Cargo.toml index 22337ad6fb..5f18ae3060 100644 --- a/roc_std/Cargo.toml +++ b/roc_std/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Roc Contributors"] description = "Rust representations of Roc data structures" -edition = "2018" +edition = "2021" license = "UPL-1.0" name = "roc_std" readme = "README.md" diff --git a/roc_std/src/lib.rs b/roc_std/src/lib.rs index d52a3c2f9f..10bffc4e32 100644 --- a/roc_std/src/lib.rs +++ b/roc_std/src/lib.rs @@ -5,6 +5,7 @@ use core::fmt; use core::mem::{ManuallyDrop, MaybeUninit}; use core::ops::Drop; use core::str; +use std::hash::{Hash, Hasher}; use std::io::Write; mod rc; @@ -210,24 +211,25 @@ impl Drop for RocResult { } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct RocDec(i128); +#[repr(C)] +pub struct RocDec([u8; 16]); impl RocDec { - pub const MIN: Self = Self(i128::MIN); - pub const MAX: Self = Self(i128::MAX); + pub const MIN: Self = Self(i128::MIN.to_ne_bytes()); + pub const MAX: Self = Self(i128::MAX.to_ne_bytes()); const DECIMAL_PLACES: usize = 18; const ONE_POINT_ZERO: i128 = 10i128.pow(Self::DECIMAL_PLACES as u32); const MAX_DIGITS: usize = 39; const MAX_STR_LENGTH: usize = Self::MAX_DIGITS + 2; // + 2 here to account for the sign & decimal dot - pub fn new(bits: i128) -> Self { - Self(bits) + pub fn new(num: i128) -> Self { + Self(num.to_ne_bytes()) } pub fn as_bits(&self) -> (i64, u64) { - let lower_bits = self.0 as u64; - let upper_bits = (self.0 >> 64) as i64; + let lower_bits = self.as_i128() as u64; + let upper_bits = (self.as_i128() >> 64) as i64; (upper_bits, lower_bits) } @@ -280,7 +282,7 @@ impl RocDec { // Calculate the high digits - the ones before the decimal point. match before_point.parse::() { Ok(answer) => match answer.checked_mul(Self::ONE_POINT_ZERO) { - Some(hi) => hi.checked_add(lo).map(Self), + Some(hi) => hi.checked_add(lo).map(|num| Self(num.to_ne_bytes())), None => None, }, Err(_) => None, @@ -288,16 +290,30 @@ impl RocDec { } pub fn from_str_to_i128_unsafe(val: &str) -> i128 { - Self::from_str(val).unwrap().0 + Self::from_str(val).unwrap().as_i128() + } + + /// This is private because RocDec being an i128 is an implementation detail + #[inline(always)] + fn as_i128(&self) -> i128 { + i128::from_ne_bytes(self.0) + } + + pub fn from_ne_bytes(bytes: [u8; 16]) -> Self { + Self(bytes) + } + + pub fn to_ne_bytes(&self) -> [u8; 16] { + self.0 } fn to_str_helper(&self, bytes: &mut [u8; Self::MAX_STR_LENGTH]) -> usize { - if self.0 == 0 { + if self.as_i128() == 0 { write!(&mut bytes[..], "{}", "0").unwrap(); return 1; } - let is_negative = (self.0 < 0) as usize; + let is_negative = (self.as_i128() < 0) as usize; static_assertions::const_assert!(Self::DECIMAL_PLACES + 1 == 19); // The :019 in the following write! is computed as Self::DECIMAL_PLACES + 1. If you change @@ -306,7 +322,7 @@ impl RocDec { // // By using the :019 format, we're guaranteeing that numbers less than 1, say 0.01234 // get their leading zeros placed in bytes for us. i.e. bytes = b"0012340000000000000" - write!(&mut bytes[..], "{:019}", self.0).unwrap(); + write!(&mut bytes[..], "{:019}", self.as_i128()).unwrap(); // If self represents 1234.5678, then bytes is b"1234567800000000000000". let mut i = Self::MAX_STR_LENGTH - 1; @@ -360,3 +376,137 @@ impl fmt::Display for RocDec { write!(fmtr, "{}", result) } } + +#[repr(C, align(16))] +#[derive(Clone, Copy, Eq, Default)] +pub struct I128([u8; 16]); + +impl From for I128 { + fn from(other: i128) -> Self { + Self(other.to_ne_bytes()) + } +} + +impl From for i128 { + fn from(other: I128) -> Self { + unsafe { core::mem::transmute::(other) } + } +} + +impl fmt::Debug for I128 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let i128: i128 = (*self).into(); + + i128.fmt(f) + } +} + +impl fmt::Display for I128 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let i128: i128 = (*self).into(); + + i128.fmt(f) + } +} + +impl PartialEq for I128 { + fn eq(&self, other: &Self) -> bool { + let i128_self: i128 = (*self).into(); + let i128_other: i128 = (*other).into(); + + i128_self.eq(&i128_other) + } +} + +impl PartialOrd for I128 { + fn partial_cmp(&self, other: &Self) -> Option { + let i128_self: i128 = (*self).into(); + let i128_other: i128 = (*other).into(); + + i128_self.partial_cmp(&i128_other) + } +} + +impl Ord for I128 { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let i128_self: i128 = (*self).into(); + let i128_other: i128 = (*other).into(); + + i128_self.cmp(&i128_other) + } +} + +impl Hash for I128 { + fn hash(&self, state: &mut H) { + let i128: i128 = (*self).into(); + + i128.hash(state); + } +} + +#[repr(C, align(16))] +#[derive(Clone, Copy, Eq, Default)] +pub struct U128([u8; 16]); + +impl From for U128 { + fn from(other: u128) -> Self { + Self(other.to_ne_bytes()) + } +} + +impl From for u128 { + fn from(other: U128) -> Self { + unsafe { core::mem::transmute::(other) } + } +} + +impl fmt::Debug for U128 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let u128: u128 = (*self).into(); + + u128.fmt(f) + } +} + +impl fmt::Display for U128 { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let u128: u128 = (*self).into(); + + u128.fmt(f) + } +} + +impl PartialEq for U128 { + fn eq(&self, other: &Self) -> bool { + let u128_self: u128 = (*self).into(); + let u128_other: u128 = (*other).into(); + + u128_self.eq(&u128_other) + } +} + +impl PartialOrd for U128 { + fn partial_cmp(&self, other: &Self) -> Option { + let u128_self: u128 = (*self).into(); + let u128_other: u128 = (*other).into(); + + u128_self.partial_cmp(&u128_other) + } +} + +impl Ord for U128 { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + let u128_self: u128 = (*self).into(); + let u128_other: u128 = (*other).into(); + + u128_self.cmp(&u128_other) + } +} + +impl Hash for U128 { + fn hash(&self, state: &mut H) { + let u128: u128 = (*self).into(); + + u128.hash(state); + } +} diff --git a/roc_std/src/roc_list.rs b/roc_std/src/roc_list.rs index 7581e36eda..b8ae1f4918 100644 --- a/roc_std/src/roc_list.rs +++ b/roc_std/src/roc_list.rs @@ -1,7 +1,12 @@ #![deny(unsafe_op_in_unsafe_fn)] use core::{ - cell::Cell, cmp, fmt::Debug, intrinsics::copy_nonoverlapping, ops::Deref, ptr::NonNull, + cell::Cell, + cmp::{self, Ordering}, + fmt::Debug, + intrinsics::copy_nonoverlapping, + ops::Deref, + ptr::NonNull, }; use crate::{rc::ReferenceCount, roc_alloc, roc_dealloc, roc_realloc, storage::Storage}; @@ -182,6 +187,55 @@ where impl Eq for RocList where T: Eq + ReferenceCount {} +impl PartialOrd> for RocList +where + T: PartialOrd + ReferenceCount, + U: ReferenceCount, +{ + fn partial_cmp(&self, other: &RocList) -> Option { + // If one is longer than the other, use that as the ordering. + match self.length.partial_cmp(&other.length) { + Some(Ordering::Equal) => {} + ord => return ord, + } + + // If they're the same length, compare their elements + for index in 0..self.len() { + match self[index].partial_cmp(&other[index]) { + Some(Ordering::Equal) => {} + ord => return ord, + } + } + + // Capacity is ignored for ordering purposes. + Some(Ordering::Equal) + } +} + +impl Ord for RocList +where + T: Ord + ReferenceCount, +{ + fn cmp(&self, other: &Self) -> Ordering { + // If one is longer than the other, use that as the ordering. + match self.length.cmp(&other.length) { + Ordering::Equal => {} + ord => return ord, + } + + // If they're the same length, compare their elements + for index in 0..self.len() { + match self[index].cmp(&other[index]) { + Ordering::Equal => {} + ord => return ord, + } + } + + // Capacity is ignored for ordering purposes. + Ordering::Equal + } +} + impl Debug for RocList where T: Debug + ReferenceCount, diff --git a/test_utils/Cargo.toml b/test_utils/Cargo.toml index 1a2efaceb1..c053106f67 100644 --- a/test_utils/Cargo.toml +++ b/test_utils/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_test_utils" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" description = "Utility functions used all over the code base." [dependencies] diff --git a/test_utils/src/lib.rs b/test_utils/src/lib.rs index 3387cedff4..78619e5e3c 100644 --- a/test_utils/src/lib.rs +++ b/test_utils/src/lib.rs @@ -1,3 +1,5 @@ +use std::path::PathBuf; + #[doc(hidden)] pub use pretty_assertions::assert_eq as _pretty_assert_eq; @@ -47,3 +49,8 @@ impl Drop for TmpDir { let _ = remove_dir_all::remove_dir_all(&self.path); } } + +pub fn workspace_root() -> PathBuf { + let root = std::env::var("ROC_WORKSPACE_DIR").expect("Can't find the ROC_WORKSPACE_DIR variable expected to be set in .cargo/config.toml. Are you running tests outside of cargo?"); + PathBuf::from(root) +} diff --git a/utils/Cargo.toml b/utils/Cargo.toml index 9797265931..de90f5ecd7 100644 --- a/utils/Cargo.toml +++ b/utils/Cargo.toml @@ -3,7 +3,7 @@ name = "roc_utils" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" -edition = "2018" +edition = "2021" description = "Utility functions used all over the code base." [dependencies] diff --git a/vendor/pathfinding/src/lib.rs b/vendor/pathfinding/src/lib.rs index c4505702d1..8044a7fed9 100644 --- a/vendor/pathfinding/src/lib.rs +++ b/vendor/pathfinding/src/lib.rs @@ -102,7 +102,7 @@ where FN: FnMut(&N) -> IN, IN: IntoIterator, { - let size_hint = nodes.size_hint().1.unwrap_or_default(); + let size_hint = nodes.size_hint().0; let mut unmarked: MutSet = nodes.collect::>(); let mut marked = HashSet::with_capacity_and_hasher(size_hint, default_hasher()); let mut temp = MutSet::default(); diff --git a/wasi-libc-sys/Cargo.toml b/wasi-libc-sys/Cargo.toml index 1732300332..259a38b2cf 100644 --- a/wasi-libc-sys/Cargo.toml +++ b/wasi-libc-sys/Cargo.toml @@ -1,7 +1,7 @@ [package] authors = ["The Roc Contributors"] description = "Rust wrapper for a WebAssembly test platform built on libc" -edition = "2018" +edition = "2021" license = "UPL-1.0" name = "wasi_libc_sys" repository = "https://github.com/rtfeldman/roc" diff --git a/wasi-libc-sys/build.rs b/wasi-libc-sys/build.rs index cc12485f27..cb84b4027a 100644 --- a/wasi-libc-sys/build.rs +++ b/wasi-libc-sys/build.rs @@ -9,8 +9,8 @@ fn main() { println!("cargo:rerun-if-changed=src/dummy.c"); let out_dir = env::var("OUT_DIR").unwrap(); - let zig_cache_dir = format!("{}/zig-cache", out_dir); - let out_file = format!("{}/wasi-libc.a", out_dir); + let zig_cache_dir = format!("{out_dir}/zig-cache"); + let out_file = format!("{out_dir}/wasi-libc.a"); // Compile a dummy C program with Zig, with our own private cache directory let zig = zig_executable(); @@ -32,15 +32,14 @@ fn main() { ); // Find the libc.a and compiler_rt.o files that Zig wrote (as a side-effect of compiling the dummy program) - let find_libc_output = run_command(Path::new("."), "find", [&zig_cache_dir, "-name", "libc.a"]); - let zig_libc_path = find_libc_output.trim(); // get rid of a newline + let cwd = std::env::current_dir().unwrap(); + let find_libc_output = run_command(&cwd, "find", [&zig_cache_dir, "-name", "libc.a"]); + // If `find` printed multiple results, take the first. + let zig_libc_path = find_libc_output.split('\n').next().unwrap(); - let find_crt_output = run_command( - Path::new("."), - "find", - [&zig_cache_dir, "-name", "compiler_rt.o"], - ); - let zig_crt_path = find_crt_output.trim(); // get rid of a newline + let find_crt_output = run_command(&cwd, "find", [&zig_cache_dir, "-name", "compiler_rt.o"]); + // If `find` printed multiple results, take the first. + let zig_crt_path = find_crt_output.split('\n').next().unwrap(); // Copy libc to where Cargo expects the output of this crate fs::copy(&zig_libc_path, &out_file).unwrap();