diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index 434d09a8ab..7b4c523a8f 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -50,14 +50,47 @@ If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.ne The editor is a :construction:WIP:construction: and not ready yet to replace your favorite editor, although if you want to try it out on nix, read on. `cargo run edit` should work from NixOS, if you use a nix-shell from inside another OS, follow the instructions below. -#### Nvidia GPU +#### from nix flake + +Running the ediotr may fail using the classic nix-shell, we recommend using the nix flake, see [enabling nix flakes](https://nixos.wiki/wiki/Flakes). + +start a nix shell using `nix develop` and follow the instructions below for your graphics configuration. + +##### Nvidia GPU + +``` +nix run --override-input nixpkgs nixpkgs/nixos-21.11 --impure github:guibou/nixGL#nixVulkanNvidia -- cargo run edit +``` + +If you get an error like: +``` +error: unable to execute '/nix/store/qk6...wjla-nixVulkanNvidia-470.103.01/bin/nixVulkanNvidia': No such file or directory +``` +The intel command should work: +``` +nix run --override-input nixpkgs nixpkgs/nixos-21.11 --impure github:guibou/nixGL#nixVulkanIntel -- cargo run edit +``` + +##### Integrated Intel Graphics + +``` +nix run --override-input nixpkgs nixpkgs/nixos-21.11 --impure github:guibou/nixGL#nixVulkanIntel -- cargo run edit +``` + +##### Other configs + +Check the [nixGL repo](https://github.com/guibou/nixGL) for other graphics configurations. Feel free to ask us for help if you get stuck. + +#### using a classic nix-shell + +##### Nvidia GPU Outside of a nix shell, execute the following: ``` nix-channel --add https://github.com/guibou/nixGL/archive/main.tar.gz nixgl && nix-channel --update nix-env -iA nixgl.auto.nixVulkanNvidia ``` -Running the editor does not work with `nix-shell --pure`. +Running the editor does not work with `nix-shell --pure`, instead run: ``` nix-shell ``` @@ -66,25 +99,11 @@ nix-shell nixVulkanNvidia-460.91.03 cargo run edit ``` -#### Integrated Intel Graphics +##### Integrated Intel Graphics -:exclamation: ** Our Nix setup currently cannot run the editor with integrated intel graphics, see #1856 ** :exclamation: +nix-shell does not work here, use the flake instead; check the section "Integrated Intel Graphics" under "from nix flake". -Outside of a nix shell, run: - -```bash -git clone https://github.com/guibou/nixGL -cd nixGL -nix-env -f ./ -iA nixVulkanIntel -``` - -cd to the roc repo, and run (without --pure): -``` -nix-shell -nixVulkanIntel cargo run edit -``` - -#### Other configs +##### Other configs Check the [nixGL repo](https://github.com/guibou/nixGL) for other graphics configurations. diff --git a/bindgen/Cargo.toml b/bindgen/Cargo.toml index e8689aeb08..43dcdc957f 100644 --- a/bindgen/Cargo.toml +++ b/bindgen/Cargo.toml @@ -29,8 +29,8 @@ 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"] } +indoc = "1.0.3" [dev-dependencies] pretty_assertions = "1.0.0" -indoc = "1.0.3" tempfile = "3.2.0" diff --git a/bindgen/src/bindgen.rs b/bindgen/src/bindgen.rs index cd12aaffdb..21bba9490e 100644 --- a/bindgen/src/bindgen.rs +++ b/bindgen/src/bindgen.rs @@ -1,5 +1,3 @@ -use std::convert::TryInto; - use crate::structs::Structs; use crate::types::{TypeId, Types}; use crate::{enums::Enums, types::RocType}; @@ -299,29 +297,82 @@ fn add_tag_union( // Sort tags alphabetically by tag name tags.sort_by(|(name1, _), (name2, _)| name1.cmp(name2)); - let tags = tags - .into_iter() - .map(|(tag_name, payload_vars)| { - let payloads = payload_vars - .iter() - .map(|payload_var| add_type(env, *payload_var, types)) - .collect::>(); + let tags: Vec<_> = + tags.into_iter() + .map(|(tag_name, payload_vars)| { + match payload_vars.len() { + 0 => { + // no payload + (tag_name, None) + } + 1 => { + // there's 1 payload item, so it doesn't need its own + // struct - e.g. for `[ Foo Str, Bar Str ]` both of them + // can have payloads of plain old Str, no struct wrapper needed. + let payload_var = payload_vars.get(0).unwrap(); + let payload_id = add_type(env, *payload_var, types); - (tag_name, payloads) - }) - .collect(); + (tag_name, Some(payload_id)) + } + _ => { + // create a struct type for the payload and save it + + let struct_name = format!("{}_{}", name, tag_name); // e.g. "MyUnion_MyVariant" + + let fields = payload_vars.iter().enumerate().map(|(index, payload_var)| { + (format!("f{}", index).into(), *payload_var) + }); + let struct_id = add_struct(env, struct_name, fields, types); + + (tag_name, Some(struct_id)) + } + } + }) + .collect(); let typ = match env.layout_cache.from_var(env.arena, var, subs).unwrap() { Layout::Struct { .. } => { // a single-tag union with multiple payload values, e.g. [ Foo Str Str ] unreachable!() } - Layout::Union(_) => todo!(), + Layout::Union(union_layout) => { + use roc_mono::layout::UnionLayout::*; + + match union_layout { + // A non-recursive tag union + // e.g. `Result ok err : [ Ok ok, Err err ]` + NonRecursive(_) => RocType::TagUnion { name, tags }, + // A recursive tag union (general case) + // e.g. `Expr : [ Sym Str, Add Expr Expr ]` + Recursive(_) => { + todo!() + } + // A recursive tag union with just one constructor + // Optimization: No need to store a tag ID (the payload is "unwrapped") + // e.g. `RoseTree a : [ Tree a (List (RoseTree a)) ]` + NonNullableUnwrapped(_) => { + todo!() + } + // A recursive tag union that has an empty variant + // Optimization: Represent the empty variant as null pointer => no memory usage & fast comparison + // It has more than one other variant, so they need tag IDs (payloads are "wrapped") + // e.g. `FingerTree a : [ Empty, Single a, More (Some a) (FingerTree (Tuple a)) (Some a) ]` + // see also: https://youtu.be/ip92VMpf_-A?t=164 + NullableWrapped { .. } => { + todo!() + } + // A recursive tag union with only two variants, where one is empty. + // Optimizations: Use null for the empty variant AND don't store a tag ID for the other variant. + // e.g. `ConsList a : [ Nil, Cons a (ConsList a) ]` + NullableUnwrapped { .. } => { + todo!() + } + } + } Layout::Builtin(builtin) => match builtin { - Builtin::Int(int_width) => RocType::TagUnion { - tag_bytes: int_width.stack_size().try_into().unwrap(), + Builtin::Int(_) => RocType::Enumeration { name, - tags, + tags: tags.into_iter().map(|(tag_name, _)| tag_name).collect(), }, Builtin::Bool => RocType::Bool, Builtin::Float(_) diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs index a4eb1006f7..87a2567a4a 100644 --- a/bindgen/src/bindgen_rs.rs +++ b/bindgen/src/bindgen_rs.rs @@ -1,49 +1,36 @@ +use roc_mono::layout::UnionLayout; + +use indoc::indoc; + use crate::types::{RocType, TypeId, Types}; -use std::fmt::{self, Write}; +use std::{ + convert::TryInto, + fmt::{self, Write}, +}; pub static TEMPLATE: &[u8] = include_bytes!("../templates/template.rs"); pub static HEADER: &[u8] = include_bytes!("../templates/header.rs"); -static INDENT: &str = " "; +const INDENT: &str = " "; pub fn write_types(types: &Types, buf: &mut String) -> fmt::Result { for id in types.sorted_ids() { match types.get(id) { RocType::Struct { name, fields } => write_struct(name, fields, id, types, buf)?, - RocType::TagUnion { - tags, - name, - tag_bytes, - } => { - let is_enumeration = tags.iter().all(|(_, payloads)| payloads.is_empty()); - - match tags.len() { - 0 => { - // Empty tag unions can never come up at runtime, - // and so don't need declared types. - } - 1 => { - if is_enumeration { - // A tag union with one tag is a zero-sized unit type, so - // represent it as a zero-sized struct (e.g. "struct Foo()"). - write_deriving(id, types, buf)?; - buf.write_str("\nstruct ")?; - write_type_name(id, types, buf)?; - buf.write_str("();\n")?; - } else { - // if it wasn't an enumeration - // this is a newtype wrapper around something, - // so write an alias for its contents - todo!(); - } - } - _ => { - if is_enumeration { - write_deriving(id, types, buf)?; - write_enum(name, tags.iter().map(|(name, _)| name), *tag_bytes, buf)?; - } else { - todo!(); - } - } + RocType::Enumeration { tags, name } => { + if tags.len() == 1 { + // An enumeration with one tag is a zero-sized unit type, so + // represent it as a zero-sized struct (e.g. "struct Foo()"). + write_deriving(types.get(id), types, buf)?; + writeln!(buf, "\nstruct {}();", type_name(id, types))?; + } else { + write_enumeration(name, types.get(id), tags.iter(), types, buf)?; + } + } + RocType::TagUnion { tags, name } => { + // Empty tag unions can never come up at runtime, + // and so don't need declared types. + if !tags.is_empty() { + write_tag_union(name, id, tags, types, buf)?; } } RocType::RecursiveTagUnion { .. } => { @@ -71,10 +58,13 @@ pub fn write_types(types: &Types, buf: &mut String) -> fmt::Result { | RocType::RocList(_) | RocType::RocBox(_) => {} RocType::TransparentWrapper { name, content } => { - write_deriving(id, types, buf)?; - write!(buf, "#[repr(transparent)]\npub struct {}(", name)?; - write_type_name(*content, types, buf)?; - buf.write_str(");\n")?; + write_deriving(types.get(id), types, buf)?; + writeln!( + buf, + "#[repr(transparent)]\npub struct {}({});", + name, + type_name(*content, types) + )?; } } } @@ -82,17 +72,624 @@ pub fn write_types(types: &Types, buf: &mut String) -> fmt::Result { Ok(()) } -fn write_enum, S: AsRef>( +fn write_tag_union( name: &str, - tags: I, - tag_bytes: u8, + type_id: TypeId, + tags: &[(String, Option)], + types: &Types, buf: &mut String, ) -> fmt::Result { + // The tag union's discriminant, e.g. + // + // #[repr(u8)] + // pub enum tag_MyTagUnion { + // Bar, + // Foo, + // } + let discriminant_name = format!("tag_{}", name); + let tag_names = tags.iter().map(|(name, _)| name); + let discriminant_type = RocType::Enumeration { + name: discriminant_name.clone(), + tags: tag_names.clone().cloned().collect(), + }; + let typ = types.get(type_id); + + write_enumeration( + &discriminant_name, + &discriminant_type, + tag_names, + types, + buf, + )?; + + // 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); + + { + // No deriving for unions; we have to add the impls ourselves! + + writeln!(buf, "\n#[repr(C)]\npub union {} {{", variant_name)?; + + for (tag_name, opt_payload_id) in tags { + // If there's no payload, we don't need a variant for it. + if let Some(payload_id) = opt_payload_id { + let payload_type = types.get(*payload_id); + + write!(buf, "{}{}: ", INDENT, tag_name)?; + + if payload_type.has_pointer(types) { + // types with pointers need ManuallyDrop + // because rust unions don't (and can't) + // know how to drop them automatically! + writeln!( + buf, + "core::mem::ManuallyDrop<{}>,", + type_name(*payload_id, types) + )?; + } else { + writeln!(buf, "{},", type_name(*payload_id, types))?; + } + } + } + + 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! + + write!( + buf, + "\n#[repr(C)]\npub struct {} {{\n{}variant: {},\n{}tag: {},\n}}\n", + name, INDENT, variant_name, INDENT, discriminant_name + )?; + } + + // The impl for the tag union + { + write!( + buf, + indoc!( + r#" + + impl MyTagUnion {{ + pub fn tag(&self) -> {} {{ + self.tag + }} + "# + ), + discriminant_name + )?; + + for (tag_name, opt_payload_id) in tags { + // Add a convenience constructor function to the impl, e.g. + // + // /// Construct a tag named Foo, with the appropriate payload + // pub fn Foo(payload: roc_std::RocStr) -> Self { + // Self { + // tag: tag_MyTagUnion::Foo, + // variant: variant_MyTagUnion { + // Foo: core::mem::ManuallyDrop::new(payload), + // }, + // } + // } + if let Some(payload_id) = opt_payload_id { + let payload_type = types.get(*payload_id); + let payload_type_name = type_name(*payload_id, types); + + let (init_payload, get_payload, deref_for_as, self_for_into) = + if payload_type.has_pointer(types) { + ( + "core::mem::ManuallyDrop::new(payload)", + format!( + "core::mem::ManuallyDrop::take(&mut self.variant.{})", + tag_name, + ), + // Since this is a ManuallyDrop, our `as_` method will need + // to dereference the variant (e.g. `&self.variant.Foo`) + "&", + // we need `mut self` for the argument because of ManuallyDrop + "mut self", + ) + } else { + ( + "payload", + format!("self.variant.{}", 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 '&') + "", + // we don't need `mut self` unless we need ManuallyDrop + "self", + ) + }; + + writeln!( + buf, + // Don't use indoc because this must be indented once! + r#" + /// Construct a tag named {}, with the appropriate payload + pub fn {}(payload: {}) -> Self {{ + Self {{ + tag: {}::{}, + variant: {} {{ + {}: {} + }}, + }} + }}"#, + tag_name, + tag_name, + payload_type_name, + discriminant_name, + tag_name, + variant_name, + tag_name, + init_payload + )?; + + writeln!( + buf, + // Don't use indoc because this must be indented once! + r#" + /// Unsafely assume the given {} has a .tag() of {} and convert it to {}'s payload. + /// (always examine .tag() first to make sure this is the correct variant!) + pub unsafe fn into_{}({}) -> {} {{ + {} + }}"#, + name, + tag_name, + tag_name, + tag_name, + self_for_into, + payload_type_name, + get_payload, + )?; + + writeln!( + buf, + // Don't use indoc because this must be indented once! + r#" + /// Unsafely assume the given {} has a .tag() of {} and return its payload. + /// (always examine .tag() first to make sure this is the correct variant!) + pub unsafe fn as_{}(&self) -> {}{} {{ + {}self.variant.{} + }}"#, + name, + tag_name, + tag_name, + deref_for_as, + payload_type_name, + deref_for_as, + tag_name + )?; + } else { + writeln!( + buf, + // Don't use indoc because this must be indented once! + r#" + /// Construct a tag named {} + pub fn {}() -> Self {{ + Self {{ + tag: {}::{}, + variant: unsafe {{ + core::mem::transmute::< + core::mem::MaybeUninit<{}>, + {}, + >(core::mem::MaybeUninit::uninit()) + }}, + }} + }}"#, + tag_name, tag_name, discriminant_name, tag_name, variant_name, variant_name, + )?; + + writeln!( + buf, + // Don't use indoc because this must be indented once! + r#" + /// Other `into_` methods return a payload, but since the {} tag + /// has no payload, this does nothing and is only here for completeness. + pub fn into_{}(self) -> () {{ + () + }}"#, + tag_name, tag_name + )?; + + writeln!( + buf, + // Don't use indoc because this must be indented once! + r#" + /// Other `as` methods return a payload, but since the {} tag + /// has no payload, this does nothing and is only here for completeness. + pub unsafe fn as_{}(&self) -> () {{ + () + }}"#, + tag_name, tag_name + )?; + } + } + + buf.write_str("}\n")?; + } + + // The Drop impl for the tag union + { + write!( + buf, + indoc!( + r#" + + impl Drop for {} {{ + fn drop(&mut self) {{ + match self.tag {{ + "# + ), + name + )?; + + write_impl_tags( + 3, + 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 + ) + } + _ => { + // If it had no payload, or if the payload had no pointers, + // there's nothing to clean up, so do `=> {}` for the branch. + "{}".to_string() + } + } + }, + )?; + + writeln!( + buf, + indoc!( + r#" + }} + }} + }} + "# + ), + )?; + } + + // The PartialEq impl for the tag union + { + write!( + buf, + indoc!( + r#" + impl PartialEq for {} {{ + fn eq(&self, other: &Self) -> bool {{ + if self.tag != other.tag {{ + return false; + }} + + unsafe {{ + match self.tag {{ + "# + ), + name + )?; + + write_impl_tags( + 4, + tags.iter(), + &discriminant_name, + buf, + |tag_name, opt_payload_id| { + if opt_payload_id.is_some() { + format!("self.variant.{} == other.variant.{},", tag_name, 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 + // and there's no payload; return true! + "true,".to_string() + } + }, + )?; + + writeln!( + buf, + indoc!( + r#" + }} + }} + }} + }} + "# + ), + )?; + } + + if !typ.has_float(types) { + writeln!(buf, "impl Eq for {} {{}}\n", name)?; + } + + // The PartialOrd impl for the tag union + { + write!( + buf, + indoc!( + r#" + impl PartialOrd for {} {{ + fn partial_cmp(&self, other: &Self) -> Option {{ + match self.tag.partial_cmp(&other.tag) {{ + Some(core::cmp::Ordering::Equal) => {{}} + not_eq => return not_eq, + }} + + unsafe {{ + match self.tag {{ + "# + ), + name + )?; + + write_impl_tags( + 4, + tags.iter(), + &discriminant_name, + buf, + |tag_name, opt_payload_id| { + if opt_payload_id.is_some() { + format!( + "self.variant.{}.partial_cmp(&other.variant.{}),", + tag_name, 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, + indoc!( + r#" + }} + }} + }} + }} + "# + ), + )?; + } + + // The Ord impl for the tag union + { + write!( + buf, + indoc!( + r#" + impl Ord for {} {{ + 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 {{ + "# + ), + name + )?; + + write_impl_tags( + 4, + tags.iter(), + &discriminant_name, + buf, + |tag_name, opt_payload_id| { + if opt_payload_id.is_some() { + format!( + "self.variant.{}.cmp(&other.variant.{}),", + tag_name, 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, + indoc!( + r#" + }} + }} + }} + }} + "# + ), + )?; + } + + // The Clone impl for the tag union + { + write!( + buf, + indoc!( + r#" + impl Clone for {} {{ + fn clone(&self) -> Self {{ + match self.tag {{ + "# + ), + name + )?; + + write_impl_tags( + 3, + tags.iter(), + &discriminant_name, + buf, + |tag_name, opt_payload_id| { + if opt_payload_id.is_some() { + format!( + r#"Self {{ + variant: {} {{ + {}: unsafe {{ self.variant.{}.clone() }}, + }}, + tag: {}::{}, + }},"#, + variant_name, tag_name, tag_name, discriminant_name, 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<{}>, + {}, + >(core::mem::MaybeUninit::uninit()) + }}, + tag: {}::{}, + }},"#, + variant_name, variant_name, discriminant_name, tag_name + ) + } + }, + )?; + + writeln!( + buf, + indoc!( + r#" + }} + }} + }} + "# + ), + )?; + } + + if !typ.has_pointer(types) { + writeln!(buf, "impl Copy for {} {{}}\n", name)?; + } + + // The Debug impl for the tag union + { + write!( + buf, + indoc!( + r#" + impl core::fmt::Debug for {} {{ + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {{ + f.write_str("{}::")?; + + unsafe {{ + match self.tag {{ + "# + ), + name, name + )?; + + write_impl_tags( + 4, + tags.iter(), + &discriminant_name, + buf, + |tag_name, opt_payload_id| { + if opt_payload_id.is_some() { + format!( + r#"f.debug_tuple("{}").field(&self.variant.{}).finish(),"#, + tag_name, tag_name + ) + } else { + format!(r#"f.write_str("{}"),"#, tag_name) + } + }, + )?; + + writeln!( + buf, + indoc!( + r#" + }} + }} + }} + }} + "# + ), + )?; + } + + Ok(()) +} + +fn write_impl_tags< + 'a, + I: IntoIterator)>, + F: Fn(&str, Option) -> String, +>( + indentations: usize, + tags: I, + discriminant_name: &str, + buf: &mut String, + to_branch_str: F, +) -> fmt::Result { + for (tag_name, opt_payload_id) in tags { + let branch_str = to_branch_str(tag_name, *opt_payload_id); + + for _ in 0..indentations { + buf.write_str(INDENT)?; + } + + writeln!(buf, "{}::{} => {}", discriminant_name, tag_name, branch_str)?; + } + + Ok(()) +} + +fn write_enumeration, S: AsRef>( + name: &str, + typ: &RocType, + tags: I, + types: &Types, + buf: &mut String, +) -> fmt::Result { + let tag_bytes: usize = UnionLayout::discriminant_size(tags.len()) + .stack_size() + .try_into() + .unwrap(); + + write_deriving(typ, types, buf)?; + // e.g. "#[repr(u8)]\npub enum Foo {\n" writeln!(buf, "#[repr(u{})]\npub enum {} {{", tag_bytes * 8, name)?; - for name in tags { - writeln!(buf, "{}{},", INDENT, name.as_ref())?; + for (index, name) in tags.enumerate() { + writeln!(buf, "{}{} = {},", INDENT, name.as_ref(), index)?; } buf.write_str("}\n") @@ -105,76 +702,65 @@ fn write_struct( types: &Types, buf: &mut String, ) -> fmt::Result { - write_deriving(struct_id, types, buf)?; + write_deriving(types.get(struct_id), types, buf)?; writeln!(buf, "#[repr(C)]\npub struct {} {{", name)?; for (label, field_id) in fields { - write!(buf, "{}{}: ", INDENT, label.as_str())?; - write_type_name(*field_id, types, buf)?; - buf.write_str(",\n")?; + writeln!( + buf, + "{}{}: {},", + INDENT, + label.as_str(), + type_name(*field_id, types) + )?; } buf.write_str("}\n") } -fn write_type_name(id: TypeId, types: &Types, buf: &mut String) -> fmt::Result { +fn type_name(id: TypeId, types: &Types) -> String { match types.get(id) { - RocType::U8 => buf.write_str("u8"), - RocType::U16 => buf.write_str("u16"), - RocType::U32 => buf.write_str("u32"), - RocType::U64 => buf.write_str("u64"), - RocType::U128 => buf.write_str("u128"), - RocType::I8 => buf.write_str("i8"), - RocType::I16 => buf.write_str("i16"), - RocType::I32 => buf.write_str("i32"), - RocType::I64 => buf.write_str("i64"), - RocType::I128 => buf.write_str("i128"), - RocType::F32 => buf.write_str("f32"), - RocType::F64 => buf.write_str("f64"), - RocType::F128 => buf.write_str("f128"), - RocType::Bool => buf.write_str("bool"), - RocType::RocDec => buf.write_str("roc_std::RocDec"), - RocType::RocStr => buf.write_str("roc_std::RocStr"), - RocType::RocDict(key_id, val_id) => { - buf.write_str("roc_std::RocDict<")?; - write_type_name(*key_id, types, buf)?; - buf.write_str(", ")?; - write_type_name(*val_id, types, buf)?; - buf.write_char('>') - } - RocType::RocSet(elem_id) => { - buf.write_str("roc_std::RocSet<")?; - write_type_name(*elem_id, types, buf)?; - buf.write_char('>') - } - RocType::RocList(elem_id) => { - buf.write_str("roc_std::RocList<")?; - write_type_name(*elem_id, types, buf)?; - buf.write_char('>') - } - RocType::RocBox(elem_id) => { - buf.write_str("roc_std::RocBox<")?; - write_type_name(*elem_id, types, buf)?; - buf.write_char('>') - } + RocType::U8 => "u8".to_string(), + RocType::U16 => "u16".to_string(), + RocType::U32 => "u32".to_string(), + RocType::U64 => "u64".to_string(), + RocType::U128 => "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::F32 => "f32".to_string(), + RocType::F64 => "f64".to_string(), + RocType::F128 => "f128".to_string(), + RocType::Bool => "bool".to_string(), + RocType::RocDec => "roc_std::RocDec".to_string(), + RocType::RocStr => "roc_std::RocStr".to_string(), + RocType::RocDict(key_id, val_id) => format!( + "roc_std::RocDict<{}, {}>", + type_name(*key_id, types), + type_name(*val_id, types) + ), + RocType::RocSet(elem_id) => format!("roc_std::RocSet<{}>", type_name(*elem_id, types)), + RocType::RocList(elem_id) => format!("roc_std::RocList<{}>", type_name(*elem_id, types)), + RocType::RocBox(elem_id) => format!("roc_std::RocBox<{}>", type_name(*elem_id, types)), RocType::Struct { name, .. } | RocType::TagUnion { name, .. } | RocType::TransparentWrapper { name, .. } - | RocType::RecursiveTagUnion { name, .. } => buf.write_str(name), + | RocType::Enumeration { name, .. } + | RocType::RecursiveTagUnion { name, .. } => name.clone(), } } -fn write_deriving(id: TypeId, types: &Types, buf: &mut String) -> fmt::Result { - let typ = types.get(id); - +fn write_deriving(typ: &RocType, types: &Types, buf: &mut String) -> fmt::Result { buf.write_str("\n#[derive(Clone, PartialEq, PartialOrd, ")?; if !typ.has_pointer(types) { buf.write_str("Copy, ")?; } - if !typ.has_tag_union(types) { + if !typ.has_enumeration(types) { buf.write_str("Default, ")?; } diff --git a/bindgen/src/types.rs b/bindgen/src/types.rs index 8339e371e1..0c6aaddf06 100644 --- a/bindgen/src/types.rs +++ b/bindgen/src/types.rs @@ -1,8 +1,10 @@ use core::mem::align_of; use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_collections::VecMap; +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)] @@ -118,12 +120,15 @@ pub enum RocType { RocBox(TypeId), RecursiveTagUnion { name: String, - tags: Vec<(String, Vec)>, + tags: Vec<(String, Option)>, + }, + Enumeration { + name: String, + tags: Vec, }, TagUnion { - tag_bytes: u8, name: String, - tags: Vec<(String, Vec)>, + tags: Vec<(String, Option)>, }, Struct { name: String, @@ -154,6 +159,7 @@ impl RocType { | RocType::F32 | RocType::F64 | RocType::F128 + | RocType::Enumeration { .. } | RocType::RocDec => false, RocType::RocStr | RocType::RocList(_) @@ -187,7 +193,8 @@ impl RocType { | RocType::U64 | RocType::I128 | RocType::U128 - | RocType::RocDec => false, + | RocType::RocDec + | RocType::Enumeration { .. } => false, RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => { types.get(*id).has_float(types) } @@ -205,9 +212,11 @@ impl RocType { } /// Useful when determining whether to derive Default in a Rust type. - pub fn has_tag_union(&self, types: &Types) -> bool { + pub fn has_enumeration(&self, types: &Types) -> bool { match self { - RocType::RecursiveTagUnion { .. } | RocType::TagUnion { .. } => true, + RocType::RecursiveTagUnion { .. } + | RocType::TagUnion { .. } + | RocType::Enumeration { .. } => true, RocType::RocStr | RocType::Bool | RocType::I8 @@ -225,15 +234,18 @@ impl RocType { | RocType::F128 | RocType::RocDec => false, RocType::RocList(id) | RocType::RocSet(id) | RocType::RocBox(id) => { - types.get(*id).has_tag_union(types) + types.get(*id).has_enumeration(types) } RocType::RocDict(key_id, val_id) => { - types.get(*key_id).has_tag_union(types) || types.get(*val_id).has_tag_union(types) + types.get(*key_id).has_enumeration(types) + || types.get(*val_id).has_enumeration(types) } RocType::Struct { fields, .. } => fields .iter() - .any(|(_, id)| types.get(*id).has_tag_union(types)), - RocType::TransparentWrapper { content, .. } => types.get(*content).has_tag_union(types), + .any(|(_, id)| types.get(*id).has_enumeration(types)), + RocType::TransparentWrapper { content, .. } => { + types.get(*content).has_enumeration(types) + } } } @@ -294,6 +306,10 @@ impl RocType { RocType::TransparentWrapper { content, .. } => { types.get(*content).alignment(types, target_info) } + RocType::Enumeration { tags, .. } => UnionLayout::discriminant_size(tags.len()) + .stack_size() + .try_into() + .unwrap(), } } } diff --git a/bindgen/templates/header.rs b/bindgen/templates/header.rs index df926e1bfc..64e3921945 100644 --- a/bindgen/templates/header.rs +++ b/bindgen/templates/header.rs @@ -4,3 +4,4 @@ #![allow(unused_imports)] #![allow(non_snake_case)] #![allow(non_camel_case_types)] +#![allow(clippy::undocumented_unsafe_blocks)] diff --git a/bindgen/tests/gen_rs.rs b/bindgen/tests/gen_rs.rs index 5208d11246..6475d4435c 100644 --- a/bindgen/tests/gen_rs.rs +++ b/bindgen/tests/gen_rs.rs @@ -170,14 +170,13 @@ fn nested_record_anonymous() { } #[test] -#[ignore] fn tag_union_aliased() { let module = indoc!( r#" - MyTagUnion : [ Foo U64, Bar U128 ] + MyTagUnion : [ Foo Str, Bar U128, Blah I32, Baz ] main : MyTagUnion - main = Foo 123 + main = Foo "blah" "# ); @@ -187,30 +186,26 @@ fn tag_union_aliased() { .unwrap_or_default(), indoc!( r#" - #[repr(C)] - pub struct MyTagUnion { - tag: tag_MyTagUnion, - variant: variant_MyTagUnion, - } - - #[repr(C)] - union variant_MyTagUnion { - Bar: u128, - Foo: std::mem::ManuallyDrop>, - } - - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] - #[repr(C)] - pub struct Payload2 { - _0: V0, - _1: V1, - } - - #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] + #[derive(Clone, PartialEq, PartialOrd, Copy, Eq, Ord, Hash, Debug)] #[repr(u8)] pub enum tag_MyTagUnion { - Bar, - Foo, + Bar = 0, + Baz = 1, + Blah = 2, + Foo = 3, + } + + #[repr(C)] + pub union union_MyTagUnion { + Bar: u128, + Blah: i32, + Foo: core::mem::ManuallyDrop, + } + + #[repr(C)] + pub struct MyTagUnion { + variant: union_MyTagUnion, + tag: tag_MyTagUnion, } impl MyTagUnion { @@ -218,50 +213,105 @@ fn tag_union_aliased() { self.tag } - /// Assume this is the tag named Foo, and return a reference to its payload. - pub unsafe fn as_Foo(&self) -> &Payload2 { - &*self.variant.Foo - } - - /// Assume this is the tag named Foo, and return a mutable reference to its payload. - pub unsafe fn as_mut_Foo(&mut self) -> &mut Payload2 { - &mut *self.variant.Foo - } - - /// Assume this is the tag named Bar, and return a reference to its payload. - pub unsafe fn as_Bar(&self) -> u128 { - self.variant.Bar - } - - /// Assume this is the tag named Bar, and return a mutable reference to its payload. - pub unsafe fn as_mut_Bar(&mut self) -> &mut u128 { - &mut self.variant.Bar - } - - /// Construct a tag named Foo, with the appropriate payload - pub fn Foo(_0: roc_std::RocStr, _1: i32) -> Self { + /// Construct a tag named Bar, with the appropriate payload + pub fn Bar(payload: u128) -> Self { Self { - tag: tag_MyTagUnion::Foo, - variant: variant_MyTagUnion { - Foo: std::mem::ManuallyDrop::new(Payload2 { _0, _1 }), + tag: tag_MyTagUnion::Bar, + variant: union_MyTagUnion { + Bar: payload }, } } - /// Construct a tag named Bar, with the appropriate payload - pub fn Bar(arg0: u128) -> Self { + /// Unsafely assume the given MyTagUnion 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 + } + + /// Unsafely assume the given MyTagUnion 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 + } + + /// Construct a tag named Baz + pub fn Baz() -> Self { Self { - tag: tag_MyTagUnion::Bar, - variant: variant_MyTagUnion { Bar: arg0 }, + tag: tag_MyTagUnion::Baz, + variant: unsafe { + core::mem::transmute::< + core::mem::MaybeUninit, + union_MyTagUnion, + >(core::mem::MaybeUninit::uninit()) + }, } } + + /// 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) -> () { + () + } + + /// 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) -> () { + () + } + + /// Construct a tag named Blah, with the appropriate payload + pub fn Blah(payload: i32) -> Self { + Self { + tag: tag_MyTagUnion::Blah, + variant: union_MyTagUnion { + Blah: payload + }, + } + } + + /// Unsafely assume the given MyTagUnion has a .tag() of Blah and convert it to Blah's payload. + /// (always examine .tag() first to make sure this is the correct variant!) + pub unsafe fn into_Blah(self) -> i32 { + self.variant.Blah + } + + /// Unsafely assume the given MyTagUnion has a .tag() of Blah and return its payload. + /// (always examine .tag() first to make sure this is the correct variant!) + pub unsafe fn as_Blah(&self) -> i32 { + self.variant.Blah + } + + /// Construct a tag named Foo, with the appropriate payload + pub fn Foo(payload: roc_std::RocStr) -> Self { + Self { + tag: tag_MyTagUnion::Foo, + variant: union_MyTagUnion { + Foo: core::mem::ManuallyDrop::new(payload) + }, + } + } + + /// Unsafely assume the given MyTagUnion has a .tag() of Foo and convert it to Foo's payload. + /// (always examine .tag() first to make sure this is the correct variant!) + pub unsafe fn into_Foo(mut self) -> roc_std::RocStr { + core::mem::ManuallyDrop::take(&mut self.variant.Foo) + } + + /// Unsafely assume the given MyTagUnion has a .tag() of Foo and return its payload. + /// (always examine .tag() first to make sure this is the correct variant!) + pub unsafe fn as_Foo(&self) -> &roc_std::RocStr { + &self.variant.Foo + } } impl Drop for MyTagUnion { fn drop(&mut self) { match self.tag { tag_MyTagUnion::Bar => {} - tag_MyTagUnion::Foo => unsafe { std::mem::ManuallyDrop::drop(&mut self.variant.Foo) }, + tag_MyTagUnion::Baz => {} + tag_MyTagUnion::Blah => {} + tag_MyTagUnion::Foo => unsafe { core::mem::ManuallyDrop::drop(&mut self.variant.Foo) }, } } } @@ -275,6 +325,8 @@ fn tag_union_aliased() { unsafe { match self.tag { tag_MyTagUnion::Bar => self.variant.Bar == other.variant.Bar, + tag_MyTagUnion::Baz => true, + tag_MyTagUnion::Blah => self.variant.Blah == other.variant.Blah, tag_MyTagUnion::Foo => self.variant.Foo == other.variant.Foo, } } @@ -293,6 +345,8 @@ fn tag_union_aliased() { unsafe { match self.tag { tag_MyTagUnion::Bar => self.variant.Bar.partial_cmp(&other.variant.Bar), + tag_MyTagUnion::Baz => Some(core::cmp::Ordering::Equal), + tag_MyTagUnion::Blah => self.variant.Blah.partial_cmp(&other.variant.Blah), tag_MyTagUnion::Foo => self.variant.Foo.partial_cmp(&other.variant.Foo), } } @@ -300,7 +354,7 @@ fn tag_union_aliased() { } impl Ord for MyTagUnion { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { match self.tag.cmp(&other.tag) { core::cmp::Ordering::Equal => {} not_eq => return not_eq, @@ -309,11 +363,63 @@ fn tag_union_aliased() { unsafe { match self.tag { tag_MyTagUnion::Bar => self.variant.Bar.cmp(&other.variant.Bar), + tag_MyTagUnion::Baz => core::cmp::Ordering::Equal, + tag_MyTagUnion::Blah => self.variant.Blah.cmp(&other.variant.Blah), tag_MyTagUnion::Foo => self.variant.Foo.cmp(&other.variant.Foo), } } } } + + impl Clone for MyTagUnion { + fn clone(&self) -> Self { + match self.tag { + tag_MyTagUnion::Bar => Self { + variant: union_MyTagUnion { + Bar: unsafe { self.variant.Bar.clone() }, + }, + tag: tag_MyTagUnion::Bar, + }, + tag_MyTagUnion::Baz => Self { + variant: unsafe { + core::mem::transmute::< + core::mem::MaybeUninit, + union_MyTagUnion, + >(core::mem::MaybeUninit::uninit()) + }, + tag: tag_MyTagUnion::Baz, + }, + tag_MyTagUnion::Blah => Self { + variant: union_MyTagUnion { + Blah: unsafe { self.variant.Blah.clone() }, + }, + tag: tag_MyTagUnion::Blah, + }, + tag_MyTagUnion::Foo => Self { + variant: union_MyTagUnion { + Foo: unsafe { self.variant.Foo.clone() }, + }, + tag: tag_MyTagUnion::Foo, + }, + } + } + } + + impl core::fmt::Debug for MyTagUnion { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("MyTagUnion::")?; + + unsafe { + match self.tag { + tag_MyTagUnion::Bar => f.debug_tuple("Bar").field(&self.variant.Bar).finish(), + tag_MyTagUnion::Baz => f.write_str("Baz"), + tag_MyTagUnion::Blah => f.debug_tuple("Blah").field(&self.variant.Blah).finish(), + tag_MyTagUnion::Foo => f.debug_tuple("Foo").field(&self.variant.Foo).finish(), + } + } + } + } + "# ) ); @@ -339,9 +445,9 @@ fn tag_union_enumeration() { #[derive(Clone, PartialEq, PartialOrd, Copy, Eq, Ord, Hash, Debug)] #[repr(u8)] pub enum MyTagUnion { - Bar, - Blah, - Foo, + Bar = 0, + Blah = 1, + Foo = 2, } "# ) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 93d598462f..9cedf556fd 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -59,7 +59,7 @@ roc_editor = { path = "../editor", optional = true } roc_linker = { path = "../linker" } roc_repl_cli = { path = "../repl_cli", optional = true } clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions"] } -const_format = "0.2.22" +const_format = { version = "0.2.23", features = ["const_generics"] } bumpalo = { version = "3.8.0", features = ["collections"] } mimalloc = { version = "0.1.26", default-features = false } diff --git a/cli_utils/Cargo.toml b/cli_utils/Cargo.toml index a309bde670..0f009564c7 100644 --- a/cli_utils/Cargo.toml +++ b/cli_utils/Cargo.toml @@ -20,7 +20,7 @@ serde = { version = "1.0.130", features = ["derive"] } serde-xml-rs = "0.5.1" strip-ansi-escapes = "0.1.1" tempfile = "3.2.0" -const_format = "0.2.22" +const_format = { version = "0.2.23", features = ["const_generics"] } [target.'cfg(unix)'.dependencies] rlimit = "0.6.2" diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 532b9c985c..83abcc8f71 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1729,30 +1729,27 @@ fn constrain_def( } } +/// Create a let-constraint for a non-recursive def. +/// Recursive defs should always use `constrain_recursive_defs`. pub fn constrain_def_make_constraint( constraints: &mut Constraints, - new_rigid_variables: impl Iterator, - new_infer_variables: impl Iterator, - expr_con: Constraint, - body_con: Constraint, + annotation_rigid_variables: impl Iterator, + annotation_infer_variables: impl Iterator, + def_expr_con: Constraint, + after_def_con: Constraint, def_pattern_state: PatternState, ) -> Constraint { - let and_constraint = constraints.and_constraint(def_pattern_state.constraints); + let all_flex_variables = (def_pattern_state.vars.into_iter()).chain(annotation_infer_variables); - let def_con = constraints.let_constraint( - [], - new_infer_variables, - [], // empty, because our functions have no arguments! - and_constraint, - expr_con, - ); + let pattern_constraints = constraints.and_constraint(def_pattern_state.constraints); + let def_pattern_and_body_con = constraints.and_constraint([pattern_constraints, def_expr_con]); constraints.let_constraint( - new_rigid_variables, - def_pattern_state.vars, + annotation_rigid_variables, + all_flex_variables, def_pattern_state.headers, - def_con, - body_con, + def_pattern_and_body_con, + after_def_con, ) } @@ -2126,15 +2123,35 @@ pub fn rec_defs_help( } } - let flex_constraints = constraints.and_constraint(flex_info.constraints); - let inner_inner = constraints.let_constraint( + // Strategy for recursive defs: + // 1. Let-generalize the type annotations we know; these are the source of truth we'll solve + // everything else with. If there are circular type errors here, they will be caught during + // the let-generalization. + // 2. Introduce all symbols of the untyped defs, but don't generalize them yet. Now, solve + // the untyped defs' bodies. This way, when checking something like + // f = \x -> f [ x ] + // we introduce `f: b -> c`, then constrain the call `f [ x ]`, + // forcing `b -> c ~ List b -> c` and correctly picking up a recursion error. + // Had we generalized `b -> c`, the call `f [ x ]` would have been generalized, and this + // error would not be found. + // 3. Now properly let-generalize the untyped body defs, since we now know their types and + // that they don't have circular type errors. + // 4. Solve the bodies of the typed body defs, and check that they agree the types of the type + // annotation. + // 5. Solve the rest of the program that happens after this recursive def block. + + // 2. Solve untyped defs without generalization of their symbols. + let untyped_body_constraints = constraints.and_constraint(flex_info.constraints); + let untyped_def_symbols_constr = constraints.let_constraint( [], [], flex_info.def_types.clone(), Constraint::True, - flex_constraints, + untyped_body_constraints, ); + // an extra constraint that propagates information to the solver to check for invalid recursion + // and generate a good error message there. let (loc_symbols, expr_regions): (Vec<_>, Vec<_>) = defs .iter() .flat_map(|def| { @@ -2145,22 +2162,21 @@ pub fn rec_defs_help( let cycle_constraint = constraints.check_cycle(loc_symbols, expr_regions, cycle_mark); - let rigid_constraints = { - let mut temp = rigid_info.constraints; - temp.push(cycle_constraint); - temp.push(body_con); - - constraints.and_constraint(temp) - }; + let typed_body_constraints = constraints.and_constraint(rigid_info.constraints); + let typed_body_and_final_constr = + constraints.and_constraint([typed_body_constraints, cycle_constraint, body_con]); + // 3. Properly generalize untyped defs after solving them. let inner = constraints.let_constraint( [], flex_info.vars, flex_info.def_types, - inner_inner, - rigid_constraints, + untyped_def_symbols_constr, + // 4 + 5. Solve the typed body defs, and the rest of the program. + typed_body_and_final_constr, ); + // 1. Let-generalize annotations we know. constraints.let_constraint( rigid_info.vars, [], diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 8a3a417cb4..a7c9111e7c 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -417,11 +417,11 @@ impl<'a> UnionLayout<'a> { } } - fn tag_id_builtin_help(union_size: usize) -> Builtin<'a> { - if union_size <= u8::MAX as usize { - Builtin::Int(IntWidth::U8) - } else if union_size <= u16::MAX as usize { - Builtin::Int(IntWidth::U16) + pub fn discriminant_size(num_tags: usize) -> IntWidth { + if num_tags <= u8::MAX as usize { + IntWidth::U8 + } else if num_tags <= u16::MAX as usize { + IntWidth::U16 } else { panic!("tag union is too big") } @@ -431,16 +431,16 @@ impl<'a> UnionLayout<'a> { match self { UnionLayout::NonRecursive(tags) => { let union_size = tags.len(); - Self::tag_id_builtin_help(union_size) + Builtin::Int(Self::discriminant_size(union_size)) } UnionLayout::Recursive(tags) => { let union_size = tags.len(); - Self::tag_id_builtin_help(union_size) + Builtin::Int(Self::discriminant_size(union_size)) } UnionLayout::NullableWrapped { other_tags, .. } => { - Self::tag_id_builtin_help(other_tags.len() + 1) + Builtin::Int(Self::discriminant_size(other_tags.len() + 1)) } UnionLayout::NonNullableUnwrapped(_) => Builtin::Bool, UnionLayout::NullableUnwrapped { .. } => Builtin::Bool, diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 7213cad66b..9ce54e72b9 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -3756,7 +3756,6 @@ mod solve_expr { } #[test] - #[ignore] fn sorting() { // based on https://github.com/elm/compiler/issues/2057 // Roc seems to do this correctly, tracking to make sure it stays that way @@ -3790,7 +3789,6 @@ mod solve_expr { g = \bs -> when bs is bx -> f bx - _ -> Nil always Nil (f list) @@ -4300,7 +4298,6 @@ mod solve_expr { } #[test] - #[ignore] fn rbtree_full_remove_min() { infer_eq_without_problem( indoc!( diff --git a/flake.nix b/flake.nix index 37c672945b..6ddfa2e9a7 100644 --- a/flake.nix +++ b/flake.nix @@ -12,7 +12,9 @@ flake-utils.lib.eachDefaultSystem (system: let overlays = [ (import rust-overlay) ]; - pkgs = import nixpkgs { inherit system overlays; }; + pkgs = import nixpkgs { + inherit system overlays; + }; llvmPkgs = pkgs.llvmPackages_13; # get current working directory @@ -33,7 +35,7 @@ alsa-lib ]; - # zig 0.8.1 from pkgs is broken on aarch64-darwin, hence the workaround + # zig 0.9.1 from pkgs is broken on aarch64-darwin, hence the workaround zig-toolchain = zig.packages.${system}."0.9.1"; sharedInputs = (with pkgs; [ @@ -70,6 +72,7 @@ LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath ([ pkg-config stdenv.cc.cc.lib libffi ncurses zlib ] ++ linuxInputs); + NIXPKGS_ALLOW_UNFREE = 1; # to run the editor with NVIDIA's closed source drivers }; } diff --git a/repl_cli/Cargo.toml b/repl_cli/Cargo.toml index 89651e21a5..708a34c4b4 100644 --- a/repl_cli/Cargo.toml +++ b/repl_cli/Cargo.toml @@ -15,7 +15,7 @@ target-x86_64 = ["roc_build/target-x86_64"] [dependencies] bumpalo = {version = "3.8.0", features = ["collections"]} -const_format = "0.2.22" +const_format = { version = "0.2.23", features = ["const_generics"] } inkwell = {path = "../vendor/inkwell"} libloading = "0.7.1" rustyline = {git = "https://github.com/rtfeldman/rustyline", rev = "e74333c"} diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 4011968fa2..cb5415154f 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -1206,6 +1206,128 @@ mod test_reporting { ) } + #[test] + fn polymorphic_mutual_recursion() { + report_problem_as( + indoc!( + r#" + f = \x -> g x + g = \x -> f [x] + + f + "# + ), + indoc!( + r#" + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + + I'm inferring a weird self-referential type for `f`: + + 1│ f = \x -> g x + ^ + + 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. + + List ∞ -> a + + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + + I'm inferring a weird self-referential type for `g`: + + 2│ g = \x -> f [x] + ^ + + 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. + + List ∞ -> a + "# + ), + ) + } + + #[test] + fn polymorphic_mutual_recursion_annotated() { + report_problem_as( + indoc!( + r#" + f : a -> List a + f = \x -> g x + g = \x -> f [x] + + f + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression is used in an unexpected way: + + 2│ f = \x -> g x + ^^^ + + This `g` call produces: + + List List a + + But you are trying to use it as: + + List a + + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `List` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "# + ), + ) + } + + #[test] + fn polymorphic_mutual_recursion_dually_annotated_lie() { + report_problem_as( + indoc!( + r#" + f : a -> List a + f = \x -> g x + g : b -> List b + g = \x -> f [x] + + f + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression is used in an unexpected way: + + 4│ g = \x -> f [x] + ^^^^^ + + This `f` call produces: + + List List b + + But you are trying to use it as: + + List b + + Tip: The type annotation uses the type variable `b` to say that this + definition can produce any type of value. But in the body I see that + it will only produce a `List` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + "# + ), + ) + } + #[test] fn record_field_mismatch() { report_problem_as(