From 41e3504779bca0524c11c9a3cc49f1a080dab771 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 6 Jun 2022 20:37:16 +0200 Subject: [PATCH 01/53] remove unneeded function --- compiler/types/src/subs.rs | 8 ++++---- compiler/types/src/unification_table.rs | 6 ------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index b252a7b6ee..8a6aac861f 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1601,8 +1601,8 @@ impl Subs { /// Unions two keys without the possibility of failure. pub fn union(&mut self, left: Variable, right: Variable, desc: Descriptor) { - let l_root = self.utable.inlined_get_root_key(left); - let r_root = self.utable.inlined_get_root_key(right); + let l_root = self.utable.root_key(left); + let r_root = self.utable.root_key(right); // NOTE this swapping is intentional! most of our unifying commands are based on the elm // source, but unify_roots is from `ena`, not the elm source. Turns out that they have @@ -1657,7 +1657,7 @@ impl Subs { #[inline(always)] pub fn get_root_key(&mut self, key: Variable) -> Variable { - self.utable.inlined_get_root_key(key) + self.utable.root_key(key) } #[inline(always)] @@ -1667,7 +1667,7 @@ impl Subs { #[inline(always)] pub fn set(&mut self, key: Variable, r_value: Descriptor) { - let l_key = self.utable.inlined_get_root_key(key); + let l_key = self.utable.root_key(key); // self.utable.update_value(l_key, |node| node.value = r_value); self.utable.set_descriptor(l_key, r_value) diff --git a/compiler/types/src/unification_table.rs b/compiler/types/src/unification_table.rs index 9f2f95a223..693a83c53f 100644 --- a/compiler/types/src/unification_table.rs +++ b/compiler/types/src/unification_table.rs @@ -263,12 +263,6 @@ impl UnificationTable { self.ranks[index] } - // TODO remove - #[inline(always)] - pub fn inlined_get_root_key(&mut self, key: Variable) -> Variable { - self.root_key(key) - } - /// NOTE: assumes variables are root pub fn unify_roots(&mut self, to: Variable, from: Variable, desc: Descriptor) { let from_index = from.index() as usize; From 074834d2183810b1b0a1af9f4f9dd3d27c1633a2 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 6 Jun 2022 20:57:04 +0200 Subject: [PATCH 02/53] just one metadata allocation --- compiler/types/src/unification_table.rs | 199 +++++++++++++++--------- 1 file changed, 128 insertions(+), 71 deletions(-) diff --git a/compiler/types/src/unification_table.rs b/compiler/types/src/unification_table.rs index 693a83c53f..1b9f05fea6 100644 --- a/compiler/types/src/unification_table.rs +++ b/compiler/types/src/unification_table.rs @@ -3,23 +3,42 @@ use crate::subs::{Content, Descriptor, Mark, OptVariable, Rank, Variable, Variab #[derive(Clone, Default)] pub struct UnificationTable { contents: Vec, - ranks: Vec, - marks: Vec, - copies: Vec, - redirects: Vec, + metadata: Vec, + // ranks: Vec, + // marks: Vec, + // copies: Vec, + // redirects: Vec, } pub struct Snapshot(UnificationTable); +#[derive(Debug, Clone, Copy)] +struct Combine { + redirect: OptVariable, + rank: Rank, + mark: Mark, + copy: OptVariable, +} + +impl Combine { + const NONE: Self = Self { + redirect: OptVariable::NONE, + rank: Rank::NONE, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; +} + impl UnificationTable { #[allow(unused)] pub fn with_capacity(cap: usize) -> Self { Self { contents: Vec::with_capacity(cap), // vec![Content::Error; cap], - ranks: Vec::with_capacity(cap), // vec![Rank::NONE; cap], - marks: Vec::with_capacity(cap), // vec![Mark::NONE; cap], - copies: Vec::with_capacity(cap), // vec![OptVariable::NONE; cap], - redirects: Vec::with_capacity(cap), // vec![OptVariable::NONE; cap], + metadata: Vec::with_capacity(cap) +// ranks: Vec::with_capacity(cap), // vec![Rank::NONE; cap], +// marks: Vec::with_capacity(cap), // vec![Mark::NONE; cap], +// copies: Vec::with_capacity(cap), // vec![OptVariable::NONE; cap], +// redirects: Vec::with_capacity(cap), // vec![OptVariable::NONE; cap], } } @@ -38,12 +57,8 @@ impl UnificationTable { self.contents .extend(repeat(Content::FlexVar(None)).take(extra_length)); - self.ranks.extend(repeat(Rank::NONE).take(extra_length)); - self.marks.extend(repeat(Mark::NONE).take(extra_length)); - self.copies - .extend(repeat(OptVariable::NONE).take(extra_length)); - self.redirects - .extend(repeat(OptVariable::NONE).take(extra_length)); + self.metadata + .extend(repeat(Combine::NONE).take(extra_length)); VariableSubsSlice::new(start as _, extra_length as _) } @@ -58,10 +73,15 @@ impl UnificationTable { let variable = unsafe { Variable::from_index(self.len() as _) }; self.contents.push(content); - self.ranks.push(rank); - self.marks.push(mark); - self.copies.push(copy); - self.redirects.push(OptVariable::NONE); + + let combine = Combine { + redirect: OptVariable::NONE, + rank, + mark, + copy, + }; + + self.metadata.push(combine); variable } @@ -75,33 +95,51 @@ impl UnificationTable { mark: Mark, copy: OptVariable, ) { - let index = self.root_key(key).index() as usize; + let root = self.root_key(key); + self.set_unchecked(root, content, rank, mark, copy) + } + + pub fn set_unchecked( + &mut self, + key: Variable, + content: Content, + rank: Rank, + mark: Mark, + copy: OptVariable, + ) { + let index = key.index() as usize; self.contents[index] = content; - self.ranks[index] = rank; - self.marks[index] = mark; - self.copies[index] = copy; + + let combine = Combine { + redirect: OptVariable::NONE, + rank, + mark, + copy, + }; + + self.metadata[index] = combine; } pub fn modify(&mut self, key: Variable, mapper: F) -> T where F: FnOnce(&mut Descriptor) -> T, { - let index = self.root_key(key).index() as usize; + let root = self.root_key(key); + let index = root.index() as usize; + + let combine = self.metadata[index]; let mut desc = Descriptor { content: self.contents[index], - rank: self.ranks[index], - mark: self.marks[index], - copy: self.copies[index], + rank: combine.rank, + mark: combine.mark, + copy: combine.copy, }; let result = mapper(&mut desc); - self.contents[index] = desc.content; - self.ranks[index] = desc.rank; - self.marks[index] = desc.mark; - self.copies[index] = desc.copy; + self.set_unchecked(key, desc.content, desc.rank, desc.mark, desc.copy); result } @@ -110,18 +148,18 @@ impl UnificationTable { #[inline(always)] pub fn get_rank_unchecked(&self, key: Variable) -> Rank { - self.ranks[key.index() as usize] + self.metadata[key.index() as usize].rank } #[inline(always)] pub fn get_mark_unchecked(&self, key: Variable) -> Mark { - self.marks[key.index() as usize] + self.metadata[key.index() as usize].mark } #[allow(unused)] #[inline(always)] pub fn get_copy_unchecked(&self, key: Variable) -> OptVariable { - self.copies[key.index() as usize] + self.metadata[key.index() as usize].copy } #[inline(always)] @@ -133,18 +171,18 @@ impl UnificationTable { #[inline(always)] pub fn get_rank(&self, key: Variable) -> Rank { - self.ranks[self.root_key_without_compacting(key).index() as usize] + self.metadata[self.root_key_without_compacting(key).index() as usize].rank } #[inline(always)] pub fn get_mark(&self, key: Variable) -> Mark { - self.marks[self.root_key_without_compacting(key).index() as usize] + self.metadata[self.root_key_without_compacting(key).index() as usize].mark } #[inline(always)] pub fn get_copy(&self, key: Variable) -> OptVariable { let index = self.root_key_without_compacting(key).index() as usize; - self.copies[index] + self.metadata[index].copy } #[inline(always)] @@ -156,18 +194,18 @@ impl UnificationTable { #[inline(always)] pub fn set_rank_unchecked(&mut self, key: Variable, value: Rank) { - self.ranks[key.index() as usize] = value; + self.metadata[key.index() as usize].rank = value; } #[inline(always)] pub fn set_mark_unchecked(&mut self, key: Variable, value: Mark) { - self.marks[key.index() as usize] = value; + self.metadata[key.index() as usize].mark = value; } #[allow(unused)] #[inline(always)] pub fn set_copy_unchecked(&mut self, key: Variable, value: OptVariable) { - self.copies[key.index() as usize] = value; + self.metadata[key.index() as usize].copy = value; } #[allow(unused)] @@ -181,19 +219,19 @@ impl UnificationTable { #[inline(always)] pub fn set_rank(&mut self, key: Variable, value: Rank) { let index = self.root_key(key).index() as usize; - self.ranks[index] = value; + self.metadata[index].rank = value; } #[inline(always)] pub fn set_mark(&mut self, key: Variable, value: Mark) { let index = self.root_key(key).index() as usize; - self.marks[index] = value; + self.metadata[index].mark = value; } #[inline(always)] pub fn set_copy(&mut self, key: Variable, value: OptVariable) { let index = self.root_key(key).index() as usize; - self.copies[index] = value; + self.metadata[index].copy = value; } #[inline(always)] @@ -208,12 +246,12 @@ impl UnificationTable { pub fn root_key(&mut self, mut key: Variable) -> Variable { let index = key.index() as usize; - while let Some(redirect) = self.redirects[key.index() as usize].into_variable() { + while let Some(redirect) = self.metadata[key.index() as usize].redirect.into_variable() { key = redirect; } if index != key.index() as usize { - self.redirects[index] = OptVariable::from(key); + self.metadata[index].redirect = OptVariable::from(key); } key @@ -221,7 +259,7 @@ impl UnificationTable { #[inline(always)] pub fn root_key_without_compacting(&self, mut key: Variable) -> Variable { - while let Some(redirect) = self.redirects[key.index() as usize].into_variable() { + while let Some(redirect) = self.metadata[key.index() as usize].redirect.into_variable() { key = redirect; } @@ -246,7 +284,7 @@ impl UnificationTable { } pub fn is_redirect(&self, key: Variable) -> bool { - self.redirects[key.index() as usize].is_some() + self.metadata[key.index() as usize].redirect.is_some() } pub fn unioned(&mut self, a: Variable, b: Variable) -> bool { @@ -257,10 +295,10 @@ impl UnificationTable { #[inline(always)] pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank { let index = self.root_key(key).index() as usize; + let metadata = &mut self.metadata[index]; - self.marks[index] = mark; - - self.ranks[index] + metadata.mark = mark; + metadata.rank } /// NOTE: assumes variables are root @@ -270,34 +308,27 @@ impl UnificationTable { // redirect from -> to if from_index != to_index { - self.redirects[from_index] = OptVariable::from(to); + self.metadata[from_index].redirect = OptVariable::from(to); } // update to's Descriptor - self.contents[to_index] = desc.content; - self.ranks[to_index] = desc.rank; - self.marks[to_index] = desc.mark; - self.copies[to_index] = desc.copy; + self.set_unchecked(to, desc.content, desc.rank, desc.mark, desc.copy); } pub fn get_descriptor(&self, key: Variable) -> Descriptor { let index = self.root_key_without_compacting(key).index() as usize; + let metadata = self.metadata[index]; Descriptor { content: self.contents[index], - rank: self.ranks[index], - mark: self.marks[index], - copy: self.copies[index], + rank: metadata.rank, + mark: metadata.mark, + copy: metadata.copy, } } pub fn set_descriptor(&mut self, key: Variable, desc: Descriptor) { - let index = self.root_key(key).index() as usize; - - self.contents[index] = desc.content; - self.ranks[index] = desc.rank; - self.marks[index] = desc.mark; - self.copies[index] = desc.copy; + self.set(key, desc.content, desc.rank, desc.mark, desc.copy); } pub(crate) fn serialize( @@ -308,10 +339,23 @@ impl UnificationTable { use crate::subs::Subs; written = Subs::serialize_slice(&self.contents, writer, written)?; - written = Subs::serialize_slice(&self.ranks, writer, written)?; - written = Subs::serialize_slice(&self.marks, writer, written)?; - written = Subs::serialize_slice(&self.copies, writer, written)?; - written = Subs::serialize_slice(&self.redirects, writer, written)?; + + let mut ranks = Vec::new(); + let mut marks = Vec::new(); + let mut copies = Vec::new(); + let mut redirects = Vec::new(); + + for c in self.metadata.iter() { + ranks.push(c.rank); + marks.push(c.mark); + copies.push(c.copy); + redirects.push(c.redirect); + } + + written = Subs::serialize_slice(&ranks, writer, written)?; + written = Subs::serialize_slice(&marks, writer, written)?; + written = Subs::serialize_slice(&copies, writer, written)?; + written = Subs::serialize_slice(&redirects, writer, written)?; Ok(written) } @@ -325,12 +369,25 @@ impl UnificationTable { let (copies, offset) = Subs::deserialize_slice::(bytes, length, offset); let (redirects, offset) = Subs::deserialize_slice::(bytes, length, offset); + let mut metadata = Vec::with_capacity(ranks.len()); + + let it = ranks + .iter() + .zip(marks.iter()) + .zip(copies.iter()) + .zip(redirects.iter()); + for (((rank, mark), copy), redirect) in it { + metadata.push(Combine { + redirect: *redirect, + rank: *rank, + mark: *mark, + copy: *copy, + }); + } + let this = Self { contents: contents.to_vec(), - ranks: ranks.to_vec(), - marks: marks.to_vec(), - copies: copies.to_vec(), - redirects: redirects.to_vec(), + metadata, }; (this, offset) From f0e4b2619627bf03aa2ef143394a7d1a9bb12847 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 7 Jun 2022 19:33:38 +0200 Subject: [PATCH 03/53] cleanup --- compiler/types/src/unification_table.rs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/compiler/types/src/unification_table.rs b/compiler/types/src/unification_table.rs index 1b9f05fea6..8561387166 100644 --- a/compiler/types/src/unification_table.rs +++ b/compiler/types/src/unification_table.rs @@ -4,10 +4,6 @@ use crate::subs::{Content, Descriptor, Mark, OptVariable, Rank, Variable, Variab pub struct UnificationTable { contents: Vec, metadata: Vec, - // ranks: Vec, - // marks: Vec, - // copies: Vec, - // redirects: Vec, } pub struct Snapshot(UnificationTable); @@ -33,12 +29,8 @@ impl UnificationTable { #[allow(unused)] pub fn with_capacity(cap: usize) -> Self { Self { - contents: Vec::with_capacity(cap), // vec![Content::Error; cap], - metadata: Vec::with_capacity(cap) -// ranks: Vec::with_capacity(cap), // vec![Rank::NONE; cap], -// marks: Vec::with_capacity(cap), // vec![Mark::NONE; cap], -// copies: Vec::with_capacity(cap), // vec![OptVariable::NONE; cap], -// redirects: Vec::with_capacity(cap), // vec![OptVariable::NONE; cap], + contents: Vec::with_capacity(cap), // vec![Content::Error; cap], + metadata: Vec::with_capacity(cap), } } @@ -111,14 +103,12 @@ impl UnificationTable { self.contents[index] = content; - let combine = Combine { + self.metadata[index] = Combine { redirect: OptVariable::NONE, rank, mark, copy, }; - - self.metadata[index] = combine; } pub fn modify(&mut self, key: Variable, mapper: F) -> T @@ -128,7 +118,7 @@ impl UnificationTable { let root = self.root_key(key); let index = root.index() as usize; - let combine = self.metadata[index]; + let combine = &self.metadata[index]; let mut desc = Descriptor { content: self.contents[index], From d48bbd0499665620c8221234d6d049d0dbe25833 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 7 Jun 2022 21:52:43 +0200 Subject: [PATCH 04/53] fix bug where root key was not used --- compiler/types/src/unification_table.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/types/src/unification_table.rs b/compiler/types/src/unification_table.rs index 8561387166..2dc4a8db05 100644 --- a/compiler/types/src/unification_table.rs +++ b/compiler/types/src/unification_table.rs @@ -29,7 +29,7 @@ impl UnificationTable { #[allow(unused)] pub fn with_capacity(cap: usize) -> Self { Self { - contents: Vec::with_capacity(cap), // vec![Content::Error; cap], + contents: Vec::with_capacity(cap), metadata: Vec::with_capacity(cap), } } @@ -129,7 +129,7 @@ impl UnificationTable { let result = mapper(&mut desc); - self.set_unchecked(key, desc.content, desc.rank, desc.mark, desc.copy); + self.set_unchecked(root, desc.content, desc.rank, desc.mark, desc.copy); result } From 7481786b6b35e741eb122cc66ca9990a222bf6d2 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 7 Jun 2022 21:54:35 +0200 Subject: [PATCH 05/53] code reuse in root key finding --- compiler/types/src/unification_table.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/compiler/types/src/unification_table.rs b/compiler/types/src/unification_table.rs index 2dc4a8db05..34983d84db 100644 --- a/compiler/types/src/unification_table.rs +++ b/compiler/types/src/unification_table.rs @@ -233,18 +233,14 @@ impl UnificationTable { // ROOT KEY #[inline(always)] - pub fn root_key(&mut self, mut key: Variable) -> Variable { - let index = key.index() as usize; + pub fn root_key(&mut self, key: Variable) -> Variable { + let root = self.root_key_without_compacting(key); - while let Some(redirect) = self.metadata[key.index() as usize].redirect.into_variable() { - key = redirect; + if root != key { + self.metadata[key.index() as usize].redirect = OptVariable::from(root); } - if index != key.index() as usize { - self.metadata[index].redirect = OptVariable::from(key); - } - - key + root } #[inline(always)] From 552c2706f61da4ab0a845a280458b9d4ae0ff7e0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 7 Jun 2022 22:17:29 +0200 Subject: [PATCH 06/53] compact all nodes on the way to the root key --- compiler/types/src/unification_table.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/compiler/types/src/unification_table.rs b/compiler/types/src/unification_table.rs index 34983d84db..0fa33f84d4 100644 --- a/compiler/types/src/unification_table.rs +++ b/compiler/types/src/unification_table.rs @@ -233,11 +233,19 @@ impl UnificationTable { // ROOT KEY #[inline(always)] - pub fn root_key(&mut self, key: Variable) -> Variable { + pub fn root_key(&mut self, mut key: Variable) -> Variable { let root = self.root_key_without_compacting(key); - if root != key { - self.metadata[key.index() as usize].redirect = OptVariable::from(root); + while root != key { + let next_key = std::mem::replace( + &mut self.metadata[key.index() as usize].redirect, + OptVariable::from(root), + ); + + match next_key.into_variable() { + Some(redirect) => key = redirect, + None => break, // no redirect; we've found the root + } } root From 96ee2c52fbc35ae47810a278b262724ec6b4f51f Mon Sep 17 00:00:00 2001 From: Folkert Date: Sat, 11 Jun 2022 18:06:18 +0200 Subject: [PATCH 07/53] rework Combined --- compiler/types/src/unification_table.rs | 202 +++++++++++++++--------- 1 file changed, 131 insertions(+), 71 deletions(-) diff --git a/compiler/types/src/unification_table.rs b/compiler/types/src/unification_table.rs index 0fa33f84d4..e134247e00 100644 --- a/compiler/types/src/unification_table.rs +++ b/compiler/types/src/unification_table.rs @@ -1,3 +1,5 @@ +use std::hint::unreachable_unchecked; + use crate::subs::{Content, Descriptor, Mark, OptVariable, Rank, Variable, VariableSubsSlice}; #[derive(Clone, Default)] @@ -9,22 +11,30 @@ pub struct UnificationTable { pub struct Snapshot(UnificationTable); #[derive(Debug, Clone, Copy)] -struct Combine { - redirect: OptVariable, +enum Combine { + Redirect(Variable), + Root(Root), +} + +#[derive(Debug, Clone, Copy)] +pub struct Root { rank: Rank, mark: Mark, copy: OptVariable, } -impl Combine { +impl Root { const NONE: Self = Self { - redirect: OptVariable::NONE, rank: Rank::NONE, mark: Mark::NONE, copy: OptVariable::NONE, }; } +impl Combine { + const NONE: Self = Self::Root(Root::NONE); +} + impl UnificationTable { #[allow(unused)] pub fn with_capacity(cap: usize) -> Self { @@ -66,12 +76,7 @@ impl UnificationTable { self.contents.push(content); - let combine = Combine { - redirect: OptVariable::NONE, - rank, - mark, - copy, - }; + let combine = Combine::Root(Root { rank, mark, copy }); self.metadata.push(combine); @@ -103,53 +108,62 @@ impl UnificationTable { self.contents[index] = content; - self.metadata[index] = Combine { - redirect: OptVariable::NONE, - rank, - mark, - copy, - }; + self.metadata[index] = Combine::Root(Root { rank, mark, copy }); } pub fn modify(&mut self, key: Variable, mapper: F) -> T where F: FnOnce(&mut Descriptor) -> T, { - let root = self.root_key(key); - let index = root.index() as usize; + let root_key = self.root_key(key); + let index = root_key.index() as usize; - let combine = &self.metadata[index]; + let root = self.get_root_unchecked(root_key); let mut desc = Descriptor { content: self.contents[index], - rank: combine.rank, - mark: combine.mark, - copy: combine.copy, + rank: root.rank, + mark: root.mark, + copy: root.copy, }; let result = mapper(&mut desc); - self.set_unchecked(root, desc.content, desc.rank, desc.mark, desc.copy); + self.set_unchecked(root_key, desc.content, desc.rank, desc.mark, desc.copy); result } // GET UNCHECKED + #[inline(always)] + pub fn get_root_unchecked(&self, key: Variable) -> Root { + match self.metadata[key.index() as usize] { + Combine::Root(root) => root, + Combine::Redirect(_) => { + if cfg!(debug_assertions) { + unreachable!("unchecked access on non-root variable {:?}", key); + } else { + unsafe { unreachable_unchecked() } + } + } + } + } + #[inline(always)] pub fn get_rank_unchecked(&self, key: Variable) -> Rank { - self.metadata[key.index() as usize].rank + self.get_root_unchecked(key).rank } #[inline(always)] pub fn get_mark_unchecked(&self, key: Variable) -> Mark { - self.metadata[key.index() as usize].mark + self.get_root_unchecked(key).mark } #[allow(unused)] #[inline(always)] pub fn get_copy_unchecked(&self, key: Variable) -> OptVariable { - self.metadata[key.index() as usize].copy + self.get_root_unchecked(key).copy } #[inline(always)] @@ -159,20 +173,34 @@ impl UnificationTable { // GET CHECKED + #[inline(always)] + pub fn get_root(&self, key: Variable) -> Root { + let root_key = self.root_key_without_compacting(key); + match self.metadata[root_key.index() as usize] { + Combine::Root(root) => root, + Combine::Redirect(_) => { + if cfg!(debug_assertions) { + unreachable!("the root key {:?} is not actually a root key", root_key); + } else { + unsafe { unreachable_unchecked() } + } + } + } + } + #[inline(always)] pub fn get_rank(&self, key: Variable) -> Rank { - self.metadata[self.root_key_without_compacting(key).index() as usize].rank + self.get_root(key).rank } #[inline(always)] pub fn get_mark(&self, key: Variable) -> Mark { - self.metadata[self.root_key_without_compacting(key).index() as usize].mark + self.get_root(key).mark } #[inline(always)] pub fn get_copy(&self, key: Variable) -> OptVariable { - let index = self.root_key_without_compacting(key).index() as usize; - self.metadata[index].copy + self.get_root(key).copy } #[inline(always)] @@ -182,20 +210,37 @@ impl UnificationTable { // SET UNCHECKED + #[inline(always)] + pub fn modify_root_unchecked(&mut self, key: Variable, f: F) -> T + where + F: Fn(&mut Root) -> T, + { + match &mut self.metadata[key.index() as usize] { + Combine::Root(root) => f(root), + Combine::Redirect(_) => { + if cfg!(debug_assertions) { + unreachable!("unchecked access on non-root variable {:?}", key); + } else { + unsafe { unreachable_unchecked() } + } + } + } + } + #[inline(always)] pub fn set_rank_unchecked(&mut self, key: Variable, value: Rank) { - self.metadata[key.index() as usize].rank = value; + self.modify_root_unchecked(key, |root| root.rank = value) } #[inline(always)] pub fn set_mark_unchecked(&mut self, key: Variable, value: Mark) { - self.metadata[key.index() as usize].mark = value; + self.modify_root_unchecked(key, |root| root.mark = value) } #[allow(unused)] #[inline(always)] pub fn set_copy_unchecked(&mut self, key: Variable, value: OptVariable) { - self.metadata[key.index() as usize].copy = value; + self.modify_root_unchecked(key, |root| root.copy = value) } #[allow(unused)] @@ -208,20 +253,20 @@ impl UnificationTable { #[inline(always)] pub fn set_rank(&mut self, key: Variable, value: Rank) { - let index = self.root_key(key).index() as usize; - self.metadata[index].rank = value; + let root_key = self.root_key(key); + self.modify_root_unchecked(root_key, |root| root.rank = value) } #[inline(always)] pub fn set_mark(&mut self, key: Variable, value: Mark) { - let index = self.root_key(key).index() as usize; - self.metadata[index].mark = value; + let root_key = self.root_key(key); + self.modify_root_unchecked(root_key, |root| root.mark = value) } #[inline(always)] pub fn set_copy(&mut self, key: Variable, value: OptVariable) { - let index = self.root_key(key).index() as usize; - self.metadata[index].copy = value; + let root_key = self.root_key(key); + self.modify_root_unchecked(root_key, |root| root.copy = value) } #[inline(always)] @@ -236,16 +281,9 @@ impl UnificationTable { pub fn root_key(&mut self, mut key: Variable) -> Variable { let root = self.root_key_without_compacting(key); - while root != key { - let next_key = std::mem::replace( - &mut self.metadata[key.index() as usize].redirect, - OptVariable::from(root), - ); - - match next_key.into_variable() { - Some(redirect) => key = redirect, - None => break, // no redirect; we've found the root - } + while let Combine::Redirect(redirect) = &mut self.metadata[key.index() as usize] { + key = *redirect; + *redirect = root; } root @@ -253,7 +291,7 @@ impl UnificationTable { #[inline(always)] pub fn root_key_without_compacting(&self, mut key: Variable) -> Variable { - while let Some(redirect) = self.metadata[key.index() as usize].redirect.into_variable() { + while let Combine::Redirect(redirect) = self.metadata[key.index() as usize] { key = redirect; } @@ -278,7 +316,7 @@ impl UnificationTable { } pub fn is_redirect(&self, key: Variable) -> bool { - self.metadata[key.index() as usize].redirect.is_some() + matches!(self.metadata[key.index() as usize], Combine::Redirect(_)) } pub fn unioned(&mut self, a: Variable, b: Variable) -> bool { @@ -288,11 +326,13 @@ impl UnificationTable { // custom very specific helpers #[inline(always)] pub fn get_rank_set_mark(&mut self, key: Variable, mark: Mark) -> Rank { - let index = self.root_key(key).index() as usize; - let metadata = &mut self.metadata[index]; + let root_key = self.root_key(key); - metadata.mark = mark; - metadata.rank + self.modify_root_unchecked(root_key, |root| { + root.mark = mark; + + root.rank + }) } /// NOTE: assumes variables are root @@ -302,7 +342,7 @@ impl UnificationTable { // redirect from -> to if from_index != to_index { - self.metadata[from_index].redirect = OptVariable::from(to); + self.metadata[from_index] = Combine::Redirect(to) } // update to's Descriptor @@ -310,14 +350,16 @@ impl UnificationTable { } pub fn get_descriptor(&self, key: Variable) -> Descriptor { - let index = self.root_key_without_compacting(key).index() as usize; - let metadata = self.metadata[index]; + let root_key = self.root_key_without_compacting(key); + let index = root_key.index() as usize; + + let root = self.get_root_unchecked(root_key); Descriptor { content: self.contents[index], - rank: metadata.rank, - mark: metadata.mark, - copy: metadata.copy, + rank: root.rank, + mark: root.mark, + copy: root.copy, } } @@ -340,10 +382,24 @@ impl UnificationTable { let mut redirects = Vec::new(); for c in self.metadata.iter() { - ranks.push(c.rank); - marks.push(c.mark); - copies.push(c.copy); - redirects.push(c.redirect); + match c { + Combine::Redirect(redirect) => { + let root = Root::NONE; + + ranks.push(root.rank); + marks.push(root.mark); + copies.push(root.copy); + + redirects.push(OptVariable::from(*redirect)); + } + Combine::Root(root) => { + ranks.push(root.rank); + marks.push(root.mark); + copies.push(root.copy); + + redirects.push(OptVariable::NONE); + } + } } written = Subs::serialize_slice(&ranks, writer, written)?; @@ -371,12 +427,16 @@ impl UnificationTable { .zip(copies.iter()) .zip(redirects.iter()); for (((rank, mark), copy), redirect) in it { - metadata.push(Combine { - redirect: *redirect, - rank: *rank, - mark: *mark, - copy: *copy, - }); + match redirect.into_variable() { + Some(redirect) => metadata.push(Combine::Redirect(redirect)), + None => { + metadata.push(Combine::Root(Root { + rank: *rank, + mark: *mark, + copy: *copy, + })); + } + } } let this = Self { From 7a3aff2bb6c0e957207c66d2b1a3eb14210ac7d2 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 15 Jun 2022 10:09:19 -0400 Subject: [PATCH 08/53] Default to main.roc in CLI --- cli/src/lib.rs | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/cli/src/lib.rs b/cli/src/lib.rs index 53c6216066..5c6e123f4a 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -3,7 +3,7 @@ extern crate const_format; use build::BuiltFile; use bumpalo::Bump; -use clap::{Arg, ArgMatches, Command}; +use clap::{Arg, ArgMatches, Command, ValueSource}; use roc_build::link::{LinkType, LinkingStrategy}; use roc_error_macros::{internal_error, user_error}; use roc_load::{LoadingProblem, Threading}; @@ -24,6 +24,8 @@ pub mod build; mod format; pub use format::format; +const DEFAULT_ROC_FILENAME: &str = "main.roc"; + pub const CMD_BUILD: &str = "build"; pub const CMD_RUN: &str = "run"; pub const CMD_REPL: &str = "repl"; @@ -57,13 +59,11 @@ pub fn build_app<'a>() -> Command<'a> { let flag_optimize = Arg::new(FLAG_OPTIMIZE) .long(FLAG_OPTIMIZE) .help("Optimize the compiled program to run faster. (Optimization takes time to complete.)") - .requires(ROC_FILE) .required(false); let flag_max_threads = Arg::new(FLAG_MAX_THREADS) .long(FLAG_MAX_THREADS) .help("Limit the number of threads (and hence cores) used during compilation.") - .requires(ROC_FILE) .takes_value(true) .validator(|s| s.parse::()) .required(false); @@ -81,7 +81,6 @@ pub fn build_app<'a>() -> Command<'a> { let flag_debug = Arg::new(FLAG_DEBUG) .long(FLAG_DEBUG) .help("Store LLVM debug information in the generated program.") - .requires(ROC_FILE) .required(false); let flag_valgrind = Arg::new(FLAG_VALGRIND) @@ -108,13 +107,17 @@ pub fn build_app<'a>() -> Command<'a> { let roc_file_to_run = Arg::new(ROC_FILE) .help("The .roc file of an app to run") - .allow_invalid_utf8(true); + .allow_invalid_utf8(true) + .required(false) + .default_value(DEFAULT_ROC_FILENAME); let args_for_app = Arg::new(ARGS_FOR_APP) - .help("Arguments to pass into the app being run") - .requires(ROC_FILE) + .help("Arguments to pass into the app being run, e.g. `roc run -- arg1 arg2`") .allow_invalid_utf8(true) - .multiple_values(true); + .multiple_values(true) + .takes_value(true) + .allow_hyphen_values(true) + .last(true); let app = Command::new("roc") .version(concatcp!(VERSION, "\n")) @@ -154,7 +157,8 @@ pub fn build_app<'a>() -> Command<'a> { Arg::new(ROC_FILE) .help("The .roc file to build") .allow_invalid_utf8(true) - .required(true), + .required(false) + .default_value(DEFAULT_ROC_FILENAME), ) ) .subcommand(Command::new(CMD_REPL) @@ -171,7 +175,7 @@ pub fn build_app<'a>() -> Command<'a> { .arg(flag_linker.clone()) .arg(flag_precompiled.clone()) .arg(flag_valgrind.clone()) - .arg(roc_file_to_run.clone().required(true)) + .arg(roc_file_to_run.clone()) .arg(args_for_app.clone()) ) .subcommand(Command::new(CMD_FORMAT) @@ -199,7 +203,8 @@ pub fn build_app<'a>() -> Command<'a> { Arg::new(ROC_FILE) .help("The .roc file of an app to check") .allow_invalid_utf8(true) - .required(true), + .required(false) + .default_value(DEFAULT_ROC_FILENAME), ) ) .subcommand( @@ -317,9 +322,17 @@ pub fn build( match err.kind() { NotFound => { - match path.to_str() { - Some(path_str) => println!("File not found: {}", path_str), - None => println!("Malformed file path : {:?}", path), + let path_string = path.to_string_lossy(); + + // TODO these should use roc_reporting to display nicer error messages. + match matches.value_source(ROC_FILE) { + Some(ValueSource::DefaultValue) => { + eprintln!( + "\nNo `.roc` file was specified, and the current directory does not contain a {} file to use as a default.\n\nYou can run `roc help` for more information on how to provide a .roc file.\n", + DEFAULT_ROC_FILENAME + ) + } + _ => eprintln!("\nThis file was not found: {}\n\nYou can run `roc help` for more information on how to provide a .roc file.\n", path_string), } process::exit(1); From f9e15c0a655492ef2b2eb4b25378f4ea185d48d6 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 15 Jun 2022 10:29:38 -0400 Subject: [PATCH 09/53] Revise hello-world example --- README.md | 2 +- cli/tests/cli_run.rs | 4 +-- examples/hello-world/.gitignore | 1 + examples/hello-world/README.md | 42 ++++++----------------------- examples/hello-world/helloWorld.roc | 11 -------- examples/hello-world/main.roc | 6 +++++ highlight/tests/peg_grammar.rs | 16 +++++------ 7 files changed, 26 insertions(+), 56 deletions(-) delete mode 100644 examples/hello-world/helloWorld.roc create mode 100644 examples/hello-world/main.roc diff --git a/README.md b/README.md index 7be87d5bba..681b6d2661 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Many programs can however be compiled correctly. Check out [examples](examples) Run examples as follows: ``` -cargo run examples/hello-world/helloWorld.roc +cargo run examples/hello-world/main.roc ``` Some examples like `examples/benchmarks/NQueens.roc` require input after running. For NQueens, input 10 in the terminal and press enter. diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index d0b9136ff7..0b78d0a857 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -382,8 +382,8 @@ mod cli_run { // ] examples! { helloWorld:"hello-world" => Example { - filename: "helloWorld.roc", - executable_filename: "helloWorld", + filename: "main.roc", + executable_filename: "hello", stdin: &[], input_file: None, expected_ending:"Hello, World!\n", diff --git a/examples/hello-world/.gitignore b/examples/hello-world/.gitignore index 0f55df43a2..520f5f5dec 100644 --- a/examples/hello-world/.gitignore +++ b/examples/hello-world/.gitignore @@ -3,4 +3,5 @@ helloRust helloSwift helloWorld helloZig +hello *.wasm diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md index bd5fbda39f..5a310c5746 100644 --- a/examples/hello-world/README.md +++ b/examples/hello-world/README.md @@ -1,44 +1,18 @@ # Hello, World! -To run, `cd` into this directory and run: +To run, `cd` into this directory and run this in your terminal: ```bash -cargo run helloWorld.roc +roc run ``` -To run in release mode instead, do: +This will run `main.roc` because, unless you explicitly give it a filename, `roc run` +defaults to running a file named `main.roc`. Other `roc` commands (like `roc build`, `roc test`, and so on) also default to `main.roc` unless you explicitly give them a filename. -```bash -cargo run --release helloWorld.roc -``` +# About this example -## Design Notes +This uses a very simple platform which does nothing more than printing the string you give it. -This demonstrates the basic design of hosts: Roc code gets compiled into a pure -function (in this case, a thunk that always returns `"Hello, World!\n"`) and -then the host calls that function. Fundamentally, that's the whole idea! The host -might not even have a `main` - it could be a library, a plugin, anything. -Everything else is built on this basic "hosts calling linked pure functions" design. +The line `main = "Hello, World!\n"` sets this string to be `"Hello, World!"` with a newline at the end, and the lines `packages { pf: "c-platform" }` and `provides [main] to pf` specify that the `c-platform/` directory contains this app's platform. -For example, things get more interesting when the compiled Roc function returns -a `Task` - that is, a tagged union data structure containing function pointers -to callback closures. This lets the Roc pure function describe arbitrary -chainable effects, which the host can interpret to perform I/O as requested by -the Roc program. (The tagged union `Task` would have a variant for each supported -I/O operation.) - -In this trivial example, it's very easy to line up the API between the host and -the Roc program. In a more involved host, this would be much trickier - especially -if the API were changing frequently during development. - -The idea there is to have a first-class concept of "glue code" which host authors -can write (it would be plain Roc code, but with some extra keywords that aren't -available in normal modules - kinda like `port module` in Elm), and which -describe both the Roc-host/C boundary as well as the Roc-host/Roc-app boundary. -Roc application authors only care about the Roc-host/Roc-app portion, and the -host author only cares about the Roc-host/C boundary when implementing the host. - -Using this glue code, the Roc compiler can generate C header files describing the -boundary. This not only gets us host compatibility with C compilers, but also -Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) -generates correct Rust FFI bindings from C headers. +This platform is called `c-platform` because its low-level code is written in C. There's also a `rust-platform`, `zig-platform`, and so on; if you like, you can try switching `pf: "c-platform"` to `pf: "zig-platform"` or `pf: "rust-platform"` to try one of those platforms instead. They all do the same thing, so the application won't look any different, but if you want to start building your own platforms, this Hello World example gives you some very simple platforms to use as starting points too. diff --git a/examples/hello-world/helloWorld.roc b/examples/hello-world/helloWorld.roc deleted file mode 100644 index 7f90a48811..0000000000 --- a/examples/hello-world/helloWorld.roc +++ /dev/null @@ -1,11 +0,0 @@ -app "helloWorld" - packages { pf: "c-platform" } - # To switch platforms, comment-out the line above and un-comment one below. - # packages { pf: "rust-platform" } - # packages { pf: "swift-platform" } - # packages { pf: "web-platform" } # See ./web-platform/README.md - # packages { pf: "zig-platform" } - imports [] - provides [main] to pf - -main = "Hello, World!\n" diff --git a/examples/hello-world/main.roc b/examples/hello-world/main.roc new file mode 100644 index 0000000000..18c447a3bc --- /dev/null +++ b/examples/hello-world/main.roc @@ -0,0 +1,6 @@ +app "hello" + packages { pf: "c-platform" } + imports [] + provides [main] to pf + +main = "Hello, World!\n" diff --git a/highlight/tests/peg_grammar.rs b/highlight/tests/peg_grammar.rs index f8e052f22f..cb73e34d61 100644 --- a/highlight/tests/peg_grammar.rs +++ b/highlight/tests/peg_grammar.rs @@ -732,7 +732,7 @@ test1 = #[test] fn test_hello() { - let tokens = tokenize(&example_path("hello-world/helloWorld.roc")); + let tokens = tokenize(&example_path("hello-world/main.roc")); assert_eq!(tokenparser::module(&tokens), Ok(())); } @@ -965,7 +965,7 @@ test1 = r#"5 |> fun - + "#, ); @@ -1028,7 +1028,7 @@ test1 = fn test_deeper_pizza() { let tokens = tokenize( r#"5 - |> fun a + |> fun a |> fun b"#, ); @@ -1039,7 +1039,7 @@ test1 = fn test_deeper_indented_pizza_a() { let tokens = tokenize( r#"5 - |> fun a + |> fun a |> fun b"#, ); @@ -1050,7 +1050,7 @@ test1 = fn test_deeper_indented_pizza_b() { let tokens = tokenize( r#"5 - |> fun a + |> fun a |> fun b "#, ); @@ -1225,7 +1225,7 @@ test1 = e = mkExpr n 1 # comment unoptimized = eval e optimized = eval (constFolding (reassoc e)) - + optimized"#, ); @@ -1310,7 +1310,7 @@ balance = \color -> Node Red Node nColor -> - when key is + when key is GT -> balance nColor"#, ); @@ -1390,7 +1390,7 @@ balance = \color -> let tokens = tokenize( r#"with = \path -> handle <- withOpen - + 4"#, ); From 8295b7cbf9d71e5a5233ab3d2ca4c51c6051c3ea Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 15 Jun 2022 10:33:03 -0400 Subject: [PATCH 10/53] Update examples/README --- examples/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/README.md b/examples/README.md index 9399ba7fed..875b089e76 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,20 +1,20 @@ # Examples -Took a look around in this folder; `examples/benchmarks/` contains some larger examples. - Run examples as follows: -1. Navigate to the examples directory +1. Navigate to this `examples` folder ```bash cd examples ``` -2. Run "Hello, World!" example +2. Run a particualr example, such as Hello World: ```bash - cargo run hello-world/helloWorld.roc + roc run hello-world/main.roc ``` +`examples/benchmarks/` contains some larger examples. + Some examples like `examples/benchmarks/NQueens.roc` require input after running. For NQueens, input 10 in the terminal and press enter. From 8ba3345311a4ed9767eef6675c3ce8e3de931b93 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 15 Jun 2022 11:14:53 -0400 Subject: [PATCH 11/53] fix typo --- examples/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 875b089e76..7097cb5397 100644 --- a/examples/README.md +++ b/examples/README.md @@ -8,7 +8,7 @@ Run examples as follows: cd examples ``` -2. Run a particualr example, such as Hello World: +2. Run a particular example, such as Hello World: ```bash roc run hello-world/main.roc From 4fbf0e8b934d30a0e998bc44ff3f860ab7572d7e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 15 Jun 2022 12:03:15 -0400 Subject: [PATCH 12/53] Pass "--" in CLI tests when needed --- cli/tests/cli_run.rs | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 0b78d0a857..95e451024d 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -94,21 +94,23 @@ mod cli_run { file: &'a Path, args: I, stdin: &[&str], - input_file: Option, + opt_input_file: Option, ) -> Out { - let compile_out = match input_file { - Some(input_file) => run_roc( + let compile_out = if let Some(input_file) = opt_input_file { + run_roc( // converting these all to String avoids lifetime issues args.into_iter().map(|arg| arg.to_string()).chain([ file.to_str().unwrap().to_string(), + "--".to_string(), input_file.to_str().unwrap().to_string(), ]), stdin, - ), - None => run_roc( + ) + } else { + run_roc( args.into_iter().chain(iter::once(file.to_str().unwrap())), stdin, - ), + ) }; // If there is any stderr, it should be reporting the runtime and that's it! @@ -131,7 +133,7 @@ mod cli_run { stdin: &[&str], executable_filename: &str, flags: &[&str], - input_file: Option, + opt_input_file: Option, expected_ending: &str, use_valgrind: bool, ) { @@ -151,7 +153,7 @@ mod cli_run { run_roc_on(file, iter::once(CMD_BUILD).chain(flags.clone()), &[], None); if use_valgrind && ALLOW_VALGRIND { - let (valgrind_out, raw_xml) = if let Some(ref input_file) = input_file { + let (valgrind_out, raw_xml) = if let Some(ref input_file) = opt_input_file { run_with_valgrind( stdin.iter().copied(), &[ @@ -201,7 +203,7 @@ mod cli_run { } valgrind_out - } else if let Some(ref input_file) = input_file { + } else if let Some(ref input_file) = opt_input_file { run_cmd( file.with_file_name(executable_filename).to_str().unwrap(), stdin.iter().copied(), @@ -215,12 +217,12 @@ mod cli_run { ) } } - CliMode::Roc => run_roc_on(file, flags.clone(), stdin, input_file.clone()), + CliMode::Roc => run_roc_on(file, flags.clone(), stdin, opt_input_file.clone()), CliMode::RocRun => run_roc_on( file, iter::once(CMD_RUN).chain(flags.clone()), stdin, - input_file.clone(), + opt_input_file.clone(), ), }; From 52dc59383bd6b1178d3dc4741fb47546a7341c8a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 15 Jun 2022 12:08:02 -0400 Subject: [PATCH 13/53] Update Cargo.lock on examples --- examples/breakout/platform/Cargo.lock | 1 + examples/false-interpreter/platform/Cargo.lock | 7 +++++++ examples/gui/platform/Cargo.lock | 1 + examples/hello-world/rust-platform/Cargo.lock | 7 +++++++ examples/hello-world/web-platform/README.md | 2 +- 5 files changed, 17 insertions(+), 1 deletion(-) diff --git a/examples/breakout/platform/Cargo.lock b/examples/breakout/platform/Cargo.lock index 8f8005c26d..a1a31eae48 100644 --- a/examples/breakout/platform/Cargo.lock +++ b/examples/breakout/platform/Cargo.lock @@ -2091,6 +2091,7 @@ checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" name = "roc_std" version = "0.1.0" dependencies = [ + "arrayvec", "static_assertions 0.1.1", ] diff --git a/examples/false-interpreter/platform/Cargo.lock b/examples/false-interpreter/platform/Cargo.lock index 9132862791..1622b303ac 100644 --- a/examples/false-interpreter/platform/Cargo.lock +++ b/examples/false-interpreter/platform/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "host" version = "0.1.0" @@ -20,6 +26,7 @@ checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5" name = "roc_std" version = "0.1.0" dependencies = [ + "arrayvec", "static_assertions", ] diff --git a/examples/gui/platform/Cargo.lock b/examples/gui/platform/Cargo.lock index 8f8005c26d..a1a31eae48 100644 --- a/examples/gui/platform/Cargo.lock +++ b/examples/gui/platform/Cargo.lock @@ -2091,6 +2091,7 @@ checksum = "f1382d1f0a252c4bf97dc20d979a2fdd05b024acd7c2ed0f7595d7817666a157" name = "roc_std" version = "0.1.0" dependencies = [ + "arrayvec", "static_assertions 0.1.1", ] diff --git a/examples/hello-world/rust-platform/Cargo.lock b/examples/hello-world/rust-platform/Cargo.lock index ebd0a436cf..c6fa4fd219 100644 --- a/examples/hello-world/rust-platform/Cargo.lock +++ b/examples/hello-world/rust-platform/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "host" version = "0.1.0" @@ -20,6 +26,7 @@ checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714" name = "roc_std" version = "0.1.0" dependencies = [ + "arrayvec", "static_assertions", ] diff --git a/examples/hello-world/web-platform/README.md b/examples/hello-world/web-platform/README.md index 9708431198..8ccf22ae00 100644 --- a/examples/hello-world/web-platform/README.md +++ b/examples/hello-world/web-platform/README.md @@ -7,7 +7,7 @@ To run this website, first compile either of these identical apps: cargo run -- build --target=wasm32 examples/hello-world/web-platform/helloWeb.roc # Option B: Compile helloWorld.roc with `pf: "web-platform"` and move the result -cargo run -- build --target=wasm32 examples/hello-world/helloWorld.roc +cargo run -- build --target=wasm32 examples/hello-world/main.roc (cd examples/hello-world && mv helloWorld.wasm web-platform/helloWeb.wasm) ``` From cfcfde87d65f8a2835026ad1309bbe130080407d Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 08:40:53 -0400 Subject: [PATCH 14/53] Correct incomplete comment --- compiler/solve/src/solve.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 58550bb4ed..d9d6d81356 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1873,7 +1873,7 @@ fn compact_lambda_set( // Default has default : {} -> a | a has Default // // {a, b} = default {} - // # ^^^^^^^ {} -[{a: t1, b: t2}:default:1] + // # ^^^^^^^ {} -[{a: t1, b: t2}:default:1]-> {a: t1, b: t2} new_unspecialized.push(uls); continue; } From 86b34a60086a8f9b71972bf27b630ddc41e56534 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 08:41:10 -0400 Subject: [PATCH 15/53] Lift outcome when unifying record fields --- compiler/unify/src/unify.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 65b5571bb5..424e6de22c 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -1130,6 +1130,8 @@ fn unify_shared_fields( let mut matching_fields = Vec::with_capacity(shared_fields.len()); let num_shared_fields = shared_fields.len(); + let mut whole_outcome = Outcome::default(); + for (name, (actual, expected)) in shared_fields { let local_outcome = unify_pool( subs, @@ -1163,6 +1165,7 @@ fn unify_shared_fields( }; matching_fields.push((name, actual)); + whole_outcome.union(local_outcome); } } @@ -1211,7 +1214,9 @@ fn unify_shared_fields( let flat_type = FlatType::Record(fields, new_ext_var); - merge(subs, ctx, Structure(flat_type)) + let merge_outcome = merge(subs, ctx, Structure(flat_type)); + whole_outcome.union(merge_outcome); + whole_outcome } else { mismatch!("in unify_shared_fields") } From 7282fbc6cd59765beb44ddae8ea5c7f0aaa299fb Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 09:02:45 -0400 Subject: [PATCH 16/53] Add record and tag encoding --- compiler/builtins/roc/Encode.roc | 4 +++ compiler/builtins/roc/Json.roc | 53 ++++++++++++++++++++++++++++++++ compiler/module/src/symbol.rs | 10 +++--- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/compiler/builtins/roc/Encode.roc b/compiler/builtins/roc/Encode.roc index 0f6ea9e80a..c180229368 100644 --- a/compiler/builtins/roc/Encode.roc +++ b/compiler/builtins/roc/Encode.roc @@ -21,6 +21,8 @@ interface Encode bool, string, list, + record, + tag, custom, appendWith, append, @@ -51,6 +53,8 @@ EncoderFormatting has bool : Bool -> Encoder fmt | fmt has EncoderFormatting string : Str -> Encoder fmt | fmt has EncoderFormatting list : List elem, (elem -> Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting + record : List { key : Str, value : Encoder fmt } -> Encoder fmt | fmt has EncoderFormatting + tag : Str, List (Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting custom : (List U8, fmt -> List U8) -> Encoder fmt | fmt has EncoderFormatting custom = \encoder -> @Encoder encoder diff --git a/compiler/builtins/roc/Json.roc b/compiler/builtins/roc/Json.roc index a8f7059de1..917f682c04 100644 --- a/compiler/builtins/roc/Json.roc +++ b/compiler/builtins/roc/Json.roc @@ -7,6 +7,7 @@ interface Json imports [ Encode.{ + Encoder, custom, appendWith, u8, @@ -25,6 +26,8 @@ interface Json bool, string, list, + record, + tag, }, ] @@ -81,3 +84,53 @@ list = \lst, encodeElem -> withList = List.walk lst head (\bytes1, elem -> appendWith bytes1 (encodeElem elem) (@Json {})) List.append withList (Num.toU8 ']') + +record = \fields -> + custom \bytes, @Json {} -> + writeRecord = \{ buffer, fieldsLeft }, { key, value } -> + bufferWithKeyValue = + List.append buffer (Num.toU8 '"') + |> List.concat (Str.toUtf8 key) + |> List.append (Num.toU8 '"') + |> List.append (Num.toU8 ':') + |> appendWith value (@Json {}) + + bufferWithSuffix = + if fieldsLeft > 0 then + List.append bufferWithKeyValue (Num.toU8 ',') + else + bufferWithKeyValue + + { buffer: bufferWithSuffix, fieldsLeft: fieldsLeft - 1 } + + bytesHead = List.append bytes (Num.toU8 '{') + { buffer: bytesWithRecord } = List.walk fields { buffer: bytesHead, fieldsLeft: List.len fields } writeRecord + + List.append bytesWithRecord (Num.toU8 '}') + +tag = \name, payload -> + custom \bytes, @Json {} -> + # Idea: encode `A v1 v2` as `{"A": [v1, v2]}` + + writePayload = \{buffer, itemsLeft}, encoder -> + bufferWithValue = appendWith buffer encoder (@Json {}) + bufferWithSuffix = + if itemsLeft > 0 then + List.append bufferWithValue (Num.toU8 ',') + else + bufferWithValue + + {buffer: bufferWithSuffix, itemsLeft: itemsLeft - 1} + + bytesHead = + List.append bytes (Num.toU8 '{') + |> List.append (Num.toU8 '"') + |> List.concat (Str.toUtf8 name) + |> List.append (Num.toU8 '"') + |> List.append (Num.toU8 ':') + |> List.append (Num.toU8 '[') + + { buffer: bytesWithPayload } = List.walk payload { buffer: bytesHead, itemsLeft: List.len payload } writePayload + + List.append bytesWithPayload (Num.toU8 ']') + |> List.append (Num.toU8 '}') diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 6891cff969..2ecb743155 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1321,10 +1321,12 @@ define_builtins! { 17 ENCODE_BOOL: "bool" 18 ENCODE_STRING: "string" 19 ENCODE_LIST: "list" - 20 ENCODE_CUSTOM: "custom" - 21 ENCODE_APPEND_WITH: "appendWith" - 22 ENCODE_APPEND: "append" - 23 ENCODE_TO_BYTES: "toBytes" + 20 ENCODE_RECORD: "record" + 21 ENCODE_TAG: "tag" + 22 ENCODE_CUSTOM: "custom" + 23 ENCODE_APPEND_WITH: "appendWith" + 24 ENCODE_APPEND: "append" + 25 ENCODE_TO_BYTES: "toBytes" } 10 JSON: "Json" => { 0 JSON_JSON: "Json" From 8062b44d11aa7d702272af5eafe5d2f548c12084 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 11:55:45 -0400 Subject: [PATCH 17/53] Add list of lambdas type test --- compiler/solve/tests/solve_expr.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index e5630ebcc1..a0daa3749e 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -6817,4 +6817,17 @@ mod solve_expr { ], ) } + + #[test] + fn list_of_lambdas() { + infer_queries!( + indoc!( + r#" + [\{} -> {}, \{} -> {}] + #^^^^^^^^^^^^^^^^^^^^^^{-1} + "# + ), + &[r#"[\{} -> {}, \{} -> {}] : List ({}* -[[1(1), 2(2)]]-> {})"#], + ) + } } From f1fa29fcfc90c688be025a67df4ccdda634009f9 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 11:56:01 -0400 Subject: [PATCH 18/53] First pass at derived record encoder --- compiler/mono/src/derivers/encoding.rs | 353 +++++++++++++++++++++++++ compiler/mono/src/derivers/mod.rs | 3 + compiler/mono/src/lib.rs | 1 + compiler/types/src/subs.rs | 20 ++ 4 files changed, 377 insertions(+) create mode 100644 compiler/mono/src/derivers/encoding.rs create mode 100644 compiler/mono/src/derivers/mod.rs diff --git a/compiler/mono/src/derivers/encoding.rs b/compiler/mono/src/derivers/encoding.rs new file mode 100644 index 0000000000..0aee9e786a --- /dev/null +++ b/compiler/mono/src/derivers/encoding.rs @@ -0,0 +1,353 @@ +//! Derivers for the `Encoding` ability. + +use std::iter::once; + +use bumpalo::Bump; + +use roc_can::abilities::AbilitiesStore; +use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Field, Recursive}; +use roc_can::pattern::Pattern; +use roc_collections::SendMap; +use roc_error_macros::internal_error; +use roc_module::called_via::CalledVia; +use roc_module::symbol::{IdentIds, ModuleId, Symbol}; +use roc_region::all::{Loc, Region}; +use roc_types::subs::{ + Content, Descriptor, ExposedTypesStorageSubs, FlatType, GetSubsSlice, LambdaSet, Mark, + OptVariable, Rank, RecordFields, Subs, SubsFmtContent, SubsSlice, UnionLambdas, Variable, + VariableSubsSlice, +}; +use roc_types::types::{AliasKind, RecordField, RecordFieldsError}; + +macro_rules! bad_input { + ($env:expr, $var:expr) => { + bad_input!($env, $var, "Invalid content") + }; + ($env:expr, $var:expr, $msg:expr) => { + internal_error!( + "{:?} for toEncoder deriver: {:?}", + $msg, + SubsFmtContent($env.subs.get_content_without_compacting($var), $env.subs) + ) + }; +} + +pub struct Env<'a> { + pub home: ModuleId, + pub arena: &'a Bump, + pub subs: &'a mut Subs, + pub ident_ids: &'a mut IdentIds, + pub exposed_encode_types: &'a mut ExposedTypesStorageSubs, +} + +impl Env<'_> { + fn unique_symbol(&mut self) -> Symbol { + let ident_id = self.ident_ids.gen_unique(); + + Symbol::new(self.home, ident_id) + } + + fn import_encode_symbol(&mut self, symbol: Symbol) -> Variable { + debug_assert_eq!(symbol.module_id(), ModuleId::ENCODE); + + let storage_var = self + .exposed_encode_types + .stored_vars_by_symbol + .get(&symbol) + .unwrap(); + + let imported = self + .exposed_encode_types + .storage_subs + .export_variable_to(self.subs, *storage_var); + + imported.variable + } + + fn unify(&mut self, left: Variable, right: Variable) { + // NOTE: I don't believe the abilities store is necessary for unification at this point! + roc_late_solve::unify( + self.arena, + self.subs, + &AbilitiesStore::default(), + left, + right, + ) + .expect("unification failed!") + } +} + +#[allow(dead_code)] +pub fn derive_to_encoder(env: &mut Env<'_>, signature: Variable) { + // Verify the signature is what we expect: input -> Encoder fmt | fmt has EncoderFormatting + // and get the input type + let input = match env.subs.get_content_without_compacting(signature) { + Content::Structure(FlatType::Func(input, _, output)) => { + // Check the output is Encoder fmt | fmt has EncoderFormatting + match env.subs.get_content_without_compacting(*output) { + Content::Alias(Symbol::ENCODE_ENCODER, args, _, AliasKind::Opaque) => { + match env.subs.get_subs_slice(args.all_variables()) { + [one] => match env.subs.get_content_without_compacting(*one) { + Content::FlexAbleVar(_, Symbol::ENCODE_ENCODERFORMATTING) => {} + _ => bad_input!(env, signature), + }, + _ => bad_input!(env, signature), + } + } + _ => bad_input!(env, signature), + } + + // Get the only parameter into toEncoder + match env.subs.get_subs_slice(*input) { + [one] => *one, + _ => bad_input!(env, signature), + } + } + _ => bad_input!(env, signature), + }; + + to_encoder_from_var(env, input) +} + +fn to_encoder_from_var(env: &mut Env<'_>, mut var: Variable) { + loop { + match *env.subs.get_content_without_compacting(var) { + Content::Alias(_, _, real_var, _) => var = real_var, + Content::RangedNumber(real_var, _) => var = real_var, + + Content::RecursionVar { .. } => todo!(), + Content::LambdaSet(_) => todo!(), + Content::Structure(flat_type) => match flat_type { + FlatType::Record(fields, ext_var) => to_encoder_record(env, var, fields, ext_var), + + FlatType::Apply(_, _) => todo!(), + FlatType::TagUnion(_, _) => todo!(), + FlatType::FunctionOrTagUnion(_, _, _) => todo!(), + FlatType::RecursiveTagUnion(_, _, _) => todo!(), + FlatType::EmptyRecord => todo!(), + FlatType::EmptyTagUnion => todo!(), + + FlatType::Func(..) => bad_input!(env, var, "functions cannot be encoded"), + FlatType::Erroneous(_) => bad_input!(env, var), + }, + + Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) => bad_input!(env, var, "unresolved variable"), + Content::Error => bad_input!(env, var), + } + } +} + +fn synth_var(subs: &mut Subs, content: Content) -> Variable { + let descriptor = Descriptor { + content, + // NOTE: this is incorrect, but that is irrelevant - derivers may only be called during + // monomorphization (or later), at which point we do not care about variable + // generalization. Hence ranks should not matter. + rank: Rank::toplevel(), + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + subs.fresh(descriptor) +} + +fn to_encoder_record( + env: &mut Env<'_>, + record_var: Variable, + fields: RecordFields, + ext_var: Variable, +) { + // Suppose rcd = { a: t1, b: t2 }. Build + // + // \rcd -> Encode.record [ + // { key: "a", value: Encode.toEncoder rcd.a }, + // { key: "b", value: Encode.toEncoder rcd.b }, + // ] + let rcd_sym = env.unique_symbol(); + let whole_rcd_var = env.subs.fresh_unnamed_flex_var(); // type of the { key, value } records in the list + + let fields_it = match fields.unsorted_iterator(env.subs, ext_var) { + Ok(it) => it, + Err(RecordFieldsError) => bad_input!(env, ext_var, "extension var for record is unbound"), + } + .map(|(name, field)| (name.clone(), field)) + .collect::>(); + + use Expr::*; + + let fields_list = fields_it + .into_iter() + .map(|(field_name, field)| { + // key: "a" + let key_field = Field { + var: Variable::STR, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(Str(field_name.as_str().into()))), + }; + + let field_var = *field.as_inner(); + + // rcd.a + let field_access = Access { + record_var, + ext_var, + field_var, + loc_expr: Box::new(Loc::at_zero(Var(rcd_sym))), + field: field_name, + }; + + // build `toEncoder rcd.a` type + // val -[uls]-> Encoder fmt | fmt has EncoderFormatting + let to_encoder_fn_var = env.import_encode_symbol(Symbol::ENCODE_TO_ENCODER); + + // (typeof rcd.a) -[clos]-> t1 + // TODO: we don't have to re-add the field var as a subs slice if we iterate over the + // original record fields' variable slice. + let field_var_slice = VariableSubsSlice::insert_into_subs(env.subs, once(field_var)); + let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos + let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1 + let this_to_encoder_fn_var = synth_var( + env.subs, + Content::Structure(FlatType::Func( + field_var_slice, + to_encoder_clos_var, + encoder_var, + )), + ); + + // val -[uls]-> Encoder fmt | fmt has EncoderFormatting + // ~ (typeof rcd.a) -[clos]-> t1 + env.unify(to_encoder_fn_var, this_to_encoder_fn_var); + + // toEncoder : (typeof rcd.a) -[clos]-> Encoder fmt | fmt has EncoderFormatting + let to_encoder_fn = Box::new(( + to_encoder_fn_var, + Loc::at_zero(Var(Symbol::ENCODE_TO_ENCODER)), + to_encoder_clos_var, + encoder_var, + )); + + // toEncoder rcd.a + let to_encoder_call = Call( + to_encoder_fn, + vec![(field_var, Loc::at_zero(field_access))], + CalledVia::Space, + ); + + // value: toEncoder rcd.a + let value_field = Field { + var: encoder_var, + region: Region::zero(), + loc_expr: Box::new(Loc::at_zero(to_encoder_call)), + }; + + // { key: "a", value: toEncoder rcd.a } + let mut kv = SendMap::default(); + kv.insert("key".into(), key_field); + kv.insert("value".into(), value_field); + + let this_record_fields = RecordFields::insert_into_subs( + env.subs, + (once(("key".into(), RecordField::Required(Variable::STR)))) + .chain(once(("value".into(), RecordField::Required(encoder_var)))), + ); + let this_record_var = synth_var( + env.subs, + Content::Structure(FlatType::Record(this_record_fields, Variable::EMPTY_RECORD)), + ); + // NOTE: must be done to unify the lambda sets under `encoder_var` + env.unify(this_record_var, whole_rcd_var); + + Loc::at_zero(Record { + record_var: whole_rcd_var, + fields: kv, + }) + }) + .collect::>(); + + // typeof [ { key: .., value: .. }, { key: .., value: .. } ] + let fields_rcd_var_slice = VariableSubsSlice::insert_into_subs(env.subs, once(whole_rcd_var)); + let fields_list_var = synth_var( + env.subs, + Content::Structure(FlatType::Apply(Symbol::LIST_LIST, fields_rcd_var_slice)), + ); + + // [ { key: .., value: ..}, .. ] + let fields_list = List { + elem_var: fields_list_var, + loc_elems: fields_list, + }; + + // build `Encode.record [ { key: .., value: ..}, .. ]` type + // List { key : Str, value : Encoder fmt } -[uls]-> Encoder fmt | fmt has EncoderFormatting + let encode_record_fn_var = env.import_encode_symbol(Symbol::ENCODE_RECORD); + + // fields_list_var -[clos]-> t1 + let fields_list_var_slice = + VariableSubsSlice::insert_into_subs(env.subs, once(fields_list_var)); + let encode_record_clos_var = env.subs.fresh_unnamed_flex_var(); // clos + let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1 + let this_encode_record_fn_var = synth_var( + env.subs, + Content::Structure(FlatType::Func( + fields_list_var_slice, + encode_record_clos_var, + encoder_var, + )), + ); + + // List { key : Str, value : Encoder fmt } -[uls]-> Encoder fmt | fmt has EncoderFormatting + // ~ fields_list_var -[clos]-> t1 + env.unify(encode_record_fn_var, this_encode_record_fn_var); + + // Encode.record : fields_list_var -[clos]-> Encoder fmt | fmt has EncoderFormatting + let encode_record_fn = Box::new(( + encode_record_fn_var, + Loc::at_zero(Var(Symbol::ENCODE_TO_ENCODER)), + encode_record_clos_var, + encoder_var, + )); + + // Encode.record [ { key: .., value: .. }, .. ] + let encode_record_call = Call( + encode_record_fn, + vec![(fields_list_var, Loc::at_zero(fields_list))], + CalledVia::Space, + ); + + let fn_name = env.unique_symbol(); + let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, vec![]))); + // -[fn_name]-> + let fn_clos_var = synth_var( + env.subs, + Content::LambdaSet(LambdaSet { + solved: fn_name_labels, + recursion_var: OptVariable::NONE, + unspecialized: SubsSlice::default(), + }), + ); + // typeof rcd -[fn_name]-> (typeof Encode.record [ .. ] = Encoder fmt) + let record_var_slice = SubsSlice::insert_into_subs(env.subs, once(record_var)); + let fn_var = synth_var( + env.subs, + Content::Structure(FlatType::Func(record_var_slice, fn_clos_var, encoder_var)), + ); + + // \rcd -[fn_name]-> Encode.record [ { key: .., value: .. }, .. ] + Closure(ClosureData { + function_type: fn_var, + closure_type: fn_clos_var, + return_type: encoder_var, + name: fn_name, + captured_symbols: vec![], + recursive: Recursive::NotRecursive, + arguments: vec![( + record_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(rcd_sym)), + )], + loc_body: Box::new(Loc::at_zero(encode_record_call)), + }); +} diff --git a/compiler/mono/src/derivers/mod.rs b/compiler/mono/src/derivers/mod.rs new file mode 100644 index 0000000000..d077c5b638 --- /dev/null +++ b/compiler/mono/src/derivers/mod.rs @@ -0,0 +1,3 @@ +//! Auto-derivers of builtin ability methods. + +pub mod encoding; diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index aa8dfba4c8..e46d8f4be7 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -5,6 +5,7 @@ pub mod borrow; pub mod code_gen_help; mod copy; +mod derivers; pub mod inc_dec; pub mod ir; pub mod layout; diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index cafc5f02e3..3f12d3194b 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1282,6 +1282,10 @@ define_const_var! { :pub F64, :pub DEC, + + // The following are abound in derived abilities, so we cache them. + :pub STR, + :pub LIST_U8, } impl Variable { @@ -1323,6 +1327,8 @@ impl Variable { Symbol::NUM_DEC => Some(Variable::DEC), + Symbol::STR_STR => Some(Variable::STR), + _ => None, } } @@ -1699,6 +1705,20 @@ impl Subs { ) }); + subs.set_content(Variable::STR, { + Content::Structure(FlatType::Apply( + Symbol::STR_STR, + VariableSubsSlice::default(), + )) + }); + + let u8_slice = + VariableSubsSlice::insert_into_subs(&mut subs, std::iter::once(Variable::U8)); + subs.set_content( + Variable::LIST_U8, + Content::Structure(FlatType::Apply(Symbol::LIST_LIST, u8_slice)), + ); + subs } From ccd78a560fb76ccd4a45f90f953091d874dd1fb6 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 15:12:40 -0400 Subject: [PATCH 19/53] Add test module for derivers --- Cargo.lock | 22 +++ Cargo.toml | 1 + compiler/can/src/abilities.rs | 134 ++++++++--------- compiler/load_internal/src/file.rs | 13 +- compiler/test_derivers/Cargo.toml | 28 ++++ compiler/test_derivers/src/encoding.rs | 198 +++++++++++++++++++++++++ compiler/test_derivers/src/tests.rs | 3 + 7 files changed, 329 insertions(+), 70 deletions(-) create mode 100644 compiler/test_derivers/Cargo.toml create mode 100644 compiler/test_derivers/src/encoding.rs create mode 100644 compiler/test_derivers/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index 407db63a00..a1247c17c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4797,6 +4797,28 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test_derivers" +version = "0.1.0" +dependencies = [ + "bumpalo", + "indoc", + "lazy_static", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_constrain", + "roc_load_internal", + "roc_module", + "roc_mono", + "roc_region", + "roc_reporting", + "roc_solve", + "roc_target", + "roc_types", + "ven_pretty", +] + [[package]] name = "test_gen" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index c104f5ddb4..8559892118 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ members = [ "compiler/mono", "compiler/alias_analysis", "compiler/test_mono", + "compiler/test_derivers", "compiler/load", "compiler/load_internal", "compiler/gen_llvm", diff --git a/compiler/can/src/abilities.rs b/compiler/can/src/abilities.rs index 28cccfedfa..464527f9e4 100644 --- a/compiler/can/src/abilities.rs +++ b/compiler/can/src/abilities.rs @@ -220,6 +220,73 @@ impl IAbilitiesStore { self.next_specialization_id += 1; id } + + /// Creates a store from [`self`] that closes over the abilities/members given by the + /// imported `symbols`, and their specializations (if any). + pub fn closure_from_imported(&self, symbols: &VecSet) -> PendingAbilitiesStore { + let Self { + members_of_ability, + ability_members, + declared_specializations, + + // Covered by `declared_specializations` + specialization_to_root: _, + + // Taking closure for a new module, so specialization IDs can be fresh + next_specialization_id: _, + resolved_specializations: _, + } = self; + + let mut new = PendingAbilitiesStore::default(); + + // 1. Figure out the abilities we need to introduce. + let mut abilities_to_introduce = VecSet::with_capacity(2); + symbols.iter().for_each(|symbol| { + if let Some(member_data) = ability_members.get(symbol) { + // If the symbol is member of an ability, we need to capture the entire ability. + abilities_to_introduce.insert(member_data.parent_ability); + } + if members_of_ability.contains_key(symbol) { + abilities_to_introduce.insert(*symbol); + } + }); + + // 2. Add each ability, and any specializations of its members we know about. + for ability in abilities_to_introduce.into_iter() { + let members = members_of_ability.get(&ability).unwrap(); + let mut imported_member_data = Vec::with_capacity(members.len()); + for member in members { + let AbilityMemberData { + parent_ability, + region, + typ: _, + } = ability_members.get(member).unwrap().clone(); + // All external members need to be marked as imported. We'll figure out their real + // type variables when it comes time to solve the module we're currently importing + // into. + let imported_data = AbilityMemberData { + parent_ability, + region, + typ: PendingMemberType::Imported, + }; + + imported_member_data.push((*member, imported_data)); + } + + new.register_ability(ability, imported_member_data); + + // Add any specializations of the ability's members we know about. + declared_specializations + .iter() + .filter(|((member, _), _)| members.contains(member)) + .for_each(|(&(member, typ), specialization)| { + new.register_specializing_symbol(specialization.symbol, member); + new.import_specialization(member, typ, specialization); + }); + } + + new + } } impl IAbilitiesStore { @@ -326,73 +393,6 @@ impl IAbilitiesStore { debug_assert!(old_spec.is_none(), "Replacing existing specialization"); } - /// Creates a store from [`self`] that closes over the abilities/members given by the - /// imported `symbols`, and their specializations (if any). - pub fn closure_from_imported(&self, symbols: &VecSet) -> Self { - let Self { - members_of_ability, - ability_members, - declared_specializations, - - // Covered by `declared_specializations` - specialization_to_root: _, - - // Taking closure for a new module, so specialization IDs can be fresh - next_specialization_id: _, - resolved_specializations: _, - } = self; - - let mut new = PendingAbilitiesStore::default(); - - // 1. Figure out the abilities we need to introduce. - let mut abilities_to_introduce = VecSet::with_capacity(2); - symbols.iter().for_each(|symbol| { - if let Some(member_data) = ability_members.get(symbol) { - // If the symbol is member of an ability, we need to capture the entire ability. - abilities_to_introduce.insert(member_data.parent_ability); - } - if members_of_ability.contains_key(symbol) { - abilities_to_introduce.insert(*symbol); - } - }); - - // 2. Add each ability, and any specializations of its members we know about. - for ability in abilities_to_introduce.into_iter() { - let members = members_of_ability.get(&ability).unwrap(); - let mut imported_member_data = Vec::with_capacity(members.len()); - for member in members { - let AbilityMemberData { - parent_ability, - region, - typ: _, - } = ability_members.get(member).unwrap().clone(); - // All external members need to be marked as imported. We'll figure out their real - // type variables when it comes time to solve the module we're currently importing - // into. - let imported_data = AbilityMemberData { - parent_ability, - region, - typ: PendingMemberType::Imported, - }; - - imported_member_data.push((*member, imported_data)); - } - - new.register_ability(ability, imported_member_data); - - // Add any specializations of the ability's members we know about. - declared_specializations - .iter() - .filter(|((member, _), _)| members.contains(member)) - .for_each(|(&(member, typ), specialization)| { - new.register_specializing_symbol(specialization.symbol, member); - new.import_specialization(member, typ, specialization); - }); - } - - new - } - pub fn union(&mut self, other: Self) { let Self { members_of_ability: other_members_of_ability, diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index 13bdbe0d22..d95a07a841 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -46,7 +46,7 @@ use roc_solve::module::SolvedModule; use roc_solve::solve; use roc_target::TargetInfo; use roc_types::solved_types::Solved; -use roc_types::subs::{Subs, VarStore, Variable}; +use roc_types::subs::{ExposedTypesStorageSubs, Subs, VarStore, Variable}; use roc_types::types::{Alias, AliasKind}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::HashMap; @@ -510,6 +510,7 @@ pub struct LoadedModule { pub dep_idents: IdentIdsByModule, pub exposed_aliases: MutMap, pub exposed_values: Vec, + pub exposed_types_storage: ExposedTypesStorageSubs, pub sources: MutMap)>, pub timings: MutMap, pub documentation: MutMap, @@ -687,6 +688,7 @@ enum Msg<'a> { solved_subs: Solved, exposed_vars_by_symbol: Vec<(Symbol, Variable)>, exposed_aliases_by_symbol: MutMap, + exposed_types_storage: ExposedTypesStorageSubs, dep_idents: IdentIdsByModule, documentation: MutMap, abilities_store: AbilitiesStore, @@ -1404,6 +1406,7 @@ fn state_thread_step<'a>( solved_subs, exposed_vars_by_symbol, exposed_aliases_by_symbol, + exposed_types_storage, dep_idents, documentation, abilities_store, @@ -1421,6 +1424,7 @@ fn state_thread_step<'a>( solved_subs, exposed_aliases_by_symbol, exposed_vars_by_symbol, + exposed_types_storage, dep_idents, documentation, abilities_store, @@ -2209,6 +2213,7 @@ fn update<'a>( solved_subs, exposed_vars_by_symbol: solved_module.exposed_vars_by_symbol, exposed_aliases_by_symbol: solved_module.aliases, + exposed_types_storage: solved_module.exposed_types, dep_idents, documentation, abilities_store, @@ -2663,6 +2668,7 @@ fn finish( solved: Solved, exposed_aliases_by_symbol: MutMap, exposed_vars_by_symbol: Vec<(Symbol, Variable)>, + exposed_types_storage: ExposedTypesStorageSubs, dep_idents: IdentIdsByModule, documentation: MutMap, abilities_store: AbilitiesStore, @@ -2697,6 +2703,7 @@ fn finish( exposed_aliases: exposed_aliases_by_symbol, exposed_values, exposed_to_host: exposed_vars_by_symbol.into_iter().collect(), + exposed_types_storage, sources, timings: state.timings, documentation, @@ -3738,7 +3745,7 @@ impl<'a> BuildTask<'a> { } } -fn add_imports( +pub fn add_imports( my_module: ModuleId, subs: &mut Subs, mut pending_abilities: PendingAbilitiesStore, @@ -5013,7 +5020,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin /// Generic number types (Num, Int, Float, etc.) are treated as `DelayedAlias`es resolved during /// type solving. /// All that remains are Signed8, Signed16, etc. -fn default_aliases() -> roc_solve::solve::Aliases { +pub fn default_aliases() -> roc_solve::solve::Aliases { use roc_types::types::Type; let mut solve_aliases = roc_solve::solve::Aliases::default(); diff --git a/compiler/test_derivers/Cargo.toml b/compiler/test_derivers/Cargo.toml new file mode 100644 index 0000000000..52b52143d1 --- /dev/null +++ b/compiler/test_derivers/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "test_derivers" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2021" + +[[test]] +name = "test_derives" +path = "src/tests.rs" + +[dev-dependencies] +roc_collections = { path = "../collections" } +roc_module = { path = "../module" } +roc_builtins = { path = "../builtins" } +roc_load_internal = { path = "../load_internal" } +roc_can = { path = "../can" } +roc_mono = { path = "../mono" } +roc_target = { path = "../roc_target" } +roc_types = { path = "../types" } +roc_reporting = { path = "../../reporting" } +roc_constrain = { path = "../constrain" } +roc_region = { path = "../region" } +roc_solve = { path = "../solve" } +bumpalo = { version = "3.8.0", features = ["collections"] } +lazy_static = "1.4.0" +indoc = "1.0.3" +ven_pretty = { path = "../../vendor/pretty" } diff --git a/compiler/test_derivers/src/encoding.rs b/compiler/test_derivers/src/encoding.rs new file mode 100644 index 0000000000..c6897785f2 --- /dev/null +++ b/compiler/test_derivers/src/encoding.rs @@ -0,0 +1,198 @@ +#![cfg(test)] + +use std::path::PathBuf; + +use bumpalo::Bump; +use roc_can::{ + abilities::{AbilitiesStore, ResolvedSpecializations}, + constraint::Constraints, + expected::Expected, + expr::Expr, + module::RigidVariables, +}; +use roc_collections::VecSet; +use roc_constrain::{ + expr::constrain_expr, + module::{ExposedByModule, ExposedForModule, ExposedModuleTypes}, +}; +use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading}; +use roc_module::{ + ident::ModuleName, + symbol::{IdentIds, Interns, ModuleId}, +}; +use roc_mono::derivers::encoding::Env; +use roc_region::all::{LineInfo, Region}; +use roc_reporting::report::{type_problem, RocDocAllocator}; +use roc_types::{ + subs::{Content, ExposedTypesStorageSubs, FlatType, RecordFields, Subs, Variable}, + types::{RecordField, Type}, +}; +use ven_pretty::DocAllocator; + +use roc_mono::derivers::{encoding, synth_var}; + +fn encode_path() -> PathBuf { + let repo_root = std::env::var("ROC_WORKSPACE_DIR").expect("are you running with `cargo test`?"); + PathBuf::from(repo_root) + .join("compiler") + .join("builtins") + .join("roc") + .join("Encode.roc") +} + +fn check_derived_typechecks( + derived: Expr, + test_module: ModuleId, + mut test_subs: Subs, + interns: &Interns, + exposed_encode_types: ExposedTypesStorageSubs, + encode_abilities_store: AbilitiesStore, +) { + let mut constraints = Constraints::new(); + let mut env = roc_constrain::expr::Env { + rigids: Default::default(), + resolutions_to_make: Default::default(), + home: test_module, + }; + let constr = constrain_expr( + &mut constraints, + &mut env, + Region::zero(), + &derived, + Expected::NoExpectation(Type::Variable(test_subs.fresh_unnamed_flex_var())), + ); + let encode_values_to_import = exposed_encode_types + .stored_vars_by_symbol + .keys() + .copied() + .collect::>(); + let pending_abilities = encode_abilities_store.closure_from_imported(&encode_values_to_import); + let mut exposed_by_module = ExposedByModule::default(); + exposed_by_module.insert( + ModuleId::ENCODE, + ExposedModuleTypes { + exposed_types_storage_subs: exposed_encode_types, + resolved_specializations: ResolvedSpecializations::default(), + }, + ); + let exposed_for_module = + ExposedForModule::new(encode_values_to_import.iter(), exposed_by_module); + let mut def_types = Default::default(); + let mut rigid_vars = Default::default(); + let (import_variables, abilities_store) = add_imports( + test_module, + &mut test_subs, + pending_abilities, + exposed_for_module, + &mut def_types, + &mut rigid_vars, + ); + let constr = + constraints.let_import_constraint(rigid_vars, def_types, constr, &import_variables); + + let (_solved_subs, _, problems, _) = roc_solve::module::run_solve( + &constraints, + constr, + RigidVariables::default(), + test_subs, + default_aliases(), + abilities_store, + Default::default(), + ); + + if !problems.is_empty() { + let filename = PathBuf::from("Test.roc"); + let lines = LineInfo::new(" "); + let src_lines = vec![" "]; + let mut reports = Vec::new(); + let alloc = RocDocAllocator::new(&src_lines, test_module, &interns); + + for problem in problems.into_iter() { + if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) { + reports.push(report); + } + } + + let has_reports = !reports.is_empty(); + + let doc = alloc + .stack(reports.into_iter().map(|v| v.pretty(&alloc))) + .append(if has_reports { + alloc.line() + } else { + alloc.nil() + }); + + let mut buf = String::new(); + doc.1 + .render_raw(80, &mut roc_reporting::report::CiWrite::new(&mut buf)) + .unwrap(); + + panic!("Derived does not typecheck:\n{}", buf); + } +} + +fn derive_test(synth_input: S) +where + S: FnOnce(&mut Subs) -> Variable, +{ + let arena = Bump::new(); + let source = roc_builtins::roc::module_source(ModuleId::ENCODE); + let target_info = roc_target::TargetInfo::default_x86_64(); + + let LoadedModule { + mut interns, + exposed_types_storage: mut exposed_encode_types, + abilities_store, + .. + } = roc_load_internal::file::load_and_typecheck_str( + &arena, + encode_path().file_name().unwrap().into(), + source, + &encode_path().parent().unwrap(), + Default::default(), + target_info, + roc_reporting::report::RenderTarget::ColorTerminal, + Threading::AllAvailable, + ) + .unwrap(); + + let test_module = interns.module_id(&ModuleName::from("Test")); + let mut test_subs = Subs::new(); + let mut test_ident_ids = IdentIds::default(); + + let mut env = Env { + home: test_module, + arena: &arena, + subs: &mut test_subs, + ident_ids: &mut test_ident_ids, + exposed_encode_types: &mut exposed_encode_types, + }; + + let signature_var = synth_input(env.subs); + + let derived = encoding::derive_to_encoder(&mut env, signature_var); + + check_derived_typechecks( + derived, + test_module, + test_subs, + &interns, + exposed_encode_types, + abilities_store, + ); +} + +macro_rules! synth { + (rcd{ $($field:literal: $typ:expr),* }) => { + |subs| { + let fields = RecordFields::insert_into_subs(subs, vec![ $( ($field.into(), RecordField::Required($typ)) ,)* ]); + synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD))) + } + } +} + +#[test] +fn one_field_record() { + derive_test(synth!(rcd{ "a": Variable::U8 })) +} diff --git a/compiler/test_derivers/src/tests.rs b/compiler/test_derivers/src/tests.rs new file mode 100644 index 0000000000..bd5082c3df --- /dev/null +++ b/compiler/test_derivers/src/tests.rs @@ -0,0 +1,3 @@ +#![cfg(test)] + +mod encoding; From 5c64e62d892534dffe82d3cd3b366e27e8806713 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 15:15:48 -0400 Subject: [PATCH 20/53] Correct encoding deriver for records --- compiler/mono/src/derivers/encoding.rs | 49 ++++++++++++-------------- compiler/mono/src/derivers/mod.rs | 15 ++++++++ compiler/mono/src/lib.rs | 2 +- compiler/solve/src/ability.rs | 21 +++++++++++ compiler/types/src/subs.rs | 2 +- 5 files changed, 61 insertions(+), 28 deletions(-) diff --git a/compiler/mono/src/derivers/encoding.rs b/compiler/mono/src/derivers/encoding.rs index 0aee9e786a..102b99d4e3 100644 --- a/compiler/mono/src/derivers/encoding.rs +++ b/compiler/mono/src/derivers/encoding.rs @@ -9,16 +9,18 @@ use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Field, Recursive}; use roc_can::pattern::Pattern; use roc_collections::SendMap; use roc_error_macros::internal_error; +use roc_late_solve::instantiate_rigids; use roc_module::called_via::CalledVia; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_region::all::{Loc, Region}; use roc_types::subs::{ - Content, Descriptor, ExposedTypesStorageSubs, FlatType, GetSubsSlice, LambdaSet, Mark, - OptVariable, Rank, RecordFields, Subs, SubsFmtContent, SubsSlice, UnionLambdas, Variable, - VariableSubsSlice, + Content, ExposedTypesStorageSubs, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields, + Subs, SubsFmtContent, SubsSlice, UnionLambdas, Variable, VariableSubsSlice, }; use roc_types::types::{AliasKind, RecordField, RecordFieldsError}; +use crate::derivers::synth_var; + macro_rules! bad_input { ($env:expr, $var:expr) => { bad_input!($env, $var, "Invalid content") @@ -61,6 +63,8 @@ impl Env<'_> { .storage_subs .export_variable_to(self.subs, *storage_var); + instantiate_rigids(self.subs, imported.variable); + imported.variable } @@ -77,11 +81,13 @@ impl Env<'_> { } } +// TODO: decide whether it will be better to pass the whole signature, or just the argument type. +// For now we are only using the argument type for convinience of testing. #[allow(dead_code)] -pub fn derive_to_encoder(env: &mut Env<'_>, signature: Variable) { +fn verify_signature(env: &mut Env<'_>, signature: Variable) { // Verify the signature is what we expect: input -> Encoder fmt | fmt has EncoderFormatting // and get the input type - let input = match env.subs.get_content_without_compacting(signature) { + match env.subs.get_content_without_compacting(signature) { Content::Structure(FlatType::Func(input, _, output)) => { // Check the output is Encoder fmt | fmt has EncoderFormatting match env.subs.get_content_without_compacting(*output) { @@ -105,11 +111,13 @@ pub fn derive_to_encoder(env: &mut Env<'_>, signature: Variable) { } _ => bad_input!(env, signature), }; - - to_encoder_from_var(env, input) } -fn to_encoder_from_var(env: &mut Env<'_>, mut var: Variable) { +pub fn derive_to_encoder(env: &mut Env<'_>, for_var: Variable) -> Expr { + to_encoder_from_var(env, for_var) +} + +fn to_encoder_from_var(env: &mut Env<'_>, mut var: Variable) -> Expr { loop { match *env.subs.get_content_without_compacting(var) { Content::Alias(_, _, real_var, _) => var = real_var, @@ -118,7 +126,9 @@ fn to_encoder_from_var(env: &mut Env<'_>, mut var: Variable) { Content::RecursionVar { .. } => todo!(), Content::LambdaSet(_) => todo!(), Content::Structure(flat_type) => match flat_type { - FlatType::Record(fields, ext_var) => to_encoder_record(env, var, fields, ext_var), + FlatType::Record(fields, ext_var) => { + return to_encoder_record(env, var, fields, ext_var) + } FlatType::Apply(_, _) => todo!(), FlatType::TagUnion(_, _) => todo!(), @@ -140,25 +150,12 @@ fn to_encoder_from_var(env: &mut Env<'_>, mut var: Variable) { } } -fn synth_var(subs: &mut Subs, content: Content) -> Variable { - let descriptor = Descriptor { - content, - // NOTE: this is incorrect, but that is irrelevant - derivers may only be called during - // monomorphization (or later), at which point we do not care about variable - // generalization. Hence ranks should not matter. - rank: Rank::toplevel(), - mark: Mark::NONE, - copy: OptVariable::NONE, - }; - subs.fresh(descriptor) -} - fn to_encoder_record( env: &mut Env<'_>, record_var: Variable, fields: RecordFields, ext_var: Variable, -) { +) -> Expr { // Suppose rcd = { a: t1, b: t2 }. Build // // \rcd -> Encode.record [ @@ -276,7 +273,7 @@ fn to_encoder_record( // [ { key: .., value: ..}, .. ] let fields_list = List { - elem_var: fields_list_var, + elem_var: whole_rcd_var, loc_elems: fields_list, }; @@ -305,7 +302,7 @@ fn to_encoder_record( // Encode.record : fields_list_var -[clos]-> Encoder fmt | fmt has EncoderFormatting let encode_record_fn = Box::new(( encode_record_fn_var, - Loc::at_zero(Var(Symbol::ENCODE_TO_ENCODER)), + Loc::at_zero(Var(Symbol::ENCODE_RECORD)), encode_record_clos_var, encoder_var, )); @@ -349,5 +346,5 @@ fn to_encoder_record( Loc::at_zero(Pattern::Identifier(rcd_sym)), )], loc_body: Box::new(Loc::at_zero(encode_record_call)), - }); + }) } diff --git a/compiler/mono/src/derivers/mod.rs b/compiler/mono/src/derivers/mod.rs index d077c5b638..531470625a 100644 --- a/compiler/mono/src/derivers/mod.rs +++ b/compiler/mono/src/derivers/mod.rs @@ -1,3 +1,18 @@ //! Auto-derivers of builtin ability methods. +use roc_types::subs::{Content, Descriptor, Mark, OptVariable, Rank, Subs, Variable}; + +pub fn synth_var(subs: &mut Subs, content: Content) -> Variable { + let descriptor = Descriptor { + content, + // NOTE: this is incorrect, but that is irrelevant - derivers may only be called during + // monomorphization (or later), at which point we do not care about variable + // generalization. Hence ranks should not matter. + rank: Rank::toplevel(), + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + subs.fresh(descriptor) +} + pub mod encoding; diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index e46d8f4be7..33f5ce5d69 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -5,7 +5,7 @@ pub mod borrow; pub mod code_gen_help; mod copy; -mod derivers; +pub mod derivers; pub mod inc_dec; pub mod ir; pub mod layout; diff --git a/compiler/solve/src/ability.rs b/compiler/solve/src/ability.rs index c9abaf2747..b0aeb8ab27 100644 --- a/compiler/solve/src/ability.rs +++ b/compiler/solve/src/ability.rs @@ -562,6 +562,27 @@ impl ObligationCache<'_> { return Err(var); } } + Alias( + Symbol::NUM_U8 + | Symbol::NUM_U16 + | Symbol::NUM_U32 + | Symbol::NUM_U64 + | Symbol::NUM_U128 + | Symbol::NUM_I8 + | Symbol::NUM_I16 + | Symbol::NUM_I32 + | Symbol::NUM_I64 + | Symbol::NUM_I128 + | Symbol::NUM_NAT + | Symbol::NUM_F32 + | Symbol::NUM_F64 + | Symbol::NUM_DEC, + _, + _, + _, + ) => { + // yes + } Alias(_, arguments, real_type_var, _) => { push_var_slice!(arguments.all_variables()); stack.push(*real_type_var); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 3f12d3194b..56bf658bb6 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -4627,7 +4627,7 @@ struct CopyImportEnv<'a> { } pub fn copy_import_to( - source: &mut Subs, // mut to set the copy + source: &mut Subs, // mut to set the copy. TODO: use a separate copy table to avoid mut target: &mut Subs, var: Variable, rank: Rank, From beeba1e19871ae34e6fd6be8802cc82e32295241 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 15:21:22 -0400 Subject: [PATCH 21/53] Add some comments to derive testing --- compiler/test_derivers/src/encoding.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/test_derivers/src/encoding.rs b/compiler/test_derivers/src/encoding.rs index c6897785f2..80ebffb28f 100644 --- a/compiler/test_derivers/src/encoding.rs +++ b/compiler/test_derivers/src/encoding.rs @@ -48,6 +48,7 @@ fn check_derived_typechecks( exposed_encode_types: ExposedTypesStorageSubs, encode_abilities_store: AbilitiesStore, ) { + // constrain the derived let mut constraints = Constraints::new(); let mut env = roc_constrain::expr::Env { rigids: Default::default(), @@ -61,6 +62,10 @@ fn check_derived_typechecks( &derived, Expected::NoExpectation(Type::Variable(test_subs.fresh_unnamed_flex_var())), ); + + // the derived depends on stuff from Encode, so + // - we need to add those dependencies as imported on the constraint + // - we need to add Encode ability info to a local abilities store let encode_values_to_import = exposed_encode_types .stored_vars_by_symbol .keys() @@ -90,6 +95,7 @@ fn check_derived_typechecks( let constr = constraints.let_import_constraint(rigid_vars, def_types, constr, &import_variables); + // run the solver, print and fail if we have errors let (_solved_subs, _, problems, _) = roc_solve::module::run_solve( &constraints, constr, @@ -183,8 +189,9 @@ where ); } +// Writing out the types into content is terrible, so let's use a DSL at least for testing macro_rules! synth { - (rcd{ $($field:literal: $typ:expr),* }) => { + ({ $($field:literal: $typ:expr),* }) => { |subs| { let fields = RecordFields::insert_into_subs(subs, vec![ $( ($field.into(), RecordField::Required($typ)) ,)* ]); synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD))) @@ -194,5 +201,5 @@ macro_rules! synth { #[test] fn one_field_record() { - derive_test(synth!(rcd{ "a": Variable::U8 })) + derive_test(synth!({ "a": Variable::U8 })) } From beed7e791bc0aac932b27707d53e87b11fe25240 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 17:06:52 -0400 Subject: [PATCH 22/53] Add printing of derived AST to tests --- Cargo.lock | 2 + compiler/debug_flags/src/lib.rs | 5 + compiler/mono/src/derivers/encoding.rs | 3 +- compiler/test_derivers/Cargo.toml | 2 + compiler/test_derivers/src/encoding.rs | 53 ++++- compiler/test_derivers/src/pretty_print.rs | 241 +++++++++++++++++++++ compiler/test_derivers/src/tests.rs | 2 + 7 files changed, 300 insertions(+), 8 deletions(-) create mode 100644 compiler/test_derivers/src/pretty_print.rs diff --git a/Cargo.lock b/Cargo.lock index a1247c17c8..d53d3029d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4804,10 +4804,12 @@ dependencies = [ "bumpalo", "indoc", "lazy_static", + "pretty_assertions", "roc_builtins", "roc_can", "roc_collections", "roc_constrain", + "roc_debug_flags", "roc_load_internal", "roc_module", "roc_mono", diff --git a/compiler/debug_flags/src/lib.rs b/compiler/debug_flags/src/lib.rs index 5af06aff4c..4980098cf6 100644 --- a/compiler/debug_flags/src/lib.rs +++ b/compiler/debug_flags/src/lib.rs @@ -62,8 +62,13 @@ flags! { // ===Solve=== /// Prints type unifications, before and after they happen. + /// Only use this in single-threaded mode! ROC_PRINT_UNIFICATIONS + /// Like ROC_PRINT_UNIFICATIONS, in the context of typechecking derived implementations. + /// Only use this in single-threaded mode! + ROC_PRINT_UNIFICATIONS_DERIVED + /// Prints all type mismatches hit during type unification. ROC_PRINT_MISMATCHES diff --git a/compiler/mono/src/derivers/encoding.rs b/compiler/mono/src/derivers/encoding.rs index 102b99d4e3..d2b5b2f589 100644 --- a/compiler/mono/src/derivers/encoding.rs +++ b/compiler/mono/src/derivers/encoding.rs @@ -162,6 +162,7 @@ fn to_encoder_record( // { key: "a", value: Encode.toEncoder rcd.a }, // { key: "b", value: Encode.toEncoder rcd.b }, // ] + dbg!(record_var); let rcd_sym = env.unique_symbol(); let whole_rcd_var = env.subs.fresh_unnamed_flex_var(); // type of the { key, value } records in the list @@ -189,7 +190,7 @@ fn to_encoder_record( // rcd.a let field_access = Access { record_var, - ext_var, + ext_var: env.subs.fresh_unnamed_flex_var(), field_var, loc_expr: Box::new(Loc::at_zero(Var(rcd_sym))), field: field_name, diff --git a/compiler/test_derivers/Cargo.toml b/compiler/test_derivers/Cargo.toml index 52b52143d1..92bc64e056 100644 --- a/compiler/test_derivers/Cargo.toml +++ b/compiler/test_derivers/Cargo.toml @@ -22,7 +22,9 @@ roc_reporting = { path = "../../reporting" } roc_constrain = { path = "../constrain" } roc_region = { path = "../region" } roc_solve = { path = "../solve" } +roc_debug_flags = { path = "../debug_flags" } bumpalo = { version = "3.8.0", features = ["collections"] } lazy_static = "1.4.0" indoc = "1.0.3" ven_pretty = { path = "../../vendor/pretty" } +pretty_assertions = "1.0.0" diff --git a/compiler/test_derivers/src/encoding.rs b/compiler/test_derivers/src/encoding.rs index 80ebffb28f..32a06b2cfb 100644 --- a/compiler/test_derivers/src/encoding.rs +++ b/compiler/test_derivers/src/encoding.rs @@ -3,6 +3,11 @@ use std::path::PathBuf; use bumpalo::Bump; +use indoc::indoc; +use pretty_assertions::assert_eq; +use ven_pretty::DocAllocator; + +use crate::pretty_print::{pretty_print, Ctx}; use roc_can::{ abilities::{AbilitiesStore, ResolvedSpecializations}, constraint::Constraints, @@ -15,21 +20,20 @@ use roc_constrain::{ expr::constrain_expr, module::{ExposedByModule, ExposedForModule, ExposedModuleTypes}, }; +use roc_debug_flags::{dbg_do, ROC_PRINT_UNIFICATIONS, ROC_PRINT_UNIFICATIONS_DERIVED}; use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading}; use roc_module::{ ident::ModuleName, symbol::{IdentIds, Interns, ModuleId}, }; use roc_mono::derivers::encoding::Env; +use roc_mono::derivers::{encoding, synth_var}; use roc_region::all::{LineInfo, Region}; use roc_reporting::report::{type_problem, RocDocAllocator}; use roc_types::{ subs::{Content, ExposedTypesStorageSubs, FlatType, RecordFields, Subs, Variable}, types::{RecordField, Type}, }; -use ven_pretty::DocAllocator; - -use roc_mono::derivers::{encoding, synth_var}; fn encode_path() -> PathBuf { let repo_root = std::env::var("ROC_WORKSPACE_DIR").expect("are you running with `cargo test`?"); @@ -96,6 +100,10 @@ fn check_derived_typechecks( constraints.let_import_constraint(rigid_vars, def_types, constr, &import_variables); // run the solver, print and fail if we have errors + dbg_do!( + ROC_PRINT_UNIFICATIONS_DERIVED, + std::env::set_var(ROC_PRINT_UNIFICATIONS, "1") + ); let (_solved_subs, _, problems, _) = roc_solve::module::run_solve( &constraints, constr, @@ -138,7 +146,7 @@ fn check_derived_typechecks( } } -fn derive_test(synth_input: S) +fn derive_test(synth_input: S, expected: &str) where S: FnOnce(&mut Subs) -> Variable, { @@ -165,19 +173,26 @@ where let test_module = interns.module_id(&ModuleName::from("Test")); let mut test_subs = Subs::new(); - let mut test_ident_ids = IdentIds::default(); + interns + .all_ident_ids + .insert(test_module, IdentIds::default()); let mut env = Env { home: test_module, arena: &arena, subs: &mut test_subs, - ident_ids: &mut test_ident_ids, + ident_ids: interns.all_ident_ids.get_mut(&test_module).unwrap(), exposed_encode_types: &mut exposed_encode_types, }; let signature_var = synth_input(env.subs); let derived = encoding::derive_to_encoder(&mut env, signature_var); + test_module.register_debug_idents(interns.all_ident_ids.get(&test_module).unwrap()); + + let ctx = Ctx { interns: &interns }; + let derived_program = pretty_print(&ctx, &derived); + assert_eq!(expected, derived_program); check_derived_typechecks( derived, @@ -201,5 +216,29 @@ macro_rules! synth { #[test] fn one_field_record() { - derive_test(synth!({ "a": Variable::U8 })) + derive_test( + synth!({ "a": Variable::U8 }), + indoc!( + r#" + \Test.0 -> + (Encode.record [ { value: (Encode.toEncoder (Test.0).a), key: "a", }, ]) + "# + ), + ) +} + +#[test] +fn two_field_record() { + derive_test( + synth!({ "a": Variable::U8, "b": Variable::STR }), + indoc!( + r#" + \Test.0 -> + (Encode.record [ + { value: (Encode.toEncoder (Test.0).a), key: "a", }, + { value: (Encode.toEncoder (Test.0).b), key: "b", }, + ]) + "# + ), + ) } diff --git a/compiler/test_derivers/src/pretty_print.rs b/compiler/test_derivers/src/pretty_print.rs new file mode 100644 index 0000000000..586ee53e29 --- /dev/null +++ b/compiler/test_derivers/src/pretty_print.rs @@ -0,0 +1,241 @@ +//! Pretty-prints the canonical AST back to check our work - do things look reasonable? + +use roc_can::expr::Expr::{self, *}; +use roc_can::expr::{ClosureData, WhenBranch}; +use roc_can::pattern::{Pattern, RecordDestruct}; + +use roc_module::symbol::Interns; +use ven_pretty::{Arena, DocAllocator, DocBuilder}; + +pub struct Ctx<'a> { + pub interns: &'a Interns, +} + +pub fn pretty_print(c: &Ctx, e: &Expr) -> String { + let f = Arena::new(); + expr(c, &f, e).append(f.hardline()).1.pretty(80).to_string() +} + +fn expr<'a>(c: &Ctx, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> { + match e { + Num(_, n, _, _) | Int(_, _, n, _, _) | Float(_, _, n, _, _) => f.text(&**n), + Str(s) => f.text(format!(r#""{}""#, s)), + SingleQuote(c) => f.text(format!("'{}'", c)), + List { + elem_var: _, + loc_elems, + } => f + .reflow("[") + .append( + f.concat(loc_elems.iter().map(|le| { + let elem = expr(c, f, &le.value); + f.line().append(elem).append(",") + })) + .group() + .nest(2), + ) + .append(f.line()) + .append("]") + .group(), + Var(sym) | AbilityMember(sym, _, _) => f.text(format!( + "{}.{}", + sym.module_string(c.interns), + sym.as_str(c.interns), + )), + When { + loc_cond, branches, .. + } => f + .reflow("when ") + .append(expr(c, f, &loc_cond.value)) + .append(f.text(" is")) + .append( + f.concat(branches.iter().map(|b| f.line().append(branch(c, f, b)))) + .nest(2) + .group(), + ) + .nest(2) + .group(), + If { + branches, + final_else, + .. + } => f + .concat(branches.iter().enumerate().map(|(i, (cond, body))| { + let head = if i == 0 { "if " } else { "else if " }; + (f.reflow(head) + .append(expr(c, f, &cond.value)) + .group() + .nest(2)) + .append(f.line()) + .append( + f.reflow("then") + .append(f.softline().append(expr(c, f, &body.value))) + .group() + .nest(2), + ) + .append(f.line()) + })) + .append( + f.reflow("else ") + .append(expr(c, f, &final_else.value)) + .group() + .nest(2), + ) + .group(), + LetRec(_, _, _) => todo!(), + LetNonRec(_, _) => todo!(), + Call(fun, args, _) => { + let (_, fun, _, _) = &**fun; + f.text("(") + .append(expr(c, f, &fun.value)) + .append(f.softline()) + .append(f.intersperse(args.iter().map(|le| expr(c, f, &le.1.value)), f.softline())) + .append(f.text(")")) + .group() + } + RunLowLevel { .. } => todo!(), + ForeignCall { .. } => todo!(), + Closure(ClosureData { + arguments, + loc_body, + .. + }) => f + .text("\\") + .append( + f.intersperse( + arguments + .iter() + .map(|(_, _, arg)| pattern(c, f, &arg.value)), + f.text(", "), + ), + ) + .append(f.text(" ->")) + .append(f.line()) + .append(expr(c, f, &loc_body.value)) + .nest(2) + .group(), + Record { fields, .. } => f + .reflow("{") + .append( + f.concat(fields.iter().map(|(name, field)| { + let field = f + .text(name.as_str()) + .append(f.reflow(": ")) + .append(expr(c, f, &field.loc_expr.value)) + .nest(2) + .group(); + f.line().append(field).append(",") + })) + .nest(2) + .group(), + ) + .append(f.line()) + .append(f.text("}")) + .group(), + EmptyRecord => f.text("{}"), + Access { + loc_expr, field, .. + } => f + .text("(") + .append(expr(c, f, &loc_expr.value)) + .append(f.text(")")) + .append(f.text(format!(".{}", field.as_str()))) + .group(), + Accessor(_) => todo!(), + Update { .. } => todo!(), + Tag { .. } => todo!(), + ZeroArgumentTag { .. } => todo!(), + OpaqueRef { .. } => todo!(), + Expect { .. } => todo!(), + TypedHole(_) => todo!(), + RuntimeError(_) => todo!(), + } +} + +fn branch<'a>(c: &Ctx, f: &'a Arena<'a>, b: &'a WhenBranch) -> DocBuilder<'a, Arena<'a>> { + let WhenBranch { + patterns, + value, + guard, + redundant: _, + } = b; + + f.intersperse( + patterns.iter().map(|lp| pattern(c, f, &lp.value)), + f.text(" | "), + ) + .append(match guard { + Some(e) => f.text("if ").append(expr(c, f, &e.value)), + None => f.nil(), + }) + .append(f.text(" ->")) + .append(f.softline()) + .append(expr(c, f, &value.value)) + .nest(2) + .group() +} + +fn pattern<'a>(c: &Ctx, f: &'a Arena<'a>, p: &'a Pattern) -> DocBuilder<'a, Arena<'a>> { + use Pattern::*; + match p { + Identifier(sym) + | AbilityMemberSpecialization { + specializes: sym, .. + } => f.text(format!( + "{}.{}", + sym.module_string(c.interns), + sym.as_str(c.interns), + )), + AppliedTag { + tag_name, + arguments, + .. + } => f + .text(format!("({}", tag_name.0.as_str())) + .append(f.intersperse( + arguments.iter().map(|(_, lp)| pattern(c, f, &lp.value)), + f.space(), + )) + .append(")") + .group(), + UnwrappedOpaque { + opaque, argument, .. + } => f + .text(format!("@{} ", opaque.module_string(c.interns))) + .append(pattern(c, f, &argument.1.value)) + .group(), + RecordDestructure { destructs, .. } => f + .text("{") + .append( + f.intersperse( + destructs.iter().map(|l| &l.value).map( + |RecordDestruct { label, typ, .. }| match typ { + roc_can::pattern::DestructType::Required => f.text(label.as_str()), + roc_can::pattern::DestructType::Optional(_, e) => f + .text(label.as_str()) + .append(f.text(" ? ")) + .append(expr(c, f, &e.value)), + roc_can::pattern::DestructType::Guard(_, p) => f + .text(label.as_str()) + .append(f.text(": ")) + .append(pattern(c, f, &p.value)), + }, + ), + f.text(", "), + ), + ) + .append(f.text("}")) + .group(), + NumLiteral(_, n, _, _) | IntLiteral(_, _, n, _, _) | FloatLiteral(_, _, n, _, _) => { + f.text(&**n) + } + StrLiteral(s) => f.text(format!(r#""{}""#, s)), + SingleQuote(c) => f.text(format!("'{}'", c)), + Underscore => f.text("_"), + + Shadowed(_, _, _) => todo!(), + OpaqueNotInScope(_) => todo!(), + UnsupportedPattern(_) => todo!(), + MalformedPattern(_, _) => todo!(), + } +} diff --git a/compiler/test_derivers/src/tests.rs b/compiler/test_derivers/src/tests.rs index bd5082c3df..3fdefff22f 100644 --- a/compiler/test_derivers/src/tests.rs +++ b/compiler/test_derivers/src/tests.rs @@ -1,3 +1,5 @@ #![cfg(test)] mod encoding; + +mod pretty_print; From cd37f4bbd64f96265486968e16259c78a09d4faa Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 17:11:35 -0400 Subject: [PATCH 23/53] Add ROC_PRINT_UNIFICATIONS_DERIVED to cargo config --- .cargo/config | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.cargo/config b/.cargo/config index d9e675b5bd..b6733e42bb 100644 --- a/.cargo/config +++ b/.cargo/config @@ -19,6 +19,7 @@ ROC_WORKSPACE_DIR = { value = "", relative = true } # Set = "1" to turn a debug flag on. ROC_PRETTY_PRINT_ALIAS_CONTENTS = "0" ROC_PRINT_UNIFICATIONS = "0" +ROC_PRINT_UNIFICATIONS_DERIVED = "0" ROC_PRINT_MISMATCHES = "0" ROC_VERIFY_RIGID_LET_GENERALIZED = "0" ROC_PRINT_IR_AFTER_SPECIALIZATION = "0" @@ -26,4 +27,4 @@ ROC_PRINT_IR_AFTER_RESET_REUSE = "0" ROC_PRINT_IR_AFTER_REFCOUNT = "0" ROC_DEBUG_ALIAS_ANALYSIS = "0" ROC_PRINT_LLVM_FN_VERIFICATION = "0" -ROC_PRINT_LOAD_LOG = "0" \ No newline at end of file +ROC_PRINT_LOAD_LOG = "0" From fe55b5a226486578411b99d0fdc921c39f271f6a Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 17:21:12 -0400 Subject: [PATCH 24/53] Perf: don't collect or re-insert variable slices --- compiler/mono/src/derivers/encoding.rs | 30 +++++++++++--------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/compiler/mono/src/derivers/encoding.rs b/compiler/mono/src/derivers/encoding.rs index d2b5b2f589..46eaa15f5f 100644 --- a/compiler/mono/src/derivers/encoding.rs +++ b/compiler/mono/src/derivers/encoding.rs @@ -17,7 +17,7 @@ use roc_types::subs::{ Content, ExposedTypesStorageSubs, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields, Subs, SubsFmtContent, SubsSlice, UnionLambdas, Variable, VariableSubsSlice, }; -use roc_types::types::{AliasKind, RecordField, RecordFieldsError}; +use roc_types::types::{AliasKind, RecordField}; use crate::derivers::synth_var; @@ -162,22 +162,23 @@ fn to_encoder_record( // { key: "a", value: Encode.toEncoder rcd.a }, // { key: "b", value: Encode.toEncoder rcd.b }, // ] - dbg!(record_var); + + debug_assert!(matches!( + env.subs.get_content_without_compacting(ext_var), + Content::Structure(FlatType::EmptyRecord) + )); let rcd_sym = env.unique_symbol(); let whole_rcd_var = env.subs.fresh_unnamed_flex_var(); // type of the { key, value } records in the list - let fields_it = match fields.unsorted_iterator(env.subs, ext_var) { - Ok(it) => it, - Err(RecordFieldsError) => bad_input!(env, ext_var, "extension var for record is unbound"), - } - .map(|(name, field)| (name.clone(), field)) - .collect::>(); - use Expr::*; - let fields_list = fields_it - .into_iter() - .map(|(field_name, field)| { + let fields_list = fields + .iter_all() + .map(|(field_name_index, field_var_index, _)| { + let field_name = env.subs[field_name_index].clone(); + let field_var = env.subs[field_var_index]; + let field_var_slice = VariableSubsSlice::new(field_var_index.index, 1); + // key: "a" let key_field = Field { var: Variable::STR, @@ -185,8 +186,6 @@ fn to_encoder_record( loc_expr: Box::new(Loc::at_zero(Str(field_name.as_str().into()))), }; - let field_var = *field.as_inner(); - // rcd.a let field_access = Access { record_var, @@ -201,9 +200,6 @@ fn to_encoder_record( let to_encoder_fn_var = env.import_encode_symbol(Symbol::ENCODE_TO_ENCODER); // (typeof rcd.a) -[clos]-> t1 - // TODO: we don't have to re-add the field var as a subs slice if we iterate over the - // original record fields' variable slice. - let field_var_slice = VariableSubsSlice::insert_into_subs(env.subs, once(field_var)); let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1 let this_to_encoder_fn_var = synth_var( From 3d1236756806ead321de42745f5266e98f762b86 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 17:21:30 -0400 Subject: [PATCH 25/53] Clippy --- compiler/load_internal/src/file.rs | 1 + compiler/test_derivers/src/encoding.rs | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index d95a07a841..a5b169f184 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -2663,6 +2663,7 @@ fn finish_specialization( }) } +#[allow(clippy::too_many_arguments)] fn finish( state: State, solved: Solved, diff --git a/compiler/test_derivers/src/encoding.rs b/compiler/test_derivers/src/encoding.rs index 32a06b2cfb..28e80d5e99 100644 --- a/compiler/test_derivers/src/encoding.rs +++ b/compiler/test_derivers/src/encoding.rs @@ -20,7 +20,7 @@ use roc_constrain::{ expr::constrain_expr, module::{ExposedByModule, ExposedForModule, ExposedModuleTypes}, }; -use roc_debug_flags::{dbg_do, ROC_PRINT_UNIFICATIONS, ROC_PRINT_UNIFICATIONS_DERIVED}; +use roc_debug_flags::dbg_do; use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading}; use roc_module::{ ident::ModuleName, @@ -101,8 +101,8 @@ fn check_derived_typechecks( // run the solver, print and fail if we have errors dbg_do!( - ROC_PRINT_UNIFICATIONS_DERIVED, - std::env::set_var(ROC_PRINT_UNIFICATIONS, "1") + roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, + std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, "1") ); let (_solved_subs, _, problems, _) = roc_solve::module::run_solve( &constraints, @@ -119,7 +119,7 @@ fn check_derived_typechecks( let lines = LineInfo::new(" "); let src_lines = vec![" "]; let mut reports = Vec::new(); - let alloc = RocDocAllocator::new(&src_lines, test_module, &interns); + let alloc = RocDocAllocator::new(&src_lines, test_module, interns); for problem in problems.into_iter() { if let Some(report) = type_problem(&alloc, &lines, filename.clone(), problem.clone()) { @@ -163,7 +163,7 @@ where &arena, encode_path().file_name().unwrap().into(), source, - &encode_path().parent().unwrap(), + encode_path().parent().unwrap(), Default::default(), target_info, roc_reporting::report::RenderTarget::ColorTerminal, From e97546238a8db5f7a6a7ff981828b3d1fd1b260e Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 14 Jun 2022 17:44:16 -0400 Subject: [PATCH 26/53] Encoding for empty records --- compiler/mono/src/derivers/encoding.rs | 4 +++- compiler/test_derivers/src/encoding.rs | 27 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/compiler/mono/src/derivers/encoding.rs b/compiler/mono/src/derivers/encoding.rs index 46eaa15f5f..b247847475 100644 --- a/compiler/mono/src/derivers/encoding.rs +++ b/compiler/mono/src/derivers/encoding.rs @@ -129,12 +129,14 @@ fn to_encoder_from_var(env: &mut Env<'_>, mut var: Variable) -> Expr { FlatType::Record(fields, ext_var) => { return to_encoder_record(env, var, fields, ext_var) } + FlatType::EmptyRecord => { + return to_encoder_record(env, var, RecordFields::empty(), var) + } FlatType::Apply(_, _) => todo!(), FlatType::TagUnion(_, _) => todo!(), FlatType::FunctionOrTagUnion(_, _, _) => todo!(), FlatType::RecursiveTagUnion(_, _, _) => todo!(), - FlatType::EmptyRecord => todo!(), FlatType::EmptyTagUnion => todo!(), FlatType::Func(..) => bad_input!(env, var, "functions cannot be encoded"), diff --git a/compiler/test_derivers/src/encoding.rs b/compiler/test_derivers/src/encoding.rs index 28e80d5e99..22dc843e63 100644 --- a/compiler/test_derivers/src/encoding.rs +++ b/compiler/test_derivers/src/encoding.rs @@ -211,9 +211,36 @@ macro_rules! synth { let fields = RecordFields::insert_into_subs(subs, vec![ $( ($field.into(), RecordField::Required($typ)) ,)* ]); synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD))) } + }; + (var $var:expr) => { + |_| { $var } } } +#[test] +fn empty_record() { + derive_test( + synth!(var Variable::EMPTY_RECORD), + indoc!( + r#" + \Test.0 -> (Encode.record [ ]) + "# + ), + ) +} + +#[test] +fn zero_field_record() { + derive_test( + synth!({}), + indoc!( + r#" + \Test.0 -> (Encode.record [ ]) + "# + ), + ) +} + #[test] fn one_field_record() { derive_test( From 7cbd13145d4491c833b7be139f0891c1cba6a787 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 08:19:04 -0400 Subject: [PATCH 27/53] Roc format --- compiler/builtins/roc/Json.roc | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/compiler/builtins/roc/Json.roc b/compiler/builtins/roc/Json.roc index 917f682c04..7d7675c98f 100644 --- a/compiler/builtins/roc/Json.roc +++ b/compiler/builtins/roc/Json.roc @@ -111,8 +111,7 @@ record = \fields -> tag = \name, payload -> custom \bytes, @Json {} -> # Idea: encode `A v1 v2` as `{"A": [v1, v2]}` - - writePayload = \{buffer, itemsLeft}, encoder -> + writePayload = \{ buffer, itemsLeft }, encoder -> bufferWithValue = appendWith buffer encoder (@Json {}) bufferWithSuffix = if itemsLeft > 0 then @@ -120,17 +119,17 @@ tag = \name, payload -> else bufferWithValue - {buffer: bufferWithSuffix, itemsLeft: itemsLeft - 1} + { buffer: bufferWithSuffix, itemsLeft: itemsLeft - 1 } bytesHead = List.append bytes (Num.toU8 '{') - |> List.append (Num.toU8 '"') - |> List.concat (Str.toUtf8 name) - |> List.append (Num.toU8 '"') - |> List.append (Num.toU8 ':') - |> List.append (Num.toU8 '[') + |> List.append (Num.toU8 '"') + |> List.concat (Str.toUtf8 name) + |> List.append (Num.toU8 '"') + |> List.append (Num.toU8 ':') + |> List.append (Num.toU8 '[') { buffer: bytesWithPayload } = List.walk payload { buffer: bytesHead, itemsLeft: List.len payload } writePayload List.append bytesWithPayload (Num.toU8 ']') - |> List.append (Num.toU8 '}') + |> List.append (Num.toU8 '}') From 8f3dfa990a4824a095572ccb0f8ba9896e2a1610 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 13:59:03 -0400 Subject: [PATCH 28/53] Rename derivers to derive --- Cargo.lock | 2 +- Cargo.toml | 2 +- compiler/mono/src/{derivers => derive}/encoding.rs | 2 +- compiler/mono/src/{derivers => derive}/mod.rs | 0 compiler/mono/src/lib.rs | 2 +- compiler/{test_derivers => test_derive}/Cargo.toml | 4 ++-- compiler/{test_derivers => test_derive}/src/encoding.rs | 6 ++++-- compiler/{test_derivers => test_derive}/src/pretty_print.rs | 0 compiler/{test_derivers => test_derive}/src/tests.rs | 0 9 files changed, 10 insertions(+), 8 deletions(-) rename compiler/mono/src/{derivers => derive}/encoding.rs (99%) rename compiler/mono/src/{derivers => derive}/mod.rs (100%) rename compiler/{test_derivers => test_derive}/Cargo.toml (94%) rename compiler/{test_derivers => test_derive}/src/encoding.rs (98%) rename compiler/{test_derivers => test_derive}/src/pretty_print.rs (100%) rename compiler/{test_derivers => test_derive}/src/tests.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index d53d3029d3..138670177a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4798,7 +4798,7 @@ dependencies = [ ] [[package]] -name = "test_derivers" +name = "test_derive" version = "0.1.0" dependencies = [ "bumpalo", diff --git a/Cargo.toml b/Cargo.toml index 8559892118..133550c56a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ members = [ "compiler/mono", "compiler/alias_analysis", "compiler/test_mono", - "compiler/test_derivers", + "compiler/test_derive", "compiler/load", "compiler/load_internal", "compiler/gen_llvm", diff --git a/compiler/mono/src/derivers/encoding.rs b/compiler/mono/src/derive/encoding.rs similarity index 99% rename from compiler/mono/src/derivers/encoding.rs rename to compiler/mono/src/derive/encoding.rs index b247847475..8394c31265 100644 --- a/compiler/mono/src/derivers/encoding.rs +++ b/compiler/mono/src/derive/encoding.rs @@ -19,7 +19,7 @@ use roc_types::subs::{ }; use roc_types::types::{AliasKind, RecordField}; -use crate::derivers::synth_var; +use crate::derive::synth_var; macro_rules! bad_input { ($env:expr, $var:expr) => { diff --git a/compiler/mono/src/derivers/mod.rs b/compiler/mono/src/derive/mod.rs similarity index 100% rename from compiler/mono/src/derivers/mod.rs rename to compiler/mono/src/derive/mod.rs diff --git a/compiler/mono/src/lib.rs b/compiler/mono/src/lib.rs index 33f5ce5d69..433509cdc7 100644 --- a/compiler/mono/src/lib.rs +++ b/compiler/mono/src/lib.rs @@ -5,7 +5,7 @@ pub mod borrow; pub mod code_gen_help; mod copy; -pub mod derivers; +pub mod derive; pub mod inc_dec; pub mod ir; pub mod layout; diff --git a/compiler/test_derivers/Cargo.toml b/compiler/test_derive/Cargo.toml similarity index 94% rename from compiler/test_derivers/Cargo.toml rename to compiler/test_derive/Cargo.toml index 92bc64e056..27a67cc59f 100644 --- a/compiler/test_derivers/Cargo.toml +++ b/compiler/test_derive/Cargo.toml @@ -1,12 +1,12 @@ [package] -name = "test_derivers" +name = "test_derive" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" edition = "2021" [[test]] -name = "test_derives" +name = "test_derive" path = "src/tests.rs" [dev-dependencies] diff --git a/compiler/test_derivers/src/encoding.rs b/compiler/test_derive/src/encoding.rs similarity index 98% rename from compiler/test_derivers/src/encoding.rs rename to compiler/test_derive/src/encoding.rs index 22dc843e63..dfda38b52f 100644 --- a/compiler/test_derivers/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -26,8 +26,10 @@ use roc_module::{ ident::ModuleName, symbol::{IdentIds, Interns, ModuleId}, }; -use roc_mono::derivers::encoding::Env; -use roc_mono::derivers::{encoding, synth_var}; +use roc_mono::derive::{ + encoding::{self, Env}, + synth_var, +}; use roc_region::all::{LineInfo, Region}; use roc_reporting::report::{type_problem, RocDocAllocator}; use roc_types::{ diff --git a/compiler/test_derivers/src/pretty_print.rs b/compiler/test_derive/src/pretty_print.rs similarity index 100% rename from compiler/test_derivers/src/pretty_print.rs rename to compiler/test_derive/src/pretty_print.rs diff --git a/compiler/test_derivers/src/tests.rs b/compiler/test_derive/src/tests.rs similarity index 100% rename from compiler/test_derivers/src/tests.rs rename to compiler/test_derive/src/tests.rs From 445a5e5c2cbeedef9b0dfe4b7a1150ac58e257cd Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 14:29:17 -0400 Subject: [PATCH 29/53] Fix abilities rebase issue --- compiler/mono/src/derive/encoding.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/mono/src/derive/encoding.rs b/compiler/mono/src/derive/encoding.rs index 8394c31265..d41e8f6753 100644 --- a/compiler/mono/src/derive/encoding.rs +++ b/compiler/mono/src/derive/encoding.rs @@ -9,7 +9,7 @@ use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Field, Recursive}; use roc_can::pattern::Pattern; use roc_collections::SendMap; use roc_error_macros::internal_error; -use roc_late_solve::instantiate_rigids; +use roc_late_solve::{instantiate_rigids, AbilitiesView}; use roc_module::called_via::CalledVia; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_region::all::{Loc, Region}; @@ -71,9 +71,10 @@ impl Env<'_> { fn unify(&mut self, left: Variable, right: Variable) { // NOTE: I don't believe the abilities store is necessary for unification at this point! roc_late_solve::unify( + self.home, self.arena, self.subs, - &AbilitiesStore::default(), + &AbilitiesView::Module(&AbilitiesStore::default()), left, right, ) From 0560fb41a117ed2bacfaf2e118781f83e8f2a4b2 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 12:09:24 -0400 Subject: [PATCH 30/53] Add content-hashing scheme for Encoding deriver --- compiler/mono/src/derive/deriver_hash.rs | 45 ++++++ compiler/mono/src/derive/encoding.rs | 137 ++++++++++++++++-- compiler/mono/src/derive/mod.rs | 2 + compiler/test_derive/src/encoding.rs | 170 +++++++++++++++++++++-- 4 files changed, 326 insertions(+), 28 deletions(-) create mode 100644 compiler/mono/src/derive/deriver_hash.rs diff --git a/compiler/mono/src/derive/deriver_hash.rs b/compiler/mono/src/derive/deriver_hash.rs new file mode 100644 index 0000000000..801d64abd3 --- /dev/null +++ b/compiler/mono/src/derive/deriver_hash.rs @@ -0,0 +1,45 @@ +//! To avoid duplicating derived implementations for the same type, derived implementations are +//! addressed by a hash of their type content. However, different derived implementations can be +//! reused based on different properties of the type. For example: +//! +//! - `Eq` does not care about surface type representations; its derived implementations can be +//! uniquely addressed by the [`Layout`][crate::layout::Layout] of a type. +//! - `Encoding` must care about surface type representations; for example, `{ a: "" }` and +//! `{ b: "" }` have different derived implementations. However, it does not need to distinguish +//! between e.g. required and optional record fields. +//! - `Decoding` is like encoding, but has some differences. For one, it *does* need to distinguish +//! between required and optional record fields. +//! +//! For these reasons the content hashing is based on a [`Strategy`] as well. + +use roc_types::subs::{Subs, Variable}; + +#[derive(Hash)] +enum Strategy { + Encoding, + #[allow(unused)] + Decoding, +} + +#[derive(Hash)] +struct DeriverHashImpl +where + H: std::hash::Hash, +{ + strategy: Strategy, + hash: H, +} + +pub trait DeriverHash: std::hash::Hash {} + +impl DeriverHash for DeriverHashImpl where H: std::hash::Hash {} + +pub struct EncodingHash; +impl EncodingHash { + pub fn from_var(subs: &Subs, var: Variable) -> impl DeriverHash + '_ { + DeriverHashImpl { + strategy: Strategy::Encoding, + hash: super::encoding::FlatEncodable::from_var(subs, var), + } + } +} diff --git a/compiler/mono/src/derive/encoding.rs b/compiler/mono/src/derive/encoding.rs index d41e8f6753..1c05f3b98b 100644 --- a/compiler/mono/src/derive/encoding.rs +++ b/compiler/mono/src/derive/encoding.rs @@ -11,6 +11,7 @@ use roc_collections::SendMap; use roc_error_macros::internal_error; use roc_late_solve::{instantiate_rigids, AbilitiesView}; use roc_module::called_via::CalledVia; +use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_region::all::{Loc, Region}; use roc_types::subs::{ @@ -22,14 +23,14 @@ use roc_types::types::{AliasKind, RecordField}; use crate::derive::synth_var; macro_rules! bad_input { - ($env:expr, $var:expr) => { - bad_input!($env, $var, "Invalid content") + ($subs:expr, $var:expr) => { + bad_input!($subs, $var, "Invalid content") }; - ($env:expr, $var:expr, $msg:expr) => { + ($subs:expr, $var:expr, $msg:expr) => { internal_error!( "{:?} for toEncoder deriver: {:?}", $msg, - SubsFmtContent($env.subs.get_content_without_compacting($var), $env.subs) + SubsFmtContent($subs.get_content_without_compacting($var), $subs) ) }; } @@ -82,6 +83,116 @@ impl Env<'_> { } } +#[derive(Hash)] +pub(super) enum FlatEncodable<'a> { + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + Dec, + F32, + F64, + List(/* takes one variable */), + Set(/* takes one variable */), + Dict(/* takes two variables */), + Str, + // Unfortunate that we must allocate here, c'est la vie + Record(Vec<&'a Lowercase>), + TagUnion(Vec<(&'a TagName, u16)>), +} + +impl FlatEncodable<'_> { + pub fn from_var(subs: &Subs, var: Variable) -> FlatEncodable { + match *subs.get_content_without_compacting(var) { + Content::Structure(flat_type) => match flat_type { + FlatType::Apply(sym, _) => match sym { + Symbol::LIST_LIST => FlatEncodable::List(), + Symbol::SET_SET => FlatEncodable::Set(), + Symbol::DICT_DICT => FlatEncodable::Dict(), + Symbol::STR_STR => FlatEncodable::Str, + _ => bad_input!(subs, var), + }, + FlatType::Record(fields, ext) => { + debug_assert!(matches!( + subs.get_content_without_compacting(ext), + Content::Structure(FlatType::EmptyRecord) + )); + + let mut field_names: Vec<_> = + subs.get_subs_slice(fields.field_names()).iter().collect(); + field_names.sort(); + FlatEncodable::Record(field_names) + } + FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => { + // The recursion var doesn't matter, because the derived implementation will only + // look on the surface of the tag union type, and more over the payloads of the + // arguments will be left generic for the monomorphizer to fill in with the + // appropriate type. That is, + // [ A t1, B t1 t2 ] + // and + // [ A t1, B t1 t2 ] as R + // look the same on the surface, because `R` is only somewhere inside of the + // `t`-prefixed payload types. + debug_assert!(matches!( + subs.get_content_without_compacting(ext), + Content::Structure(FlatType::EmptyTagUnion) + )); + let mut tag_names_and_payload_sizes: Vec<_> = tags + .iter_all() + .map(|(name_index, payload_slice_index)| { + let payload_slice = subs[payload_slice_index]; + let payload_size = payload_slice.length; + let name = &subs[name_index]; + (name, payload_size) + }) + .collect(); + tag_names_and_payload_sizes.sort_by_key(|t| t.0); + FlatEncodable::TagUnion(tag_names_and_payload_sizes) + } + FlatType::FunctionOrTagUnion(name_index, _, _) => { + FlatEncodable::TagUnion(vec![(&subs[name_index], 0)]) + } + FlatType::EmptyRecord => FlatEncodable::Record(vec![]), + FlatType::EmptyTagUnion => FlatEncodable::TagUnion(vec![]), + // + FlatType::Erroneous(_) => bad_input!(subs, var), + FlatType::Func(..) => bad_input!(subs, var, "functions cannot be encoded"), + }, + Content::Alias(sym, _, real_var, _) => match sym { + Symbol::NUM_U8 => FlatEncodable::U8, + Symbol::NUM_U16 => FlatEncodable::U16, + Symbol::NUM_U32 => FlatEncodable::U32, + Symbol::NUM_U64 => FlatEncodable::U64, + Symbol::NUM_U128 => FlatEncodable::U128, + Symbol::NUM_I8 => FlatEncodable::I8, + Symbol::NUM_I16 => FlatEncodable::I16, + Symbol::NUM_I32 => FlatEncodable::I32, + Symbol::NUM_I64 => FlatEncodable::I64, + Symbol::NUM_I128 => FlatEncodable::I128, + Symbol::NUM_DEC => FlatEncodable::Dec, + Symbol::NUM_F32 => FlatEncodable::F32, + Symbol::NUM_F64 => FlatEncodable::F64, + _ => Self::from_var(subs, real_var), + }, + Content::RangedNumber(real_var, _) => Self::from_var(subs, real_var), + // + Content::RecursionVar { .. } => bad_input!(subs, var), + Content::Error => bad_input!(subs, var), + Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) => bad_input!(subs, var, "flex vars cannot be encoded"), + Content::LambdaSet(_) => bad_input!(subs, var, "errors cannot be encoded"), + } + } +} + // TODO: decide whether it will be better to pass the whole signature, or just the argument type. // For now we are only using the argument type for convinience of testing. #[allow(dead_code)] @@ -96,21 +207,21 @@ fn verify_signature(env: &mut Env<'_>, signature: Variable) { match env.subs.get_subs_slice(args.all_variables()) { [one] => match env.subs.get_content_without_compacting(*one) { Content::FlexAbleVar(_, Symbol::ENCODE_ENCODERFORMATTING) => {} - _ => bad_input!(env, signature), + _ => bad_input!(env.subs, signature), }, - _ => bad_input!(env, signature), + _ => bad_input!(env.subs, signature), } } - _ => bad_input!(env, signature), + _ => bad_input!(env.subs, signature), } // Get the only parameter into toEncoder match env.subs.get_subs_slice(*input) { [one] => *one, - _ => bad_input!(env, signature), + _ => bad_input!(env.subs, signature), } } - _ => bad_input!(env, signature), + _ => bad_input!(env.subs, signature), }; } @@ -140,15 +251,15 @@ fn to_encoder_from_var(env: &mut Env<'_>, mut var: Variable) -> Expr { FlatType::RecursiveTagUnion(_, _, _) => todo!(), FlatType::EmptyTagUnion => todo!(), - FlatType::Func(..) => bad_input!(env, var, "functions cannot be encoded"), - FlatType::Erroneous(_) => bad_input!(env, var), + FlatType::Func(..) => bad_input!(env.subs, var, "functions cannot be encoded"), + FlatType::Erroneous(_) => bad_input!(env.subs, var), }, Content::FlexVar(_) | Content::RigidVar(_) | Content::FlexAbleVar(_, _) - | Content::RigidAbleVar(_, _) => bad_input!(env, var, "unresolved variable"), - Content::Error => bad_input!(env, var), + | Content::RigidAbleVar(_, _) => bad_input!(env.subs, var, "unresolved variable"), + Content::Error => bad_input!(env.subs, var), } } } diff --git a/compiler/mono/src/derive/mod.rs b/compiler/mono/src/derive/mod.rs index 531470625a..9e1f235d57 100644 --- a/compiler/mono/src/derive/mod.rs +++ b/compiler/mono/src/derive/mod.rs @@ -15,4 +15,6 @@ pub fn synth_var(subs: &mut Subs, content: Content) -> Variable { subs.fresh(descriptor) } +pub mod deriver_hash; + pub mod encoding; diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index dfda38b52f..1b08692603 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -1,6 +1,9 @@ #![cfg(test)] -use std::path::PathBuf; +use std::{ + hash::{BuildHasher, Hash, Hasher}, + path::PathBuf, +}; use bumpalo::Bump; use indoc::indoc; @@ -15,7 +18,7 @@ use roc_can::{ expr::Expr, module::RigidVariables, }; -use roc_collections::VecSet; +use roc_collections::{default_hasher, VecSet}; use roc_constrain::{ expr::constrain_expr, module::{ExposedByModule, ExposedForModule, ExposedModuleTypes}, @@ -23,17 +26,21 @@ use roc_constrain::{ use roc_debug_flags::dbg_do; use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading}; use roc_module::{ - ident::ModuleName, + ident::{ModuleName, TagName}, symbol::{IdentIds, Interns, ModuleId}, }; use roc_mono::derive::{ + deriver_hash::EncodingHash, encoding::{self, Env}, synth_var, }; use roc_region::all::{LineInfo, Region}; use roc_reporting::report::{type_problem, RocDocAllocator}; use roc_types::{ - subs::{Content, ExposedTypesStorageSubs, FlatType, RecordFields, Subs, Variable}, + subs::{ + Content, ExposedTypesStorageSubs, FlatType, RecordFields, Subs, SubsIndex, UnionTags, + Variable, + }, types::{RecordField, Type}, }; @@ -206,23 +213,154 @@ where ); } +fn check_hash(eq: bool, synth1: S1, synth2: S2) +where + S1: FnOnce(&mut Subs) -> Variable, + S2: FnOnce(&mut Subs) -> Variable, +{ + let mut subs = Subs::new(); + let var1 = synth1(&mut subs); + let var2 = synth2(&mut subs); + + let hash1 = EncodingHash::from_var(&subs, var1); + let hash2 = EncodingHash::from_var(&subs, var2); + + let hash1 = { + let mut hasher = default_hasher().build_hasher(); + hash1.hash(&mut hasher); + hasher.finish() + }; + let hash2 = { + let mut hasher = default_hasher().build_hasher(); + hash2.hash(&mut hasher); + hasher.finish() + }; + + if eq { + assert_eq!(hash1, hash2); + } else { + assert_ne!(hash1, hash2); + } +} + // Writing out the types into content is terrible, so let's use a DSL at least for testing -macro_rules! synth { - ({ $($field:literal: $typ:expr),* }) => { - |subs| { - let fields = RecordFields::insert_into_subs(subs, vec![ $( ($field.into(), RecordField::Required($typ)) ,)* ]); +macro_rules! v { + ({ $($field:ident: $make_v:expr),* }) => { + |subs: &mut Subs| { + $(let $field = $make_v(subs);)* + let fields = RecordFields::insert_into_subs(subs, vec![ $( (stringify!($field).into(), RecordField::Required($field)) ,)* ]); synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD))) } }; - (var $var:expr) => { - |_| { $var } - } + ([ $($tag:ident $($payload:expr)*),* ]) => { + |subs: &mut Subs| { + $( + let $tag = vec![ $( $payload(subs), )* ]; + )* + let tags = UnionTags::insert_into_subs::<_, Vec>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]); + synth_var(subs, Content::Structure(FlatType::TagUnion(tags, Variable::EMPTY_TAG_UNION))) + } + }; + ([ $($tag:ident $($payload:expr)*),* ] as $rec_var:ident) => { + |subs: &mut Subs| { + let $rec_var = subs.fresh_unnamed_flex_var(); + let rec_name_index = + SubsIndex::push_new(&mut subs.field_names, stringify!($rec).into()); + + $( + let $tag = vec![ $( $payload(subs), )* ]; + )* + let tags = UnionTags::insert_into_subs::<_, Vec>(subs, vec![ $( (TagName(stringify!($tag).into()), $tag) ,)* ]); + let tag_union_var = synth_var(subs, Content::Structure(FlatType::RecursiveTagUnion($rec_var, tags, Variable::EMPTY_TAG_UNION))); + + subs.set_content( + $rec_var, + Content::RecursionVar { + structure: tag_union_var, + opt_name: Some(rec_name_index), + }, + ); + tag_union_var + } + }; + (*$rec_var:ident) => { + |_: &mut Subs| { $rec_var } + }; + ($var:ident) => { + |_: &mut Subs| { Variable::$var } + }; } +macro_rules! test_hash_eq { + ($($name:ident: $synth1:expr, $synth2:expr)*) => {$( + #[test] + fn $name() { + #![allow(non_snake_case)] + check_hash(true, $synth1, $synth2) + } + )*}; +} + +macro_rules! test_hash_neq { + ($($name:ident: $synth1:expr, $synth2:expr)*) => {$( + #[test] + fn $name() { + #![allow(non_snake_case)] + check_hash(false, $synth1, $synth2) + } + )*}; +} + +// {{{ hash tests + +test_hash_eq! { + same_record: + v!({ a: v!(U8) }), v!({ a: v!(U8) }) + same_record_fields_diff_types: + v!({ a: v!(U8) }), v!({ a: v!(STR) }) + same_record_fields_any_order: + v!({ a: v!(U8), b: v!(U8), c: v!(U8) }), + v!({ c: v!(U8), a: v!(U8), b: v!(U8) }) + explicit_empty_record_and_implicit_empty_record: + v!(EMPTY_RECORD), v!({}) + + same_tag_union: + v!([ A v!(U8) v!(STR), B v!(STR) ]), v!([ A v!(U8) v!(STR), B v!(STR) ]) + same_tag_union_tags_diff_types: + v!([ A v!(U8) v!(U8), B v!(U8) ]), v!([ A v!(STR) v!(STR), B v!(STR) ]) + same_tag_union_tags_any_order: + v!([ A v!(U8) v!(U8), B v!(U8), C ]), v!([ C, B v!(STR), A v!(STR) v!(STR) ]) + explicit_empty_tag_union_and_implicit_empty_tag_union: + v!(EMPTY_TAG_UNION), v!([]) + + same_recursive_tag_union: + v!([ Nil, Cons v!(*lst)] as lst), v!([ Nil, Cons v!(*lst)] as lst) + same_tag_union_and_recursive_tag_union_fields: + v!([ Nil, Cons v!(STR)]), v!([ Nil, Cons v!(*lst)] as lst) +} + +test_hash_neq! { + different_record_fields: + v!({ a: v!(U8) }), v!({ b: v!(U8) }) + record_empty_vs_nonempty: + v!(EMPTY_RECORD), v!({ a: v!(U8) }) + + different_tag_union_tags: + v!([ A v!(U8) ]), v!([ B v!(U8) ]) + tag_union_empty_vs_nonempty: + v!(EMPTY_TAG_UNION), v!([ B v!(U8) ]) + different_recursive_tag_union_tags: + v!([ Nil, Cons v!(*lst) ] as lst), v!([ Nil, Next v!(*lst) ] as lst) +} + +// }}} hash tests + +// {{{ deriver tests + #[test] fn empty_record() { derive_test( - synth!(var Variable::EMPTY_RECORD), + v!(EMPTY_RECORD), indoc!( r#" \Test.0 -> (Encode.record [ ]) @@ -234,7 +372,7 @@ fn empty_record() { #[test] fn zero_field_record() { derive_test( - synth!({}), + v!({}), indoc!( r#" \Test.0 -> (Encode.record [ ]) @@ -246,7 +384,7 @@ fn zero_field_record() { #[test] fn one_field_record() { derive_test( - synth!({ "a": Variable::U8 }), + v!({ a: v!(U8) }), indoc!( r#" \Test.0 -> @@ -259,7 +397,7 @@ fn one_field_record() { #[test] fn two_field_record() { derive_test( - synth!({ "a": Variable::U8, "b": Variable::STR }), + v!({ a: v!(U8), b: v!(STR) }), indoc!( r#" \Test.0 -> @@ -271,3 +409,5 @@ fn two_field_record() { ), ) } + +// }}} deriver tests From 9e0f61a5964aec5a366f57d0c2698404d208c5df Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 12:32:13 -0400 Subject: [PATCH 31/53] Add more tests for builtin types and aliases --- compiler/test_derive/src/encoding.rs | 42 +++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index 1b08692603..14cfef033d 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -27,7 +27,7 @@ use roc_debug_flags::dbg_do; use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading}; use roc_module::{ ident::{ModuleName, TagName}, - symbol::{IdentIds, Interns, ModuleId}, + symbol::{IdentIds, Interns, ModuleId, Symbol}, }; use roc_mono::derive::{ deriver_hash::EncodingHash, @@ -38,10 +38,10 @@ use roc_region::all::{LineInfo, Region}; use roc_reporting::report::{type_problem, RocDocAllocator}; use roc_types::{ subs::{ - Content, ExposedTypesStorageSubs, FlatType, RecordFields, Subs, SubsIndex, UnionTags, - Variable, + AliasVariables, Content, ExposedTypesStorageSubs, FlatType, RecordFields, Subs, SubsIndex, + SubsSlice, UnionTags, Variable, }, - types::{RecordField, Type}, + types::{AliasKind, RecordField, Type}, }; fn encode_path() -> PathBuf { @@ -283,6 +283,21 @@ macro_rules! v { tag_union_var } }; + (Symbol::$sym:ident $($arg:expr)*) => { + |subs: &mut Subs| { + let $sym = vec![ $( $arg(subs) ,)* ]; + let var_slice = SubsSlice::insert_into_subs(subs, $sym); + synth_var(subs, Content::Structure(FlatType::Apply(Symbol::$sym, var_slice))) + } + }; + (Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => { + |subs: &mut Subs| { + let args = vec![$( $arg(subs) )*]; + let alias_variables = AliasVariables::insert_into_subs::, Vec<_>>(subs, args, vec![]); + let real_var = $real_var(subs); + synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Structural)) + } + }; (*$rec_var:ident) => { |_: &mut Subs| { $rec_var } }; @@ -337,6 +352,20 @@ test_hash_eq! { v!([ Nil, Cons v!(*lst)] as lst), v!([ Nil, Cons v!(*lst)] as lst) same_tag_union_and_recursive_tag_union_fields: v!([ Nil, Cons v!(STR)]), v!([ Nil, Cons v!(*lst)] as lst) + + list_list_diff_types: + v!(Symbol::LIST_LIST v!(STR)), v!(Symbol::LIST_LIST v!(U8)) + set_set_diff_types: + v!(Symbol::SET_SET v!(STR)), v!(Symbol::SET_SET v!(U8)) + dict_dict_diff_types: + v!(Symbol::DICT_DICT v!(STR) v!(STR)), v!(Symbol::DICT_DICT v!(U8) v!(U8)) + str_str: + v!(Symbol::STR_STR), v!(Symbol::STR_STR) + + alias_eq_real_type: + v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!([False, True]) + diff_alias_same_real_type: + v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([False, True])) } test_hash_neq! { @@ -351,6 +380,11 @@ test_hash_neq! { v!(EMPTY_TAG_UNION), v!([ B v!(U8) ]) different_recursive_tag_union_tags: v!([ Nil, Cons v!(*lst) ] as lst), v!([ Nil, Next v!(*lst) ] as lst) + + same_alias_diff_real_type: + v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::BOOL_BOOL => v!([ False, True, Maybe ])) + diff_alias_diff_real_type: + v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([ False, True, Maybe ])) } // }}} hash tests From f3a4d594fb31b5884cdc2d1fb64eb248f40df3a4 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 12:48:23 -0400 Subject: [PATCH 32/53] Add tests for opaques --- compiler/mono/src/derive/encoding.rs | 2 ++ compiler/test_derive/src/encoding.rs | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/compiler/mono/src/derive/encoding.rs b/compiler/mono/src/derive/encoding.rs index 1c05f3b98b..db2ea1c931 100644 --- a/compiler/mono/src/derive/encoding.rs +++ b/compiler/mono/src/derive/encoding.rs @@ -178,6 +178,8 @@ impl FlatEncodable<'_> { Symbol::NUM_DEC => FlatEncodable::Dec, Symbol::NUM_F32 => FlatEncodable::F32, Symbol::NUM_F64 => FlatEncodable::F64, + // TODO: I believe it is okay to unwrap opaques here because derivers are only used + // by the backend, and the backend treats opaques like structural aliases. _ => Self::from_var(subs, real_var), }, Content::RangedNumber(real_var, _) => Self::from_var(subs, real_var), diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index 14cfef033d..5dc1043fee 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -298,6 +298,14 @@ macro_rules! v { synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Structural)) } }; + (@Symbol::$alias:ident $($arg:expr)* => $real_var:expr) => { + |subs: &mut Subs| { + let args = vec![$( $arg(subs) )*]; + let alias_variables = AliasVariables::insert_into_subs::, Vec<_>>(subs, args, vec![]); + let real_var = $real_var(subs); + synth_var(subs, Content::Alias(Symbol::$alias, alias_variables, real_var, AliasKind::Opaque)) + } + }; (*$rec_var:ident) => { |_: &mut Subs| { $rec_var } }; @@ -366,6 +374,14 @@ test_hash_eq! { v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!([False, True]) diff_alias_same_real_type: v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([False, True])) + + opaque_eq_real_type: + v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!([False, True]) + diff_opaque_same_real_type: + v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(@Symbol::UNDERSCORE => v!([False, True])) + + opaque_real_type_eq_alias_real_type: + v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([False, True])) } test_hash_neq! { @@ -385,6 +401,11 @@ test_hash_neq! { v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::BOOL_BOOL => v!([ False, True, Maybe ])) diff_alias_diff_real_type: v!(Symbol::BOOL_BOOL => v!([ True, False ])), v!(Symbol::UNDERSCORE => v!([ False, True, Maybe ])) + + same_opaque_diff_real_type: + v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(@Symbol::BOOL_BOOL => v!([ False, True, Maybe ])) + diff_opaque_diff_real_type: + v!(@Symbol::BOOL_BOOL => v!([ True, False ])), v!(@Symbol::UNDERSCORE => v!([ False, True, Maybe ])) } // }}} hash tests From ed45c5c38f2848bacd36e817c060c5f7c54539cd Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 13:41:59 -0400 Subject: [PATCH 33/53] Create generic derivers rather than tied to type --- compiler/mono/src/derive/encoding.rs | 85 ++++++++++++++-------------- 1 file changed, 42 insertions(+), 43 deletions(-) diff --git a/compiler/mono/src/derive/encoding.rs b/compiler/mono/src/derive/encoding.rs index db2ea1c931..79b7a80319 100644 --- a/compiler/mono/src/derive/encoding.rs +++ b/compiler/mono/src/derive/encoding.rs @@ -228,50 +228,53 @@ fn verify_signature(env: &mut Env<'_>, signature: Variable) { } pub fn derive_to_encoder(env: &mut Env<'_>, for_var: Variable) -> Expr { - to_encoder_from_var(env, for_var) -} + match FlatEncodable::from_var(env.subs, for_var) { + FlatEncodable::U8 => todo!(), + FlatEncodable::U16 => todo!(), + FlatEncodable::U32 => todo!(), + FlatEncodable::U64 => todo!(), + FlatEncodable::U128 => todo!(), + FlatEncodable::I8 => todo!(), + FlatEncodable::I16 => todo!(), + FlatEncodable::I32 => todo!(), + FlatEncodable::I64 => todo!(), + FlatEncodable::I128 => todo!(), + FlatEncodable::Dec => todo!(), + FlatEncodable::F32 => todo!(), + FlatEncodable::F64 => todo!(), + FlatEncodable::List() => todo!(), + FlatEncodable::Set() => todo!(), + FlatEncodable::Dict() => todo!(), + FlatEncodable::Str => todo!(), + FlatEncodable::Record(fields) => { + // Generalized record var so we can reuse this impl between many records: + // if fields = { a, b }, this is { a: t1, b: t2 } for fresh t1, t2. + let flex_fields = fields + .iter() + .copied() + .cloned() + .collect::>() + .into_iter() + .map(|name| { + ( + name, + RecordField::Required(env.subs.fresh_unnamed_flex_var()), + ) + }) + .collect::>(); + let fields = RecordFields::insert_into_subs(env.subs, flex_fields); + let record_var = synth_var( + env.subs, + Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD)), + ); -fn to_encoder_from_var(env: &mut Env<'_>, mut var: Variable) -> Expr { - loop { - match *env.subs.get_content_without_compacting(var) { - Content::Alias(_, _, real_var, _) => var = real_var, - Content::RangedNumber(real_var, _) => var = real_var, - - Content::RecursionVar { .. } => todo!(), - Content::LambdaSet(_) => todo!(), - Content::Structure(flat_type) => match flat_type { - FlatType::Record(fields, ext_var) => { - return to_encoder_record(env, var, fields, ext_var) - } - FlatType::EmptyRecord => { - return to_encoder_record(env, var, RecordFields::empty(), var) - } - - FlatType::Apply(_, _) => todo!(), - FlatType::TagUnion(_, _) => todo!(), - FlatType::FunctionOrTagUnion(_, _, _) => todo!(), - FlatType::RecursiveTagUnion(_, _, _) => todo!(), - FlatType::EmptyTagUnion => todo!(), - - FlatType::Func(..) => bad_input!(env.subs, var, "functions cannot be encoded"), - FlatType::Erroneous(_) => bad_input!(env.subs, var), - }, - - Content::FlexVar(_) - | Content::RigidVar(_) - | Content::FlexAbleVar(_, _) - | Content::RigidAbleVar(_, _) => bad_input!(env.subs, var, "unresolved variable"), - Content::Error => bad_input!(env.subs, var), + to_encoder_record(env, record_var, fields) } + FlatEncodable::TagUnion(_) => todo!(), } } -fn to_encoder_record( - env: &mut Env<'_>, - record_var: Variable, - fields: RecordFields, - ext_var: Variable, -) -> Expr { +fn to_encoder_record(env: &mut Env<'_>, record_var: Variable, fields: RecordFields) -> Expr { // Suppose rcd = { a: t1, b: t2 }. Build // // \rcd -> Encode.record [ @@ -279,10 +282,6 @@ fn to_encoder_record( // { key: "b", value: Encode.toEncoder rcd.b }, // ] - debug_assert!(matches!( - env.subs.get_content_without_compacting(ext_var), - Content::Structure(FlatType::EmptyRecord) - )); let rcd_sym = env.unique_symbol(); let whole_rcd_var = env.subs.fresh_unnamed_flex_var(); // type of the { key, value } records in the list From d621834af374017104c45a85ed3a87dfb2beaa0d Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 13:47:06 -0400 Subject: [PATCH 34/53] Test expected derived type --- compiler/test_derive/src/encoding.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index 5dc1043fee..fe28252593 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -37,6 +37,7 @@ use roc_mono::derive::{ use roc_region::all::{LineInfo, Region}; use roc_reporting::report::{type_problem, RocDocAllocator}; use roc_types::{ + pretty_print::{name_and_print_var, DebugPrint}, subs::{ AliasVariables, Content, ExposedTypesStorageSubs, FlatType, RecordFields, Subs, SubsIndex, SubsSlice, UnionTags, Variable, @@ -60,6 +61,7 @@ fn check_derived_typechecks( interns: &Interns, exposed_encode_types: ExposedTypesStorageSubs, encode_abilities_store: AbilitiesStore, + expected_type: &str, ) { // constrain the derived let mut constraints = Constraints::new(); @@ -68,12 +70,13 @@ fn check_derived_typechecks( resolutions_to_make: Default::default(), home: test_module, }; + let real_type = test_subs.fresh_unnamed_flex_var(); let constr = constrain_expr( &mut constraints, &mut env, Region::zero(), &derived, - Expected::NoExpectation(Type::Variable(test_subs.fresh_unnamed_flex_var())), + Expected::NoExpectation(Type::Variable(real_type)), ); // the derived depends on stuff from Encode, so @@ -113,7 +116,7 @@ fn check_derived_typechecks( roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, "1") ); - let (_solved_subs, _, problems, _) = roc_solve::module::run_solve( + let (mut solved_subs, _, problems, _) = roc_solve::module::run_solve( &constraints, constr, RigidVariables::default(), @@ -122,6 +125,7 @@ fn check_derived_typechecks( abilities_store, Default::default(), ); + let subs = solved_subs.inner_mut(); if !problems.is_empty() { let filename = PathBuf::from("Test.roc"); @@ -153,9 +157,14 @@ fn check_derived_typechecks( panic!("Derived does not typecheck:\n{}", buf); } + + let pretty_type = + name_and_print_var(real_type, subs, test_module, interns, DebugPrint::NOTHING); + + assert_eq!(expected_type, pretty_type); } -fn derive_test(synth_input: S, expected: &str) +fn derive_test(synth_input: S, expected_type: &str, expected_source: &str) where S: FnOnce(&mut Subs) -> Variable, { @@ -201,7 +210,7 @@ where let ctx = Ctx { interns: &interns }; let derived_program = pretty_print(&ctx, &derived); - assert_eq!(expected, derived_program); + assert_eq!(expected_source, derived_program); check_derived_typechecks( derived, @@ -210,6 +219,7 @@ where &interns, exposed_encode_types, abilities_store, + expected_type, ); } @@ -416,6 +426,7 @@ test_hash_neq! { fn empty_record() { derive_test( v!(EMPTY_RECORD), + "{} -> Encoder fmt | fmt has EncoderFormatting", indoc!( r#" \Test.0 -> (Encode.record [ ]) @@ -428,6 +439,7 @@ fn empty_record() { fn zero_field_record() { derive_test( v!({}), + "{} -> Encoder fmt | fmt has EncoderFormatting", indoc!( r#" \Test.0 -> (Encode.record [ ]) @@ -440,6 +452,7 @@ fn zero_field_record() { fn one_field_record() { derive_test( v!({ a: v!(U8) }), + "{ a : val } -> Encoder fmt | fmt has EncoderFormatting, val has Encoding", indoc!( r#" \Test.0 -> @@ -453,6 +466,7 @@ fn one_field_record() { fn two_field_record() { derive_test( v!({ a: v!(U8), b: v!(STR) }), + "{ a : val, b : a } -> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding", indoc!( r#" \Test.0 -> From 834f75099f9e2e2f9c18d0c18d1ce18fddba8b6a Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 14:25:39 -0400 Subject: [PATCH 35/53] Move derive content hashing to its own crate --- Cargo.lock | 14 ++ Cargo.toml | 1 + compiler/derive_hash/Cargo.toml | 14 ++ compiler/derive_hash/src/encoding.rs | 127 ++++++++++++++++++ .../src/lib.rs} | 24 ++-- compiler/mono/Cargo.toml | 1 + compiler/mono/src/derive/encoding.rs | 118 +--------------- compiler/mono/src/derive/mod.rs | 2 - compiler/test_derive/Cargo.toml | 1 + compiler/test_derive/src/encoding.rs | 6 +- 10 files changed, 177 insertions(+), 131 deletions(-) create mode 100644 compiler/derive_hash/Cargo.toml create mode 100644 compiler/derive_hash/src/encoding.rs rename compiler/{mono/src/derive/deriver_hash.rs => derive_hash/src/lib.rs} (74%) diff --git a/Cargo.lock b/Cargo.lock index 138670177a..b1d9a29536 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3670,6 +3670,18 @@ dependencies = [ name = "roc_debug_flags" version = "0.1.0" +[[package]] +name = "roc_derive_hash" +version = "0.1.0" +dependencies = [ + "roc_can", + "roc_collections", + "roc_error_macros", + "roc_module", + "roc_region", + "roc_types", +] + [[package]] name = "roc_docs" version = "0.1.0" @@ -3951,6 +3963,7 @@ dependencies = [ "roc_can", "roc_collections", "roc_debug_flags", + "roc_derive_hash", "roc_error_macros", "roc_exhaustive", "roc_late_solve", @@ -4810,6 +4823,7 @@ dependencies = [ "roc_collections", "roc_constrain", "roc_debug_flags", + "roc_derive_hash", "roc_load_internal", "roc_module", "roc_mono", diff --git a/Cargo.toml b/Cargo.toml index 133550c56a..ebd1f481ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "compiler/solve", "compiler/late_solve", "compiler/fmt", + "compiler/derive_hash", "compiler/mono", "compiler/alias_analysis", "compiler/test_mono", diff --git a/compiler/derive_hash/Cargo.toml b/compiler/derive_hash/Cargo.toml new file mode 100644 index 0000000000..0a77773e6b --- /dev/null +++ b/compiler/derive_hash/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "roc_derive_hash" +version = "0.1.0" +authors = ["The Roc Contributors"] +license = "UPL-1.0" +edition = "2021" + +[dependencies] +roc_collections = { path = "../collections" } +roc_error_macros = { path = "../../error_macros" } +roc_region = { path = "../region" } +roc_module = { path = "../module" } +roc_types = { path = "../types" } +roc_can = { path = "../can" } diff --git a/compiler/derive_hash/src/encoding.rs b/compiler/derive_hash/src/encoding.rs new file mode 100644 index 0000000000..34ff52c30c --- /dev/null +++ b/compiler/derive_hash/src/encoding.rs @@ -0,0 +1,127 @@ +use roc_error_macros::internal_error; +use roc_module::{ + ident::{Lowercase, TagName}, + symbol::Symbol, +}; +use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, SubsFmtContent, Variable}; + +#[derive(Hash)] +pub enum FlatEncodable<'a> { + U8, + U16, + U32, + U64, + U128, + I8, + I16, + I32, + I64, + I128, + Dec, + F32, + F64, + List(/* takes one variable */), + Set(/* takes one variable */), + Dict(/* takes two variables */), + Str, + // Unfortunate that we must allocate here, c'est la vie + Record(Vec<&'a Lowercase>), + TagUnion(Vec<(&'a TagName, u16)>), +} + +macro_rules! unexpected { + ($subs:expr, $var:expr) => { + internal_error!( + "Invalid content for toEncoder: {:?}", + SubsFmtContent($subs.get_content_without_compacting($var), $subs) + ) + }; +} + +impl FlatEncodable<'_> { + pub(crate) fn from_var(subs: &Subs, var: Variable) -> FlatEncodable { + match *subs.get_content_without_compacting(var) { + Content::Structure(flat_type) => match flat_type { + FlatType::Apply(sym, _) => match sym { + Symbol::LIST_LIST => FlatEncodable::List(), + Symbol::SET_SET => FlatEncodable::Set(), + Symbol::DICT_DICT => FlatEncodable::Dict(), + Symbol::STR_STR => FlatEncodable::Str, + _ => unexpected!(subs, var), + }, + FlatType::Record(fields, ext) => { + debug_assert!(matches!( + subs.get_content_without_compacting(ext), + Content::Structure(FlatType::EmptyRecord) + )); + + let mut field_names: Vec<_> = + subs.get_subs_slice(fields.field_names()).iter().collect(); + field_names.sort(); + FlatEncodable::Record(field_names) + } + FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => { + // The recursion var doesn't matter, because the derived implementation will only + // look on the surface of the tag union type, and more over the payloads of the + // arguments will be left generic for the monomorphizer to fill in with the + // appropriate type. That is, + // [ A t1, B t1 t2 ] + // and + // [ A t1, B t1 t2 ] as R + // look the same on the surface, because `R` is only somewhere inside of the + // `t`-prefixed payload types. + debug_assert!(matches!( + subs.get_content_without_compacting(ext), + Content::Structure(FlatType::EmptyTagUnion) + )); + let mut tag_names_and_payload_sizes: Vec<_> = tags + .iter_all() + .map(|(name_index, payload_slice_index)| { + let payload_slice = subs[payload_slice_index]; + let payload_size = payload_slice.length; + let name = &subs[name_index]; + (name, payload_size) + }) + .collect(); + tag_names_and_payload_sizes.sort_by_key(|t| t.0); + FlatEncodable::TagUnion(tag_names_and_payload_sizes) + } + FlatType::FunctionOrTagUnion(name_index, _, _) => { + FlatEncodable::TagUnion(vec![(&subs[name_index], 0)]) + } + FlatType::EmptyRecord => FlatEncodable::Record(vec![]), + FlatType::EmptyTagUnion => FlatEncodable::TagUnion(vec![]), + // + FlatType::Erroneous(_) => unexpected!(subs, var), + FlatType::Func(..) => unexpected!(subs, var), + }, + Content::Alias(sym, _, real_var, _) => match sym { + Symbol::NUM_U8 => FlatEncodable::U8, + Symbol::NUM_U16 => FlatEncodable::U16, + Symbol::NUM_U32 => FlatEncodable::U32, + Symbol::NUM_U64 => FlatEncodable::U64, + Symbol::NUM_U128 => FlatEncodable::U128, + Symbol::NUM_I8 => FlatEncodable::I8, + Symbol::NUM_I16 => FlatEncodable::I16, + Symbol::NUM_I32 => FlatEncodable::I32, + Symbol::NUM_I64 => FlatEncodable::I64, + Symbol::NUM_I128 => FlatEncodable::I128, + Symbol::NUM_DEC => FlatEncodable::Dec, + Symbol::NUM_F32 => FlatEncodable::F32, + Symbol::NUM_F64 => FlatEncodable::F64, + // TODO: I believe it is okay to unwrap opaques here because derivers are only used + // by the backend, and the backend treats opaques like structural aliases. + _ => Self::from_var(subs, real_var), + }, + Content::RangedNumber(real_var, _) => Self::from_var(subs, real_var), + // + Content::RecursionVar { .. } => unexpected!(subs, var), + Content::Error => unexpected!(subs, var), + Content::FlexVar(_) + | Content::RigidVar(_) + | Content::FlexAbleVar(_, _) + | Content::RigidAbleVar(_, _) => unexpected!(subs, var), + Content::LambdaSet(_) => unexpected!(subs, var), + } + } +} diff --git a/compiler/mono/src/derive/deriver_hash.rs b/compiler/derive_hash/src/lib.rs similarity index 74% rename from compiler/mono/src/derive/deriver_hash.rs rename to compiler/derive_hash/src/lib.rs index 801d64abd3..ac393d28fa 100644 --- a/compiler/mono/src/derive/deriver_hash.rs +++ b/compiler/derive_hash/src/lib.rs @@ -12,9 +12,14 @@ //! //! For these reasons the content hashing is based on a [`Strategy`] as well. +pub mod encoding; + +use encoding::FlatEncodable; + use roc_types::subs::{Subs, Variable}; #[derive(Hash)] +#[repr(u8)] enum Strategy { Encoding, #[allow(unused)] @@ -22,24 +27,19 @@ enum Strategy { } #[derive(Hash)] -struct DeriverHashImpl +pub struct DeriveHash where - H: std::hash::Hash, + R: std::hash::Hash, { strategy: Strategy, - hash: H, + pub repr: R, } -pub trait DeriverHash: std::hash::Hash {} - -impl DeriverHash for DeriverHashImpl where H: std::hash::Hash {} - -pub struct EncodingHash; -impl EncodingHash { - pub fn from_var(subs: &Subs, var: Variable) -> impl DeriverHash + '_ { - DeriverHashImpl { +impl<'a> DeriveHash> { + pub fn encoding(subs: &'a Subs, var: Variable) -> Self { + DeriveHash { strategy: Strategy::Encoding, - hash: super::encoding::FlatEncodable::from_var(subs, var), + repr: encoding::FlatEncodable::from_var(subs, var), } } } diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index 04aa87b880..5660046154 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -12,6 +12,7 @@ roc_region = { path = "../region" } roc_module = { path = "../module" } roc_types = { path = "../types" } roc_can = { path = "../can" } +roc_derive_hash = { path = "../derive_hash" } roc_late_solve = { path = "../late_solve" } roc_std = { path = "../../roc_std", default-features = false } roc_problem = { path = "../problem" } diff --git a/compiler/mono/src/derive/encoding.rs b/compiler/mono/src/derive/encoding.rs index 79b7a80319..76d5a96a0c 100644 --- a/compiler/mono/src/derive/encoding.rs +++ b/compiler/mono/src/derive/encoding.rs @@ -8,10 +8,12 @@ use roc_can::abilities::AbilitiesStore; use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Field, Recursive}; use roc_can::pattern::Pattern; use roc_collections::SendMap; +use roc_derive_hash::encoding::FlatEncodable; +use roc_derive_hash::DeriveHash; use roc_error_macros::internal_error; use roc_late_solve::{instantiate_rigids, AbilitiesView}; use roc_module::called_via::CalledVia; -use roc_module::ident::{Lowercase, TagName}; +use roc_module::ident::Lowercase; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_region::all::{Loc, Region}; use roc_types::subs::{ @@ -83,118 +85,6 @@ impl Env<'_> { } } -#[derive(Hash)] -pub(super) enum FlatEncodable<'a> { - U8, - U16, - U32, - U64, - U128, - I8, - I16, - I32, - I64, - I128, - Dec, - F32, - F64, - List(/* takes one variable */), - Set(/* takes one variable */), - Dict(/* takes two variables */), - Str, - // Unfortunate that we must allocate here, c'est la vie - Record(Vec<&'a Lowercase>), - TagUnion(Vec<(&'a TagName, u16)>), -} - -impl FlatEncodable<'_> { - pub fn from_var(subs: &Subs, var: Variable) -> FlatEncodable { - match *subs.get_content_without_compacting(var) { - Content::Structure(flat_type) => match flat_type { - FlatType::Apply(sym, _) => match sym { - Symbol::LIST_LIST => FlatEncodable::List(), - Symbol::SET_SET => FlatEncodable::Set(), - Symbol::DICT_DICT => FlatEncodable::Dict(), - Symbol::STR_STR => FlatEncodable::Str, - _ => bad_input!(subs, var), - }, - FlatType::Record(fields, ext) => { - debug_assert!(matches!( - subs.get_content_without_compacting(ext), - Content::Structure(FlatType::EmptyRecord) - )); - - let mut field_names: Vec<_> = - subs.get_subs_slice(fields.field_names()).iter().collect(); - field_names.sort(); - FlatEncodable::Record(field_names) - } - FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => { - // The recursion var doesn't matter, because the derived implementation will only - // look on the surface of the tag union type, and more over the payloads of the - // arguments will be left generic for the monomorphizer to fill in with the - // appropriate type. That is, - // [ A t1, B t1 t2 ] - // and - // [ A t1, B t1 t2 ] as R - // look the same on the surface, because `R` is only somewhere inside of the - // `t`-prefixed payload types. - debug_assert!(matches!( - subs.get_content_without_compacting(ext), - Content::Structure(FlatType::EmptyTagUnion) - )); - let mut tag_names_and_payload_sizes: Vec<_> = tags - .iter_all() - .map(|(name_index, payload_slice_index)| { - let payload_slice = subs[payload_slice_index]; - let payload_size = payload_slice.length; - let name = &subs[name_index]; - (name, payload_size) - }) - .collect(); - tag_names_and_payload_sizes.sort_by_key(|t| t.0); - FlatEncodable::TagUnion(tag_names_and_payload_sizes) - } - FlatType::FunctionOrTagUnion(name_index, _, _) => { - FlatEncodable::TagUnion(vec![(&subs[name_index], 0)]) - } - FlatType::EmptyRecord => FlatEncodable::Record(vec![]), - FlatType::EmptyTagUnion => FlatEncodable::TagUnion(vec![]), - // - FlatType::Erroneous(_) => bad_input!(subs, var), - FlatType::Func(..) => bad_input!(subs, var, "functions cannot be encoded"), - }, - Content::Alias(sym, _, real_var, _) => match sym { - Symbol::NUM_U8 => FlatEncodable::U8, - Symbol::NUM_U16 => FlatEncodable::U16, - Symbol::NUM_U32 => FlatEncodable::U32, - Symbol::NUM_U64 => FlatEncodable::U64, - Symbol::NUM_U128 => FlatEncodable::U128, - Symbol::NUM_I8 => FlatEncodable::I8, - Symbol::NUM_I16 => FlatEncodable::I16, - Symbol::NUM_I32 => FlatEncodable::I32, - Symbol::NUM_I64 => FlatEncodable::I64, - Symbol::NUM_I128 => FlatEncodable::I128, - Symbol::NUM_DEC => FlatEncodable::Dec, - Symbol::NUM_F32 => FlatEncodable::F32, - Symbol::NUM_F64 => FlatEncodable::F64, - // TODO: I believe it is okay to unwrap opaques here because derivers are only used - // by the backend, and the backend treats opaques like structural aliases. - _ => Self::from_var(subs, real_var), - }, - Content::RangedNumber(real_var, _) => Self::from_var(subs, real_var), - // - Content::RecursionVar { .. } => bad_input!(subs, var), - Content::Error => bad_input!(subs, var), - Content::FlexVar(_) - | Content::RigidVar(_) - | Content::FlexAbleVar(_, _) - | Content::RigidAbleVar(_, _) => bad_input!(subs, var, "flex vars cannot be encoded"), - Content::LambdaSet(_) => bad_input!(subs, var, "errors cannot be encoded"), - } - } -} - // TODO: decide whether it will be better to pass the whole signature, or just the argument type. // For now we are only using the argument type for convinience of testing. #[allow(dead_code)] @@ -228,7 +118,7 @@ fn verify_signature(env: &mut Env<'_>, signature: Variable) { } pub fn derive_to_encoder(env: &mut Env<'_>, for_var: Variable) -> Expr { - match FlatEncodable::from_var(env.subs, for_var) { + match DeriveHash::encoding(env.subs, for_var).repr { FlatEncodable::U8 => todo!(), FlatEncodable::U16 => todo!(), FlatEncodable::U32 => todo!(), diff --git a/compiler/mono/src/derive/mod.rs b/compiler/mono/src/derive/mod.rs index 9e1f235d57..531470625a 100644 --- a/compiler/mono/src/derive/mod.rs +++ b/compiler/mono/src/derive/mod.rs @@ -15,6 +15,4 @@ pub fn synth_var(subs: &mut Subs, content: Content) -> Variable { subs.fresh(descriptor) } -pub mod deriver_hash; - pub mod encoding; diff --git a/compiler/test_derive/Cargo.toml b/compiler/test_derive/Cargo.toml index 27a67cc59f..3226b51dd9 100644 --- a/compiler/test_derive/Cargo.toml +++ b/compiler/test_derive/Cargo.toml @@ -15,6 +15,7 @@ roc_module = { path = "../module" } roc_builtins = { path = "../builtins" } roc_load_internal = { path = "../load_internal" } roc_can = { path = "../can" } +roc_derive_hash = { path = "../derive_hash" } roc_mono = { path = "../mono" } roc_target = { path = "../roc_target" } roc_types = { path = "../types" } diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index fe28252593..dab191026d 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -24,13 +24,13 @@ use roc_constrain::{ module::{ExposedByModule, ExposedForModule, ExposedModuleTypes}, }; use roc_debug_flags::dbg_do; +use roc_derive_hash::DeriveHash; use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading}; use roc_module::{ ident::{ModuleName, TagName}, symbol::{IdentIds, Interns, ModuleId, Symbol}, }; use roc_mono::derive::{ - deriver_hash::EncodingHash, encoding::{self, Env}, synth_var, }; @@ -232,8 +232,8 @@ where let var1 = synth1(&mut subs); let var2 = synth2(&mut subs); - let hash1 = EncodingHash::from_var(&subs, var1); - let hash2 = EncodingHash::from_var(&subs, var2); + let hash1 = DeriveHash::encoding(&subs, var1); + let hash2 = DeriveHash::encoding(&subs, var2); let hash1 = { let mut hasher = default_hasher().build_hasher(); From 420458b1003e4f7d920c41656d4c861511d4de07 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 15:19:49 -0400 Subject: [PATCH 36/53] Print timings across make specialization passes Now that we may run make specialization passes multiple times, we should record and print all timings, not just one. --- cli/src/build.rs | 24 +++++++++--------------- compiler/load_internal/src/file.rs | 18 +++++++++++------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/cli/src/build.rs b/cli/src/build.rs index a485a7fde1..abad31d730 100644 --- a/cli/src/build.rs +++ b/cli/src/build.rs @@ -176,11 +176,15 @@ pub fn build_file<'a>( "Find Specializations", module_timing.find_specializations, ); - report_timing( - buf, - "Make Specializations", - module_timing.make_specializations, - ); + let multiple_make_specializations_passes = module_timing.make_specializations.len() > 1; + for (i, pass_time) in module_timing.make_specializations.iter().enumerate() { + let suffix = if multiple_make_specializations_passes { + format!(" (Pass {})", i) + } else { + String::new() + }; + report_timing(buf, &format!("Make Specializations{}", suffix), *pass_time); + } report_timing(buf, "Other", module_timing.other()); buf.push('\n'); report_timing(buf, "Total", module_timing.total()); @@ -460,16 +464,6 @@ pub fn check_file( report_timing(buf, "Canonicalize", module_timing.canonicalize); report_timing(buf, "Constrain", module_timing.constrain); report_timing(buf, "Solve", module_timing.solve); - report_timing( - buf, - "Find Specializations", - module_timing.find_specializations, - ); - report_timing( - buf, - "Make Specializations", - module_timing.make_specializations, - ); report_timing(buf, "Other", module_timing.other()); buf.push('\n'); report_timing(buf, "Total", module_timing.total()); diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index 13bdbe0d22..71e0ab6158 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -877,7 +877,8 @@ pub struct ModuleTiming { pub constrain: Duration, pub solve: Duration, pub find_specializations: Duration, - pub make_specializations: Duration, + // indexed by make specializations pass + pub make_specializations: Vec, // TODO pub monomorphize: Duration, /// Total duration will always be more than the sum of the other fields, due /// to things like state lookups in between phases, waiting on other threads, etc. @@ -895,7 +896,7 @@ impl ModuleTiming { constrain: Duration::default(), solve: Duration::default(), find_specializations: Duration::default(), - make_specializations: Duration::default(), + make_specializations: Vec::with_capacity(2), start_time, end_time: start_time, // just for now; we'll overwrite this at the end } @@ -921,8 +922,9 @@ impl ModuleTiming { } = self; let calculate = |t: Result| -> Option { - t.ok()? - .checked_sub(*make_specializations)? + make_specializations + .iter() + .fold(t.ok(), |t, pass_time| t?.checked_sub(*pass_time))? .checked_sub(*find_specializations)? .checked_sub(*solve)? .checked_sub(*constrain)? @@ -4407,9 +4409,11 @@ fn make_specializations<'a>( mono_env.home.register_debug_idents(mono_env.ident_ids); let make_specializations_end = SystemTime::now(); - module_timing.make_specializations = make_specializations_end - .duration_since(make_specializations_start) - .unwrap(); + module_timing.make_specializations.push( + make_specializations_end + .duration_since(make_specializations_start) + .unwrap(), + ); Msg::MadeSpecializations { module_id: home, From 5c1f003242d688d1e80b3a391ea0d8ec8151c1df Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 16:32:33 -0400 Subject: [PATCH 37/53] Call it derive keying --- Cargo.lock | 6 +++--- Cargo.toml | 2 +- compiler/{derive_hash => derive_key}/Cargo.toml | 2 +- compiler/{derive_hash => derive_key}/src/encoding.rs | 0 compiler/{derive_hash => derive_key}/src/lib.rs | 10 +++++----- compiler/mono/Cargo.toml | 2 +- compiler/mono/src/derive/encoding.rs | 6 +++--- compiler/test_derive/Cargo.toml | 2 +- compiler/test_derive/src/encoding.rs | 6 +++--- 9 files changed, 18 insertions(+), 18 deletions(-) rename compiler/{derive_hash => derive_key}/Cargo.toml (93%) rename compiler/{derive_hash => derive_key}/src/encoding.rs (100%) rename compiler/{derive_hash => derive_key}/src/lib.rs (82%) diff --git a/Cargo.lock b/Cargo.lock index b1d9a29536..cb42e21bf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3671,7 +3671,7 @@ name = "roc_debug_flags" version = "0.1.0" [[package]] -name = "roc_derive_hash" +name = "roc_derive_key" version = "0.1.0" dependencies = [ "roc_can", @@ -3963,7 +3963,7 @@ dependencies = [ "roc_can", "roc_collections", "roc_debug_flags", - "roc_derive_hash", + "roc_derive_key", "roc_error_macros", "roc_exhaustive", "roc_late_solve", @@ -4823,7 +4823,7 @@ dependencies = [ "roc_collections", "roc_constrain", "roc_debug_flags", - "roc_derive_hash", + "roc_derive_key", "roc_load_internal", "roc_module", "roc_mono", diff --git a/Cargo.toml b/Cargo.toml index ebd1f481ed..0c6dfa106d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ members = [ "compiler/solve", "compiler/late_solve", "compiler/fmt", - "compiler/derive_hash", + "compiler/derive_key", "compiler/mono", "compiler/alias_analysis", "compiler/test_mono", diff --git a/compiler/derive_hash/Cargo.toml b/compiler/derive_key/Cargo.toml similarity index 93% rename from compiler/derive_hash/Cargo.toml rename to compiler/derive_key/Cargo.toml index 0a77773e6b..e674089371 100644 --- a/compiler/derive_hash/Cargo.toml +++ b/compiler/derive_key/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "roc_derive_hash" +name = "roc_derive_key" version = "0.1.0" authors = ["The Roc Contributors"] license = "UPL-1.0" diff --git a/compiler/derive_hash/src/encoding.rs b/compiler/derive_key/src/encoding.rs similarity index 100% rename from compiler/derive_hash/src/encoding.rs rename to compiler/derive_key/src/encoding.rs diff --git a/compiler/derive_hash/src/lib.rs b/compiler/derive_key/src/lib.rs similarity index 82% rename from compiler/derive_hash/src/lib.rs rename to compiler/derive_key/src/lib.rs index ac393d28fa..c263e671de 100644 --- a/compiler/derive_hash/src/lib.rs +++ b/compiler/derive_key/src/lib.rs @@ -1,5 +1,5 @@ //! To avoid duplicating derived implementations for the same type, derived implementations are -//! addressed by a hash of their type content. However, different derived implementations can be +//! addressed by a key of their type content. However, different derived implementations can be //! reused based on different properties of the type. For example: //! //! - `Eq` does not care about surface type representations; its derived implementations can be @@ -10,7 +10,7 @@ //! - `Decoding` is like encoding, but has some differences. For one, it *does* need to distinguish //! between required and optional record fields. //! -//! For these reasons the content hashing is based on a [`Strategy`] as well. +//! For these reasons the content keying is based on a [`Strategy`] as well. pub mod encoding; @@ -27,7 +27,7 @@ enum Strategy { } #[derive(Hash)] -pub struct DeriveHash +pub struct DeriveKey where R: std::hash::Hash, { @@ -35,9 +35,9 @@ where pub repr: R, } -impl<'a> DeriveHash> { +impl<'a> DeriveKey> { pub fn encoding(subs: &'a Subs, var: Variable) -> Self { - DeriveHash { + DeriveKey { strategy: Strategy::Encoding, repr: encoding::FlatEncodable::from_var(subs, var), } diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index 5660046154..fb5a44434c 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -12,7 +12,7 @@ roc_region = { path = "../region" } roc_module = { path = "../module" } roc_types = { path = "../types" } roc_can = { path = "../can" } -roc_derive_hash = { path = "../derive_hash" } +roc_derive_key = { path = "../derive_key" } roc_late_solve = { path = "../late_solve" } roc_std = { path = "../../roc_std", default-features = false } roc_problem = { path = "../problem" } diff --git a/compiler/mono/src/derive/encoding.rs b/compiler/mono/src/derive/encoding.rs index 76d5a96a0c..5e34ca540a 100644 --- a/compiler/mono/src/derive/encoding.rs +++ b/compiler/mono/src/derive/encoding.rs @@ -8,8 +8,8 @@ use roc_can::abilities::AbilitiesStore; use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Field, Recursive}; use roc_can::pattern::Pattern; use roc_collections::SendMap; -use roc_derive_hash::encoding::FlatEncodable; -use roc_derive_hash::DeriveHash; +use roc_derive_key::encoding::FlatEncodable; +use roc_derive_key::DeriveKey; use roc_error_macros::internal_error; use roc_late_solve::{instantiate_rigids, AbilitiesView}; use roc_module::called_via::CalledVia; @@ -118,7 +118,7 @@ fn verify_signature(env: &mut Env<'_>, signature: Variable) { } pub fn derive_to_encoder(env: &mut Env<'_>, for_var: Variable) -> Expr { - match DeriveHash::encoding(env.subs, for_var).repr { + match DeriveKey::encoding(env.subs, for_var).repr { FlatEncodable::U8 => todo!(), FlatEncodable::U16 => todo!(), FlatEncodable::U32 => todo!(), diff --git a/compiler/test_derive/Cargo.toml b/compiler/test_derive/Cargo.toml index 3226b51dd9..1633ef165d 100644 --- a/compiler/test_derive/Cargo.toml +++ b/compiler/test_derive/Cargo.toml @@ -15,7 +15,7 @@ roc_module = { path = "../module" } roc_builtins = { path = "../builtins" } roc_load_internal = { path = "../load_internal" } roc_can = { path = "../can" } -roc_derive_hash = { path = "../derive_hash" } +roc_derive_key = { path = "../derive_key" } roc_mono = { path = "../mono" } roc_target = { path = "../roc_target" } roc_types = { path = "../types" } diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index dab191026d..14524ba23f 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -24,7 +24,7 @@ use roc_constrain::{ module::{ExposedByModule, ExposedForModule, ExposedModuleTypes}, }; use roc_debug_flags::dbg_do; -use roc_derive_hash::DeriveHash; +use roc_derive_key::DeriveKey; use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading}; use roc_module::{ ident::{ModuleName, TagName}, @@ -232,8 +232,8 @@ where let var1 = synth1(&mut subs); let var2 = synth2(&mut subs); - let hash1 = DeriveHash::encoding(&subs, var1); - let hash2 = DeriveHash::encoding(&subs, var2); + let hash1 = DeriveKey::encoding(&subs, var1); + let hash2 = DeriveKey::encoding(&subs, var2); let hash1 = { let mut hasher = default_hasher().build_hasher(); From 0dac546aba8b52d85801eee5068128ee9800e14b Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 15 Jun 2022 22:53:16 +0200 Subject: [PATCH 38/53] canonicalize toplevel expect --- compiler/can/src/def.rs | 79 ++++++++++++++++--- .../hello-world/zig-platform/helloZig.roc | 2 + 2 files changed, 71 insertions(+), 10 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index a22ccc5624..7f99094d9d 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -79,10 +79,28 @@ pub struct Annotation { #[derive(Debug)] pub(crate) struct CanDefs { defs: Vec>, + expects: Expects, def_ordering: DefOrdering, aliases: VecMap, } +#[derive(Debug)] +struct Expects { + conditions: Vec, + regions: Vec, + preceding_comment: Vec, +} + +impl Expects { + fn with_capacity(capacity: usize) -> Self { + Self { + conditions: Vec::with_capacity(capacity), + regions: Vec::with_capacity(capacity), + preceding_comment: Vec::with_capacity(capacity), + } + } +} + /// A Def that has had patterns and type annnotations canonicalized, /// but no Expr canonicalization has happened yet. Also, it has had spaces /// and nesting resolved, and knows whether annotations are standalone or not. @@ -541,18 +559,21 @@ fn canonicalize_value_defs<'a>( // the ast::Expr values in pending_exprs for further canonicalization // once we've finished assembling the entire scope. let mut pending_value_defs = Vec::with_capacity(value_defs.len()); + let mut pending_expects = Vec::with_capacity(value_defs.len()); + for loc_def in value_defs { let mut new_output = Output::default(); - match to_pending_value_def( + let pending = to_pending_value_def( env, var_store, loc_def.value, scope, &mut new_output, pattern_type, - ) { - None => { /* skip */ } - Some(pending_def) => { + ); + + match pending { + PendingValue::Def(pending_def) => { // Record the ast::Expr for later. We'll do another pass through these // once we have the entire scope assembled. If we were to canonicalize // the exprs right now, they wouldn't have symbols in scope from defs @@ -560,6 +581,10 @@ fn canonicalize_value_defs<'a>( pending_value_defs.push(pending_def); output.union(new_output); } + PendingValue::SignatureDefMismatch => { /* skip */ } + PendingValue::Expect(pending_expect) => { + pending_expects.push(pending_expect); + } } } @@ -607,8 +632,27 @@ fn canonicalize_value_defs<'a>( def_ordering.insert_symbol_references(def_id as u32, &temp_output.references) } + let mut expects = Expects::with_capacity(pending_expects.len()); + + for pending in pending_expects { + let (loc_can_condition, can_output) = canonicalize_expr( + env, + var_store, + scope, + pending.condition.region, + &pending.condition.value, + ); + + expects.conditions.push(loc_can_condition.value); + expects.regions.push(loc_can_condition.region); + expects.preceding_comment.push(pending.preceding_comment); + + output.union(can_output); + } + let can_defs = CanDefs { defs, + expects, def_ordering, aliases, }; @@ -1009,6 +1053,7 @@ pub(crate) fn sort_can_defs( ) -> (Vec, Output) { let CanDefs { mut defs, + expects: _, def_ordering, aliases, } = defs; @@ -1739,6 +1784,17 @@ fn to_pending_type_def<'a>( } } +enum PendingValue<'a> { + Def(PendingValueDef<'a>), + Expect(PendingExpect<'a>), + SignatureDefMismatch, +} + +struct PendingExpect<'a> { + condition: &'a Loc>, + preceding_comment: Region, +} + fn to_pending_value_def<'a>( env: &mut Env<'a>, var_store: &mut VarStore, @@ -1746,7 +1802,7 @@ fn to_pending_value_def<'a>( scope: &mut Scope, output: &mut Output, pattern_type: PatternType, -) -> Option> { +) -> PendingValue<'a> { use ast::ValueDef::*; match def { @@ -1762,7 +1818,7 @@ fn to_pending_value_def<'a>( loc_pattern.region, ); - Some(PendingValueDef::AnnotationOnly( + PendingValue::Def(PendingValueDef::AnnotationOnly( loc_pattern, loc_can_pattern, loc_ann, @@ -1780,7 +1836,7 @@ fn to_pending_value_def<'a>( loc_pattern.region, ); - Some(PendingValueDef::Body( + PendingValue::Def(PendingValueDef::Body( loc_pattern, loc_can_pattern, loc_expr, @@ -1812,7 +1868,7 @@ fn to_pending_value_def<'a>( body_pattern.region, ); - Some(PendingValueDef::TypedBody( + PendingValue::Def(PendingValueDef::TypedBody( body_pattern, loc_can_pattern, ann_type, @@ -1828,11 +1884,14 @@ fn to_pending_value_def<'a>( // TODO: Should we instead build some PendingValueDef::InvalidAnnotatedBody ? This would // remove the `Option` on this function (and be probably more reliable for further // problem/error reporting) - None + PendingValue::SignatureDefMismatch } } - Expect(_condition) => todo!(), + Expect(condition) => PendingValue::Expect(PendingExpect { + condition, + preceding_comment: Region::zero(), + }), } } diff --git a/examples/hello-world/zig-platform/helloZig.roc b/examples/hello-world/zig-platform/helloZig.roc index 9ef3b5d311..0b3ee157ee 100644 --- a/examples/hello-world/zig-platform/helloZig.roc +++ b/examples/hello-world/zig-platform/helloZig.roc @@ -3,4 +3,6 @@ app "helloZig" imports [] provides [main] to pf +expect 1 == 1 + main = "Hello, World!\n" From f7246d2774b0a5a4faa8f69dcedecedb88f32a65 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 15 Jun 2022 23:15:43 +0200 Subject: [PATCH 39/53] typecheck toplevel expects --- compiler/can/src/def.rs | 24 +++++++++++++++------ compiler/can/src/module.rs | 3 ++- compiler/can/src/traverse.rs | 6 ++++++ compiler/constrain/src/expr.rs | 34 +++++++++++++++++++++++++++++- compiler/load_internal/src/file.rs | 1 + 5 files changed, 60 insertions(+), 8 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 7f99094d9d..6b35368b08 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -84,11 +84,11 @@ pub(crate) struct CanDefs { aliases: VecMap, } -#[derive(Debug)] -struct Expects { - conditions: Vec, - regions: Vec, - preceding_comment: Vec, +#[derive(Clone, Debug)] +pub struct Expects { + pub conditions: Vec, + pub regions: Vec, + pub preceding_comment: Vec, } impl Expects { @@ -219,6 +219,7 @@ pub enum Declaration { Declare(Def), DeclareRec(Vec, IllegalCycleMark), Builtin(Def), + Expects(Expects), /// If we know a cycle is illegal during canonicalization. /// Otherwise we will try to detect this during solving; see [`IllegalCycleMark`]. InvalidCycle(Vec), @@ -232,6 +233,7 @@ impl Declaration { DeclareRec(defs, _) => defs.len(), InvalidCycle { .. } => 0, Builtin(_) => 0, + Expects(_) => 0, } } @@ -247,6 +249,10 @@ impl Declaration { &cycles.first().unwrap().expr_region, &cycles.last().unwrap().expr_region, ), + Declaration::Expects(expects) => Region::span_across( + &expects.regions.first().unwrap(), + &expects.regions.last().unwrap(), + ), } } } @@ -1053,7 +1059,7 @@ pub(crate) fn sort_can_defs( ) -> (Vec, Output) { let CanDefs { mut defs, - expects: _, + expects, def_ordering, aliases, } = defs; @@ -1164,6 +1170,8 @@ pub(crate) fn sort_can_defs( } } + declarations.push(Declaration::Expects(expects)); + (declarations, output) } @@ -1619,6 +1627,10 @@ fn decl_to_let(decl: Declaration, loc_ret: Loc) -> Loc { // Builtins should only be added to top-level decls, not to let-exprs! unreachable!() } + Declaration::Expects(_) => { + // Expects should only be added to top-level decls, not to let-exprs! + unreachable!() + } } } diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index b25e5d49ea..020c932c87 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -493,6 +493,7 @@ pub fn canonicalize_module_defs<'a>( .iter() .all(|(symbol, _)| !exposed_but_not_defined.contains(symbol))); } + Expects(_) => { /* ignore */ } } } @@ -574,7 +575,7 @@ pub fn canonicalize_module_defs<'a>( DeclareRec(defs, _) => { fix_values_captured_in_closure_defs(defs, &mut VecSet::default()) } - InvalidCycle(_) | Builtin(_) => {} + InvalidCycle(_) | Builtin(_) | Expects(_) => {} } } diff --git a/compiler/can/src/traverse.rs b/compiler/can/src/traverse.rs index bd5af6bc14..525b7c1af8 100644 --- a/compiler/can/src/traverse.rs +++ b/compiler/can/src/traverse.rs @@ -31,6 +31,12 @@ pub fn walk_decl(visitor: &mut V, decl: &Declaration) { Declaration::DeclareRec(defs, _cycle_mark) => { visit_list!(visitor, visit_def, defs) } + Declaration::Expects(expects) => { + let it = expects.regions.iter().zip(expects.conditions.iter()); + for (region, condition) in it { + visitor.visit_expr(condition, *region, Variable::BOOL); + } + } Declaration::Builtin(def) => visitor.visit_def(def), Declaration::InvalidCycle(_cycles) => { // ignore diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index eaf144b9eb..bfc33636f6 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -4,7 +4,7 @@ use crate::builtins::{ use crate::pattern::{constrain_pattern, PatternState}; use roc_can::annotation::IntroducedVariables; use roc_can::constraint::{Constraint, Constraints, OpportunisticResolve}; -use roc_can::def::{Declaration, Def}; +use roc_can::def::{Declaration, Def, Expects}; use roc_can::exhaustive::{sketch_pattern_to_rows, sketch_when_branches, ExhaustiveContext}; use roc_can::expected::Expected::{self, *}; use roc_can::expected::PExpected; @@ -1309,6 +1309,9 @@ pub fn constrain_decls( constraint = constrain_recursive_defs(constraints, &mut env, defs, constraint, *cycle_mark); } + Declaration::Expects(expects) => { + constraint = constrain_expects(constraints, &mut env, expects, constraint); + } Declaration::InvalidCycle(_) => { // invalid cycles give a canonicalization error. we skip them here. continue; @@ -1705,6 +1708,35 @@ fn attach_resolution_constraints( constraints.and_constraint([constraint, resolution_constrs]) } +fn constrain_expects( + constraints: &mut Constraints, + env: &mut Env, + expects: &Expects, + body_con: Constraint, +) -> Constraint { + let expect_bool = |region| { + let bool_type = Type::Variable(Variable::BOOL); + Expected::ForReason(Reason::ExpectCondition, bool_type, region) + }; + + let mut expect_constraints = Vec::with_capacity(expects.conditions.len()); + + let it = expects.regions.iter().zip(expects.conditions.iter()); + for (region, condition) in it { + let expected = expect_bool(*region); + expect_constraints.push(constrain_expr( + constraints, + env, + *region, + condition, + expected, + )); + } + + let defs_constraint = constraints.and_constraint(expect_constraints); + constraints.let_constraint([], [], [], defs_constraint, body_con) +} + fn constrain_def( constraints: &mut Constraints, env: &mut Env, diff --git a/compiler/load_internal/src/file.rs b/compiler/load_internal/src/file.rs index 13bdbe0d22..062739455b 100644 --- a/compiler/load_internal/src/file.rs +++ b/compiler/load_internal/src/file.rs @@ -4495,6 +4495,7 @@ fn build_pending_specializations<'a>( ) } } + Expects(_) => todo!("toplevel expect codgen"), InvalidCycle(_) | DeclareRec(..) => { // do nothing? // this may mean the loc_symbols are not defined during codegen; is that a problem? From 858abd670d651da6492b8f5f082a86518aef8bcc Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 15 Jun 2022 23:16:40 +0200 Subject: [PATCH 40/53] revert helloZig --- examples/hello-world/zig-platform/helloZig.roc | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/hello-world/zig-platform/helloZig.roc b/examples/hello-world/zig-platform/helloZig.roc index 0b3ee157ee..9ef3b5d311 100644 --- a/examples/hello-world/zig-platform/helloZig.roc +++ b/examples/hello-world/zig-platform/helloZig.roc @@ -3,6 +3,4 @@ app "helloZig" imports [] provides [main] to pf -expect 1 == 1 - main = "Hello, World!\n" From 0fef1e1576941e633a60a996c27ab645df3d5cee Mon Sep 17 00:00:00 2001 From: Brian Carroll Date: Wed, 15 Jun 2022 22:30:10 +0100 Subject: [PATCH 41/53] Create to_exposed_symbol_string helper function for all backends --- compiler/gen_dev/src/object_builder.rs | 4 +++- compiler/gen_llvm/src/llvm/build.rs | 21 ++++++++++++++++----- compiler/gen_wasm/src/backend.rs | 2 ++ compiler/gen_wasm/src/lib.rs | 13 ++++++++----- compiler/mono/src/layout.rs | 9 ++++++++- compiler/test_gen/src/helpers/dev.rs | 6 ++---- 6 files changed, 39 insertions(+), 16 deletions(-) diff --git a/compiler/gen_dev/src/object_builder.rs b/compiler/gen_dev/src/object_builder.rs index 92c01583d1..8dc88576a6 100644 --- a/compiler/gen_dev/src/object_builder.rs +++ b/compiler/gen_dev/src/object_builder.rs @@ -330,7 +330,9 @@ fn build_proc_symbol<'a, B: Backend<'a>>( let base_name = backend.symbol_to_string(sym, layout_id); let fn_name = if backend.env().exposed_to_host.contains(&sym) { - format!("roc_{}_exposed", base_name) + layout_ids + .get_toplevel(sym, &layout) + .to_exposed_symbol_string(sym, backend.interns()) } else { base_name }; diff --git a/compiler/gen_llvm/src/llvm/build.rs b/compiler/gen_llvm/src/llvm/build.rs index 7b7a6404ae..0d8f7536e8 100644 --- a/compiler/gen_llvm/src/llvm/build.rs +++ b/compiler/gen_llvm/src/llvm/build.rs @@ -3296,12 +3296,20 @@ fn expose_function_to_host<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, symbol: Symbol, roc_function: FunctionValue<'ctx>, - arguments: &[Layout<'a>], + arguments: &'a [Layout<'a>], return_layout: Layout<'a>, + layout_ids: &mut LayoutIds<'a>, ) { - // Assumption: there is only one specialization of a host-exposed function let ident_string = symbol.as_str(&env.interns); - let c_function_name: String = format!("roc__{}_1_exposed", ident_string); + + let proc_layout = ProcLayout { + arguments, + result: return_layout, + }; + + let c_function_name: String = layout_ids + .get_toplevel(symbol, &proc_layout) + .to_exposed_symbol_string(symbol, &env.interns); expose_function_to_host_help_c_abi( env, @@ -4077,6 +4085,7 @@ pub fn build_proc_headers<'a, 'ctx, 'env>( mod_solutions: &'a ModSolutions, procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>, scope: &mut Scope<'a, 'ctx>, + layout_ids: &mut LayoutIds<'a>, // alias_analysis_solutions: AliasAnalysisSolutions, ) -> Vec< 'a, @@ -4096,7 +4105,7 @@ pub fn build_proc_headers<'a, 'ctx, 'env>( let it = func_solutions.specs(); let mut function_values = Vec::with_capacity_in(it.size_hint().0, env.arena); for specialization in it { - let fn_val = build_proc_header(env, *specialization, symbol, &proc); + let fn_val = build_proc_header(env, *specialization, symbol, &proc, layout_ids); if proc.args.is_empty() { // this is a 0-argument thunk, i.e. a top-level constant definition @@ -4167,7 +4176,7 @@ fn build_procedures_help<'a, 'ctx, 'env>( // Add all the Proc headers to the module. // We have to do this in a separate pass first, // because their bodies may reference each other. - let headers = build_proc_headers(env, mod_solutions, procedures, &mut scope); + let headers = build_proc_headers(env, mod_solutions, procedures, &mut scope, &mut layout_ids); let (_, function_pass) = construct_optimization_passes(env.module, opt_level); @@ -4256,6 +4265,7 @@ fn build_proc_header<'a, 'ctx, 'env>( func_spec: FuncSpec, symbol: Symbol, proc: &roc_mono::ir::Proc<'a>, + layout_ids: &mut LayoutIds<'a>, ) -> FunctionValue<'ctx> { let args = proc.args; let arena = env.arena; @@ -4293,6 +4303,7 @@ fn build_proc_header<'a, 'ctx, 'env>( fn_val, arguments.into_bump_slice(), proc.ret_layout, + layout_ids, ); } diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index a5e8477703..499a68a09a 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -257,6 +257,8 @@ impl<'a> WasmBackend<'a> { .to_symbol_string(symbol, self.interns); let name = String::from_str_in(&name, self.env.arena).into_bump_str(); + // dbg!(name); + self.proc_lookup.push(ProcLookupData { name: symbol, layout, diff --git a/compiler/gen_wasm/src/lib.rs b/compiler/gen_wasm/src/lib.rs index ab44287fc5..aa73207b7f 100644 --- a/compiler/gen_wasm/src/lib.rs +++ b/compiler/gen_wasm/src/lib.rs @@ -84,7 +84,7 @@ pub fn build_app_module<'a>( host_module: WasmModule<'a>, procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>, ) -> (WasmModule<'a>, Vec<'a, u32>, u32) { - let layout_ids = LayoutIds::default(); + let mut layout_ids = LayoutIds::default(); let mut procs = Vec::with_capacity_in(procedures.len(), env.arena); let mut proc_lookup = Vec::with_capacity_in(procedures.len() * 2, env.arena); let mut host_to_app_map = Vec::with_capacity_in(env.exposed_to_host.len(), env.arena); @@ -109,10 +109,13 @@ pub fn build_app_module<'a>( if env.exposed_to_host.contains(&sym) { maybe_main_fn_index = Some(fn_index); - // Assumption: there is only one specialization of a host-exposed function - let ident_string = sym.as_str(interns); - let c_function_name = bumpalo::format!(in env.arena, "roc__{}_1_exposed", ident_string); - host_to_app_map.push((c_function_name.into_bump_str(), fn_index)); + let exposed_name = layout_ids + .get_toplevel(sym, &proc_layout) + .to_exposed_symbol_string(sym, interns); + + let exposed_name_bump: &'a str = env.arena.alloc_str(&exposed_name); + + host_to_app_map.push((exposed_name_bump, fn_index)); } proc_lookup.push(ProcLookupData { diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 4606ba32ab..df89ee1049 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -2913,13 +2913,20 @@ pub fn list_layout_from_elem<'a>( pub struct LayoutId(u32); impl LayoutId { - // Returns something like "foo#1" when given a symbol that interns to "foo" + // Returns something like "#UserApp_foo_1" when given a symbol that interns to "foo" // and a LayoutId of 1. pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String { let ident_string = symbol.as_str(interns); let module_string = interns.module_ids.get_name(symbol.module_id()).unwrap(); format!("{}_{}_{}", module_string, ident_string, self.0) } + + // Returns something like "roc__foo_1_exposed" when given a symbol that interns to "foo" + // and a LayoutId of 1. + pub fn to_exposed_symbol_string(self, symbol: Symbol, interns: &Interns) -> String { + let ident_string = symbol.as_str(interns); + format!("roc__{}_{}_exposed", ident_string, self.0) + } } struct IdsByLayout<'a> { diff --git a/compiler/test_gen/src/helpers/dev.rs b/compiler/test_gen/src/helpers/dev.rs index 59f65b93e4..963c117a71 100644 --- a/compiler/test_gen/src/helpers/dev.rs +++ b/compiler/test_gen/src/helpers/dev.rs @@ -98,11 +98,9 @@ pub fn helper( let main_fn_layout = loaded.entry_point.layout; let mut layout_ids = roc_mono::layout::LayoutIds::default(); - let main_fn_name_base = layout_ids + let main_fn_name = layout_ids .get_toplevel(main_fn_symbol, &main_fn_layout) - .to_symbol_string(main_fn_symbol, &interns); - - let main_fn_name = format!("roc_{}_exposed", main_fn_name_base); + .to_exposed_symbol_string(main_fn_symbol, &interns); let mut lines = Vec::new(); // errors whose reporting we delay (so we can see that code gen generates runtime errors) From 178850462e92ef3451ce352968bfb81f34cac093 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 17:52:29 -0400 Subject: [PATCH 42/53] Pass at tag union toEncoder deriver --- compiler/mono/src/derive/encoding.rs | 250 ++++++++++++++++++++++++++- 1 file changed, 246 insertions(+), 4 deletions(-) diff --git a/compiler/mono/src/derive/encoding.rs b/compiler/mono/src/derive/encoding.rs index 5e34ca540a..8ffc7a5416 100644 --- a/compiler/mono/src/derive/encoding.rs +++ b/compiler/mono/src/derive/encoding.rs @@ -5,7 +5,7 @@ use std::iter::once; use bumpalo::Bump; use roc_can::abilities::AbilitiesStore; -use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Field, Recursive}; +use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Field, Recursive, WhenBranch}; use roc_can::pattern::Pattern; use roc_collections::SendMap; use roc_derive_key::encoding::FlatEncodable; @@ -17,8 +17,9 @@ use roc_module::ident::Lowercase; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_region::all::{Loc, Region}; use roc_types::subs::{ - Content, ExposedTypesStorageSubs, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields, - Subs, SubsFmtContent, SubsSlice, UnionLambdas, Variable, VariableSubsSlice, + Content, ExhaustiveMark, ExposedTypesStorageSubs, FlatType, GetSubsSlice, LambdaSet, + OptVariable, RecordFields, RedundantMark, Subs, SubsFmtContent, SubsSlice, UnionLambdas, + UnionTags, Variable, VariableSubsSlice, }; use roc_types::types::{AliasKind, RecordField}; @@ -160,7 +161,32 @@ pub fn derive_to_encoder(env: &mut Env<'_>, for_var: Variable) -> Expr { to_encoder_record(env, record_var, fields) } - FlatEncodable::TagUnion(_) => todo!(), + FlatEncodable::TagUnion(tags) => { + // Generalized tag union var so we can reuse this impl between many unions: + // if tags = [ A arity=2, B arity=1 ], this is [ A t1 t2, B t3 ] for fresh t1, t2, t3 + let flex_tag_labels = tags + .iter() + .copied() + .map(|(label, arity)| (label.clone(), arity)) + .collect::>() + .into_iter() + .map(|(label, arity)| { + let variables_slice = + VariableSubsSlice::reserve_into_subs(env.subs, arity.into()); + for var_index in variables_slice { + env.subs[var_index] = env.subs.fresh_unnamed_flex_var(); + } + (label, variables_slice) + }) + .collect::>(); + let union_tags = UnionTags::insert_slices_into_subs(env.subs, flex_tag_labels); + let tag_union_var = synth_var( + env.subs, + Content::Structure(FlatType::TagUnion(union_tags, Variable::EMPTY_TAG_UNION)), + ); + + to_encoder_tag_union(env, tag_union_var, union_tags) + } } } @@ -350,3 +376,219 @@ fn to_encoder_record(env: &mut Env<'_>, record_var: Variable, fields: RecordFiel loc_body: Box::new(Loc::at_zero(encode_record_call)), }) } + +fn to_encoder_tag_union(env: &mut Env<'_>, tag_union_var: Variable, tags: UnionTags) -> Expr { + // Suppose tag = [ A t1 t2, B t3 ]. Build + // + // \tag -> when tag is + // A v1 v2 -> Encode.tag "A" [ Encode.toEncoder v1, Encode.toEncoder v2 ] + // B v3 -> Encode.tag "B" [ Encode.toEncoder v3 ] + + let tag_sym = env.unique_symbol(); + let whole_tag_encoders_var = env.subs.fresh_unnamed_flex_var(); // type of the Encode.tag ... calls in the branch bodies + + use Expr::*; + + let branches = tags + .iter_all() + .map(|(tag_name_index, tag_vars_slice_index)| { + // A + let tag_name = &env.subs[tag_name_index].clone(); + let vars_slice = env.subs[tag_vars_slice_index]; + // t1 t2 + let payload_vars = env.subs.get_subs_slice(vars_slice).to_vec(); + // v1 v2 + let payload_syms: Vec<_> = std::iter::repeat_with(|| env.unique_symbol()) + .take(payload_vars.len()) + .collect(); + + // `A v1 v2` pattern + let pattern = Pattern::AppliedTag { + whole_var: tag_union_var, + tag_name: tag_name.clone(), + ext_var: Variable::EMPTY_TAG_UNION, + // (t1, v1) (t2, v2) + arguments: (payload_vars.iter()) + .zip(payload_syms.iter()) + .map(|(var, sym)| (*var, Loc::at_zero(Pattern::Identifier(*sym)))) + .collect(), + }; + + // whole type of the elements in [ Encode.toEncoder v1, Encode.toEncoder v2 ] + let whole_payload_encoders_var = env.subs.fresh_unnamed_flex_var(); + // [ Encode.toEncoder v1, Encode.toEncoder v2 ] + let payload_to_encoders = (payload_syms.iter()) + .zip(payload_vars.iter()) + .map(|(&sym, &sym_var)| { + // build `toEncoder v1` type + // expected: val -[uls]-> Encoder fmt | fmt has EncoderFormatting + let to_encoder_fn_var = env.import_encode_symbol(Symbol::ENCODE_TO_ENCODER); + + // wanted: t1 -[clos]-> t' + let var_slice_of_sym_var = + VariableSubsSlice::insert_into_subs(env.subs, [sym_var]); // [ t1 ] + let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos + let encoder_var = env.subs.fresh_unnamed_flex_var(); // t' + let this_to_encoder_fn_var = synth_var( + env.subs, + Content::Structure(FlatType::Func( + var_slice_of_sym_var, + to_encoder_clos_var, + encoder_var, + )), + ); + + // val -[uls]-> Encoder fmt | fmt has EncoderFormatting + // ~ t1 -[clos]-> t' + env.unify(to_encoder_fn_var, this_to_encoder_fn_var); + + // toEncoder : t1 -[clos]-> Encoder fmt | fmt has EncoderFormatting + let to_encoder_fn = Box::new(( + this_to_encoder_fn_var, + Loc::at_zero(Var(Symbol::ENCODE_TO_ENCODER)), + to_encoder_clos_var, + encoder_var, + )); + + // toEncoder rcd.a + let to_encoder_call = Call( + to_encoder_fn, + vec![(sym_var, Loc::at_zero(Var(sym)))], + CalledVia::Space, + ); + + // NOTE: must be done to unify the lambda sets under `encoder_var` + env.unify(encoder_var, whole_payload_encoders_var); + + Loc::at_zero(to_encoder_call) + }) + .collect(); + + // typeof [ Encode.toEncoder v1, Encode.toEncoder v2 ] + let whole_encoders_var_slice = + VariableSubsSlice::insert_into_subs(env.subs, [whole_payload_encoders_var]); + let payload_encoders_list_var = synth_var( + env.subs, + Content::Structure(FlatType::Apply(Symbol::LIST_LIST, whole_encoders_var_slice)), + ); + + // [ Encode.toEncoder v1, Encode.toEncoder v2 ] + let payload_encoders_list = List { + elem_var: whole_payload_encoders_var, + loc_elems: payload_to_encoders, + }; + + // build `Encode.tag "A" [ ... ]` type + // expected: Str, List (Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting + let encode_tag_fn_var = env.import_encode_symbol(Symbol::ENCODE_TAG); + + // wanted: Str, List whole_encoders_var -[clos]-> t' + // wanted: Str, List whole_encoders_var + let this_encode_tag_args_var_slice = VariableSubsSlice::insert_into_subs( + env.subs, + [Variable::STR, payload_encoders_list_var], + ); + let this_encode_tag_clos_var = env.subs.fresh_unnamed_flex_var(); // -[clos]-> + let this_encoder_var = env.subs.fresh_unnamed_flex_var(); // t' + let this_encode_tag_fn_var = synth_var( + env.subs, + Content::Structure(FlatType::Func( + this_encode_tag_args_var_slice, + this_encode_tag_clos_var, + this_encoder_var, + )), + ); + + // Str, List (Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting + // ~ Str, List whole_encoders_var -[clos]-> t' + env.unify(encode_tag_fn_var, this_encode_tag_fn_var); + + // Encode.tag : Str, List whole_encoders_var -[clos]-> Encoder fmt | fmt has EncoderFormatting + let encode_tag_fn = Box::new(( + this_encode_tag_fn_var, + Loc::at_zero(Var(Symbol::ENCODE_TO_ENCODER)), + this_encode_tag_clos_var, + this_encoder_var, + )); + + // Encode.tag "A" [ Encode.toEncoder v1, Encode.toEncoder v2 ] + let encode_tag_call = Call( + encode_tag_fn, + vec![ + // (Str, "A") + (Variable::STR, Loc::at_zero(Str(tag_name.0.as_str().into()))), + // (List (Encoder fmt), [ Encode.toEncoder v1, Encode.toEncoder v2 ]) + ( + payload_encoders_list_var, + Loc::at_zero(payload_encoders_list), + ), + ], + CalledVia::Space, + ); + + // NOTE: must be done to unify the lambda sets under `encoder_var` + // Encode.tag "A" [ Encode.toEncoder v1, Encode.toEncoder v2 ] ~ whole_encoders + env.unify(this_encoder_var, whole_tag_encoders_var); + + WhenBranch { + patterns: vec![Loc::at_zero(pattern)], + value: Loc::at_zero(encode_tag_call), + guard: None, + redundant: RedundantMark::known_non_redundant(), + } + }) + .collect::>(); + + // when tag is + // A v1 v2 -> Encode.tag "A" [ Encode.toEncoder v1, Encode.toEncoder v2 ] + // B v3 -> Encode.tag "B" [ Encode.toEncoder v3 ] + let when_branches = When { + loc_cond: Box::new(Loc::at_zero(Var(tag_sym))), + cond_var: tag_union_var, + expr_var: whole_tag_encoders_var, + region: Region::zero(), + branches, + branches_cond_var: tag_union_var, + exhaustive: ExhaustiveMark::known_exhaustive(), + }; + + let fn_name = env.unique_symbol(); + let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, vec![]))); + // -[fn_name]-> + let fn_clos_var = synth_var( + env.subs, + Content::LambdaSet(LambdaSet { + solved: fn_name_labels, + recursion_var: OptVariable::NONE, + unspecialized: SubsSlice::default(), + }), + ); + // tag_union_var -[fn_name]-> whole_tag_encoders_var + let tag_union_var_slice = SubsSlice::insert_into_subs(env.subs, once(tag_union_var)); + let fn_var = synth_var( + env.subs, + Content::Structure(FlatType::Func( + tag_union_var_slice, + fn_clos_var, + whole_tag_encoders_var, + )), + ); + + // \tag -> when tag is + // A v1 v2 -> Encode.tag "A" [ Encode.toEncoder v1, Encode.toEncoder v2 ] + // B v3 -> Encode.tag "B" [ Encode.toEncoder v3 ] + Closure(ClosureData { + function_type: fn_var, + closure_type: fn_clos_var, + return_type: whole_tag_encoders_var, + name: fn_name, + captured_symbols: vec![], + recursive: Recursive::NotRecursive, + arguments: vec![( + tag_union_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(tag_sym)), + )], + loc_body: Box::new(Loc::at_zero(when_branches)), + }) +} From ef350e4aed21c749f8484f9743e933532eeecea2 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 18:01:18 -0400 Subject: [PATCH 43/53] Add tests for tag union toEncoder derivers --- compiler/test_derive/src/encoding.rs | 34 ++++++++++++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index 14524ba23f..15fddee888 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -1,4 +1,8 @@ #![cfg(test)] +// Even with #[allow(non_snake_case)] on individual idents, rust-analyzer issues diagnostics. +// See https://github.com/rust-lang/rust-analyzer/issues/6541. +// For the `v!` macro we use uppercase variables when constructing tag unions. +#![allow(non_snake_case)] use std::{ hash::{BuildHasher, Hash, Hasher}, @@ -328,7 +332,6 @@ macro_rules! test_hash_eq { ($($name:ident: $synth1:expr, $synth2:expr)*) => {$( #[test] fn $name() { - #![allow(non_snake_case)] check_hash(true, $synth1, $synth2) } )*}; @@ -338,7 +341,6 @@ macro_rules! test_hash_neq { ($($name:ident: $synth1:expr, $synth2:expr)*) => {$( #[test] fn $name() { - #![allow(non_snake_case)] check_hash(false, $synth1, $synth2) } )*}; @@ -479,4 +481,32 @@ fn two_field_record() { ) } +#[test] +#[ignore = "NOTE: this would never actually happen, because [] is uninhabited, and hence toEncoder can never be called with a value of []! +Rightfully it induces broken assertions in other parts of the compiler, so we ignore it."] +fn empty_tag_union() { + derive_test( + v!(EMPTY_TAG_UNION), + "[] -> Encoder fmt | fmt has EncoderFormatting", + indoc!( + r#" + \Test.0 -> when Test.0 is + "# + ), + ) +} + +#[test] +fn tag_one_label_zero_args() { + derive_test( + v!([A]), + "[ A ] -> Encoder fmt | fmt has EncoderFormatting", + indoc!( + r#" + \Test.0 -> when Test.0 is (A) -> (Encode.toEncoder "A" [ ]) + "# + ), + ) +} + // }}} deriver tests From 8cb6121fc347baaf5dda9cb7e25ec420de4f09b5 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 20:37:49 -0400 Subject: [PATCH 44/53] Exhaustive and redundant marks don't need to be introduced They are never generalized --- compiler/constrain/src/expr.rs | 7 +------ compiler/types/src/subs.rs | 16 ---------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index eaf144b9eb..56e215a066 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -801,12 +801,7 @@ pub fn constrain_expr( let branch_constraints = constraints.and_constraint(total_cons); constraints.exists( - [ - exhaustive.variable_for_introduction(), - branches_cond_var, - real_cond_var, - *expr_var, - ], + [branches_cond_var, real_cond_var, *expr_var], branch_constraints, ) } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 56bf658bb6..efd11c23aa 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1078,14 +1078,6 @@ impl ExhaustiveMark { Self(Variable::EMPTY_TAG_UNION) } - pub fn variable_for_introduction(&self) -> Variable { - debug_assert!( - self.0 != Variable::EMPTY_TAG_UNION, - "Attempting to introduce known mark" - ); - self.0 - } - pub fn set_non_exhaustive(&self, subs: &mut Subs) { subs.set_content(self.0, Content::Error); } @@ -1110,14 +1102,6 @@ impl RedundantMark { Self(Variable::EMPTY_TAG_UNION) } - pub fn variable_for_introduction(&self) -> Variable { - debug_assert!( - self.0 != Variable::EMPTY_TAG_UNION, - "Attempting to introduce known mark" - ); - self.0 - } - pub fn set_redundant(&self, subs: &mut Subs) { subs.set_content(self.0, Content::Error); } From 74d498d7620aa842e20398d72fa977c6f9178056 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 20:43:49 -0400 Subject: [PATCH 45/53] Add more tag union deriving tests --- compiler/mono/src/derive/encoding.rs | 2 +- compiler/test_derive/src/encoding.rs | 41 ++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/compiler/mono/src/derive/encoding.rs b/compiler/mono/src/derive/encoding.rs index 8ffc7a5416..ced10deb27 100644 --- a/compiler/mono/src/derive/encoding.rs +++ b/compiler/mono/src/derive/encoding.rs @@ -506,7 +506,7 @@ fn to_encoder_tag_union(env: &mut Env<'_>, tag_union_var: Variable, tags: UnionT // Encode.tag : Str, List whole_encoders_var -[clos]-> Encoder fmt | fmt has EncoderFormatting let encode_tag_fn = Box::new(( this_encode_tag_fn_var, - Loc::at_zero(Var(Symbol::ENCODE_TO_ENCODER)), + Loc::at_zero(Var(Symbol::ENCODE_TAG)), this_encode_tag_clos_var, this_encoder_var, )); diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index 15fddee888..9ddbc6f641 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -500,10 +500,47 @@ fn empty_tag_union() { fn tag_one_label_zero_args() { derive_test( v!([A]), - "[ A ] -> Encoder fmt | fmt has EncoderFormatting", + "[A] -> Encoder fmt | fmt has EncoderFormatting", indoc!( r#" - \Test.0 -> when Test.0 is (A) -> (Encode.toEncoder "A" [ ]) + \Test.0 -> when Test.0 is (A) -> (Encode.tag "A" [ ]) + "# + ), + ) +} + +#[test] +fn tag_one_label_two_args() { + derive_test( + v!([A v!(U8) v!(STR)]), + "[A val a] -> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding", + indoc!( + r#" + \Test.0 -> + when Test.0 is + (ATest.1 Test.2) -> (Encode.tag "A" [ + (Encode.toEncoder Test.1), + (Encode.toEncoder Test.2), + ]) + "# + ), + ) +} + +#[test] +fn tag_two_labels() { + derive_test( + v!([A v!(U8) v!(STR), B v!(STR)]), + "[A val a, B b] -> Encoder fmt | a has Encoding, b has Encoding, fmt has EncoderFormatting, val has Encoding", + indoc!( + r#" + \Test.0 -> + when Test.0 is + (ATest.1 Test.2) -> (Encode.tag "A" [ + (Encode.toEncoder Test.1), + (Encode.toEncoder Test.2), + ]) + (BTest.3) -> (Encode.tag "B" [ (Encode.toEncoder Test.3), ]) "# ), ) From c94c02ae3d59229c866f3677ee763873fbeea99c Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 22:04:41 -0400 Subject: [PATCH 46/53] Make debug printing prettier --- compiler/test_derive/src/encoding.rs | 39 ++++--- compiler/test_derive/src/pretty_print.rs | 138 ++++++++++++++++------- 2 files changed, 117 insertions(+), 60 deletions(-) diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index 9ddbc6f641..4f26299644 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -431,7 +431,7 @@ fn empty_record() { "{} -> Encoder fmt | fmt has EncoderFormatting", indoc!( r#" - \Test.0 -> (Encode.record [ ]) + \Test.0 -> Encode.record [] "# ), ) @@ -444,7 +444,7 @@ fn zero_field_record() { "{} -> Encoder fmt | fmt has EncoderFormatting", indoc!( r#" - \Test.0 -> (Encode.record [ ]) + \Test.0 -> Encode.record [] "# ), ) @@ -457,8 +457,7 @@ fn one_field_record() { "{ a : val } -> Encoder fmt | fmt has EncoderFormatting, val has Encoding", indoc!( r#" - \Test.0 -> - (Encode.record [ { value: (Encode.toEncoder (Test.0).a), key: "a", }, ]) + \Test.0 -> Encode.record [{ value: Encode.toEncoder Test.0.a, key: "a", }] "# ), ) @@ -472,10 +471,10 @@ fn two_field_record() { indoc!( r#" \Test.0 -> - (Encode.record [ - { value: (Encode.toEncoder (Test.0).a), key: "a", }, - { value: (Encode.toEncoder (Test.0).b), key: "b", }, - ]) + Encode.record [ + { value: Encode.toEncoder Test.0.a, key: "a", }, + { value: Encode.toEncoder Test.0.b, key: "b", }, + ] "# ), ) @@ -503,7 +502,7 @@ fn tag_one_label_zero_args() { "[A] -> Encoder fmt | fmt has EncoderFormatting", indoc!( r#" - \Test.0 -> when Test.0 is (A) -> (Encode.tag "A" [ ]) + \Test.0 -> when Test.0 is A -> Encode.tag "A" [] "# ), ) @@ -518,10 +517,8 @@ fn tag_one_label_two_args() { r#" \Test.0 -> when Test.0 is - (ATest.1 Test.2) -> (Encode.tag "A" [ - (Encode.toEncoder Test.1), - (Encode.toEncoder Test.2), - ]) + A Test.1 Test.2 -> + Encode.tag "A" [Encode.toEncoder Test.1, Encode.toEncoder Test.2] "# ), ) @@ -530,17 +527,19 @@ fn tag_one_label_two_args() { #[test] fn tag_two_labels() { derive_test( - v!([A v!(U8) v!(STR), B v!(STR)]), - "[A val a, B b] -> Encoder fmt | a has Encoding, b has Encoding, fmt has EncoderFormatting, val has Encoding", + v!([A v!(U8) v!(STR) v!(U16), B v!(STR)]), + "[A val a b, B c] -> Encoder fmt | a has Encoding, b has Encoding, c has Encoding, fmt has EncoderFormatting, val has Encoding", indoc!( r#" \Test.0 -> when Test.0 is - (ATest.1 Test.2) -> (Encode.tag "A" [ - (Encode.toEncoder Test.1), - (Encode.toEncoder Test.2), - ]) - (BTest.3) -> (Encode.tag "B" [ (Encode.toEncoder Test.3), ]) + A Test.1 Test.2 Test.3 -> + Encode.tag "A" [ + Encode.toEncoder Test.1, + Encode.toEncoder Test.2, + Encode.toEncoder Test.3, + ] + B Test.4 -> Encode.tag "B" [Encode.toEncoder Test.4] "# ), ) diff --git a/compiler/test_derive/src/pretty_print.rs b/compiler/test_derive/src/pretty_print.rs index 586ee53e29..c0065d7384 100644 --- a/compiler/test_derive/src/pretty_print.rs +++ b/compiler/test_derive/src/pretty_print.rs @@ -13,10 +13,31 @@ pub struct Ctx<'a> { pub fn pretty_print(c: &Ctx, e: &Expr) -> String { let f = Arena::new(); - expr(c, &f, e).append(f.hardline()).1.pretty(80).to_string() + expr(c, EPrec::Free, &f, e) + .append(f.hardline()) + .1 + .pretty(80) + .to_string() } -fn expr<'a>(c: &Ctx, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> { +macro_rules! maybe_paren { + ($paren_if_above:expr, $my_prec:expr, $doc:expr) => { + if $my_prec > $paren_if_above { + $doc.parens().group() + } else { + $doc + } + }; +} + +#[derive(PartialEq, PartialOrd)] +enum EPrec { + Free, + CallArg, +} + +fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> { + use EPrec::*; match e { Num(_, n, _, _) | Int(_, _, n, _, _) | Float(_, _, n, _, _) => f.text(&**n), Str(s) => f.text(format!(r#""{}""#, s)), @@ -28,15 +49,26 @@ fn expr<'a>(c: &Ctx, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> .reflow("[") .append( f.concat(loc_elems.iter().map(|le| { - let elem = expr(c, f, &le.value); - f.line().append(elem).append(",") + f.hardline() + .append(expr(c, Free, f, &le.value)) + .append(f.text(",")) })) .group() .nest(2), ) - .append(f.line()) + .append(f.line_()) .append("]") - .group(), + .group() + .flat_alt( + f.reflow("[") + .append(f.intersperse( + loc_elems.iter().map(|le| expr(c, Free, f, &le.value)), + f.reflow(", "), + )) + .append(f.line_()) + .append("]") + .group(), + ), Var(sym) | AbilityMember(sym, _, _) => f.text(format!( "{}.{}", sym.module_string(c.interns), @@ -46,11 +78,10 @@ fn expr<'a>(c: &Ctx, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> loc_cond, branches, .. } => f .reflow("when ") - .append(expr(c, f, &loc_cond.value)) + .append(expr(c, Free, f, &loc_cond.value)) .append(f.text(" is")) .append( f.concat(branches.iter().map(|b| f.line().append(branch(c, f, b)))) - .nest(2) .group(), ) .nest(2) @@ -63,13 +94,13 @@ fn expr<'a>(c: &Ctx, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> .concat(branches.iter().enumerate().map(|(i, (cond, body))| { let head = if i == 0 { "if " } else { "else if " }; (f.reflow(head) - .append(expr(c, f, &cond.value)) + .append(expr(c, Free, f, &cond.value)) .group() .nest(2)) .append(f.line()) .append( f.reflow("then") - .append(f.softline().append(expr(c, f, &body.value))) + .append(f.softline().append(expr(c, Free, f, &body.value))) .group() .nest(2), ) @@ -77,7 +108,7 @@ fn expr<'a>(c: &Ctx, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> })) .append( f.reflow("else ") - .append(expr(c, f, &final_else.value)) + .append(expr(c, Free, f, &final_else.value)) .group() .nest(2), ) @@ -86,12 +117,17 @@ fn expr<'a>(c: &Ctx, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> LetNonRec(_, _) => todo!(), Call(fun, args, _) => { let (_, fun, _, _) = &**fun; - f.text("(") - .append(expr(c, f, &fun.value)) - .append(f.softline()) - .append(f.intersperse(args.iter().map(|le| expr(c, f, &le.1.value)), f.softline())) - .append(f.text(")")) - .group() + maybe_paren!( + Free, + p, + expr(c, CallArg, f, &fun.value) + .append(f.softline()) + .append(f.intersperse( + args.iter().map(|le| expr(c, CallArg, f, &le.1.value)), + f.softline() + )) + .group() + ) } RunLowLevel { .. } => todo!(), ForeignCall { .. } => todo!(), @@ -105,13 +141,13 @@ fn expr<'a>(c: &Ctx, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> f.intersperse( arguments .iter() - .map(|(_, _, arg)| pattern(c, f, &arg.value)), + .map(|(_, _, arg)| pattern(c, PPrec::Free, f, &arg.value)), f.text(", "), ), ) .append(f.text(" ->")) .append(f.line()) - .append(expr(c, f, &loc_body.value)) + .append(expr(c, Free, f, &loc_body.value)) .nest(2) .group(), Record { fields, .. } => f @@ -121,7 +157,7 @@ fn expr<'a>(c: &Ctx, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> let field = f .text(name.as_str()) .append(f.reflow(": ")) - .append(expr(c, f, &field.loc_expr.value)) + .append(expr(c, Free, f, &field.loc_expr.value)) .nest(2) .group(); f.line().append(field).append(",") @@ -135,10 +171,7 @@ fn expr<'a>(c: &Ctx, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, Arena<'a>> EmptyRecord => f.text("{}"), Access { loc_expr, field, .. - } => f - .text("(") - .append(expr(c, f, &loc_expr.value)) - .append(f.text(")")) + } => expr(c, CallArg, f, &loc_expr.value) .append(f.text(format!(".{}", field.as_str()))) .group(), Accessor(_) => todo!(), @@ -161,21 +194,35 @@ fn branch<'a>(c: &Ctx, f: &'a Arena<'a>, b: &'a WhenBranch) -> DocBuilder<'a, Ar } = b; f.intersperse( - patterns.iter().map(|lp| pattern(c, f, &lp.value)), + patterns + .iter() + .map(|lp| pattern(c, PPrec::Free, f, &lp.value)), f.text(" | "), ) .append(match guard { - Some(e) => f.text("if ").append(expr(c, f, &e.value)), + Some(e) => f.text("if ").append(expr(c, EPrec::Free, f, &e.value)), None => f.nil(), }) .append(f.text(" ->")) - .append(f.softline()) - .append(expr(c, f, &value.value)) + .append(f.line()) + .append(expr(c, EPrec::Free, f, &value.value)) .nest(2) .group() } -fn pattern<'a>(c: &Ctx, f: &'a Arena<'a>, p: &'a Pattern) -> DocBuilder<'a, Arena<'a>> { +#[derive(PartialEq, PartialOrd)] +enum PPrec { + Free, + AppArg, +} + +fn pattern<'a>( + c: &Ctx, + prec: PPrec, + f: &'a Arena<'a>, + p: &'a Pattern, +) -> DocBuilder<'a, Arena<'a>> { + use PPrec::*; use Pattern::*; match p { Identifier(sym) @@ -190,19 +237,30 @@ fn pattern<'a>(c: &Ctx, f: &'a Arena<'a>, p: &'a Pattern) -> DocBuilder<'a, Aren tag_name, arguments, .. - } => f - .text(format!("({}", tag_name.0.as_str())) - .append(f.intersperse( - arguments.iter().map(|(_, lp)| pattern(c, f, &lp.value)), - f.space(), - )) - .append(")") - .group(), + } => maybe_paren!( + Free, + prec, + f.text(tag_name.0.as_str()) + .append(if arguments.is_empty() { + f.nil() + } else { + f.space() + }) + .append( + f.intersperse( + arguments + .iter() + .map(|(_, lp)| pattern(c, AppArg, f, &lp.value)), + f.space(), + ) + ) + .group() + ), UnwrappedOpaque { opaque, argument, .. } => f .text(format!("@{} ", opaque.module_string(c.interns))) - .append(pattern(c, f, &argument.1.value)) + .append(pattern(c, Free, f, &argument.1.value)) .group(), RecordDestructure { destructs, .. } => f .text("{") @@ -214,11 +272,11 @@ fn pattern<'a>(c: &Ctx, f: &'a Arena<'a>, p: &'a Pattern) -> DocBuilder<'a, Aren roc_can::pattern::DestructType::Optional(_, e) => f .text(label.as_str()) .append(f.text(" ? ")) - .append(expr(c, f, &e.value)), + .append(expr(c, EPrec::Free, f, &e.value)), roc_can::pattern::DestructType::Guard(_, p) => f .text(label.as_str()) .append(f.text(": ")) - .append(pattern(c, f, &p.value)), + .append(pattern(c, Free, f, &p.value)), }, ), f.text(", "), From 51e1b9b30c06b14b52326eee0441747bdba95b8b Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Wed, 15 Jun 2022 22:11:35 -0400 Subject: [PATCH 47/53] Add recursive tag union test --- compiler/test_derive/src/encoding.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index 4f26299644..717eea0e71 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -545,4 +545,21 @@ fn tag_two_labels() { ) } +#[test] +fn recursive_tag_union() { + derive_test( + v!([Nil, Cons v!(U8) v!(*lst) ] as lst), + "[Cons val a, Nil] -> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding", + indoc!( + r#" + \Test.0 -> + when Test.0 is + Cons Test.1 Test.2 -> + Encode.tag "Cons" [Encode.toEncoder Test.1, Encode.toEncoder Test.2] + Nil -> Encode.tag "Nil" [] + "# + ), + ) +} + // }}} deriver tests From 5371f057be1ff5b4cf247d191acde57775d8c087 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 16 Jun 2022 21:38:12 +0200 Subject: [PATCH 48/53] fix empty expect being added --- bindgen/src/load.rs | 1 + compiler/can/src/def.rs | 12 +++++++----- compiler/solve/src/solve.rs | 4 +++- compiler/unify/src/unify.rs | 1 - 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/bindgen/src/load.rs b/bindgen/src/load.rs index 5deb77ff61..48dde27ccb 100644 --- a/bindgen/src/load.rs +++ b/bindgen/src/load.rs @@ -69,6 +69,7 @@ pub fn load_types( unreachable!("Builtin decl in userspace module?") } Declaration::InvalidCycle(..) => Vec::new(), + Declaration::Expects(..) => Vec::new(), }); let vars_iter = defs_iter.filter_map( diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 6b35368b08..58a744cbe1 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -250,8 +250,8 @@ impl Declaration { &cycles.last().unwrap().expr_region, ), Declaration::Expects(expects) => Region::span_across( - &expects.regions.first().unwrap(), - &expects.regions.last().unwrap(), + expects.regions.first().unwrap(), + expects.regions.last().unwrap(), ), } } @@ -1170,7 +1170,9 @@ pub(crate) fn sort_can_defs( } } - declarations.push(Declaration::Expects(expects)); + if !expects.conditions.is_empty() { + declarations.push(Declaration::Expects(expects)); + } (declarations, output) } @@ -1627,9 +1629,9 @@ fn decl_to_let(decl: Declaration, loc_ret: Loc) -> Loc { // Builtins should only be added to top-level decls, not to let-exprs! unreachable!() } - Declaration::Expects(_) => { + Declaration::Expects(expects) => { // Expects should only be added to top-level decls, not to let-exprs! - unreachable!() + unreachable!("{:?}", &expects) } } } diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 58550bb4ed..2b1ffcf576 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -792,7 +792,9 @@ fn solve( let result = offenders.len(); if result > 0 { - dbg!(&subs, &offenders, &let_con.def_types); + eprintln!("subs = {:?}", &subs); + eprintln!("offenders = {:?}", &offenders); + eprintln!("let_con.def_types = {:?}", &let_con.def_types); } result diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 65b5571bb5..2231bfaa93 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -528,7 +528,6 @@ fn unify_two_aliases( outcome } else { - dbg!(args.len(), other_args.len()); mismatch!("{:?}", _symbol) } } From 375f29691a50e24c46dafe44285bcaf37cbbc450 Mon Sep 17 00:00:00 2001 From: Folkert de Vries Date: Fri, 17 Jun 2022 13:33:56 +0200 Subject: [PATCH 49/53] add missing case in test --- compiler/load_internal/tests/test_load.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/compiler/load_internal/tests/test_load.rs b/compiler/load_internal/tests/test_load.rs index af351b7490..870867209a 100644 --- a/compiler/load_internal/tests/test_load.rs +++ b/compiler/load_internal/tests/test_load.rs @@ -290,6 +290,10 @@ mod test_load { cycle @ InvalidCycle(_) => { panic!("Unexpected cyclic def in module declarations: {:?}", cycle); } + expects @ Expects(_) => { + // at least at the moment this does not happen + panic!("Unexpected expects in module declarations: {:?}", expects); + } }; } From 370b85ed63572e812ff15e51f0be80d5cafb4e94 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Fri, 17 Jun 2022 09:03:35 -0400 Subject: [PATCH 50/53] Add support for immediates, and check strict equality --- compiler/derive_key/src/encoding.rs | 65 ++++++++++++-------------- compiler/derive_key/src/lib.rs | 34 ++++++++++---- compiler/mono/src/derive/encoding.rs | 69 +++++++++++++++------------- compiler/test_derive/src/encoding.rs | 48 +++++++++---------- 4 files changed, 113 insertions(+), 103 deletions(-) diff --git a/compiler/derive_key/src/encoding.rs b/compiler/derive_key/src/encoding.rs index 34ff52c30c..6169830f4e 100644 --- a/compiler/derive_key/src/encoding.rs +++ b/compiler/derive_key/src/encoding.rs @@ -7,23 +7,15 @@ use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, SubsFmtContent, Var #[derive(Hash)] pub enum FlatEncodable<'a> { - U8, - U16, - U32, - U64, - U128, - I8, - I16, - I32, - I64, - I128, - Dec, - F32, - F64, + Immediate(Symbol), + Key(FlatEncodableKey<'a>), +} + +#[derive(Hash, PartialEq, Eq, Debug)] +pub enum FlatEncodableKey<'a> { List(/* takes one variable */), Set(/* takes one variable */), Dict(/* takes two variables */), - Str, // Unfortunate that we must allocate here, c'est la vie Record(Vec<&'a Lowercase>), TagUnion(Vec<(&'a TagName, u16)>), @@ -40,13 +32,14 @@ macro_rules! unexpected { impl FlatEncodable<'_> { pub(crate) fn from_var(subs: &Subs, var: Variable) -> FlatEncodable { + use FlatEncodable::*; match *subs.get_content_without_compacting(var) { Content::Structure(flat_type) => match flat_type { FlatType::Apply(sym, _) => match sym { - Symbol::LIST_LIST => FlatEncodable::List(), - Symbol::SET_SET => FlatEncodable::Set(), - Symbol::DICT_DICT => FlatEncodable::Dict(), - Symbol::STR_STR => FlatEncodable::Str, + Symbol::LIST_LIST => Key(FlatEncodableKey::List()), + Symbol::SET_SET => Key(FlatEncodableKey::Set()), + Symbol::DICT_DICT => Key(FlatEncodableKey::Dict()), + Symbol::STR_STR => Immediate(Symbol::ENCODE_STRING), _ => unexpected!(subs, var), }, FlatType::Record(fields, ext) => { @@ -58,7 +51,7 @@ impl FlatEncodable<'_> { let mut field_names: Vec<_> = subs.get_subs_slice(fields.field_names()).iter().collect(); field_names.sort(); - FlatEncodable::Record(field_names) + Key(FlatEncodableKey::Record(field_names)) } FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => { // The recursion var doesn't matter, because the derived implementation will only @@ -84,31 +77,31 @@ impl FlatEncodable<'_> { }) .collect(); tag_names_and_payload_sizes.sort_by_key(|t| t.0); - FlatEncodable::TagUnion(tag_names_and_payload_sizes) + Key(FlatEncodableKey::TagUnion(tag_names_and_payload_sizes)) } FlatType::FunctionOrTagUnion(name_index, _, _) => { - FlatEncodable::TagUnion(vec![(&subs[name_index], 0)]) + Key(FlatEncodableKey::TagUnion(vec![(&subs[name_index], 0)])) } - FlatType::EmptyRecord => FlatEncodable::Record(vec![]), - FlatType::EmptyTagUnion => FlatEncodable::TagUnion(vec![]), + FlatType::EmptyRecord => Key(FlatEncodableKey::Record(vec![])), + FlatType::EmptyTagUnion => Key(FlatEncodableKey::TagUnion(vec![])), // FlatType::Erroneous(_) => unexpected!(subs, var), FlatType::Func(..) => unexpected!(subs, var), }, Content::Alias(sym, _, real_var, _) => match sym { - Symbol::NUM_U8 => FlatEncodable::U8, - Symbol::NUM_U16 => FlatEncodable::U16, - Symbol::NUM_U32 => FlatEncodable::U32, - Symbol::NUM_U64 => FlatEncodable::U64, - Symbol::NUM_U128 => FlatEncodable::U128, - Symbol::NUM_I8 => FlatEncodable::I8, - Symbol::NUM_I16 => FlatEncodable::I16, - Symbol::NUM_I32 => FlatEncodable::I32, - Symbol::NUM_I64 => FlatEncodable::I64, - Symbol::NUM_I128 => FlatEncodable::I128, - Symbol::NUM_DEC => FlatEncodable::Dec, - Symbol::NUM_F32 => FlatEncodable::F32, - Symbol::NUM_F64 => FlatEncodable::F64, + Symbol::NUM_U8 => Immediate(Symbol::ENCODE_U8), + Symbol::NUM_U16 => Immediate(Symbol::ENCODE_U16), + Symbol::NUM_U32 => Immediate(Symbol::ENCODE_U32), + Symbol::NUM_U64 => Immediate(Symbol::ENCODE_U64), + Symbol::NUM_U128 => Immediate(Symbol::ENCODE_U128), + Symbol::NUM_I8 => Immediate(Symbol::ENCODE_I8), + Symbol::NUM_I16 => Immediate(Symbol::ENCODE_I16), + Symbol::NUM_I32 => Immediate(Symbol::ENCODE_I32), + Symbol::NUM_I64 => Immediate(Symbol::ENCODE_I64), + Symbol::NUM_I128 => Immediate(Symbol::ENCODE_I128), + Symbol::NUM_DEC => Immediate(Symbol::ENCODE_DEC), + Symbol::NUM_F32 => Immediate(Symbol::ENCODE_F32), + Symbol::NUM_F64 => Immediate(Symbol::ENCODE_F64), // TODO: I believe it is okay to unwrap opaques here because derivers are only used // by the backend, and the backend treats opaques like structural aliases. _ => Self::from_var(subs, real_var), diff --git a/compiler/derive_key/src/lib.rs b/compiler/derive_key/src/lib.rs index c263e671de..7a7c006ac8 100644 --- a/compiler/derive_key/src/lib.rs +++ b/compiler/derive_key/src/lib.rs @@ -14,11 +14,12 @@ pub mod encoding; -use encoding::FlatEncodable; +use encoding::{FlatEncodable, FlatEncodableKey}; +use roc_module::symbol::Symbol; use roc_types::subs::{Subs, Variable}; -#[derive(Hash)] +#[derive(Hash, PartialEq, Eq, Debug)] #[repr(u8)] enum Strategy { Encoding, @@ -26,20 +27,37 @@ enum Strategy { Decoding, } -#[derive(Hash)] +#[derive(Hash, PartialEq, Eq, Debug)] +pub enum Derived +where + R: std::hash::Hash + PartialEq + Eq + std::fmt::Debug, +{ + /// If a derived implementation name is well-known ahead-of-time, we can inline the symbol + /// directly rather than associating a key for an implementation to be made later on. + Immediate(Symbol), + /// Key of the derived implementation to use. This allows association of derived implementation + /// names to a key, when the key is known ahead-of-time but the implementation (and it's name) + /// is yet-to-be-made. + Key(DeriveKey), +} + +#[derive(Hash, PartialEq, Eq, Debug)] pub struct DeriveKey where - R: std::hash::Hash, + R: std::hash::Hash + PartialEq + Eq + std::fmt::Debug, { strategy: Strategy, pub repr: R, } -impl<'a> DeriveKey> { +impl<'a> Derived> { pub fn encoding(subs: &'a Subs, var: Variable) -> Self { - DeriveKey { - strategy: Strategy::Encoding, - repr: encoding::FlatEncodable::from_var(subs, var), + match encoding::FlatEncodable::from_var(subs, var) { + FlatEncodable::Immediate(imm) => Derived::Immediate(imm), + FlatEncodable::Key(repr) => Derived::Key(DeriveKey { + strategy: Strategy::Encoding, + repr, + }), } } } diff --git a/compiler/mono/src/derive/encoding.rs b/compiler/mono/src/derive/encoding.rs index ced10deb27..ecda52e61d 100644 --- a/compiler/mono/src/derive/encoding.rs +++ b/compiler/mono/src/derive/encoding.rs @@ -8,12 +8,11 @@ use roc_can::abilities::AbilitiesStore; use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Field, Recursive, WhenBranch}; use roc_can::pattern::Pattern; use roc_collections::SendMap; -use roc_derive_key::encoding::FlatEncodable; -use roc_derive_key::DeriveKey; +use roc_derive_key::encoding::FlatEncodableKey; use roc_error_macros::internal_error; use roc_late_solve::{instantiate_rigids, AbilitiesView}; use roc_module::called_via::CalledVia; -use roc_module::ident::Lowercase; +use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_region::all::{Loc, Region}; use roc_types::subs::{ @@ -118,33 +117,43 @@ fn verify_signature(env: &mut Env<'_>, signature: Variable) { }; } -pub fn derive_to_encoder(env: &mut Env<'_>, for_var: Variable) -> Expr { - match DeriveKey::encoding(env.subs, for_var).repr { - FlatEncodable::U8 => todo!(), - FlatEncodable::U16 => todo!(), - FlatEncodable::U32 => todo!(), - FlatEncodable::U64 => todo!(), - FlatEncodable::U128 => todo!(), - FlatEncodable::I8 => todo!(), - FlatEncodable::I16 => todo!(), - FlatEncodable::I32 => todo!(), - FlatEncodable::I64 => todo!(), - FlatEncodable::I128 => todo!(), - FlatEncodable::Dec => todo!(), - FlatEncodable::F32 => todo!(), - FlatEncodable::F64 => todo!(), - FlatEncodable::List() => todo!(), - FlatEncodable::Set() => todo!(), - FlatEncodable::Dict() => todo!(), - FlatEncodable::Str => todo!(), - FlatEncodable::Record(fields) => { +enum OwnedFlatEncodable { + List, + Set, + Dict, + Record(Vec), + TagUnion(Vec<(TagName, u16)>), +} + +pub struct OwnedFlatEncodableKey(OwnedFlatEncodable); + +impl From> for OwnedFlatEncodableKey { + fn from(key: FlatEncodableKey) -> Self { + use OwnedFlatEncodable::*; + let key = match key { + FlatEncodableKey::List() => List, + FlatEncodableKey::Set() => Set, + FlatEncodableKey::Dict() => Dict, + FlatEncodableKey::Record(fields) => Record(fields.into_iter().cloned().collect()), + FlatEncodableKey::TagUnion(tags) => TagUnion( + tags.into_iter() + .map(|(tag, arity)| (tag.clone(), arity)) + .collect(), + ), + }; + Self(key) + } +} + +pub fn derive_to_encoder(env: &mut Env<'_>, key: OwnedFlatEncodableKey) -> Expr { + match key.0 { + OwnedFlatEncodable::List => todo!(), + OwnedFlatEncodable::Set => todo!(), + OwnedFlatEncodable::Dict => todo!(), + OwnedFlatEncodable::Record(fields) => { // Generalized record var so we can reuse this impl between many records: // if fields = { a, b }, this is { a: t1, b: t2 } for fresh t1, t2. let flex_fields = fields - .iter() - .copied() - .cloned() - .collect::>() .into_iter() .map(|name| { ( @@ -161,14 +170,10 @@ pub fn derive_to_encoder(env: &mut Env<'_>, for_var: Variable) -> Expr { to_encoder_record(env, record_var, fields) } - FlatEncodable::TagUnion(tags) => { + OwnedFlatEncodable::TagUnion(tags) => { // Generalized tag union var so we can reuse this impl between many unions: // if tags = [ A arity=2, B arity=1 ], this is [ A t1 t2, B t3 ] for fresh t1, t2, t3 let flex_tag_labels = tags - .iter() - .copied() - .map(|(label, arity)| (label.clone(), arity)) - .collect::>() .into_iter() .map(|(label, arity)| { let variables_slice = diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index 717eea0e71..5ac805a0c7 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -4,10 +4,7 @@ // For the `v!` macro we use uppercase variables when constructing tag unions. #![allow(non_snake_case)] -use std::{ - hash::{BuildHasher, Hash, Hasher}, - path::PathBuf, -}; +use std::path::PathBuf; use bumpalo::Bump; use indoc::indoc; @@ -22,13 +19,13 @@ use roc_can::{ expr::Expr, module::RigidVariables, }; -use roc_collections::{default_hasher, VecSet}; +use roc_collections::VecSet; use roc_constrain::{ expr::constrain_expr, module::{ExposedByModule, ExposedForModule, ExposedModuleTypes}, }; use roc_debug_flags::dbg_do; -use roc_derive_key::DeriveKey; +use roc_derive_key::{encoding::FlatEncodableKey, Derived}; use roc_load_internal::file::{add_imports, default_aliases, LoadedModule, Threading}; use roc_module::{ ident::{ModuleName, TagName}, @@ -199,6 +196,9 @@ where .all_ident_ids .insert(test_module, IdentIds::default()); + let signature_var = synth_input(&mut test_subs); + let key = get_key(&test_subs, signature_var).into(); + let mut env = Env { home: test_module, arena: &arena, @@ -207,9 +207,7 @@ where exposed_encode_types: &mut exposed_encode_types, }; - let signature_var = synth_input(env.subs); - - let derived = encoding::derive_to_encoder(&mut env, signature_var); + let derived = encoding::derive_to_encoder(&mut env, key); test_module.register_debug_idents(interns.all_ident_ids.get(&test_module).unwrap()); let ctx = Ctx { interns: &interns }; @@ -227,7 +225,14 @@ where ); } -fn check_hash(eq: bool, synth1: S1, synth2: S2) +fn get_key(subs: &Subs, var: Variable) -> FlatEncodableKey { + match Derived::encoding(subs, var) { + Derived::Immediate(_) => unreachable!(), + Derived::Key(key) => key.repr, + } +} + +fn check_key(eq: bool, synth1: S1, synth2: S2) where S1: FnOnce(&mut Subs) -> Variable, S2: FnOnce(&mut Subs) -> Variable, @@ -236,24 +241,13 @@ where let var1 = synth1(&mut subs); let var2 = synth2(&mut subs); - let hash1 = DeriveKey::encoding(&subs, var1); - let hash2 = DeriveKey::encoding(&subs, var2); - - let hash1 = { - let mut hasher = default_hasher().build_hasher(); - hash1.hash(&mut hasher); - hasher.finish() - }; - let hash2 = { - let mut hasher = default_hasher().build_hasher(); - hash2.hash(&mut hasher); - hasher.finish() - }; + let key1 = Derived::encoding(&subs, var1); + let key2 = Derived::encoding(&subs, var2); if eq { - assert_eq!(hash1, hash2); + assert_eq!(key1, key2); } else { - assert_ne!(hash1, hash2); + assert_ne!(key1, key2); } } @@ -332,7 +326,7 @@ macro_rules! test_hash_eq { ($($name:ident: $synth1:expr, $synth2:expr)*) => {$( #[test] fn $name() { - check_hash(true, $synth1, $synth2) + check_key(true, $synth1, $synth2) } )*}; } @@ -341,7 +335,7 @@ macro_rules! test_hash_neq { ($($name:ident: $synth1:expr, $synth2:expr)*) => {$( #[test] fn $name() { - check_hash(false, $synth1, $synth2) + check_key(false, $synth1, $synth2) } )*}; } From ef5dee14f75f4408f7665115ecc1ef1adac1f749 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Fri, 17 Jun 2022 09:20:13 -0400 Subject: [PATCH 51/53] Make sure that required and optional fields are treated the same for encoding --- compiler/test_derive/src/encoding.rs | 58 +++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 10 deletions(-) diff --git a/compiler/test_derive/src/encoding.rs b/compiler/test_derive/src/encoding.rs index 5ac805a0c7..4466ee29d5 100644 --- a/compiler/test_derive/src/encoding.rs +++ b/compiler/test_derive/src/encoding.rs @@ -251,12 +251,29 @@ where } } +fn check_immediate(synth: S, immediate: Symbol) +where + S: FnOnce(&mut Subs) -> Variable, +{ + let mut subs = Subs::new(); + let var = synth(&mut subs); + + let key = Derived::encoding(&subs, var); + + assert_eq!(key, Derived::Immediate(immediate)); +} + // Writing out the types into content is terrible, so let's use a DSL at least for testing macro_rules! v { - ({ $($field:ident: $make_v:expr),* }) => { + ({ $($field:ident: $make_v:expr,)* $(?$opt_field:ident : $make_opt_v:expr,)* }) => { |subs: &mut Subs| { $(let $field = $make_v(subs);)* - let fields = RecordFields::insert_into_subs(subs, vec![ $( (stringify!($field).into(), RecordField::Required($field)) ,)* ]); + $(let $opt_field = $make_opt_v(subs);)* + let fields = vec![ + $( (stringify!($field).into(), RecordField::Required($field)) ,)* + $( (stringify!($opt_field).into(), RecordField::Required($opt_field)) ,)* + ]; + let fields = RecordFields::insert_into_subs(subs, fields); synth_var(subs, Content::Structure(FlatType::Record(fields, Variable::EMPTY_RECORD))) } }; @@ -344,14 +361,17 @@ macro_rules! test_hash_neq { test_hash_eq! { same_record: - v!({ a: v!(U8) }), v!({ a: v!(U8) }) + v!({ a: v!(U8), }), v!({ a: v!(U8), }) same_record_fields_diff_types: - v!({ a: v!(U8) }), v!({ a: v!(STR) }) + v!({ a: v!(U8), }), v!({ a: v!(STR), }) same_record_fields_any_order: - v!({ a: v!(U8), b: v!(U8), c: v!(U8) }), - v!({ c: v!(U8), a: v!(U8), b: v!(U8) }) + v!({ a: v!(U8), b: v!(U8), c: v!(U8), }), + v!({ c: v!(U8), a: v!(U8), b: v!(U8), }) explicit_empty_record_and_implicit_empty_record: v!(EMPTY_RECORD), v!({}) + same_record_fields_required_vs_optional: + v!({ a: v!(U8), b: v!(U8), }), + v!({ ?a: v!(U8), ?b: v!(U8), }) same_tag_union: v!([ A v!(U8) v!(STR), B v!(STR) ]), v!([ A v!(U8) v!(STR), B v!(STR) ]) @@ -392,9 +412,9 @@ test_hash_eq! { test_hash_neq! { different_record_fields: - v!({ a: v!(U8) }), v!({ b: v!(U8) }) + v!({ a: v!(U8), }), v!({ b: v!(U8), }) record_empty_vs_nonempty: - v!(EMPTY_RECORD), v!({ a: v!(U8) }) + v!(EMPTY_RECORD), v!({ a: v!(U8), }) different_tag_union_tags: v!([ A v!(U8) ]), v!([ B v!(U8) ]) @@ -418,6 +438,24 @@ test_hash_neq! { // {{{ deriver tests +#[test] +fn immediates() { + check_immediate(v!(U8), Symbol::ENCODE_U8); + check_immediate(v!(U16), Symbol::ENCODE_U16); + check_immediate(v!(U32), Symbol::ENCODE_U32); + check_immediate(v!(U64), Symbol::ENCODE_U64); + check_immediate(v!(U128), Symbol::ENCODE_U128); + check_immediate(v!(I8), Symbol::ENCODE_I8); + check_immediate(v!(I16), Symbol::ENCODE_I16); + check_immediate(v!(I32), Symbol::ENCODE_I32); + check_immediate(v!(I64), Symbol::ENCODE_I64); + check_immediate(v!(I128), Symbol::ENCODE_I128); + check_immediate(v!(DEC), Symbol::ENCODE_DEC); + check_immediate(v!(F32), Symbol::ENCODE_F32); + check_immediate(v!(F64), Symbol::ENCODE_F64); + check_immediate(v!(STR), Symbol::ENCODE_STRING); +} + #[test] fn empty_record() { derive_test( @@ -447,7 +485,7 @@ fn zero_field_record() { #[test] fn one_field_record() { derive_test( - v!({ a: v!(U8) }), + v!({ a: v!(U8), }), "{ a : val } -> Encoder fmt | fmt has EncoderFormatting, val has Encoding", indoc!( r#" @@ -460,7 +498,7 @@ fn one_field_record() { #[test] fn two_field_record() { derive_test( - v!({ a: v!(U8), b: v!(STR) }), + v!({ a: v!(U8), b: v!(STR), }), "{ a : val, b : a } -> Encoder fmt | a has Encoding, fmt has EncoderFormatting, val has Encoding", indoc!( r#" From d71dabfe92909527e4e22600f87dbabd05aaa7cd Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 31 May 2022 14:34:59 -0500 Subject: [PATCH 52/53] Use macro for new_report_problem_as to avoid conflicting directories Closes #3120 --- reporting/tests/test_reporting.rs | 2119 ++++++++++++++--------------- 1 file changed, 1009 insertions(+), 1110 deletions(-) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index fa1994f1eb..42391d33dc 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -399,7 +399,8 @@ mod test_reporting { assert_eq!(readable, expected_rendering); } - fn new_report_problem_as(subdir: &str, src: &str, expected_rendering: &str) { + /// Do not call this directly! Use the test_report macro below! + fn __new_report_problem_as(subdir: &str, src: &str, expected_rendering: &str) { let arena = Bump::new(); let finalize_render = |doc: RocDocBuilder<'_>, buf: &mut String| { @@ -420,6 +421,15 @@ mod test_reporting { assert_multiline_str_eq!(expected_rendering, buf.as_str()); } + macro_rules! test_report { + ($test_name:ident, $program:expr, $output:expr) => { + #[test] + fn $test_name() { + __new_report_problem_as(std::stringify!($test_name), $program, $output) + } + }; + } + fn human_readable(str: &str) -> String { str.replace(ANSI_STYLE_CODES.red, "") .replace(ANSI_STYLE_CODES.white, "") @@ -8821,49 +8831,44 @@ All branches in an `if` must have the same type! ) } - #[test] - fn ability_demands_not_indented_with_first() { - new_report_problem_as( - "ability_demands_not_indented_with_first", - indoc!( - r#" - Eq has - eq : a, a -> U64 | a has Eq - neq : a, a -> U64 | a has Eq + test_report!( + ability_demands_not_indented_with_first, + indoc!( + r#" + Eq has + eq : a, a -> U64 | a has Eq + neq : a, a -> U64 | a has Eq - 1 - "# - ), - indoc!( - r#" - ── UNFINISHED ABILITY ─── tmp/ability_demands_not_indented_with_first/Test.roc ─ + 1 + "# + ), + indoc!( + r#" + ── UNFINISHED ABILITY ─── tmp/ability_demands_not_indented_with_first/Test.roc ─ - I was partway through parsing an ability definition, but I got stuck - here: + I was partway through parsing an ability definition, but I got stuck + here: - 5│ eq : a, a -> U64 | a has Eq - 6│ neq : a, a -> U64 | a has Eq - ^ + 5│ eq : a, a -> U64 | a has Eq + 6│ neq : a, a -> U64 | a has Eq + ^ - I suspect this line is indented too much (by 4 spaces)"# - ), + I suspect this line is indented too much (by 4 spaces)"# ) - } + ); - #[test] - fn ability_demand_value_has_args() { - new_report_problem_as( - "ability_demand_value_has_args", - indoc!( - r#" + test_report!( + ability_demand_value_has_args, + indoc!( + r#" Eq has eq b c : a, a -> U64 | a has Eq 1 "# - ), - indoc!( - r#" + ), + indoc!( + r#" ── UNFINISHED ABILITY ───────────── tmp/ability_demand_value_has_args/Test.roc ─ I was partway through parsing an ability definition, but I got stuck @@ -8874,9 +8879,8 @@ All branches in an `if` must have the same type! I was expecting to see a : annotating the signature of this value next."# - ), ) - } + ); #[test] fn ability_non_signature_expression() { @@ -9038,722 +9042,668 @@ All branches in an `if` must have the same type! ) } - #[test] - fn ability_bad_type_parameter() { - new_report_problem_as( - "ability_bad_type_parameter", - indoc!( - r#" - app "test" provides [] to "./platform" + test_report!( + ability_bad_type_parameter, + indoc!( + r#" + app "test" provides [] to "./platform" - Hash a b c has - hash : a -> U64 | a has Hash - "# - ), - indoc!( - r#" - ── ABILITY HAS TYPE VARIABLES ──────────────────────────── /code/proj/Main.roc ─ + Hash a b c has + hash : a -> U64 | a has Hash + "# + ), + indoc!( + r#" + ── ABILITY HAS TYPE VARIABLES ──────────────────────────── /code/proj/Main.roc ─ - The definition of the `Hash` ability includes type variables: + The definition of the `Hash` ability includes type variables: - 3│ Hash a b c has - ^^^^^ + 3│ Hash a b c has + ^^^^^ - Abilities cannot depend on type variables, but their member values - can! + Abilities cannot depend on type variables, but their member values + can! - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - `Hash` is not used anywhere in your code. + `Hash` is not used anywhere in your code. - 3│ Hash a b c has - ^^^^ + 3│ Hash a b c has + ^^^^ - If you didn't intend on using `Hash` then remove it so future readers of - your code don't wonder why it is there. - "# - ), + If you didn't intend on using `Hash` then remove it so future readers of + your code don't wonder why it is there. + "# ) - } + ); - #[test] - fn alias_in_has_clause() { - new_report_problem_as( - "alias_in_has_clause", - indoc!( - r#" - app "test" provides [hash] to "./platform" + test_report!( + alias_in_has_clause, + indoc!( + r#" + app "test" provides [hash] to "./platform" - Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool - "# - ), - indoc!( - r#" - ── HAS CLAUSE IS NOT AN ABILITY ────────────────────────── /code/proj/Main.roc ─ + Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool + "# + ), + indoc!( + r#" + ── HAS CLAUSE IS NOT AN ABILITY ────────────────────────── /code/proj/Main.roc ─ - The type referenced in this "has" clause is not an ability: + The type referenced in this "has" clause is not an ability: - 3│ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool - ^^^^^^^^^ - "# - ), + 3│ Hash has hash : a, b -> Num.U64 | a has Hash, b has Bool.Bool + ^^^^^^^^^ + "# ) - } + ); - #[test] - fn shadowed_type_variable_in_has_clause() { - new_report_problem_as( - "shadowed_type_variable_in_has_clause", - indoc!( - r#" - app "test" provides [ab1] to "./platform" + test_report!( + shadowed_type_variable_in_has_clause, + indoc!( + r#" + app "test" provides [ab1] to "./platform" - Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 - "# - ), - indoc!( - r#" - ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ + Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 + "# + ), + indoc!( + r#" + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ - The `a` name is first defined here: + The `a` name is first defined here: - 3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 - ^^^^^^^^^ + 3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 + ^^^^^^^^^ - But then it's defined a second time here: + But then it's defined a second time here: - 3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 - ^^^^^^^^^ + 3│ Ab1 has ab1 : a -> {} | a has Ab1, a has Ab1 + ^^^^^^^^^ - Since these variables have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. - "# - ), + Since these variables have the same name, it's easy to use the wrong + one on accident. Give one of them a new name. + "# ) - } + ); - #[test] - fn ability_shadows_ability() { - new_report_problem_as( - "ability_shadows_ability", - indoc!( - r#" - app "test" provides [ab] to "./platform" + test_report!( + ability_shadows_ability, + indoc!( + r#" + app "test" provides [ab] to "./platform" - Ability has ab : a -> U64 | a has Ability + Ability has ab : a -> U64 | a has Ability - Ability has ab1 : a -> U64 | a has Ability - "# - ), - indoc!( - r#" - ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ + Ability has ab1 : a -> U64 | a has Ability + "# + ), + indoc!( + r#" + ── DUPLICATE NAME ──────────────────────────────────────── /code/proj/Main.roc ─ - The `Ability` name is first defined here: + The `Ability` name is first defined here: - 3│ Ability has ab : a -> U64 | a has Ability - ^^^^^^^ + 3│ Ability has ab : a -> U64 | a has Ability + ^^^^^^^ - But then it's defined a second time here: + But then it's defined a second time here: - 5│ Ability has ab1 : a -> U64 | a has Ability - ^^^^^^^ + 5│ Ability has ab1 : a -> U64 | a has Ability + ^^^^^^^ - Since these abilities have the same name, it's easy to use the wrong - one on accident. Give one of them a new name. - "# - ), + Since these abilities have the same name, it's easy to use the wrong + one on accident. Give one of them a new name. + "# ) - } + ); - #[test] - fn ability_member_does_not_bind_ability() { - new_report_problem_as( - "ability_member_does_not_bind_ability", - indoc!( - r#" - app "test" provides [] to "./platform" + test_report!( + ability_member_does_not_bind_ability, + indoc!( + r#" + app "test" provides [] to "./platform" - Ability has ab : {} -> {} - "# - ), - indoc!( - r#" - ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ + Ability has ab : {} -> {} + "# + ), + indoc!( + r#" + ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ - The definition of the ability member `ab` does not include a `has` clause - binding a type variable to the ability `Ability`: + The definition of the ability member `ab` does not include a `has` clause + binding a type variable to the ability `Ability`: - 3│ Ability has ab : {} -> {} - ^^ + 3│ Ability has ab : {} -> {} + ^^ - Ability members must include a `has` clause binding a type variable to - an ability, like + Ability members must include a `has` clause binding a type variable to + an ability, like - a has Ability + a has Ability - Otherwise, the function does not need to be part of the ability! + Otherwise, the function does not need to be part of the ability! - ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ + ── UNUSED DEFINITION ───────────────────────────────────── /code/proj/Main.roc ─ - `Ability` is not used anywhere in your code. + `Ability` is not used anywhere in your code. - 3│ Ability has ab : {} -> {} - ^^^^^^^ + 3│ Ability has ab : {} -> {} + ^^^^^^^ - If you didn't intend on using `Ability` then remove it so future readers - of your code don't wonder why it is there. - "# - ), + If you didn't intend on using `Ability` then remove it so future readers + of your code don't wonder why it is there. + "# ) - } + ); - #[test] - fn ability_member_binds_parent_twice() { - new_report_problem_as( - "ability_member_binds_parent_twice", - indoc!( - r#" - app "test" provides [] to "./platform" + test_report!( + ability_member_binds_parent_twice, + indoc!( + r#" + app "test" provides [] to "./platform" - Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq - "# - ), - indoc!( - r#" - ── ABILITY MEMBER BINDS MULTIPLE VARIABLES ─────────────── /code/proj/Main.roc ─ + Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq + "# + ), + indoc!( + r#" + ── ABILITY MEMBER BINDS MULTIPLE VARIABLES ─────────────── /code/proj/Main.roc ─ - The definition of the ability member `eq` includes multiple variables - bound to the `Eq`` ability:` + The definition of the ability member `eq` includes multiple variables + bound to the `Eq`` ability:` - 3│ Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq - ^^^^^^^^^^^^^^^^^^ + 3│ Eq has eq : a, b -> Bool.Bool | a has Eq, b has Eq + ^^^^^^^^^^^^^^^^^^ - Ability members can only bind one type variable to their parent - ability. Otherwise, I wouldn't know what type implements an ability by - looking at specializations! + Ability members can only bind one type variable to their parent + ability. Otherwise, I wouldn't know what type implements an ability by + looking at specializations! - Hint: Did you mean to only bind `a` to `Eq`? - "# - ), + Hint: Did you mean to only bind `a` to `Eq`? + "# ) - } + ); - #[test] - fn has_clause_not_on_toplevel() { - new_report_problem_as( - "has_clause_outside_of_ability", - indoc!( - r#" - app "test" provides [f] to "./platform" + test_report!( + has_clause_not_on_toplevel, + indoc!( + r#" + app "test" provides [f] to "./platform" - Hash has hash : (a | a has Hash) -> Num.U64 + Hash has hash : (a | a has Hash) -> Num.U64 - f : a -> Num.U64 | a has Hash - "# - ), - indoc!( - r#" - ── ILLEGAL HAS CLAUSE ──────────────────────────────────── /code/proj/Main.roc ─ + f : a -> Num.U64 | a has Hash + "# + ), + indoc!( + r#" + ── ILLEGAL HAS CLAUSE ──────────────────────────────────── /code/proj/Main.roc ─ - A `has` clause is not allowed here: + A `has` clause is not allowed here: - 3│ Hash has hash : (a | a has Hash) -> Num.U64 - ^^^^^^^^^^ + 3│ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^^^^^^^ - `has` clauses can only be specified on the top-level type annotations. + `has` clauses can only be specified on the top-level type annotations. - ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ + ── ABILITY MEMBER MISSING HAS CLAUSE ───────────────────── /code/proj/Main.roc ─ - The definition of the ability member `hash` does not include a `has` - clause binding a type variable to the ability `Hash`: + The definition of the ability member `hash` does not include a `has` + clause binding a type variable to the ability `Hash`: - 3│ Hash has hash : (a | a has Hash) -> Num.U64 - ^^^^ + 3│ Hash has hash : (a | a has Hash) -> Num.U64 + ^^^^ - Ability members must include a `has` clause binding a type variable to - an ability, like + Ability members must include a `has` clause binding a type variable to + an ability, like - a has Hash + a has Hash - Otherwise, the function does not need to be part of the ability! - "# - ), + Otherwise, the function does not need to be part of the ability! + "# ) - } + ); - #[test] - fn ability_specialization_with_non_implementing_type() { - new_report_problem_as( - "ability_specialization_with_non_implementing_type", - indoc!( - r#" - app "test" provides [hash] to "./platform" + test_report!( + ability_specialization_with_non_implementing_type, + indoc!( + r#" + app "test" provides [hash] to "./platform" - Hash has hash : a -> Num.U64 | a has Hash + Hash has hash : a -> Num.U64 | a has Hash - hash = \{} -> 0u64 - "# - ), - indoc!( - r#" - ── ILLEGAL SPECIALIZATION ──────────────────────────────── /code/proj/Main.roc ─ + hash = \{} -> 0u64 + "# + ), + indoc!( + r#" + ── ILLEGAL SPECIALIZATION ──────────────────────────────── /code/proj/Main.roc ─ - This specialization of `hash` is for a non-opaque type: + This specialization of `hash` is for a non-opaque type: - 5│ hash = \{} -> 0u64 - ^^^^ + 5│ hash = \{} -> 0u64 + ^^^^ - It is specialized for + It is specialized for - {}a + {}a - but structural types can never specialize abilities! + but structural types can never specialize abilities! - Note: `hash` is a member of `#UserApp.Hash` - "# - ), + Note: `hash` is a member of `#UserApp.Hash` + "# ) - } + ); - #[test] - fn ability_specialization_does_not_match_type() { - new_report_problem_as( - "ability_specialization_does_not_match_type", - indoc!( - r#" - app "test" provides [hash] to "./platform" + test_report!( + ability_specialization_does_not_match_type, + indoc!( + r#" + app "test" provides [hash] to "./platform" - Hash has hash : a -> U64 | a has Hash + Hash has hash : a -> U64 | a has Hash - Id := U32 + Id := U32 - hash = \@Id n -> n - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + hash = \@Id n -> n + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - Something is off with this specialization of `hash`: + Something is off with this specialization of `hash`: - 7│ hash = \@Id n -> n - ^^^^ + 7│ hash = \@Id n -> n + ^^^^ - This value is a declared specialization of type: + This value is a declared specialization of type: - Id -> U32 + Id -> U32 - But the type annotation on `hash` says it must match: + But the type annotation on `hash` says it must match: - Id -> U64 - "# - ), + Id -> U64 + "# ) - } + ); - #[test] - fn ability_specialization_is_incomplete() { - new_report_problem_as( - "ability_specialization_is_incomplete", - indoc!( - r#" - app "test" provides [eq, le] to "./platform" + test_report!( + ability_specialization_is_incomplete, + indoc!( + r#" + app "test" provides [eq, le] to "./platform" - Eq has - eq : a, a -> Bool | a has Eq - le : a, a -> Bool | a has Eq + Eq has + eq : a, a -> Bool | a has Eq + le : a, a -> Bool | a has Eq - Id := U64 + Id := U64 - eq = \@Id m, @Id n -> m == n - "# - ), - indoc!( - r#" - ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + eq = \@Id m, @Id n -> m == n + "# + ), + indoc!( + r#" + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ - The type `Id` does not fully implement the ability `Eq`. The following - specializations are missing: + The type `Id` does not fully implement the ability `Eq`. The following + specializations are missing: - A specialization for `le`, which is defined here: + A specialization for `le`, which is defined here: - 5│ le : a, a -> Bool | a has Eq - ^^ - "# - ), - ) - } - - #[test] - fn ability_specialization_overly_generalized() { - new_report_problem_as( - "ability_specialization_overly_generalized", - indoc!( - r#" - app "test" provides [hash] to "./platform" - - Hash has - hash : a -> U64 | a has Hash - - hash = \_ -> 0u64 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - This specialization of `hash` is overly general: - - 6│ hash = \_ -> 0u64 - ^^^^ - - This value is a declared specialization of type: - - a -> U64 - - But the type annotation on `hash` says it must match: - - a -> U64 | a has Hash - - Note: The specialized type is too general, and does not provide a - concrete type where a type variable is bound to an ability. - - Specializations can only be made for concrete types. If you have a - generic implementation for this value, perhaps you don't need an - ability? - "# - ), - ) - } - - #[test] - fn ability_specialization_conflicting_specialization_types() { - new_report_problem_as( - "ability_specialization_conflicting_specialization_types", - indoc!( - r#" - app "test" provides [eq] to "./platform" - - Eq has - eq : a, a -> Bool | a has Eq - - You := {} - AndI := {} - - eq = \@You {}, @AndI {} -> False - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - Something is off with this specialization of `eq`: - - 9│ eq = \@You {}, @AndI {} -> False + 5│ le : a, a -> Bool | a has Eq ^^ - - This value is a declared specialization of type: - - You, AndI -> [False, True] - - But the type annotation on `eq` says it must match: - - You, You -> Bool - - Tip: Type comparisons between an opaque type are only ever equal if - both types are the same opaque type. Did you mean to create an opaque - type by wrapping it? If I have an opaque type Age := U32 I can create - an instance of this opaque type by doing @Age 23. - "# - ), + "# ) - } + ); - #[test] - fn ability_specialization_checked_against_annotation() { - new_report_problem_as( - "ability_specialization_checked_against_annotation", - indoc!( - r#" - app "test" provides [hash] to "./platform" + test_report!( + ability_specialization_overly_generalized, + indoc!( + r#" + app "test" provides [hash] to "./platform" - Hash has - hash : a -> U64 | a has Hash + Hash has + hash : a -> U64 | a has Hash - Id := U64 + hash = \_ -> 0u64 + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - hash : Id -> U32 - hash = \@Id n -> n - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + This specialization of `hash` is overly general: - Something is off with the body of this definition: + 6│ hash = \_ -> 0u64 + ^^^^ - 8│ hash : Id -> U32 - 9│ hash = \@Id n -> n - ^ + This value is a declared specialization of type: - This `n` value is a: + a -> U64 - U64 + But the type annotation on `hash` says it must match: - But the type annotation says it should be: + a -> U64 | a has Hash - U32 + Note: The specialized type is too general, and does not provide a + concrete type where a type variable is bound to an ability. - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + Specializations can only be made for concrete types. If you have a + generic implementation for this value, perhaps you don't need an + ability? + "# + ) + ); - Something is off with this specialization of `hash`: + test_report!( + ability_specialization_conflicting_specialization_types, + indoc!( + r#" + app "test" provides [eq] to "./platform" - 9│ hash = \@Id n -> n + Eq has + eq : a, a -> Bool | a has Eq + + You := {} + AndI := {} + + eq = \@You {}, @AndI {} -> False + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with this specialization of `eq`: + + 9│ eq = \@You {}, @AndI {} -> False + ^^ + + This value is a declared specialization of type: + + You, AndI -> [False, True] + + But the type annotation on `eq` says it must match: + + You, You -> Bool + + Tip: Type comparisons between an opaque type are only ever equal if + both types are the same opaque type. Did you mean to create an opaque + type by wrapping it? If I have an opaque type Age := U32 I can create + an instance of this opaque type by doing @Age 23. + "# + ) + ); + + test_report!( + ability_specialization_checked_against_annotation, + indoc!( + r#" + app "test" provides [hash] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + Id := U64 + + hash : Id -> U32 + hash = \@Id n -> n + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of this definition: + + 8│ hash : Id -> U32 + 9│ hash = \@Id n -> n + ^ + + This `n` value is a: + + U64 + + But the type annotation says it should be: + + U32 + + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with this specialization of `hash`: + + 9│ hash = \@Id n -> n + ^^^^ + + This value is a declared specialization of type: + + Id -> U32 + + But the type annotation on `hash` says it must match: + + Id -> U64 + "# + ) + ); + + test_report!( + ability_specialization_called_with_non_specializing, + indoc!( + r#" + app "test" provides [noGoodVeryBadTerrible] to "./platform" + + Hash has + hash : a -> U64 | a has Hash + + Id := U64 + + hash = \@Id n -> n + + User := {} + + noGoodVeryBadTerrible = + { + nope: hash (@User {}), + notYet: hash (A 1), + } + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 15│ notYet: hash (A 1), + ^^^ + + Roc can't generate an implementation of the `#UserApp.Hash` ability for + + [A (Num a)]b + + Only builtin abilities can have generated implementations! + + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 14│ nope: hash (@User {}), + ^^^^^^^^ + + The type `User` does not fully implement the ability `Hash`. The following + specializations are missing: + + A specialization for `hash`, which is defined here: + + 4│ hash : a -> U64 | a has Hash ^^^^ - - This value is a declared specialization of type: - - Id -> U32 - - But the type annotation on `hash` says it must match: - - Id -> U64 - "# - ), + "# ) - } + ); - #[test] - fn ability_specialization_called_with_non_specializing() { - new_report_problem_as( - "ability_specialization_called_with_non_specializing", - indoc!( - r#" - app "test" provides [noGoodVeryBadTerrible] to "./platform" + test_report!( + ability_not_on_toplevel, + indoc!( + r#" + app "test" provides [main] to "./platform" + main = Hash has hash : a -> U64 | a has Hash - Id := U64 + 123 + "# + ), + indoc!( + r#" + ── ABILITY NOT ON TOP-LEVEL ────────────────────────────── /code/proj/Main.roc ─ - hash = \@Id n -> n + This ability definition is not on the top-level of a module: - User := {} + 4│> Hash has + 5│> hash : a -> U64 | a has Hash - noGoodVeryBadTerrible = - { - nope: hash (@User {}), - notYet: hash (A 1), - } - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - This expression has a type that does not implement the abilities it's expected to: - - 15│ notYet: hash (A 1), - ^^^ - - Roc can't generate an implementation of the `#UserApp.Hash` ability for - - [A (Num a)]b - - Only builtin abilities can have generated implementations! - - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - This expression has a type that does not implement the abilities it's expected to: - - 14│ nope: hash (@User {}), - ^^^^^^^^ - - The type `User` does not fully implement the ability `Hash`. The following - specializations are missing: - - A specialization for `hash`, which is defined here: - - 4│ hash : a -> U64 | a has Hash - ^^^^ - "# - ), + Abilities can only be defined on the top-level of a Roc module. + "# ) - } + ); - #[test] - fn ability_not_on_toplevel() { - new_report_problem_as( - "ability_not_on_toplevel", - indoc!( - r#" - app "test" provides [main] to "./platform" + test_report!( + expression_generalization_to_ability_is_an_error, + indoc!( + r#" + app "test" provides [hash, hashable] to "./platform" - main = - Hash has - hash : a -> U64 | a has Hash + Hash has + hash : a -> U64 | a has Hash - 123 - "# - ), - indoc!( - r#" - ── ABILITY NOT ON TOP-LEVEL ────────────────────────────── /code/proj/Main.roc ─ + Id := U64 + hash = \@Id n -> n - This ability definition is not on the top-level of a module: + hashable : a | a has Hash + hashable = @Id 15 + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - 4│> Hash has - 5│> hash : a -> U64 | a has Hash + Something is off with the body of the `hashable` definition: - Abilities can only be defined on the top-level of a Roc module. - "# - ), + 9│ hashable : a | a has Hash + 10│ hashable = @Id 15 + ^^^^^^ + + This Id opaque wrapping has the type: + + Id + + But the type annotation on `hashable` says it should be: + + a | a has Hash + + Tip: The type annotation uses the type variable `a` to say that this + definition can produce any value implementing the `Hash` ability. But in + the body I see that it will only produce a `Id` 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 expression_generalization_to_ability_is_an_error() { - new_report_problem_as( - "expression_generalization_to_ability_is_an_error", - indoc!( - r#" - app "test" provides [hash, hashable] to "./platform" + test_report!( + ability_value_annotations_are_an_error, + indoc!( + r#" + app "test" provides [result] to "./platform" - Hash has - hash : a -> U64 | a has Hash + Hash has + hash : a -> U64 | a has Hash - Id := U64 - hash = \@Id n -> n + mulHashes : Hash, Hash -> U64 + mulHashes = \x, y -> hash x * hash y - hashable : a | a has Hash - hashable = @Id 15 - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + Id := U64 + hash = \@Id n -> n - Something is off with the body of the `hashable` definition: + Three := {} + hash = \@Three _ -> 3 - 9│ hashable : a | a has Hash - 10│ hashable = @Id 15 - ^^^^^^ + result = mulHashes (@Id 100) (@Three {}) + "# + ), + indoc!( + r#" + ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ - This Id opaque wrapping has the type: + You are attempting to use the ability `Hash` as a type directly: - Id + 6│ mulHashes : Hash, Hash -> U64 + ^^^^ - But the type annotation on `hashable` says it should be: + Abilities can only be used in type annotations to constrain type + variables. - a | a has Hash + Hint: Perhaps you meant to include a `has` annotation, like - Tip: The type annotation uses the type variable `a` to say that this - definition can produce any value implementing the `Hash` ability. But in - the body I see that it will only produce a `Id` value of a single - specific type. Maybe change the type annotation to be more specific? - Maybe change the code to be more general? - "# - ), + a has Hash + + ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ + + You are attempting to use the ability `Hash` as a type directly: + + 6│ mulHashes : Hash, Hash -> U64 + ^^^^ + + Abilities can only be used in type annotations to constrain type + variables. + + Hint: Perhaps you meant to include a `has` annotation, like + + b has Hash + "# ) - } + ); - #[test] - fn ability_value_annotations_are_an_error() { - new_report_problem_as( - "ability_value_annotations_are_an_error", - indoc!( - r#" - app "test" provides [result] to "./platform" + test_report!( + branches_have_more_cases_than_condition, + indoc!( + r#" + foo : Bool -> Str + foo = \bool -> + when bool is + True -> "true" + False -> "false" + Wat -> "surprise!" + foo + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - Hash has - hash : a -> U64 | a has Hash + The branches of this `when` expression don't match the condition: - mulHashes : Hash, Hash -> U64 - mulHashes = \x, y -> hash x * hash y + 6│> when bool is + 7│ True -> "true" + 8│ False -> "false" + 9│ Wat -> "surprise!" - Id := U64 - hash = \@Id n -> n + This `bool` value is a: - Three := {} - hash = \@Three _ -> 3 + Bool - result = mulHashes (@Id 100) (@Three {}) - "# - ), - indoc!( - r#" - ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ + But the branch patterns have type: - You are attempting to use the ability `Hash` as a type directly: + [False, True, Wat] - 6│ mulHashes : Hash, Hash -> U64 - ^^^^ - - Abilities can only be used in type annotations to constrain type - variables. - - Hint: Perhaps you meant to include a `has` annotation, like - - a has Hash - - ── ABILITY USED AS TYPE ────────────────────────────────── /code/proj/Main.roc ─ - - You are attempting to use the ability `Hash` as a type directly: - - 6│ mulHashes : Hash, Hash -> U64 - ^^^^ - - Abilities can only be used in type annotations to constrain type - variables. - - Hint: Perhaps you meant to include a `has` annotation, like - - b has Hash - "# - ), + The branches must be cases of the `when` condition's type! + "# ) - } - - #[test] - fn branches_have_more_cases_than_condition() { - new_report_problem_as( - "branches_have_more_cases_than_condition", - indoc!( - r#" - foo : Bool -> Str - foo = \bool -> - when bool is - True -> "true" - False -> "false" - Wat -> "surprise!" - foo - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The branches of this `when` expression don't match the condition: - - 6│> when bool is - 7│ True -> "true" - 8│ False -> "false" - 9│ Wat -> "surprise!" - - This `bool` value is a: - - Bool - - But the branch patterns have type: - - [False, True, Wat] - - The branches must be cases of the `when` condition's type! - "# - ), - ) - } + ); #[test] fn always_function() { @@ -9774,578 +9724,527 @@ All branches in an `if` must have the same type! ) } - #[test] - fn imports_missing_comma() { - new_report_problem_as( - "imports_missing_comma", - indoc!( - r#" - app "test-missing-comma" - packages { pf: "platform" } - imports [pf.Task Base64] - provides [main, @Foo] to pf - "# - ), - indoc!( - r#" - ── WEIRD IMPORTS ────────────────────────── tmp/imports_missing_comma/Test.roc ─ + test_report!( + imports_missing_comma, + indoc!( + r#" + app "test-missing-comma" + packages { pf: "platform" } + imports [pf.Task Base64] + provides [main, @Foo] to pf + "# + ), + indoc!( + r#" + ── WEIRD IMPORTS ────────────────────────── tmp/imports_missing_comma/Test.roc ─ - I am partway through parsing a imports list, but I got stuck here: + I am partway through parsing a imports list, but I got stuck here: - 2│ packages { pf: "platform" } - 3│ imports [pf.Task Base64] - ^ + 2│ packages { pf: "platform" } + 3│ imports [pf.Task Base64] + ^ - I am expecting a comma or end of list, like + I am expecting a comma or end of list, like - imports [Shape, Vector]"# - ), + imports [Shape, Vector]"# ) - } + ); - #[test] - fn not_enough_cases_for_open_union() { - new_report_problem_as( - "branches_have_more_cases_than_condition", - indoc!( - r#" - foo : [A, B]a -> Str - foo = \it -> - when it is - A -> "" - foo - "# - ), - indoc!( - r#" - ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ + test_report!( + not_enough_cases_for_open_union, + indoc!( + r#" + foo : [A, B]a -> Str + foo = \it -> + when it is + A -> "" + foo + "# + ), + indoc!( + r#" + ── UNSAFE PATTERN ──────────────────────────────────────── /code/proj/Main.roc ─ - This `when` does not cover all the possibilities: + This `when` does not cover all the possibilities: - 6│> when it is - 7│> A -> "" + 6│> when it is + 7│> A -> "" - Other possibilities include: + Other possibilities include: - B - _ + B + _ - I would have to crash if I saw one of those! Add branches for them! - "# - ), + I would have to crash if I saw one of those! Add branches for them! + "# ) - } + ); - #[test] - fn issue_2778_specialization_is_not_a_redundant_pattern() { - new_report_problem_as( - "issue_2778_specialization_is_not_a_redundant_pattern", - indoc!( - r#" - formatColor = \color -> - when color is - Red -> "red" - Yellow -> "yellow" - _ -> "unknown" + test_report!( + issue_2778_specialization_is_not_a_redundant_pattern, + indoc!( + r#" + formatColor = \color -> + when color is + Red -> "red" + Yellow -> "yellow" + _ -> "unknown" - Red |> formatColor |> Str.concat (formatColor Orange) - "# - ), - "", // no problem - ) - } + Red |> formatColor |> Str.concat (formatColor Orange) + "# + ), + "" // no problem + ); - #[test] - fn nested_specialization() { - new_report_problem_as( - "nested_specialization", - indoc!( - r#" - app "test" provides [main] to "./platform" + test_report!( + nested_specialization, + indoc!( + r#" + app "test" provides [main] to "./platform" - Default has default : {} -> a | a has Default - - main = - A := {} - default = \{} -> @A {} - default {} - "# - ), - indoc!( - r#" - ── SPECIALIZATION NOT ON TOP-LEVEL ─────────────────────── /code/proj/Main.roc ─ - - This specialization of the `default` ability member is in a nested - scope: - - 7│ default = \{} -> @A {} - ^^^^^^^ - - Specializations can only be defined on the top-level of a module. - "# - ), - ) - } - - #[test] - fn recursion_var_specialization_error() { - new_report_problem_as( - "recursion_var_specialization_error", - indoc!( - r#" - Job a : [Job (List (Job a))] - - job : Job Str - - when job is - Job lst -> lst == "" - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 2nd argument to `isEq` is not what I expect: - - 9│ Job lst -> lst == "" - ^^ - - This argument is a string of type: - - Str - - But `isEq` needs the 2nd argument to be: - - List [Job ∞] as ∞ - "# - ), - ) - } - - #[test] - fn type_error_in_apply_is_circular() { - new_report_problem_as( - "type_error_in_apply_is_circular", - indoc!( - r#" - app "test" provides [go] to "./platform" - - S a : { set : Set a } - - go : a, S a -> Result (List a) * - go = \goal, model -> - if goal == goal - then Ok [] - else - new = { model & set : Set.remove goal model.set } - go goal new - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - The 1st argument to `remove` is not what I expect: - - 10│ new = { model & set : Set.remove goal model.set } - ^^^^ - - This `goal` value is a: - - a - - But `remove` needs the 1st argument to be: - - Set 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 `Set` value of a single specific type. Maybe - change the type annotation to be more specific? Maybe change the code - to be more general? - - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - - I'm inferring a weird self-referential type for `new`: - - 10│ new = { model & set : Set.remove goal model.set } - ^^^ - - 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. - - { set : Set ∞ } - - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - - I'm inferring a weird self-referential type for `goal`: - - 6│ go = \goal, model -> - ^^^^ - - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. - - Set ∞ - "# - ), - ) - } - - #[test] - fn cycle_through_non_function() { - new_report_problem_as( - "cycle_through_non_function", - indoc!( - r#" - force : ({} -> I64) -> I64 - force = \eval -> eval {} - - t1 = \_ -> force (\_ -> t2) - - t2 = t1 {} - - t2 - "# - ), - indoc!( - r#" - ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ - - The `t1` definition is causing a very tricky infinite loop: - - 7│ t1 = \_ -> force (\_ -> t2) - ^^ - - The `t1` value depends on itself through the following chain of - definitions: - - ┌─────┐ - │ t1 - │ ↓ - │ t2 - └─────┘ - "# - ), - ) - } - - #[test] - fn function_does_not_implement_encoding() { - new_report_problem_as( - "function_does_not_implement_encoding", - indoc!( - r#" - app "test" imports [Encode] provides [main] to "./platform" - - main = Encode.toEncoder \x -> x - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - This expression has a type that does not implement the abilities it's expected to: - - 3│ main = Encode.toEncoder \x -> x - ^^^^^^^ - - Roc can't generate an implementation of the `Encode.Encoding` ability - for - - a -> a - - Note: `Encoding` cannot be generated for functions. - "# - ), - ) - } - - #[test] - fn unbound_type_in_record_does_not_implement_encoding() { - new_report_problem_as( - "unbound_type_in_record_does_not_implement_encoding", - indoc!( - r#" - app "test" imports [Encode] provides [main] to "./platform" - - main = \x -> Encode.toEncoder { x: x } - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - - This expression has a type that does not implement the abilities it's expected to: - - 3│ main = \x -> Encode.toEncoder { x: x } - ^^^^^^^^ - - Roc can't generate an implementation of the `Encode.Encoding` ability - for - - { x : a } - - In particular, an implementation for - - a - - cannot be generated. - - Tip: This type variable is not bound to `Encoding`. Consider adding a - `has` clause to bind the type variable, like `| a has Encode.Encoding` - "# - ), - ) - } - - #[test] - fn nested_opaque_does_not_implement_encoding() { - new_report_problem_as( - "nested_opaque_does_not_implement_encoding", - indoc!( - r#" - app "test" imports [Encode] provides [main] to "./platform" + Default has default : {} -> a | a has Default + main = A := {} - main = Encode.toEncoder { x: @A {} } - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + default = \{} -> @A {} + default {} + "# + ), + indoc!( + r#" + ── SPECIALIZATION NOT ON TOP-LEVEL ─────────────────────── /code/proj/Main.roc ─ - This expression has a type that does not implement the abilities it's expected to: + This specialization of the `default` ability member is in a nested + scope: - 4│ main = Encode.toEncoder { x: @A {} } - ^^^^^^^^^^^^ + 7│ default = \{} -> @A {} + ^^^^^^^ - Roc can't generate an implementation of the `Encode.Encoding` ability - for - - { x : A } - - In particular, an implementation for - - A - - cannot be generated. - - Tip: `A` does not implement `Encoding`. Consider adding a custom - implementation or `has Encode.Encoding` to the definition of `A`. - "# - ), + Specializations can only be defined on the top-level of a module. + "# ) - } + ); - #[test] - fn derive_non_builtin_ability() { - new_report_problem_as( - "derive_non_builtin_ability", - indoc!( - r#" - app "test" provides [A] to "./platform" + test_report!( + recursion_var_specialization_error, + indoc!( + r#" + Job a : [Job (List (Job a))] - Ab has ab : a -> a | a has Ab + job : Job Str - A := {} has [Ab] - "# - ), - indoc!( - r#" - ── ILLEGAL DERIVE ──────────────────────────────────────── /code/proj/Main.roc ─ + when job is + Job lst -> lst == "" + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - This ability cannot be derived: + The 2nd argument to `isEq` is not what I expect: - 5│ A := {} has [Ab] - ^^ + 9│ Job lst -> lst == "" + ^^ - Only builtin abilities can be derived. + This argument is a string of type: - Note: The builtin abilities are `Encode.Encoding` - "# - ), + Str + + But `isEq` needs the 2nd argument to be: + + List [Job ∞] as ∞ + "# ) - } + ); - #[test] - fn has_encoding_for_function() { - new_report_problem_as( - "has_encoding_for_function", - indoc!( - r#" - app "test" imports [Encode] provides [A] to "./platform" + test_report!( + type_error_in_apply_is_circular, + indoc!( + r#" + app "test" provides [go] to "./platform" - A a := a -> a has [Encode.Encoding] - "# - ), - indoc!( - r#" - ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + S a : { set : Set a } - Roc can't derive an implementation of the `Encode.Encoding` for `A`: + go : a, S a -> Result (List a) * + go = \goal, model -> + if goal == goal + then Ok [] + else + new = { model & set : Set.remove goal model.set } + go goal new + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - 3│ A a := a -> a has [Encode.Encoding] - ^^^^^^^^^^^^^^^ + The 1st argument to `remove` is not what I expect: - Note: `Encoding` cannot be generated for functions. + 10│ new = { model & set : Set.remove goal model.set } + ^^^^ - Tip: You can define a custom implementation of `Encode.Encoding` for `A`. - "# - ), + This `goal` value is a: + + a + + But `remove` needs the 1st argument to be: + + Set 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 `Set` value of a single specific type. Maybe + change the type annotation to be more specific? Maybe change the code + to be more general? + + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + + I'm inferring a weird self-referential type for `new`: + + 10│ new = { model & set : Set.remove goal model.set } + ^^^ + + 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. + + { set : Set ∞ } + + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + + I'm inferring a weird self-referential type for `goal`: + + 6│ go = \goal, model -> + ^^^^ + + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. + + Set ∞ + "# ) - } + ); - #[test] - fn has_encoding_for_non_encoding_alias() { - new_report_problem_as( - "has_encoding_for_non_encoding_alias", - indoc!( - r#" - app "test" imports [Encode] provides [A] to "./platform" + test_report!( + cycle_through_non_function, + indoc!( + r#" + force : ({} -> I64) -> I64 + force = \eval -> eval {} - A := B has [Encode.Encoding] + t1 = \_ -> force (\_ -> t2) - B := {} - "# - ), - indoc!( - r#" - ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + t2 = t1 {} - Roc can't derive an implementation of the `Encode.Encoding` for `A`: + t2 + "# + ), + indoc!( + r#" + ── CIRCULAR DEFINITION ─────────────────────────────────── /code/proj/Main.roc ─ - 3│ A := B has [Encode.Encoding] - ^^^^^^^^^^^^^^^ + The `t1` definition is causing a very tricky infinite loop: - Tip: `B` does not implement `Encoding`. Consider adding a custom - implementation or `has Encode.Encoding` to the definition of `B`. + 7│ t1 = \_ -> force (\_ -> t2) + ^^ - Tip: You can define a custom implementation of `Encode.Encoding` for `A`. - "# - ), + The `t1` value depends on itself through the following chain of + definitions: + + ┌─────┐ + │ t1 + │ ↓ + │ t2 + └─────┘ + "# ) - } + ); - #[test] - fn has_encoding_for_other_has_encoding() { - new_report_problem_as( - "has_encoding_for_other_has_encoding", - indoc!( - r#" - app "test" imports [Encode] provides [A] to "./platform" + test_report!( + function_does_not_implement_encoding, + indoc!( + r#" + app "test" imports [Encode] provides [main] to "./platform" - A := B has [Encode.Encoding] + main = Encode.toEncoder \x -> x + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - B := {} has [Encode.Encoding] - "# - ), - indoc!(""), // no error + This expression has a type that does not implement the abilities it's expected to: + + 3│ main = Encode.toEncoder \x -> x + ^^^^^^^ + + Roc can't generate an implementation of the `Encode.Encoding` ability + for + + a -> a + + Note: `Encoding` cannot be generated for functions. + "# ) - } + ); - #[test] - fn has_encoding_for_recursive_deriving() { - new_report_problem_as( - "has_encoding_for_recursive_deriving", - indoc!( - r#" - app "test" imports [Encode] provides [MyNat] to "./platform" + test_report!( + unbound_type_in_record_does_not_implement_encoding, + indoc!( + r#" + app "test" imports [Encode] provides [main] to "./platform" - MyNat := [S MyNat, Z] has [Encode.Encoding] - "# - ), - indoc!(""), // no error + main = \x -> Encode.toEncoder { x: x } + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + This expression has a type that does not implement the abilities it's expected to: + + 3│ main = \x -> Encode.toEncoder { x: x } + ^^^^^^^^ + + Roc can't generate an implementation of the `Encode.Encoding` ability + for + + { x : a } + + In particular, an implementation for + + a + + cannot be generated. + + Tip: This type variable is not bound to `Encoding`. Consider adding a + `has` clause to bind the type variable, like `| a has Encode.Encoding` + "# ) - } + ); - #[test] - fn has_encoding_dominated_by_custom() { - new_report_problem_as( - "has_encoding_dominated_by_custom", - indoc!( - r#" - app "test" imports [Encode.{ Encoding, toEncoder, custom }] provides [A] to "./platform" + test_report!( + nested_opaque_does_not_implement_encoding, + indoc!( + r#" + app "test" imports [Encode] provides [main] to "./platform" - A := {} has [Encode.Encoding] + A := {} + main = Encode.toEncoder { x: @A {} } + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ - toEncoder = \@A {} -> custom \l, _ -> l - "# - ), - indoc!( - r#" - ── CONFLICTING DERIVE AND IMPLEMENTATION ───────────────── /code/proj/Main.roc ─ + This expression has a type that does not implement the abilities it's expected to: - `A` both derives and custom-implements `Encode.Encoding`. We found the - derive here: + 4│ main = Encode.toEncoder { x: @A {} } + ^^^^^^^^^^^^ - 3│ A := {} has [Encode.Encoding] - ^^^^^^^^^^^^^^^ + Roc can't generate an implementation of the `Encode.Encoding` ability + for - and one custom implementation of `Encode.Encoding` here: + { x : A } - 5│ toEncoder = \@A {} -> custom \l, _ -> l - ^^^^^^^^^ + In particular, an implementation for - Derived and custom implementations can conflict, so one of them needs - to be removed! + A - Note: We'll try to compile your program using the custom - implementation first, and fall-back on the derived implementation if - needed. Make sure to disambiguate which one you want! - "# - ), + cannot be generated. + + Tip: `A` does not implement `Encoding`. Consider adding a custom + implementation or `has Encode.Encoding` to the definition of `A`. + "# ) - } + ); - #[test] - fn issue_1755() { - new_report_problem_as( - "issue_1755", - indoc!( - r#" - Handle := {} + test_report!( + derive_non_builtin_ability, + indoc!( + r#" + app "test" provides [A] to "./platform" - await : Result a err, (a -> Result b err) -> Result b err - open : {} -> Result Handle * - close : Handle -> Result {} * + Ab has ab : a -> a | a has Ab - withOpen : (Handle -> Result {} *) -> Result {} * - withOpen = \callback -> - handle <- await (open {}) - {} <- await (callback handle) - close handle + A := {} has [Ab] + "# + ), + indoc!( + r#" + ── ILLEGAL DERIVE ──────────────────────────────────────── /code/proj/Main.roc ─ - withOpen - "# - ), - indoc!( - r#" - ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + This ability cannot be derived: - Something is off with the body of the `withOpen` definition: + 5│ A := {} has [Ab] + ^^ - 10│ withOpen : (Handle -> Result {} *) -> Result {} * - 11│ withOpen = \callback -> - 12│> handle <- await (open {}) - 13│> {} <- await (callback handle) - 14│> close handle + Only builtin abilities can be derived. - The type annotation on `withOpen` says this `await` call should have the - type: - - Result {} * - - However, the type of this `await` call is connected to another type in a - way that isn't reflected in this annotation. - - Tip: Any connection between types must use a named type variable, not - a `*`! Maybe the annotation on `withOpen` should have a named type - variable in place of the `*`? - "# - ), + Note: The builtin abilities are `Encode.Encoding` + "# ) - } + ); + + test_report!( + has_encoding_for_function, + indoc!( + r#" + app "test" imports [Encode] provides [A] to "./platform" + + A a := a -> a has [Encode.Encoding] + "# + ), + indoc!( + r#" + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + + Roc can't derive an implementation of the `Encode.Encoding` for `A`: + + 3│ A a := a -> a has [Encode.Encoding] + ^^^^^^^^^^^^^^^ + + Note: `Encoding` cannot be generated for functions. + + Tip: You can define a custom implementation of `Encode.Encoding` for `A`. + "# + ) + ); + + test_report!( + has_encoding_for_non_encoding_alias, + indoc!( + r#" + app "test" imports [Encode] provides [A] to "./platform" + + A := B has [Encode.Encoding] + + B := {} + "# + ), + indoc!( + r#" + ── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────── /code/proj/Main.roc ─ + + Roc can't derive an implementation of the `Encode.Encoding` for `A`: + + 3│ A := B has [Encode.Encoding] + ^^^^^^^^^^^^^^^ + + Tip: `B` does not implement `Encoding`. Consider adding a custom + implementation or `has Encode.Encoding` to the definition of `B`. + + Tip: You can define a custom implementation of `Encode.Encoding` for `A`. + "# + ) + ); + + test_report!( + has_encoding_for_other_has_encoding, + indoc!( + r#" + app "test" imports [Encode] provides [A] to "./platform" + + A := B has [Encode.Encoding] + + B := {} has [Encode.Encoding] + "# + ), + indoc!("") // no error + ); + + test_report!( + has_encoding_for_recursive_deriving, + indoc!( + r#" + app "test" imports [Encode] provides [MyNat] to "./platform" + + MyNat := [S MyNat, Z] has [Encode.Encoding] + "# + ), + indoc!("") // no error + ); + + test_report!( + has_encoding_dominated_by_custom, + indoc!( + r#" + app "test" imports [Encode.{ Encoding, toEncoder, custom }] provides [A] to "./platform" + + A := {} has [Encode.Encoding] + + toEncoder = \@A {} -> custom \l, _ -> l + "# + ), + indoc!( + r#" + ── CONFLICTING DERIVE AND IMPLEMENTATION ───────────────── /code/proj/Main.roc ─ + + `A` both derives and custom-implements `Encode.Encoding`. We found the + derive here: + + 3│ A := {} has [Encode.Encoding] + ^^^^^^^^^^^^^^^ + + and one custom implementation of `Encode.Encoding` here: + + 5│ toEncoder = \@A {} -> custom \l, _ -> l + ^^^^^^^^^ + + Derived and custom implementations can conflict, so one of them needs + to be removed! + + Note: We'll try to compile your program using the custom + implementation first, and fall-back on the derived implementation if + needed. Make sure to disambiguate which one you want! + "# + ) + ); + + test_report!( + "issue_1755", + indoc!( + r#" + Handle := {} + + await : Result a err, (a -> Result b err) -> Result b err + open : {} -> Result Handle * + close : Handle -> Result {} * + + withOpen : (Handle -> Result {} *) -> Result {} * + withOpen = \callback -> + handle <- await (open {}) + {} <- await (callback handle) + close handle + + withOpen + "# + ), + indoc!( + r#" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + Something is off with the body of the `withOpen` definition: + + 10│ withOpen : (Handle -> Result {} *) -> Result {} * + 11│ withOpen = \callback -> + 12│> handle <- await (open {}) + 13│> {} <- await (callback handle) + 14│> close handle + + The type annotation on `withOpen` says this `await` call should have the + type: + + Result {} * + + However, the type of this `await` call is connected to another type in a + way that isn't reflected in this annotation. + + Tip: Any connection between types must use a named type variable, not + a `*`! Maybe the annotation on `withOpen` should have a named type + variable in place of the `*`? + "# + ), + ); } From 47f50a91be4aed73729260d0c4109f88c6e12bb0 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Fri, 17 Jun 2022 11:40:58 -0400 Subject: [PATCH 53/53] Fix macro invocation typo --- reporting/tests/test_reporting.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reporting/tests/test_reporting.rs b/reporting/tests/test_reporting.rs index 42391d33dc..4d46fae593 100644 --- a/reporting/tests/test_reporting.rs +++ b/reporting/tests/test_reporting.rs @@ -10203,7 +10203,7 @@ All branches in an `if` must have the same type! ); test_report!( - "issue_1755", + issue_1755, indoc!( r#" Handle := {} @@ -10245,6 +10245,6 @@ All branches in an `if` must have the same type! a `*`! Maybe the annotation on `withOpen` should have a named type variable in place of the `*`? "# - ), + ) ); }