Merge remote-tracking branch 'origin/trunk' into more-tyck

This commit is contained in:
Folkert 2022-05-14 14:48:54 +02:00
commit c161140aec
No known key found for this signature in database
GPG Key ID: 1F17F6FFD112B97C
15 changed files with 1160 additions and 243 deletions

View File

@ -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.

View File

@ -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"

View File

@ -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(_)

View File

@ -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, ")?;
}

View File

@ -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(),
}
}
}

View File

@ -4,3 +4,4 @@
#![allow(unused_imports)]
#![allow(non_snake_case)]
#![allow(non_camel_case_types)]
#![allow(clippy::undocumented_unsafe_blocks)]

View File

@ -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,
}
"#
)

View File

@ -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 }

View File

@ -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"

View File

@ -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,
[],

View File

@ -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,

View File

@ -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!(

View File

@ -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
};
}

View File

@ -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"}

View File

@ -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(