mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 00:24:34 +00:00
Bindgen our first struct
This commit is contained in:
parent
131a633f7f
commit
ebcd72f7af
5 changed files with 228 additions and 24 deletions
3
Cargo.lock
generated
3
Cargo.lock
generated
|
@ -3439,6 +3439,7 @@ dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"indoc",
|
"indoc",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
"regex",
|
||||||
"roc_builtins",
|
"roc_builtins",
|
||||||
"roc_can",
|
"roc_can",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
|
@ -3446,9 +3447,11 @@ dependencies = [
|
||||||
"roc_load",
|
"roc_load",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
"roc_mono",
|
"roc_mono",
|
||||||
|
"roc_reporting",
|
||||||
"roc_std",
|
"roc_std",
|
||||||
"roc_target",
|
"roc_target",
|
||||||
"roc_types",
|
"roc_types",
|
||||||
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -21,3 +21,8 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.0.0"
|
pretty_assertions = "1.0.0"
|
||||||
indoc = "1.0.3"
|
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"
|
||||||
|
|
|
@ -3,10 +3,16 @@ use crate::structs::Structs;
|
||||||
use crate::types::RocType;
|
use crate::types::RocType;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use roc_collections::MutMap;
|
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_mono::layout::{Layout, LayoutCache};
|
||||||
use roc_target::TargetInfo;
|
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::{
|
use std::{
|
||||||
fmt::{self, Write},
|
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>(
|
pub fn write_layout_type<'a>(
|
||||||
arena: &'a Bump,
|
env: &mut Env<'a>,
|
||||||
layout_cache: &mut LayoutCache<'a>,
|
|
||||||
layout: Layout<'a>,
|
layout: Layout<'a>,
|
||||||
content: &Content,
|
content: &Content,
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
|
@ -93,6 +104,14 @@ pub fn write_layout_type<'a>(
|
||||||
use roc_builtins::bitcode::IntWidth::*;
|
use roc_builtins::bitcode::IntWidth::*;
|
||||||
use roc_mono::layout::Builtin;
|
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 {
|
match layout {
|
||||||
Layout::Builtin(builtin) => match builtin {
|
Layout::Builtin(builtin) => match builtin {
|
||||||
Builtin::Int(width) => match width {
|
Builtin::Int(width) => match width {
|
||||||
|
@ -117,19 +136,19 @@ pub fn write_layout_type<'a>(
|
||||||
Builtin::Str => buf.write_str("RocStr"),
|
Builtin::Str => buf.write_str("RocStr"),
|
||||||
Builtin::Dict(key_layout, val_layout) => {
|
Builtin::Dict(key_layout, val_layout) => {
|
||||||
buf.write_str("RocDict<")?;
|
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(", ")?;
|
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('>')
|
buf.write_char('>')
|
||||||
}
|
}
|
||||||
Builtin::Set(elem_type) => {
|
Builtin::Set(elem_type) => {
|
||||||
buf.write_str("RocSet<")?;
|
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('>')
|
buf.write_char('>')
|
||||||
}
|
}
|
||||||
Builtin::List(elem_type) => {
|
Builtin::List(elem_type) => {
|
||||||
buf.write_str("RocList<")?;
|
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('>')
|
buf.write_char('>')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -141,7 +160,7 @@ pub fn write_layout_type<'a>(
|
||||||
Content::RigidAbleVar(_, _) => todo!(),
|
Content::RigidAbleVar(_, _) => todo!(),
|
||||||
Content::RecursionVar { .. } => todo!(),
|
Content::RecursionVar { .. } => todo!(),
|
||||||
Content::Structure(FlatType::Record(fields, ext)) => {
|
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, _)) => {
|
Content::Structure(FlatType::TagUnion(tags, _)) => {
|
||||||
debug_assert_eq!(tags.len(), 1);
|
debug_assert_eq!(tags.len(), 1);
|
||||||
|
@ -191,29 +210,55 @@ pub fn write_layout_type<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_struct<'a>(
|
fn write_struct<'a>(
|
||||||
arena: &'a Bump,
|
env: &mut Env<'a>,
|
||||||
layout_cache: &mut LayoutCache<'a>,
|
opt_name: Option<Symbol>,
|
||||||
record_fields: &RecordFields,
|
record_fields: &RecordFields,
|
||||||
ext: Variable,
|
ext: Variable,
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
buf: &mut String,
|
buf: &mut String,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
for (label, field) in record_fields.sorted_iterator(subs, ext) {
|
let mut pairs = bumpalo::collections::Vec::with_capacity_in(record_fields.len(), env.arena);
|
||||||
// We recalculate the layouts here because we will have compiled the record so that its fields
|
let it = record_fields
|
||||||
// are sorted by descending alignment, and then alphabetic, but the type of the record is
|
.unsorted_iterator(subs, ext)
|
||||||
// always only sorted alphabetically. We want to arrange the rendered record in the order of
|
.expect("something weird in content");
|
||||||
// the type.
|
for (label, field) in it {
|
||||||
let field_content = subs.get_content_without_compacting(field.into_inner());
|
// drop optional fields
|
||||||
let field_layout = layout_cache
|
let var = match field {
|
||||||
.from_var(arena, field.into_inner(), subs)
|
RecordField::Optional(_) => continue,
|
||||||
.unwrap();
|
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(INDENT)?;
|
||||||
buf.write_str(label.as_str())?;
|
buf.write_str(label.as_str())?;
|
||||||
buf.write_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")?;
|
buf.write_str(",\n")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
buf.write_str("}\n")
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,13 +4,142 @@ extern crate pretty_assertions;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate indoc;
|
extern crate indoc;
|
||||||
|
|
||||||
|
use bumpalo::Bump;
|
||||||
use core::mem;
|
use core::mem;
|
||||||
use roc_bindgen::{
|
use roc_bindgen::{
|
||||||
bindgen_rs::write_roc_type,
|
bindgen_rs::{write_layout_type, write_roc_type, Env},
|
||||||
enums::Enums,
|
enums::Enums,
|
||||||
structs::Structs,
|
structs::Structs,
|
||||||
types::{self, RocType},
|
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]
|
#[test]
|
||||||
fn struct_without_different_pointer_alignment() {
|
fn struct_without_different_pointer_alignment() {
|
||||||
|
@ -40,3 +169,25 @@ fn struct_without_different_pointer_alignment() {
|
||||||
out,
|
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,
|
||||||
|
}
|
||||||
|
"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1328,7 +1328,7 @@ impl<'a> Layout<'a> {
|
||||||
/// But if we're careful when to invalidate certain keys, we still get some benefit
|
/// But if we're careful when to invalidate certain keys, we still get some benefit
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LayoutCache<'a> {
|
pub struct LayoutCache<'a> {
|
||||||
target_info: TargetInfo,
|
pub target_info: TargetInfo,
|
||||||
_marker: std::marker::PhantomData<&'a u8>,
|
_marker: std::marker::PhantomData<&'a u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue