Merge pull request #3815 from roc-lang/glue-dict

Generate glue for `Dict` and `Set`
This commit is contained in:
Richard Feldman 2022-08-21 18:06:05 -04:00 committed by GitHub
commit f2f4fb59b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 472 additions and 3 deletions

View File

@ -41,10 +41,39 @@ insert = \@Set dict, key ->
|> Dict.insert key {}
|> @Set
# Inserting a duplicate key has no effect.
expect
actual =
Set.empty
|> Set.insert "foo"
|> Set.insert "bar"
|> Set.insert "foo"
|> Set.insert "baz"
expected =
Set.empty
|> Set.insert "foo"
|> Set.insert "bar"
|> Set.insert "baz"
expected == actual
len : Set k -> Nat
len = \@Set dict ->
Dict.len dict
# Inserting a duplicate key has no effect on length.
expect
actual =
Set.empty
|> Set.insert "foo"
|> Set.insert "bar"
|> Set.insert "foo"
|> Set.insert "baz"
|> Set.len
actual == 3
## Drops the given element from the set.
remove : Set k, k -> Set k
remove = \@Set dict, key ->

View File

@ -3018,7 +3018,7 @@ impl RecordFields {
(it, ext)
}
/// Get a sorted iterator over the fields of this record type
/// get a sorted iterator over the fields of this record type
///
/// Implementation: When the record has an `ext` variable that is the empty record, then
/// we read the (assumed sorted) fields directly from Subs. Otherwise we have to chase the

View File

@ -18,7 +18,7 @@ use roc_mono::layout::{
use roc_target::TargetInfo;
use roc_types::{
subs::{Content, FlatType, GetSubsSlice, Subs, UnionLabels, UnionTags, Variable},
types::RecordField,
types::{AliasKind, RecordField},
};
use std::fmt::Display;
@ -934,7 +934,97 @@ fn add_builtin_type<'a>(
list_id
}
(layout, typ) => todo!("Handle builtin layout {:?} and type {:?}", layout, typ),
(
Builtin::List(elem_layout),
Alias(Symbol::DICT_DICT, _alias_variables, alias_var, AliasKind::Opaque),
) => {
match (
elem_layout,
env.subs.get_content_without_compacting(*alias_var),
) {
(
Layout::Struct { field_layouts, .. },
Content::Structure(FlatType::Apply(Symbol::LIST_LIST, args_subs_slice)),
) => {
let (key_var, val_var) = {
let args_tuple = env.subs.get_subs_slice(*args_subs_slice);
debug_assert_eq!(args_tuple.len(), 1);
match env.subs.get_content_without_compacting(args_tuple[0]) {
Content::Structure(FlatType::TagUnion(union_tags, ext_var)) => {
let (mut iter, _) = union_tags.sorted_iterator_and_ext(env.subs, *ext_var);
let payloads = iter.next().unwrap().1;
debug_assert_eq!(iter.next(), None);
(payloads[0], payloads[1])
}
_ => {
unreachable!()
}
}
};
debug_assert_eq!(field_layouts.len(), 2);
let key_id = add_type_help(env, field_layouts[0], key_var, opt_name, types);
let val_id = add_type_help(env, field_layouts[1], val_var, opt_name, types);
let dict_id = types.add_anonymous(RocType::RocDict(key_id, val_id), layout);
types.depends(dict_id, key_id);
types.depends(dict_id, val_id);
dict_id
}
(elem_layout, alias_content) => unreachable!(
"Unrecognized List element for Dict. Layout was: {:?} and alias_content was: {:?}",
elem_layout,
alias_content
),
}
}
(
Builtin::List(elem_layout),
Alias(Symbol::SET_SET, _alias_vars, alias_var, AliasKind::Opaque),
) => {
match (
elem_layout,
env.subs.get_content_without_compacting(*alias_var),
) {
(
Layout::Struct { field_layouts, .. },
Alias(Symbol::DICT_DICT, alias_args, _alias_var, AliasKind::Opaque),
) => {
let dict_type_vars = env.subs.get_subs_slice(alias_args.type_variables());
debug_assert_eq!(dict_type_vars.len(), 2);
// Sets only use the key of the Dict they wrap, not the value
let elem_var = dict_type_vars[0];
debug_assert_eq!(field_layouts.len(), 2);
let elem_id = add_type_help(env, field_layouts[0], elem_var, opt_name, types);
let set_id = types.add_anonymous(RocType::RocSet(elem_id), layout);
types.depends(set_id, elem_id);
set_id
}
(elem_layout, alias_content) => unreachable!(
"Unrecognized List element for Set. Layout was: {:?} and alias_content was: {:?}",
elem_layout,
alias_content
),
}
}
(Builtin::List(elem_layout), alias) => {
unreachable!(
"The type alias {:?} was not an Apply(Symbol::LIST_LIST) as expected, given that its builtin was Builtin::List({:?})",
alias, elem_layout
);
}
}
}

10
crates/glue/tests/fixtures/dict/app.roc vendored Normal file
View File

@ -0,0 +1,10 @@
app "app"
packages { pf: "platform.roc" }
imports []
provides [main] to pf
main =
Dict.empty
|> Dict.insert "foo" "this will be overwritten"
|> Dict.insert "baz" "blah"
|> Dict.insert "foo" "bar"

View File

@ -0,0 +1,9 @@
platform "test-platform"
requires {} { main : _ }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Dict Str Str
mainForHost = main

View File

@ -0,0 +1,93 @@
mod test_glue;
use roc_std::{RocDict, RocStr};
extern "C" {
#[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(_: *mut RocDict<RocStr, RocStr>);
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
use std::cmp::Ordering;
use std::collections::hash_set::HashSet;
let dict = unsafe {
let mut ret: core::mem::MaybeUninit<RocDict<RocStr, RocStr>> =
core::mem::MaybeUninit::uninit();
roc_main(ret.as_mut_ptr());
ret.assume_init()
};
// Verify that it has all the expected traits.
assert!(dict == dict); // PartialEq
assert_eq!(dict.len(), 2); // len
assert!(dict.clone() == dict.clone()); // Clone
assert!(dict.partial_cmp(&dict) == Some(Ordering::Equal)); // PartialOrd
assert!(dict.cmp(&dict) == Ordering::Equal); // Ord
let mut set = HashSet::new();
set.insert(dict.clone()); // Eq, Hash
set.insert(dict.clone());
assert_eq!(set.len(), 1);
println!("dict was: {:?}", dict); // Debug
// Exit code
0
}
// Externs required by roc_std and by the Roc app
use core::ffi::c_void;
use std::ffi::CStr;
use std::os::raw::c_char;
#[no_mangle]
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
return libc::malloc(size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
return libc::realloc(c_ptr, new_size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
return libc::free(c_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
_ => todo!(),
}
}
#[no_mangle]
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
libc::memcpy(dst, src, n)
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}

11
crates/glue/tests/fixtures/set/app.roc vendored Normal file
View File

@ -0,0 +1,11 @@
app "app"
packages { pf: "platform.roc" }
imports []
provides [main] to pf
main =
Set.empty
|> Set.insert "foo"
|> Set.insert "bar"
|> Set.insert "foo"
|> Set.insert "baz"

View File

@ -0,0 +1,9 @@
platform "test-platform"
requires {} { main : _ }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Set Str
mainForHost = main

View File

@ -0,0 +1,92 @@
mod test_glue;
use roc_std::{RocSet, RocStr};
extern "C" {
#[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(_: *mut RocSet<RocStr>);
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
use std::cmp::Ordering;
use std::collections::hash_set::HashSet;
let set = unsafe {
let mut ret: core::mem::MaybeUninit<RocSet<RocStr>> = core::mem::MaybeUninit::uninit();
roc_main(ret.as_mut_ptr());
ret.assume_init()
};
// Verify that it has all the expected traits.
assert!(set == set); // PartialEq
assert_eq!(set.len(), 3); // len
assert!(set.clone() == set.clone()); // Clone
assert!(set.partial_cmp(&set) == Some(Ordering::Equal)); // PartialOrd
assert!(set.cmp(&set) == Ordering::Equal); // Ord
let mut hash_set = HashSet::new();
hash_set.insert(set.clone()); // Eq, Hash
hash_set.insert(set.clone());
assert_eq!(hash_set.len(), 1);
println!("set was: {:?}", set); // Debug
// Exit code
0
}
// Externs required by roc_std and by the Roc app
use core::ffi::c_void;
use std::ffi::CStr;
use std::os::raw::c_char;
#[no_mangle]
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
return libc::malloc(size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
return libc::realloc(c_ptr, new_size);
}
#[no_mangle]
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
return libc::free(c_ptr);
}
#[no_mangle]
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
let string = slice.to_str().unwrap();
eprintln!("Roc hit a panic: {}", string);
std::process::exit(1);
}
_ => todo!(),
}
}
#[no_mangle]
pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
libc::memcpy(dst, src, n)
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}

View File

@ -70,6 +70,12 @@ mod glue_cli_run {
fixtures! {
basic_record:"basic-record" => "Record was: MyRcd { b: 42, a: 1995 }\n",
nested_record:"nested-record" => "Record was: Outer { y: \"foo\", z: [1, 2], x: Inner { b: 24.0, a: 5 } }\n",
dict:"dict" => indoc!(r#"
dict was: RocDict {"foo": "bar", "baz": "blah"}
"#),
set:"set" => indoc!(r#"
set was: RocSet {"foo", "bar", "baz"}
"#),
enumeration:"enumeration" => "tag_union was: MyEnum::Foo, Bar is: MyEnum::Bar, Baz is: MyEnum::Baz\n",
union_with_padding:"union-with-padding" => indoc!(r#"
tag_union was: NonRecursive::Foo("This is a test")

View File

@ -11,12 +11,16 @@ use core::ops::Drop;
use core::str;
mod roc_box;
mod roc_dict;
mod roc_list;
mod roc_set;
mod roc_str;
mod storage;
pub use roc_box::RocBox;
pub use roc_dict::RocDict;
pub use roc_list::RocList;
pub use roc_set::RocSet;
pub use roc_str::{InteriorNulError, RocStr};
pub use storage::Storage;

View File

@ -0,0 +1,77 @@
use crate::roc_list::{self, RocList};
use core::{
fmt::{self, Debug},
hash::Hash,
};
#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RocDict<K, V>(RocList<(K, V)>);
impl<K, V> RocDict<K, V> {
pub fn len(&self) -> usize {
self.0.len()
}
pub fn with_capacity(capacity: usize) -> Self {
Self(RocList::with_capacity(capacity))
}
pub fn iter(&self) -> impl Iterator<Item = &(K, V)> {
self.into_iter()
}
pub fn iter_keys(&self) -> impl Iterator<Item = &K> {
self.0.iter().map(|(key, _)| key)
}
pub fn iter_values(&self) -> impl Iterator<Item = &V> {
self.0.iter().map(|(_, val)| val)
}
}
impl<K: Hash, V> RocDict<K, V> {
#[allow(unused)]
pub fn from_iter<I: Iterator<Item = (K, V)>>(src: I) -> Self {
let mut ret = Self::with_capacity(src.size_hint().0);
for (key, val) in src {
unsafe {
ret.insert_unchecked(key, val);
}
}
ret
}
unsafe fn insert_unchecked(&mut self, _key: K, _val: V) {
todo!();
}
}
impl<K: Debug, V: Debug> Debug for RocDict<K, V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("RocDict ")?;
f.debug_map()
.entries(self.iter().map(|(k, v)| (k, v)))
.finish()
}
}
impl<K, V> IntoIterator for RocDict<K, V> {
type Item = (K, V);
type IntoIter = roc_list::IntoIter<(K, V)>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a, K, V> IntoIterator for &'a RocDict<K, V> {
type Item = &'a (K, V);
type IntoIter = core::slice::Iter<'a, (K, V)>;
fn into_iter(self) -> Self::IntoIter {
self.0.as_slice().iter()
}
}

View File

@ -0,0 +1,39 @@
use crate::roc_dict::RocDict;
use core::{
fmt::{self, Debug},
hash::Hash,
};
#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RocSet<T>(RocDict<T, ()>);
impl<T> RocSet<T> {
pub fn len(&self) -> usize {
self.0.len()
}
#[allow(unused)]
pub fn with_capacity(capacity: usize) -> Self {
Self(RocDict::with_capacity(capacity))
}
#[allow(unused)]
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.0.iter_keys()
}
}
impl<T: Hash> RocSet<T> {
#[allow(unused)]
pub fn from_iter<I: Iterator<Item = T>>(src: I) -> Self {
Self(RocDict::from_iter(src.map(|elem| (elem, ()))))
}
}
impl<T: Debug> Debug for RocSet<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("RocSet ")?;
f.debug_set().entries(self.iter()).finish()
}
}