diff --git a/Cargo.lock b/Cargo.lock index bf183d5dd7..3116650af8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3439,6 +3439,7 @@ dependencies = [ "bumpalo", "indoc", "pretty_assertions", + "regex", "roc_builtins", "roc_can", "roc_collections", @@ -3446,9 +3447,11 @@ dependencies = [ "roc_load", "roc_module", "roc_mono", + "roc_reporting", "roc_std", "roc_target", "roc_types", + "tempfile", ] [[package]] diff --git a/bindgen/Cargo.toml b/bindgen/Cargo.toml index f6a16298aa..a2234e0ee3 100644 --- a/bindgen/Cargo.toml +++ b/bindgen/Cargo.toml @@ -21,3 +21,8 @@ bumpalo = { version = "3.8.0", features = ["collections"] } [dev-dependencies] pretty_assertions = "1.0.0" indoc = "1.0.3" +bumpalo = { version = "3.8.0", features = ["collections"] } +regex = "1.5.5" +roc_load = { path = "../compiler/load" } +roc_reporting = { path = "../reporting" } +tempfile = "3.2.0" diff --git a/bindgen/src/bindgen_rs.rs b/bindgen/src/bindgen_rs.rs index 38456b1b65..dac995f3ab 100644 --- a/bindgen/src/bindgen_rs.rs +++ b/bindgen/src/bindgen_rs.rs @@ -3,10 +3,16 @@ use crate::structs::Structs; use crate::types::RocType; use bumpalo::Bump; use roc_collections::MutMap; -use roc_module::ident::Lowercase; +use roc_module::{ + ident::Lowercase, + symbol::{Interns, Symbol}, +}; use roc_mono::layout::{Layout, LayoutCache}; use roc_target::TargetInfo; -use roc_types::subs::{Content, FlatType, RecordFields, Subs, Variable}; +use roc_types::{ + subs::{Content, FlatType, RecordFields, Subs, Variable}, + types::RecordField, +}; use std::{ fmt::{self, Write}, @@ -81,9 +87,14 @@ pub fn write_roc_type( } } +pub struct Env<'a> { + pub arena: &'a Bump, + pub layout_cache: &'a mut LayoutCache<'a>, + pub interns: &'a Interns, +} + pub fn write_layout_type<'a>( - arena: &'a Bump, - layout_cache: &mut LayoutCache<'a>, + env: &mut Env<'a>, layout: Layout<'a>, content: &Content, subs: &Subs, @@ -93,6 +104,14 @@ pub fn write_layout_type<'a>( use roc_builtins::bitcode::IntWidth::*; use roc_mono::layout::Builtin; + let (opt_name, content) = match content { + Content::Alias(name, _variable, real_var, _kind) => { + // todo handle type variables + (Some(*name), subs.get_content_without_compacting(*real_var)) + } + _ => (None, content), + }; + match layout { Layout::Builtin(builtin) => match builtin { Builtin::Int(width) => match width { @@ -117,19 +136,19 @@ pub fn write_layout_type<'a>( Builtin::Str => buf.write_str("RocStr"), Builtin::Dict(key_layout, val_layout) => { buf.write_str("RocDict<")?; - write_layout_type(arena, layout_cache, *key_layout, content, subs, buf)?; + write_layout_type(env, *key_layout, content, subs, buf)?; buf.write_str(", ")?; - write_layout_type(arena, layout_cache, *val_layout, content, subs, buf)?; + write_layout_type(env, *val_layout, content, subs, buf)?; buf.write_char('>') } Builtin::Set(elem_type) => { buf.write_str("RocSet<")?; - write_layout_type(arena, layout_cache, *elem_type, content, subs, buf)?; + write_layout_type(env, *elem_type, content, subs, buf)?; buf.write_char('>') } Builtin::List(elem_type) => { buf.write_str("RocList<")?; - write_layout_type(arena, layout_cache, *elem_type, content, subs, buf)?; + write_layout_type(env, *elem_type, content, subs, buf)?; buf.write_char('>') } }, @@ -141,7 +160,7 @@ pub fn write_layout_type<'a>( Content::RigidAbleVar(_, _) => todo!(), Content::RecursionVar { .. } => todo!(), Content::Structure(FlatType::Record(fields, ext)) => { - write_struct(arena, layout_cache, fields, *ext, subs, buf) + write_struct(env, opt_name, fields, *ext, subs, buf) } Content::Structure(FlatType::TagUnion(tags, _)) => { debug_assert_eq!(tags.len(), 1); @@ -191,29 +210,55 @@ pub fn write_layout_type<'a>( } fn write_struct<'a>( - arena: &'a Bump, - layout_cache: &mut LayoutCache<'a>, + env: &mut Env<'a>, + opt_name: Option, record_fields: &RecordFields, ext: Variable, subs: &Subs, buf: &mut String, ) -> fmt::Result { - for (label, field) in record_fields.sorted_iterator(subs, ext) { - // We recalculate the layouts here because we will have compiled the record so that its fields - // are sorted by descending alignment, and then alphabetic, but the type of the record is - // always only sorted alphabetically. We want to arrange the rendered record in the order of - // the type. - let field_content = subs.get_content_without_compacting(field.into_inner()); - let field_layout = layout_cache - .from_var(arena, field.into_inner(), subs) - .unwrap(); + let mut pairs = bumpalo::collections::Vec::with_capacity_in(record_fields.len(), env.arena); + let it = record_fields + .unsorted_iterator(subs, ext) + .expect("something weird in content"); + for (label, field) in it { + // drop optional fields + let var = match field { + RecordField::Optional(_) => continue, + RecordField::Required(var) => var, + RecordField::Demanded(var) => var, + }; + + pairs.push(( + label, + var, + env.layout_cache.from_var(env.arena, var, subs).unwrap(), + )); + } + + pairs.sort_by(|(label1, _, layout1), (label2, _, layout2)| { + let size1 = layout1.alignment_bytes(env.layout_cache.target_info); + let size2 = layout2.alignment_bytes(env.layout_cache.target_info); + + size2.cmp(&size1).then(label1.cmp(label2)) + }); + + let struct_name = opt_name + .map(|sym| sym.as_str(env.interns)) + .unwrap_or("Unknown"); + buf.write_str("struct "); + buf.write_str(struct_name); + buf.write_str(" {\n"); + + for (label, field_var, field_layout) in pairs.into_iter() { + let field_content = subs.get_content_without_compacting(field_var); buf.write_str(INDENT)?; buf.write_str(label.as_str())?; buf.write_str(": ")?; - write_layout_type(arena, layout_cache, field_layout, field_content, subs, buf)?; + write_layout_type(env, field_layout, field_content, subs, buf)?; buf.write_str(",\n")?; } - Ok(()) + buf.write_str("}\n") } diff --git a/bindgen/tests/gen_rs.rs b/bindgen/tests/gen_rs.rs index b1cad14ce0..a8cba786b8 100644 --- a/bindgen/tests/gen_rs.rs +++ b/bindgen/tests/gen_rs.rs @@ -4,13 +4,142 @@ extern crate pretty_assertions; #[macro_use] extern crate indoc; +use bumpalo::Bump; use core::mem; use roc_bindgen::{ - bindgen_rs::write_roc_type, + bindgen_rs::{write_layout_type, write_roc_type, Env}, enums::Enums, structs::Structs, types::{self, RocType}, }; +use roc_can::{ + def::{Declaration, Def}, + pattern::Pattern, +}; +use roc_load::LoadedModule; +use roc_mono::layout::LayoutCache; +use roc_reporting::report::RenderTarget; +use roc_target::TargetInfo; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn run_load_and_typecheck( + src: &str, + target_info: TargetInfo, +) -> Result<(LoadedModule), std::io::Error> { + use bumpalo::Bump; + use tempfile::tempdir; + + let arena = &Bump::new(); + + assert!( + src.starts_with("app"), + "I need a module source, not an expr" + ); + + let subs_by_module = Default::default(); + let loaded = { + let dir = tempdir()?; + let filename = PathBuf::from("Test.roc"); + let file_path = dir.path().join(filename); + let full_file_path = file_path.clone(); + let mut file = File::create(file_path).unwrap(); + writeln!(file, "{}", &src).unwrap(); + let result = roc_load::load_and_typecheck( + arena, + full_file_path, + dir.path(), + subs_by_module, + roc_target::TargetInfo::default_x86_64(), + RenderTarget::Generic, + ); + + dir.close()?; + + result + }; + + Ok(loaded.expect("had problems loading")) +} + +pub fn generate_bindings(src: &str, target_info: TargetInfo) -> String { + let (LoadedModule { + module_id: home, + mut can_problems, + mut type_problems, + mut declarations_by_id, + mut solved, + interns, + .. + }) = run_load_and_typecheck(src, target_info).expect("Something went wrong with IO"); + + let decls = declarations_by_id.remove(&home).unwrap(); + let subs = solved.inner_mut(); + + let can_problems = can_problems.remove(&home).unwrap_or_default(); + let type_problems = type_problems.remove(&home).unwrap_or_default(); + + if !can_problems.is_empty() || !type_problems.is_empty() { + assert!( + false, + "There were problems: {:?}, {:?}", + can_problems, type_problems + ); + } + + let arena = Bump::new(); + let mut layout_cache = LayoutCache::new(target_info); + let mut env = Env { + arena: &arena, + layout_cache: &mut layout_cache, + interns: &interns, + }; + + let mut bindgen_result = String::new(); + + for decl in decls.into_iter() { + let defs = match decl { + Declaration::Declare(def) => { + vec![def] + } + Declaration::DeclareRec(defs) => defs, + Declaration::Builtin(..) => { + unreachable!("Builtin decl in userspace module?") + } + Declaration::InvalidCycle(..) => { + vec![] + } + }; + for Def { + loc_pattern, + pattern_vars, + .. + } in defs.into_iter() + { + match loc_pattern.value { + Pattern::Identifier(sym) => { + let var = pattern_vars + .get(&sym) + .expect("Indetifier known but it has no var?"); + let layout = env + .layout_cache + .from_var(&arena, *var, &subs) + .expect("Something weird ended up in the content"); + let content = subs.get_content_without_compacting(*var); + + write_layout_type(&mut env, layout, content, subs, &mut bindgen_result); + } + _ => { + // figure out if we need to export non-identifier defs - when would that + // happen? + } + } + } + } + + bindgen_result +} #[test] fn struct_without_different_pointer_alignment() { @@ -40,3 +169,25 @@ fn struct_without_different_pointer_alignment() { out, ); } + +#[test] +fn my_struct_in_rust() { + let module = r#"app "main" provides [ main ] to "./platform" + +MyRcd : { a: U64, b: U128 } + +main : MyRcd +main = { a: 1u64, b: 2u128 } +"#; + + let bindings_rust = generate_bindings(module, TargetInfo::default_x86_64()); + + assert_eq!( + bindings_rust, + "struct MyRcd { + b: u128, + a: u64, +} +" + ); +} diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 79e193e1e5..e110dbab86 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -1328,7 +1328,7 @@ impl<'a> Layout<'a> { /// But if we're careful when to invalidate certain keys, we still get some benefit #[derive(Debug)] pub struct LayoutCache<'a> { - target_info: TargetInfo, + pub target_info: TargetInfo, _marker: std::marker::PhantomData<&'a u8>, }