mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-20 23:37:56 +03:00
Merge remote-tracking branch 'origin/trunk' into more-tyck
This commit is contained in:
commit
c161140aec
@ -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.
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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::<Vec<TypeId>>();
|
||||
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(_)
|
||||
|
@ -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<I: IntoIterator<Item = S>, S: AsRef<str>>(
|
||||
fn write_tag_union(
|
||||
name: &str,
|
||||
tags: I,
|
||||
tag_bytes: u8,
|
||||
type_id: TypeId,
|
||||
tags: &[(String, Option<TypeId>)],
|
||||
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<roc_std::RocStr>,
|
||||
// }
|
||||
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<core::cmp::Ordering> {{
|
||||
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<Item = &'a (String, Option<TypeId>)>,
|
||||
F: Fn(&str, Option<TypeId>) -> 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<I: ExactSizeIterator<Item = S>, S: AsRef<str>>(
|
||||
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, ")?;
|
||||
}
|
||||
|
||||
|
@ -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<TypeId>)>,
|
||||
tags: Vec<(String, Option<TypeId>)>,
|
||||
},
|
||||
Enumeration {
|
||||
name: String,
|
||||
tags: Vec<String>,
|
||||
},
|
||||
TagUnion {
|
||||
tag_bytes: u8,
|
||||
name: String,
|
||||
tags: Vec<(String, Vec<TypeId>)>,
|
||||
tags: Vec<(String, Option<TypeId>)>,
|
||||
},
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,3 +4,4 @@
|
||||
#![allow(unused_imports)]
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(clippy::undocumented_unsafe_blocks)]
|
||||
|
@ -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<Payload2<roc_std::RocStr, i32>>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[repr(C)]
|
||||
pub struct Payload2<V0, V1> {
|
||||
_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<roc_std::RocStr>,
|
||||
}
|
||||
|
||||
#[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<roc_std::RocStr, i32> {
|
||||
&*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<roc_std::RocStr, i32> {
|
||||
&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>,
|
||||
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>,
|
||||
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,
|
||||
}
|
||||
"#
|
||||
)
|
||||
|
@ -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 }
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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<Item = Variable>,
|
||||
new_infer_variables: impl Iterator<Item = Variable>,
|
||||
expr_con: Constraint,
|
||||
body_con: Constraint,
|
||||
annotation_rigid_variables: impl Iterator<Item = Variable>,
|
||||
annotation_infer_variables: impl Iterator<Item = Variable>,
|
||||
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,
|
||||
[],
|
||||
|
@ -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,
|
||||
|
@ -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!(
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -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"}
|
||||
|
@ -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(
|
||||
|
Loading…
Reference in New Issue
Block a user