Merge branch 'trunk' into refactor-builtin-list-drop

This commit is contained in:
satotake 2021-11-15 11:37:32 +00:00 committed by GitHub
commit c253273490
52 changed files with 1771 additions and 910 deletions

View File

@ -132,7 +132,7 @@ pub fn expr_to_expr2<'a>(
Str(literal) => flatten_str_literal(env, scope, literal),
List { items, .. } => {
List(items) => {
let mut output = Output::default();
let output_ref = &mut output;
@ -185,13 +185,12 @@ pub fn expr_to_expr2<'a>(
RecordUpdate {
fields,
update: loc_update,
final_comments: _,
} => {
let (can_update, update_out) =
expr_to_expr2(env, scope, &loc_update.value, loc_update.region);
if let Expr2::Var(symbol) = &can_update {
match canonicalize_fields(env, scope, fields) {
match canonicalize_fields(env, scope, fields.items) {
Ok((can_fields, mut output)) => {
output.references.union_mut(update_out.references);
@ -236,14 +235,11 @@ pub fn expr_to_expr2<'a>(
}
}
Record {
fields,
final_comments: _,
} => {
Record(fields) => {
if fields.is_empty() {
(Expr2::EmptyRecord, Output::default())
} else {
match canonicalize_fields(env, scope, fields) {
match canonicalize_fields(env, scope, fields.items) {
Ok((can_fields, output)) => (
Expr2::Record {
record_var: env.var_store.fresh(),

View File

@ -428,7 +428,7 @@ pub fn to_type2<'a>(
Type2::Record(field_types, ext_type)
}
TagUnion { tags, ext, .. } => {
let tag_types_vec = can_tags(env, scope, references, tags, region);
let tag_types_vec = can_tags(env, scope, references, tags.items, region);
let tag_types = PoolVec::with_capacity(tag_types_vec.len() as u32, env.pool);

View File

@ -247,7 +247,7 @@ pub fn build_file<'a>(
link(
target,
binary_path.clone(),
&inputs,
&inputs,
link_type
)
.map_err(|_| {

View File

@ -7,7 +7,7 @@ use roc_module::operator::CalledVia;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::ProcLayout;
use roc_mono::layout::{union_sorted_tags_help, Builtin, Layout, UnionLayout, UnionVariant};
use roc_parse::ast::{AssignedField, Expr, StrLiteral};
use roc_parse::ast::{AssignedField, Collection, Expr, StrLiteral};
use roc_region::all::{Located, Region};
use roc_types::subs::{Content, FlatType, GetSubsSlice, RecordFields, Subs, UnionTags, Variable};
@ -133,10 +133,7 @@ fn jit_to_ast_help<'a>(
),
Layout::Builtin(Builtin::EmptyList) => {
Ok(run_jit_function!(lib, main_fn_name, &'static str, |_| {
Expr::List {
items: &[],
final_comments: &[],
}
Expr::List(Collection::empty())
}))
}
Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!(
@ -431,10 +428,7 @@ fn ptr_to_ast<'a>(
num_to_ast(env, number_literal_to_ast(env.arena, num), content)
}
Layout::Builtin(Builtin::EmptyList) => Expr::List {
items: &[],
final_comments: &[],
},
Layout::Builtin(Builtin::EmptyList) => Expr::List(Collection::empty()),
Layout::Builtin(Builtin::List(elem_layout)) => {
// Turn the (ptr, len) wrapper struct into actual ptr and len values.
let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
@ -522,10 +516,7 @@ fn list_to_ast<'a>(
let output = output.into_bump_slice();
Expr::List {
items: output,
final_comments: &[],
}
Expr::List(Collection::with_items(output))
}
fn single_tag_union_to_ast<'a>(
@ -621,10 +612,7 @@ fn struct_to_ast<'a>(
let output = env.arena.alloc([loc_field]);
Expr::Record {
fields: output,
final_comments: &[],
}
Expr::Record(Collection::with_items(output))
} else {
debug_assert_eq!(sorted_fields.len(), field_layouts.len());
@ -658,10 +646,7 @@ fn struct_to_ast<'a>(
let output = output.into_bump_slice();
Expr::Record {
fields: output,
final_comments: &[],
}
Expr::Record(Collection::with_items(output))
}
}
@ -735,10 +720,7 @@ fn bool_to_ast<'a>(env: &Env<'a, '_>, value: bool, content: &Content) -> Expr<'a
region: Region::zero(),
};
Expr::Record {
fields: arena.alloc([loc_assigned_field]),
final_comments: arena.alloc([]),
}
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
}
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
@ -850,10 +832,7 @@ fn byte_to_ast<'a>(env: &Env<'a, '_>, value: u8, content: &Content) -> Expr<'a>
region: Region::zero(),
};
Expr::Record {
fields: arena.alloc([loc_assigned_field]),
final_comments: &[],
}
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
}
FlatType::TagUnion(tags, _) if tags.len() == 1 => {
let (tag_name, payload_vars) = unpack_single_element_tag_union(env.subs, *tags);
@ -972,10 +951,7 @@ fn num_to_ast<'a>(env: &Env<'a, '_>, num_expr: Expr<'a>, content: &Content) -> E
region: Region::zero(),
};
Expr::Record {
fields: arena.alloc([loc_assigned_field]),
final_comments: arena.alloc([]),
}
Expr::Record(Collection::with_items(arena.alloc([loc_assigned_field])))
}
FlatType::TagUnion(tags, _) => {
// This was a single-tag union that got unwrapped at runtime.

View File

@ -218,8 +218,8 @@ pub fn gen_and_eval<'a>(
// Verify the module
if let Err(errors) = env.module.verify() {
panic!(
"Errors defining module: {}\n\nUncomment things nearby to see more details.",
errors
"Errors defining module:\n{}\n\nUncomment things nearby to see more details.",
errors.to_string()
);
}

24
cli_utils/Cargo.lock generated
View File

@ -2398,8 +2398,6 @@ name = "roc_build"
version = "0.1.0"
dependencies = [
"bumpalo",
"im",
"im-rc",
"inkwell 0.1.0",
"libloading 0.7.1",
"roc_builtins",
@ -2440,8 +2438,6 @@ name = "roc_can"
version = "0.1.0"
dependencies = [
"bumpalo",
"im",
"im-rc",
"roc_builtins",
"roc_collections",
"roc_module",
@ -2459,8 +2455,6 @@ dependencies = [
"bumpalo",
"clap 3.0.0-beta.5",
"const_format",
"im",
"im-rc",
"inkwell 0.1.0",
"libloading 0.7.1",
"mimalloc",
@ -2562,8 +2556,6 @@ dependencies = [
"fs_extra",
"futures",
"glyph_brush",
"im",
"im-rc",
"libc",
"log",
"nonempty",
@ -2599,8 +2591,6 @@ name = "roc_fmt"
version = "0.1.0"
dependencies = [
"bumpalo",
"im",
"im-rc",
"roc_collections",
"roc_module",
"roc_parse",
@ -2612,8 +2602,6 @@ name = "roc_gen_dev"
version = "0.1.0"
dependencies = [
"bumpalo",
"im",
"im-rc",
"object 0.26.2",
"roc_builtins",
"roc_collections",
@ -2632,20 +2620,13 @@ name = "roc_gen_llvm"
version = "0.1.0"
dependencies = [
"bumpalo",
"im",
"im-rc",
"inkwell 0.1.0",
"morphic_lib",
"roc_builtins",
"roc_collections",
"roc_module",
"roc_mono",
"roc_problem",
"roc_region",
"roc_solve",
"roc_std",
"roc_types",
"roc_unify",
"target-lexicon",
]
@ -2654,6 +2635,7 @@ name = "roc_gen_wasm"
version = "0.1.0"
dependencies = [
"bumpalo",
"roc_builtins",
"roc_collections",
"roc_module",
"roc_mono",
@ -2726,7 +2708,6 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"hashbrown 0.11.2",
"linked-hash-map",
"morphic_lib",
"roc_can",
"roc_collections",
@ -2737,7 +2718,6 @@ dependencies = [
"roc_std",
"roc_types",
"roc_unify",
"ven_ena",
"ven_graph",
"ven_pretty",
]
@ -2773,8 +2753,6 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"distance",
"im",
"im-rc",
"roc_can",
"roc_collections",
"roc_module",

View File

@ -126,6 +126,7 @@ comptime {
exportStrFn(str.repeat, "repeat");
exportStrFn(str.strTrim, "trim");
exportStrFn(str.strTrimLeft, "trim_left");
exportStrFn(str.strTrimRight, "trim_right");
inline for (INTEGERS) |T| {
str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int.");

View File

@ -1584,6 +1584,41 @@ pub fn strTrimLeft(string: RocStr) callconv(.C) RocStr {
return RocStr.empty();
}
pub fn strTrimRight(string: RocStr) callconv(.C) RocStr {
if (string.str_bytes) |bytes_ptr| {
const trailing_bytes = countTrailingWhitespaceBytes(string);
const original_len = string.len();
if (original_len == trailing_bytes) {
string.deinit();
return RocStr.empty();
}
const new_len = original_len - trailing_bytes;
const small_or_shared = new_len <= SMALL_STR_MAX_LENGTH or !string.isRefcountOne();
if (small_or_shared) {
return RocStr.init(string.asU8ptr(), new_len);
}
// nonempty, large, and unique:
var i: usize = 0;
while (i < new_len) : (i += 1) {
const dest = bytes_ptr + i;
const source = dest;
@memcpy(dest, source, 1);
}
var new_string = string;
new_string.str_len = new_len;
return new_string;
}
return RocStr.empty();
}
fn countLeadingWhitespaceBytes(string: RocStr) usize {
var byte_count: usize = 0;
@ -1820,6 +1855,77 @@ test "strTrimLeft: small to small" {
try expect(trimmed.isSmallStr());
}
test "strTrimRight: empty" {
const trimmedEmpty = strTrimRight(RocStr.empty());
try expect(trimmedEmpty.eq(RocStr.empty()));
}
test "strTrimRight: blank" {
const original_bytes = " ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
const trimmed = strTrimRight(original);
try expect(trimmed.eq(RocStr.empty()));
}
test "strTrimRight: large to large" {
const original_bytes = " hello giant world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(!original.isSmallStr());
const expected_bytes = " hello giant world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(!expected.isSmallStr());
const trimmed = strTrimRight(original);
try expect(trimmed.eq(expected));
}
test "strTrimRight: large to small" {
const original_bytes = " hello world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(!original.isSmallStr());
const expected_bytes = " hello world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(expected.isSmallStr());
const trimmed = strTrimRight(original);
try expect(trimmed.eq(expected));
try expect(trimmed.isSmallStr());
}
test "strTrimRight: small to small" {
const original_bytes = " hello world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(original.isSmallStr());
const expected_bytes = " hello world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(expected.isSmallStr());
const trimmed = strTrimRight(original);
try expect(trimmed.eq(expected));
try expect(trimmed.isSmallStr());
}
test "ReverseUtf8View: hello world" {
const original_bytes = "hello world";
const expected_bytes = "dlrow olleh";

View File

@ -149,6 +149,7 @@ pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
pub const STR_TRIM: &str = "roc_builtins.str.trim";
pub const STR_TRIM_LEFT: &str = "roc_builtins.str.trim_left";
pub const STR_TRIM_RIGHT: &str = "roc_builtins.str.trim_right";
pub const DICT_HASH: &str = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";

View File

@ -639,6 +639,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(str_type())
);
// trimRight : Str -> Str
add_top_level_function_type!(
Symbol::STR_TRIM_RIGHT,
vec![str_type()],
Box::new(str_type())
);
// trim : Str -> Str
add_top_level_function_type!(Symbol::STR_TRIM, vec![str_type()], Box::new(str_type()));

View File

@ -417,7 +417,7 @@ fn can_annotation_help(
TagUnion { tags, ext, .. } => {
let tag_types = can_tags(
env,
tags,
tags.items,
region,
scope,
var_store,

View File

@ -69,6 +69,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_REPEAT => str_repeat,
STR_TRIM => str_trim,
STR_TRIM_LEFT => str_trim_left,
STR_TRIM_RIGHT => str_trim_right,
LIST_LEN => list_len,
LIST_GET => list_get,
LIST_SET => list_set,
@ -1295,6 +1296,11 @@ fn str_trim_left(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::StrTrimLeft, var_store)
}
/// Str.trimRight : Str -> Str
fn str_trim_right(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::StrTrimRight, var_store)
}
/// Str.repeat : Str, Nat -> Str
fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();

View File

@ -228,14 +228,11 @@ pub fn canonicalize_expr<'a>(
(answer, Output::default())
}
ast::Expr::Record {
fields,
final_comments: _,
} => {
ast::Expr::Record(fields) => {
if fields.is_empty() {
(EmptyRecord, Output::default())
} else {
match canonicalize_fields(env, var_store, scope, region, fields) {
match canonicalize_fields(env, var_store, scope, region, fields.items) {
Ok((can_fields, output)) => (
Record {
record_var: var_store.fresh(),
@ -261,12 +258,11 @@ pub fn canonicalize_expr<'a>(
ast::Expr::RecordUpdate {
fields,
update: loc_update,
final_comments: _,
} => {
let (can_update, update_out) =
canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value);
if let Var(symbol) = &can_update.value {
match canonicalize_fields(env, var_store, scope, region, fields) {
match canonicalize_fields(env, var_store, scope, region, fields.items) {
Ok((can_fields, mut output)) => {
output.references = output.references.union(update_out.references);
@ -307,9 +303,7 @@ pub fn canonicalize_expr<'a>(
}
}
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
ast::Expr::List {
items: loc_elems, ..
} => {
ast::Expr::List(loc_elems) => {
if loc_elems.is_empty() {
(
List {

View File

@ -144,77 +144,46 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
arena.alloc(Located { region, value })
}
List {
items,
final_comments,
} => {
List(items) => {
let mut new_items = Vec::with_capacity_in(items.len(), arena);
for item in items.iter() {
new_items.push(desugar_expr(arena, item));
}
let new_items = new_items.into_bump_slice();
let value: Expr<'a> = List {
items: new_items,
final_comments,
};
let value: Expr<'a> = List(items.replace_items(new_items));
arena.alloc(Located {
region: loc_expr.region,
value,
})
}
Record {
fields,
final_comments,
} => {
let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
for field in fields.iter() {
Record(fields) => arena.alloc(Located {
region: loc_expr.region,
value: Record(fields.map_items(arena, |field| {
let value = desugar_field(arena, &field.value);
new_fields.push(Located {
Located {
value,
region: field.region,
});
}
}
})),
}),
let new_fields = new_fields.into_bump_slice();
arena.alloc(Located {
region: loc_expr.region,
value: Record {
fields: new_fields,
final_comments,
},
})
}
RecordUpdate {
fields,
update,
final_comments,
} => {
RecordUpdate { fields, update } => {
// NOTE the `update` field is always a `Var { .. }` and does not need to be desugared
let mut new_fields = Vec::with_capacity_in(fields.len(), arena);
for field in fields.iter() {
let new_fields = fields.map_items(arena, |field| {
let value = desugar_field(arena, &field.value);
new_fields.push(Located {
Located {
value,
region: field.region,
});
}
let new_fields = new_fields.into_bump_slice();
}
});
arena.alloc(Located {
region: loc_expr.region,
value: RecordUpdate {
update: *update,
fields: new_fields,
final_comments,
},
})
}

View File

@ -246,7 +246,7 @@ pub fn canonicalize_pattern<'a>(
let mut destructs = Vec::with_capacity(patterns.len());
let mut opt_erroneous = None;
for loc_pattern in *patterns {
for loc_pattern in patterns.iter() {
match loc_pattern.value {
Identifier(label) => {
match scope.introduce(

View File

@ -1,6 +1,6 @@
use crate::spaces::{fmt_comments_only, fmt_spaces, newline, NewlineAt, INDENT};
use bumpalo::collections::String;
use roc_parse::ast::{AssignedField, Collection, Expr, Tag, TypeAnnotation};
use roc_parse::ast::{AssignedField, Expr, Tag, TypeAnnotation};
use roc_region::all::Located;
/// Does an AST node need parens around it?
@ -81,9 +81,9 @@ where
}
macro_rules! format_sequence {
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $final_comments:expr, $newline:expr, $t:ident) => {
let is_multiline =
$items.iter().any(|item| item.value.is_multiline()) || !$final_comments.is_empty();
($buf: expr, $indent:expr, $start:expr, $end:expr, $items:expr, $newline:expr, $t:ident) => {
let is_multiline = $items.iter().any(|item| item.value.is_multiline())
|| !$items.final_comments().is_empty();
if is_multiline {
let braces_indent = $indent + INDENT;
@ -138,7 +138,12 @@ macro_rules! format_sequence {
}
}
}
fmt_comments_only($buf, $final_comments.iter(), NewlineAt::Top, item_indent);
fmt_comments_only(
$buf,
$items.final_comments().iter(),
NewlineAt::Top,
item_indent,
);
newline($buf, braces_indent);
$buf.push($end);
} else {
@ -192,11 +197,7 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
fields.items.iter().any(|field| field.value.is_multiline())
}
TagUnion {
tags,
ext,
final_comments: _,
} => {
TagUnion { tags, ext } => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
@ -279,36 +280,16 @@ impl<'a> Formattable<'a> for TypeAnnotation<'a> {
BoundVariable(v) => buf.push_str(v),
Wildcard => buf.push('*'),
TagUnion {
tags,
ext,
final_comments,
} => {
format_sequence!(buf, indent, '[', ']', tags, final_comments, newlines, Tag);
TagUnion { tags, ext } => {
format_sequence!(buf, indent, '[', ']', tags, newlines, Tag);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);
}
}
Record {
fields:
Collection {
items,
final_comments,
},
ext,
} => {
format_sequence!(
buf,
indent,
'{',
'}',
items,
final_comments,
newlines,
AssignedField
);
Record { fields, ext } => {
format_sequence!(buf, indent, '{', '}', fields, newlines, AssignedField);
if let Some(loc_ext_ann) = *ext {
loc_ext_ann.value.format(buf, indent);

View File

@ -5,7 +5,9 @@ use crate::spaces::{add_spaces, fmt_comments_only, fmt_spaces, newline, NewlineA
use bumpalo::collections::String;
use roc_module::operator::{self, BinOp};
use roc_parse::ast::StrSegment;
use roc_parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern, WhenBranch};
use roc_parse::ast::{
AssignedField, Base, Collection, CommentOrNewline, Expr, Pattern, WhenBranch,
};
use roc_region::all::Located;
impl<'a> Formattable<'a> for Expr<'a> {
@ -39,7 +41,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
// These expressions always have newlines
Defs(_, _) | When(_, _) => true,
List { items, .. } => items.iter().any(|loc_expr| loc_expr.is_multiline()),
List(items) => items.iter().any(|loc_expr| loc_expr.is_multiline()),
Str(literal) => {
use roc_parse::ast::StrLiteral::*;
@ -98,7 +100,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
.any(|loc_pattern| loc_pattern.is_multiline())
}
Record { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
Record(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()),
RecordUpdate { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
}
}
@ -250,18 +252,11 @@ impl<'a> Formattable<'a> for Expr<'a> {
buf.push_str(string);
}
Record {
fields,
final_comments,
} => {
fmt_record(buf, None, fields, final_comments, indent);
Record(fields) => {
fmt_record(buf, None, *fields, indent);
}
RecordUpdate {
fields,
update,
final_comments,
} => {
fmt_record(buf, Some(*update), fields, final_comments, indent);
RecordUpdate { update, fields } => {
fmt_record(buf, Some(*update), *fields, indent);
}
Closure(loc_patterns, loc_ret) => {
fmt_closure(buf, loc_patterns, loc_ret, indent);
@ -295,11 +290,8 @@ impl<'a> Formattable<'a> for Expr<'a> {
fmt_if(buf, branches, final_else, self.is_multiline(), indent);
}
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
List {
items,
final_comments,
} => {
fmt_list(buf, items, final_comments, indent);
List(items) => {
fmt_list(buf, *items, indent);
}
BinOps(lefts, right) => fmt_bin_ops(buf, lefts, right, false, parens, indent),
UnaryOp(sub_expr, unary_op) => {
@ -416,12 +408,9 @@ fn fmt_bin_ops<'a>(
loc_right_side.format_with_options(buf, apply_needs_parens, Newlines::Yes, indent);
}
fn fmt_list<'a>(
buf: &mut String<'a>,
loc_items: &[&Located<Expr<'a>>],
final_comments: &'a [CommentOrNewline<'a>],
indent: u16,
) {
fn fmt_list<'a>(buf: &mut String<'a>, items: Collection<'a, &'a Located<Expr<'a>>>, indent: u16) {
let loc_items = items.items;
let final_comments = items.final_comments();
if loc_items.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
buf.push_str("[]");
} else {
@ -917,10 +906,11 @@ fn fmt_backpassing<'a>(
fn fmt_record<'a>(
buf: &mut String<'a>,
update: Option<&'a Located<Expr<'a>>>,
loc_fields: &[Located<AssignedField<'a, Expr<'a>>>],
final_comments: &'a [CommentOrNewline<'a>],
fields: Collection<'a, Located<AssignedField<'a, Expr<'a>>>>,
indent: u16,
) {
let loc_fields = fields.items;
let final_comments = fields.final_comments();
if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
buf.push_str("{}");
} else {

View File

@ -1,6 +1,6 @@
use crate::spaces::{fmt_spaces, INDENT};
use bumpalo::collections::{String, Vec};
use roc_parse::ast::Module;
use bumpalo::collections::String;
use roc_parse::ast::{Collection, Module};
use roc_parse::header::{AppHeader, ExposesEntry, ImportsEntry, InterfaceHeader, PlatformHeader};
use roc_region::all::Located;
@ -64,7 +64,7 @@ pub fn fmt_interface_header<'a>(buf: &mut String<'a>, header: &'a InterfaceHeade
fmt_spaces(buf, header.after_imports.iter(), indent);
}
fmt_imports(buf, &header.imports, indent);
fmt_imports(buf, header.imports, indent);
}
pub fn fmt_app_header<'a>(buf: &mut String<'a>, header: &'a AppHeader<'a>) {
@ -76,7 +76,7 @@ pub fn fmt_app_header<'a>(buf: &mut String<'a>, header: &'a AppHeader<'a>) {
buf.push_str("imports");
fmt_spaces(buf, header.before_imports.iter(), indent);
fmt_imports(buf, &header.imports, indent);
fmt_imports(buf, header.imports, indent);
fmt_spaces(buf, header.after_imports.iter(), indent);
}
@ -86,7 +86,7 @@ pub fn fmt_platform_header<'a>(_buf: &mut String<'a>, _header: &'a PlatformHeade
fn fmt_imports<'a>(
buf: &mut String<'a>,
loc_entries: &'a Vec<'a, Located<ImportsEntry<'a>>>,
loc_entries: Collection<'a, Located<ImportsEntry<'a>>>,
indent: u16,
) {
buf.push('[');
@ -112,7 +112,7 @@ fn fmt_imports<'a>(
fn fmt_exposes<'a>(
buf: &mut String<'a>,
loc_entries: &'a Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
loc_entries: &'a Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
indent: u16,
) {
buf.push('[');

View File

@ -128,20 +128,19 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>(
[tag_id, tag_value_ptr] => {
let tag_type = basic_type_from_layout(env, &Layout::Union(union_layout));
let argument_cast = env
.builder
.build_bitcast(
*tag_value_ptr,
tag_type.ptr_type(AddressSpace::Generic),
"load_opaque",
)
.into_pointer_value();
let tag_value = env.builder.build_load(argument_cast, "get_value");
let tag_value = env.builder.build_pointer_cast(
tag_value_ptr.into_pointer_value(),
tag_type.ptr_type(AddressSpace::Generic),
"load_opaque_get_tag_id",
);
let actual_tag_id = {
let tag_id_i64 =
crate::llvm::build::get_tag_id(env, function_value, &union_layout, tag_value);
let tag_id_i64 = crate::llvm::build::get_tag_id(
env,
function_value,
&union_layout,
tag_value.into(),
);
env.builder
.build_int_cast(tag_id_i64, env.context.i16_type(), "to_i16")
@ -155,18 +154,11 @@ fn build_has_tag_id_help<'a, 'ctx, 'env>(
);
let tag_data_ptr = {
let data_index = env
.context
.i64_type()
.const_int(TAG_DATA_INDEX as u64, false);
let ptr = env
.builder
.build_struct_gep(tag_value, TAG_DATA_INDEX, "get_data_ptr")
.unwrap();
let ptr = unsafe {
env.builder.build_gep(
tag_value_ptr.into_pointer_value(),
&[data_index],
"get_data_ptr",
)
};
env.builder.build_bitcast(ptr, i8_ptr_type, "to_opaque")
};
@ -191,6 +183,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>(
function: FunctionValue<'ctx>,
closure_data_layout: LambdaSet<'a>,
argument_layouts: &[Layout<'a>],
result_layout: Layout<'a>,
) -> FunctionValue<'ctx> {
let fn_name: &str = &format!(
"{}_zig_function_caller",
@ -204,6 +197,7 @@ pub fn build_transform_caller<'a, 'ctx, 'env>(
function,
closure_data_layout,
argument_layouts,
result_layout,
fn_name,
),
}
@ -214,6 +208,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
roc_function: FunctionValue<'ctx>,
closure_data_layout: LambdaSet<'a>,
argument_layouts: &[Layout<'a>],
result_layout: Layout<'a>,
fn_name: &str,
) -> FunctionValue<'ctx> {
debug_assert!(argument_layouts.len() <= 7);
@ -260,12 +255,22 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) {
let basic_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
let argument_cast = env
.builder
.build_bitcast(*argument_ptr, basic_type, "load_opaque")
.into_pointer_value();
let argument = if layout.is_passed_by_reference() {
env.builder
.build_pointer_cast(
argument_ptr.into_pointer_value(),
basic_type,
"cast_ptr_to_tag_build_transform_caller_help",
)
.into()
} else {
let argument_cast = env
.builder
.build_bitcast(*argument_ptr, basic_type, "load_opaque_1")
.into_pointer_value();
let argument = env.builder.build_load(argument_cast, "load_opaque");
env.builder.build_load(argument_cast, "load_opaque_2")
};
arguments_cast.push(argument);
}
@ -288,31 +293,19 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
}
}
let call = {
env.builder
.build_call(roc_function, arguments_cast.as_slice(), "tmp")
};
call.set_call_convention(FAST_CALL_CONV);
let result = call
.try_as_basic_value()
.left()
.unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer."));
let result = crate::llvm::build::call_roc_function(
env,
roc_function,
&result_layout,
arguments_cast.as_slice(),
);
let result_u8_ptr = function_value
.get_nth_param(argument_layouts.len() as u32 + 1)
.unwrap();
let result_ptr = env
.builder
.build_bitcast(
result_u8_ptr,
result.get_type().ptr_type(AddressSpace::Generic),
"write_result",
)
.unwrap()
.into_pointer_value();
env.builder.build_store(result_ptr, result);
crate::llvm::build::store_roc_value_opaque(env, result_layout, result_u8_ptr, result);
env.builder.build_return(None);
env.builder.position_at_end(block);
@ -414,12 +407,18 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
let value_cast = env
.builder
.build_bitcast(value_ptr, value_type, "load_opaque")
.into_pointer_value();
let value = if layout.is_passed_by_reference() {
env.builder
.build_pointer_cast(value_ptr, value_type, "cast_ptr_to_tag_build_rc_wrapper")
.into()
} else {
let value_cast = env
.builder
.build_bitcast(value_ptr, value_type, "load_opaque")
.into_pointer_value();
let value = env.builder.build_load(value_cast, "load_opaque");
env.builder.build_load(value_cast, "load_opaque")
};
match rc_operation {
Mode::Inc => {

File diff suppressed because it is too large Load Diff

View File

@ -2,9 +2,9 @@ use crate::debug_info_init;
use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build::tag_pointer_clear_tag_id;
use crate::llvm::build::Env;
use crate::llvm::build::{cast_block_of_memory_to_tag, get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX};
use crate::llvm::build::{get_tag_id, FAST_CALL_CONV, TAG_DATA_INDEX};
use crate::llvm::build_str;
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1};
use bumpalo::collections::Vec;
use inkwell::values::{
BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
@ -339,7 +339,8 @@ fn build_hash_tag<'a, 'ctx, 'env>(
None => {
let seed_type = env.context.i64_type();
let arg_type = basic_type_from_layout(env, layout);
let arg_type = basic_type_from_layout_1(env, layout);
dbg!(layout, arg_type);
let function_value = crate::llvm::refcounting::build_header_help(
env,
@ -423,14 +424,6 @@ fn hash_tag<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let struct_layout = Layout::Struct(field_layouts);
let wrapper_type = basic_type_from_layout(env, &struct_layout);
debug_assert!(wrapper_type.is_struct_type());
let as_struct =
cast_block_of_memory_to_tag(env.builder, tag.into_struct_value(), wrapper_type);
// hash the tag id
let hash_bytes = store_and_use_as_u8_ptr(
env,
@ -440,7 +433,6 @@ fn hash_tag<'a, 'ctx, 'env>(
.into(),
&tag_id_layout,
);
let seed = hash_bitcode_fn(
env,
seed,
@ -449,14 +441,9 @@ fn hash_tag<'a, 'ctx, 'env>(
);
// hash the tag data
let answer = build_hash_struct(
env,
layout_ids,
field_layouts,
WhenRecursive::Unreachable,
seed,
as_struct,
);
let tag = tag.into_pointer_value();
let answer =
hash_ptr_to_struct(env, layout_ids, union_layout, field_layouts, seed, tag);
merge_phi.add_incoming(&[(&answer, block)]);
env.builder.build_unconditional_branch(merge_block);
@ -793,7 +780,15 @@ fn hash_list<'a, 'ctx, 'env>(
env.builder.build_store(result, answer);
};
incrementing_elem_loop(env, parent, ptr, length, "current_index", loop_fn);
incrementing_elem_loop(
env,
parent,
*element_layout,
ptr,
length,
"current_index",
loop_fn,
);
env.builder.build_unconditional_branch(done_block);
@ -822,12 +817,12 @@ fn hash_ptr_to_struct<'a, 'ctx, 'env>(
) -> IntValue<'ctx> {
use inkwell::types::BasicType;
let wrapper_type = basic_type_from_layout(env, &Layout::Union(*union_layout));
let wrapper_type = basic_type_from_layout_1(env, &Layout::Union(*union_layout));
// cast the opaque pointer to a pointer of the correct shape
let wrapper_ptr = env
.builder
.build_bitcast(tag, wrapper_type, "opaque_to_correct")
.build_bitcast(tag, wrapper_type, "hash_ptr_to_struct_opaque_to_correct")
.into_pointer_value();
let struct_ptr = env

View File

@ -17,6 +17,8 @@ use morphic_lib::UpdateMode;
use roc_builtins::bitcode;
use roc_mono::layout::{Builtin, Layout, LayoutIds};
use super::build::{load_roc_value, store_roc_value};
pub fn pass_update_mode<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
update_mode: UpdateMode,
@ -53,9 +55,13 @@ pub fn call_bitcode_fn_returns_list<'a, 'ctx, 'env>(
fn pass_element_as_opaque<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
element: BasicValueEnum<'ctx>,
layout: Layout<'a>,
) -> BasicValueEnum<'ctx> {
let element_ptr = env.builder.build_alloca(element.get_type(), "element");
env.builder.build_store(element_ptr, element);
let element_type = basic_type_from_layout(env, &layout);
let element_ptr = env
.builder
.build_alloca(element_type, "element_to_pass_as_opaque");
store_roc_value(env, layout, element_ptr, element);
env.builder.build_bitcast(
element_ptr,
@ -106,7 +112,7 @@ pub fn list_single<'a, 'ctx, 'env>(
env,
&[
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
],
bitcode::LIST_SINGLE,
@ -128,7 +134,7 @@ pub fn list_repeat<'a, 'ctx, 'env>(
&[
list_len.into(),
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
],
@ -216,10 +222,11 @@ pub fn list_get_unsafe<'a, 'ctx, 'env>(
// Assume the bounds have already been checked earlier
// (e.g. by List.get or List.first, which wrap List.#getUnsafe)
let elem_ptr =
unsafe { builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "elem") };
let elem_ptr = unsafe {
builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "list_get_element")
};
let result = builder.build_load(elem_ptr, "List.get");
let result = load_roc_value(env, **elem_layout, elem_ptr, "list_get_load_element");
increment_refcount_layout(env, parent, layout_ids, 1, result, elem_layout);
@ -247,7 +254,7 @@ pub fn list_append<'a, 'ctx, 'env>(
&[
pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
pass_update_mode(env, update_mode),
],
@ -267,7 +274,7 @@ pub fn list_prepend<'a, 'ctx, 'env>(
&[
pass_list_cc(env, original_wrapper.into()),
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
],
bitcode::LIST_PREPEND,
@ -367,7 +374,7 @@ pub fn list_set<'a, 'ctx, 'env>(
&[
bytes.into(),
index.into(),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
@ -380,7 +387,7 @@ pub fn list_set<'a, 'ctx, 'env>(
length.into(),
env.alignment_intvalue(element_layout),
index.into(),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
@ -556,7 +563,7 @@ pub fn list_contains<'a, 'ctx, 'env>(
env,
&[
pass_list_cc(env, list),
pass_element_as_opaque(env, element),
pass_element_as_opaque(env, element, *element_layout),
layout_width(env, element_layout),
eq_fn,
],
@ -1115,6 +1122,7 @@ where
pub fn incrementing_elem_loop<'a, 'ctx, 'env, LoopFn>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
element_layout: Layout<'a>,
ptr: PointerValue<'ctx>,
len: IntValue<'ctx>,
index_name: &str,
@ -1127,9 +1135,14 @@ where
incrementing_index_loop(env, parent, len, index_name, |index| {
// The pointer to the element in the list
let elem_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index], "load_index") };
let element_ptr = unsafe { builder.build_in_bounds_gep(ptr, &[index], "load_index") };
let elem = builder.build_load(elem_ptr, "get_elem");
let elem = load_roc_value(
env,
element_layout,
element_ptr,
"incrementing_element_loop_load",
);
loop_fn(index, elem);
})

View File

@ -269,6 +269,16 @@ pub fn str_trim_left<'a, 'ctx, 'env>(
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_LEFT)
}
/// Str.trimRight : Str -> Str
pub fn str_trim_right<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
str_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_RIGHT)
}
/// Str.fromInt : Int -> Str
pub fn str_from_int<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View File

@ -1,10 +1,8 @@
use crate::llvm::bitcode::call_bitcode_fn;
use crate::llvm::build::{
cast_block_of_memory_to_tag, get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV,
};
use crate::llvm::build::{get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV};
use crate::llvm::build_list::{list_len, load_list_ptr};
use crate::llvm::build_str::str_equal;
use crate::llvm::convert::basic_type_from_layout;
use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1};
use bumpalo::collections::Vec;
use inkwell::types::BasicType;
use inkwell::values::{
@ -751,7 +749,7 @@ fn build_tag_eq<'a, 'ctx, 'env>(
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let arg_type = basic_type_from_layout(env, tag_layout);
let arg_type = basic_type_from_layout_1(env, tag_layout);
let function_value = crate::llvm::refcounting::build_header_help(
env,
@ -844,9 +842,29 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
match union_layout {
NonRecursive(tags) => {
let ptr_equal = env.builder.build_int_compare(
IntPredicate::EQ,
env.builder
.build_ptr_to_int(tag1.into_pointer_value(), env.ptr_int(), "pti"),
env.builder
.build_ptr_to_int(tag2.into_pointer_value(), env.ptr_int(), "pti"),
"compare_pointers",
);
let compare_tag_ids = ctx.append_basic_block(parent, "compare_tag_ids");
env.builder
.build_conditional_branch(ptr_equal, return_true, compare_tag_ids);
env.builder.position_at_end(compare_tag_ids);
let id1 = get_tag_id(env, parent, union_layout, tag1);
let id2 = get_tag_id(env, parent, union_layout, tag2);
// clear the tag_id so we get a pointer to the actual data
let tag1 = tag1.into_pointer_value();
let tag2 = tag2.into_pointer_value();
let compare_tag_fields = ctx.append_basic_block(parent, "compare_tag_fields");
let same_tag =
@ -866,30 +884,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
// TODO drop tag id?
let struct_layout = Layout::Struct(field_layouts);
let wrapper_type = basic_type_from_layout(env, &struct_layout);
debug_assert!(wrapper_type.is_struct_type());
let struct1 = cast_block_of_memory_to_tag(
env.builder,
tag1.into_struct_value(),
wrapper_type,
);
let struct2 = cast_block_of_memory_to_tag(
env.builder,
tag2.into_struct_value(),
wrapper_type,
);
let answer = build_struct_eq(
let answer = eq_ptr_to_struct(
env,
layout_ids,
union_layout,
Some(when_recursive.clone()),
field_layouts,
when_recursive.clone(),
struct1,
struct2,
tag1,
tag2,
);
env.builder.build_return(Some(&answer));
@ -946,8 +948,15 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let answer =
eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2);
let answer = eq_ptr_to_struct(
env,
layout_ids,
union_layout,
None,
field_layouts,
tag1,
tag2,
);
env.builder.build_return(Some(&answer));
@ -1003,6 +1012,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env,
layout_ids,
union_layout,
None,
other_fields,
tag1.into_pointer_value(),
tag2.into_pointer_value(),
@ -1093,8 +1103,15 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
let answer =
eq_ptr_to_struct(env, layout_ids, union_layout, field_layouts, tag1, tag2);
let answer = eq_ptr_to_struct(
env,
layout_ids,
union_layout,
None,
field_layouts,
tag1,
tag2,
);
env.builder.build_return(Some(&answer));
@ -1128,6 +1145,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env,
layout_ids,
union_layout,
None,
field_layouts,
tag1.into_pointer_value(),
tag2.into_pointer_value(),
@ -1142,6 +1160,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
union_layout: &UnionLayout<'a>,
opt_when_recursive: Option<WhenRecursive<'a>>,
field_layouts: &'a [Layout<'a>],
tag1: PointerValue<'ctx>,
tag2: PointerValue<'ctx>,
@ -1184,7 +1203,7 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
env,
layout_ids,
field_layouts,
WhenRecursive::Loop(*union_layout),
opt_when_recursive.unwrap_or(WhenRecursive::Loop(*union_layout)),
struct1,
struct2,
)

View File

@ -76,6 +76,66 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
}
}
pub fn basic_type_from_layout_1<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
layout: &Layout<'_>,
) -> BasicTypeEnum<'ctx> {
use Layout::*;
match layout {
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
LambdaSet(lambda_set) => {
basic_type_from_layout_1(env, &lambda_set.runtime_representation())
}
Union(union_layout) => {
use UnionLayout::*;
let tag_id_type = basic_type_from_layout(env, &union_layout.tag_id_layout());
match union_layout {
NonRecursive(tags) => {
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
let struct_type = env.context.struct_type(&[data, tag_id_type], false);
struct_type.ptr_type(AddressSpace::Generic).into()
}
Recursive(tags)
| NullableWrapped {
other_tags: tags, ..
} => {
let data = block_of_memory_slices(env.context, tags, env.ptr_bytes);
if union_layout.stores_tag_id_as_data(env.ptr_bytes) {
env.context
.struct_type(&[data, tag_id_type], false)
.ptr_type(AddressSpace::Generic)
.into()
} else {
data.ptr_type(AddressSpace::Generic).into()
}
}
NullableUnwrapped { other_fields, .. } => {
let block = block_of_memory_slices(env.context, &[other_fields], env.ptr_bytes);
block.ptr_type(AddressSpace::Generic).into()
}
NonNullableUnwrapped(fields) => {
let block = block_of_memory_slices(env.context, &[fields], env.ptr_bytes);
block.ptr_type(AddressSpace::Generic).into()
}
}
}
RecursivePointer => {
// TODO make this dynamic
env.context
.i64_type()
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum()
}
Builtin(builtin) => basic_type_from_builtin(env, builtin),
}
}
pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
env: &crate::llvm::build::Env<'a, 'ctx, 'env>,
builtin: &Builtin<'_>,

View File

@ -1,11 +1,11 @@
use crate::debug_info_init;
use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::{
add_func, cast_basic_basic, cast_block_of_memory_to_tag, get_tag_id, get_tag_id_non_recursive,
tag_pointer_clear_tag_id, Env, FAST_CALL_CONV, TAG_DATA_INDEX,
add_func, cast_basic_basic, get_tag_id, tag_pointer_clear_tag_id, use_roc_value, Env,
FAST_CALL_CONV, TAG_DATA_INDEX, TAG_ID_INDEX,
};
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
use crate::llvm::convert::{basic_type_from_layout, ptr_int};
use crate::llvm::convert::{basic_type_from_layout, basic_type_from_layout_1, ptr_int};
use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock;
use inkwell::context::Context;
@ -139,8 +139,10 @@ impl<'ctx> PointerToRefcount<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let parent = block.get_parent().unwrap();
let modify_block = env.context.append_basic_block(parent, "inc_str_modify");
let cont_block = env.context.append_basic_block(parent, "inc_str_cont");
let modify_block = env
.context
.append_basic_block(parent, "inc_refcount_modify");
let cont_block = env.context.append_basic_block(parent, "inc_refcount_cont");
env.builder
.build_conditional_branch(is_static_allocation, cont_block, modify_block);
@ -349,18 +351,25 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>(
for (i, field_layout) in layouts.iter().enumerate() {
if field_layout.contains_refcounted() {
let field_ptr = env
let raw_value = env
.builder
.build_extract_value(wrapper_struct, i as u32, "decrement_struct_field")
.unwrap();
let field_value = use_roc_value(
env,
*field_layout,
raw_value,
"load_struct_tag_field_for_decrement",
);
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode.to_call_mode(fn_val),
when_recursive,
field_ptr,
field_value,
field_layout,
);
}
@ -753,7 +762,15 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
);
};
incrementing_elem_loop(env, parent, ptr, len, "modify_rc_index", loop_fn);
incrementing_elem_loop(
env,
parent,
*element_layout,
ptr,
len,
"modify_rc_index",
loop_fn,
);
}
let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
@ -1289,7 +1306,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
.build_bitcast(
value_ptr,
wrapper_type.ptr_type(AddressSpace::Generic),
"opaque_to_correct",
"opaque_to_correct_recursive_decrement",
)
.into_pointer_value();
@ -1602,7 +1619,7 @@ fn modify_refcount_union<'a, 'ctx, 'env>(
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
let basic_type = basic_type_from_layout(env, &layout);
let basic_type = basic_type_from_layout_1(env, &layout);
let function_value = build_header(env, basic_type, mode, &fn_name);
modify_refcount_union_help(
@ -1647,18 +1664,24 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
// Add args to scope
let arg_symbol = Symbol::ARG_1;
let arg_val = fn_val.get_param_iter().next().unwrap();
let arg_ptr = fn_val.get_param_iter().next().unwrap().into_pointer_value();
arg_val.set_name(arg_symbol.as_str(&env.interns));
arg_ptr.set_name(arg_symbol.as_str(&env.interns));
let parent = fn_val;
let before_block = env.builder.get_insert_block().expect("to be in a function");
let wrapper_struct = arg_val.into_struct_value();
// read the tag_id
let tag_id = get_tag_id_non_recursive(env, wrapper_struct);
let tag_id_ptr = env
.builder
.build_struct_gep(arg_ptr, TAG_ID_INDEX, "tag_id_ptr")
.unwrap();
let tag_id = env
.builder
.build_load(tag_id_ptr, "load_tag_id")
.into_int_value();
let tag_id_u8 = env
.builder
@ -1686,12 +1709,16 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
let wrapper_type = basic_type_from_layout(env, &Layout::Struct(field_layouts));
debug_assert!(wrapper_type.is_struct_type());
let data_bytes = env
let opaque_tag_data_ptr = env
.builder
.build_extract_value(wrapper_struct, TAG_DATA_INDEX, "read_tag_id")
.unwrap()
.into_struct_value();
let wrapper_struct = cast_block_of_memory_to_tag(env.builder, data_bytes, wrapper_type);
.build_struct_gep(arg_ptr, TAG_DATA_INDEX, "field_ptr")
.unwrap();
let cast_tag_data_pointer = env.builder.build_pointer_cast(
opaque_tag_data_ptr,
wrapper_type.ptr_type(AddressSpace::Generic),
"cast_to_concrete_tag",
);
for (i, field_layout) in field_layouts.iter().enumerate() {
if let Layout::RecursivePointer = field_layout {
@ -1699,16 +1726,22 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
} else if field_layout.contains_refcounted() {
let field_ptr = env
.builder
.build_extract_value(wrapper_struct, i as u32, "modify_tag_field")
.build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field")
.unwrap();
let field_value = if field_layout.is_passed_by_reference() {
field_ptr.into()
} else {
env.builder.build_load(field_ptr, "field_value")
};
modify_refcount_layout_help(
env,
parent,
layout_ids,
mode.to_call_mode(fn_val),
when_recursive,
field_ptr,
field_value,
field_layout,
);
}

View File

@ -22,8 +22,8 @@ use crate::wasm_module::{
LocalId, Signature, SymInfo, ValueType,
};
use crate::{
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_TYPE,
STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME,
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_SIZE,
PTR_TYPE, STACK_POINTER_GLOBAL_ID, STACK_POINTER_NAME,
};
/// The memory address where the constants data will be loaded during module instantiation.
@ -540,6 +540,29 @@ impl<'a> WasmBackend<'a> {
Expr::Struct(fields) => self.create_struct(sym, layout, fields),
Expr::StructAtIndex {
index,
field_layouts,
structure,
} => {
if let StoredValue::StackMemory { location, .. } = self.storage.get(structure) {
let (local_id, mut offset) =
location.local_and_offset(self.storage.stack_frame_pointer);
for field in field_layouts.iter().take(*index as usize) {
offset += field.stack_size(PTR_SIZE);
}
self.storage.copy_value_from_memory(
&mut self.code_builder,
*sym,
local_id,
offset,
);
} else {
unreachable!("Unexpected storage for {:?}", structure)
}
Ok(())
}
x => Err(format!("Expression is not yet implemented {:?}", x)),
}
}

View File

@ -29,14 +29,14 @@ pub fn build_call_low_level<'a>(
match lowlevel {
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft
| StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim | ListLen
| ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
| ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2
| ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil
| ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist
| ListDropAt | ListSwap | ListAny | ListFindUnsafe | DictSize | DictEmpty | DictInsert
| DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues | DictUnion
| DictIntersection | DictDifference | DictWalk | SetFromList => {
| StrTrimRight | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim
| ListLen | ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse
| ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap
| ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
| ListSublist | ListDropAt | ListSwap | ListAny | ListFindUnsafe | DictSize | DictEmpty
| DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues
| DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => {
return NotImplemented;
}

View File

@ -5,7 +5,7 @@ use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use crate::layout::WasmLayout;
use crate::wasm_module::{CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState};
use crate::wasm_module::{CodeBuilder, LocalId, ValueType, VmSymbolState};
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE};
pub enum StoredValueKind {
@ -33,7 +33,7 @@ impl StackMemoryLocation {
pub enum StoredValue {
/// A value stored implicitly in the VM stack (primitives only)
VirtualMachineStack {
vm_state: VirtualMachineSymbolState,
vm_state: VmSymbolState,
value_type: ValueType,
size: u32,
},
@ -126,7 +126,7 @@ impl<'a> Storage<'a> {
}
}
_ => StoredValue::VirtualMachineStack {
vm_state: VirtualMachineSymbolState::NotYetPushed,
vm_state: VmSymbolState::NotYetPushed,
value_type: *value_type,
size: *size,
},
@ -319,6 +319,67 @@ impl<'a> Storage<'a> {
}
}
/// Generate code to copy a StoredValue from an arbitrary memory location
/// (defined by a pointer and offset).
pub fn copy_value_from_memory(
&mut self,
code_builder: &mut CodeBuilder,
to_symbol: Symbol,
from_ptr: LocalId,
from_offset: u32,
) -> u32 {
let to_storage = self.get(&to_symbol).to_owned();
match to_storage {
StoredValue::StackMemory {
location,
size,
alignment_bytes,
} => {
let (to_ptr, to_offset) = location.local_and_offset(self.stack_frame_pointer);
copy_memory(
code_builder,
CopyMemoryConfig {
from_ptr,
from_offset,
to_ptr,
to_offset,
size,
alignment_bytes,
},
);
size
}
StoredValue::VirtualMachineStack {
value_type, size, ..
}
| StoredValue::Local {
value_type, size, ..
} => {
use crate::wasm_module::Align::*;
code_builder.get_local(from_ptr);
match (value_type, size) {
(ValueType::I64, 8) => code_builder.i64_load(Bytes8, from_offset),
(ValueType::I32, 4) => code_builder.i32_load(Bytes4, from_offset),
(ValueType::I32, 2) => code_builder.i32_load16_s(Bytes2, from_offset),
(ValueType::I32, 1) => code_builder.i32_load8_s(Bytes1, from_offset),
(ValueType::F32, 4) => code_builder.f32_load(Bytes4, from_offset),
(ValueType::F64, 8) => code_builder.f64_load(Bytes8, from_offset),
_ => {
panic!("Cannot store {:?} with alignment of {:?}", value_type, size);
}
};
if let StoredValue::Local { local_id, .. } = to_storage {
code_builder.set_local(local_id);
}
size
}
}
}
/// Generate code to copy from one StoredValue to another
/// Copies the _entire_ value. For struct fields etc., see `copy_value_to_memory`
pub fn clone_value(
@ -422,7 +483,7 @@ impl<'a> Storage<'a> {
} = storage
{
let local_id = self.get_next_local_id();
if vm_state != VirtualMachineSymbolState::NotYetPushed {
if vm_state != VmSymbolState::NotYetPushed {
code_builder.load_symbol(symbol, vm_state, local_id);
code_builder.set_local(local_id);
}

View File

@ -1,7 +1,6 @@
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use core::panic;
use std::fmt::Debug;
use roc_module::symbol::Symbol;
@ -10,6 +9,13 @@ use super::opcodes::{OpCode, OpCode::*};
use super::serialize::{SerialBuffer, Serialize};
use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
const ENABLE_DEBUG_LOG: bool = true;
macro_rules! log_instruction {
($($x: expr),+) => {
if ENABLE_DEBUG_LOG { println!($($x,)*); }
};
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocalId(pub u32);
@ -29,6 +35,7 @@ impl Serialize for ValueType {
}
}
#[derive(PartialEq, Eq, Debug)]
pub enum BlockType {
NoResult,
Value(ValueType),
@ -43,6 +50,31 @@ impl BlockType {
}
}
/// A control block in our model of the VM
/// Child blocks cannot "see" values from their parent block
struct VmBlock<'a> {
/// opcode indicating what kind of block this is
opcode: OpCode,
/// the stack of values for this block
value_stack: Vec<'a, Symbol>,
/// whether this block pushes a result value to its parent
has_result: bool,
}
impl std::fmt::Debug for VmBlock<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"{:?} {}",
self.opcode,
if self.has_result {
"Result"
} else {
"NoResult"
}
))
}
}
/// Wasm memory alignment. (Rust representation matches Wasm encoding)
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
@ -73,7 +105,7 @@ impl From<u32> for Align {
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum VirtualMachineSymbolState {
pub enum VmSymbolState {
/// Value doesn't exist yet
NotYetPushed,
@ -113,6 +145,8 @@ macro_rules! instruction_memargs {
#[derive(Debug)]
pub struct CodeBuilder<'a> {
arena: &'a Bump,
/// The main container for the instructions
code: Vec<'a, u8>,
@ -135,8 +169,8 @@ pub struct CodeBuilder<'a> {
inner_length: Vec<'a, u8>,
/// Our simulation model of the Wasm stack machine
/// Keeps track of where Symbol values are in the VM stack
vm_stack: Vec<'a, Symbol>,
/// Nested blocks of instructions. A child block can't "see" the stack of its parent block
vm_block_stack: Vec<'a, VmBlock<'a>>,
/// Linker info to help combine the Roc module with builtin & platform modules,
/// e.g. to modify call instructions when function indices change
@ -146,13 +180,22 @@ pub struct CodeBuilder<'a> {
#[allow(clippy::new_without_default)]
impl<'a> CodeBuilder<'a> {
pub fn new(arena: &'a Bump) -> Self {
let mut vm_block_stack = Vec::with_capacity_in(8, arena);
let function_block = VmBlock {
opcode: BLOCK,
has_result: true,
value_stack: Vec::with_capacity_in(8, arena),
};
vm_block_stack.push(function_block);
CodeBuilder {
arena,
code: Vec::with_capacity_in(1024, arena),
insertions: Vec::with_capacity_in(32, arena),
insert_bytes: Vec::with_capacity_in(64, arena),
preamble: Vec::with_capacity_in(32, arena),
inner_length: Vec::with_capacity_in(5, arena),
vm_stack: Vec::with_capacity_in(32, arena),
vm_block_stack,
relocations: Vec::with_capacity_in(32, arena),
}
}
@ -167,35 +210,39 @@ impl<'a> CodeBuilder<'a> {
***********************************************************/
fn current_stack(&self) -> &Vec<'a, Symbol> {
let block = self.vm_block_stack.last().unwrap();
&block.value_stack
}
fn current_stack_mut(&mut self) -> &mut Vec<'a, Symbol> {
let block = self.vm_block_stack.last_mut().unwrap();
&mut block.value_stack
}
/// Set the Symbol that is at the top of the VM stack right now
/// We will use this later when we need to load the Symbol
pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState {
let len = self.vm_stack.len();
pub fn set_top_symbol(&mut self, sym: Symbol) -> VmSymbolState {
let current_stack = &mut self.vm_block_stack.last_mut().unwrap().value_stack;
let pushed_at = self.code.len();
let top_symbol: &mut Symbol = current_stack.last_mut().unwrap();
*top_symbol = sym;
if len == 0 {
panic!(
"trying to set symbol with nothing on stack, code = {:?}",
self.code
);
}
self.vm_stack[len - 1] = sym;
VirtualMachineSymbolState::Pushed { pushed_at }
VmSymbolState::Pushed { pushed_at }
}
/// Verify if a sequence of symbols is at the top of the stack
pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool {
let current_stack = self.current_stack();
let n_symbols = symbols.len();
let stack_depth = self.vm_stack.len();
let stack_depth = current_stack.len();
if n_symbols > stack_depth {
return false;
}
let offset = stack_depth - n_symbols;
for (i, sym) in symbols.iter().enumerate() {
if self.vm_stack[offset + i] != *sym {
if current_stack[offset + i] != *sym {
return false;
}
}
@ -214,7 +261,12 @@ impl<'a> CodeBuilder<'a> {
end: self.insert_bytes.len(),
});
// println!("insert {:?} {} at byte offset {} ", opcode, immediate, insert_at);
log_instruction!(
"**insert {:?} {} at byte offset {}**",
opcode,
immediate,
insert_at
);
}
/// Load a Symbol that is stored in the VM stack
@ -227,41 +279,56 @@ impl<'a> CodeBuilder<'a> {
pub fn load_symbol(
&mut self,
symbol: Symbol,
vm_state: VirtualMachineSymbolState,
vm_state: VmSymbolState,
next_local_id: LocalId,
) -> Option<VirtualMachineSymbolState> {
use VirtualMachineSymbolState::*;
) -> Option<VmSymbolState> {
use VmSymbolState::*;
match vm_state {
NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol),
NotYetPushed => unreachable!("Symbol {:?} has no value yet. Nothing to load.", symbol),
Pushed { pushed_at } => {
let &top = self.vm_stack.last().unwrap();
if top == symbol {
// We're lucky, the symbol is already on top of the VM stack
// No code to generate! (This reduces code size by up to 25% in tests.)
// Just let the caller know what happened
Some(Popped { pushed_at })
} else {
// Symbol is not on top of the stack. Find it.
if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) {
// Insert a local.set where the value was created
self.add_insertion(pushed_at, SETLOCAL, next_local_id.0);
match self.current_stack().last() {
Some(top_symbol) if *top_symbol == symbol => {
// We're lucky, the symbol is already on top of the current block's stack.
// No code to generate! (This reduces code size by up to 25% in tests.)
// Just let the caller know what happened
Some(Popped { pushed_at })
}
_ => {
// Symbol is not on top of the stack.
// We should have saved it to a local, so go back and do that now.
// Take the value out of the stack where local.set was inserted
self.vm_stack.remove(found_index);
// It should still be on the stack in the block where it was assigned. Remove it.
let mut found = false;
for block in self.vm_block_stack.iter_mut() {
if let Some(found_index) =
block.value_stack.iter().position(|&s| s == symbol)
{
block.value_stack.remove(found_index);
found = true;
}
}
// Insert a local.get at the current position
// Go back to the code position where it was pushed, and save it to a local
if found {
self.add_insertion(pushed_at, SETLOCAL, next_local_id.0);
} else {
if ENABLE_DEBUG_LOG {
println!(
"{:?} has been popped implicitly. Leaving it on the stack.",
symbol
);
}
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
}
// Recover the value again at the current position
self.get_local(next_local_id);
self.set_top_symbol(symbol);
// This Symbol is no longer stored in the VM stack, but in a local
None
} else {
panic!(
"{:?} has state {:?} but not found in VM stack",
symbol, vm_state
);
}
}
}
@ -284,7 +351,7 @@ impl<'a> CodeBuilder<'a> {
/**********************************************************
FINALIZE AND SERIALIZE
FUNCTION HEADER
***********************************************************/
@ -377,6 +444,12 @@ impl<'a> CodeBuilder<'a> {
self.insertions.sort_by_key(|ins| ins.at);
}
/**********************************************************
SERIALIZE
***********************************************************/
/// Serialize all byte vectors in the right order
/// Also update relocation offsets relative to the base offset (code section body start)
pub fn serialize_with_relocs<T: SerialBuffer>(
@ -435,33 +508,68 @@ impl<'a> CodeBuilder<'a> {
/// Base method for generating instructions
/// Emits the opcode and simulates VM stack push/pop
fn inst(&mut self, opcode: OpCode, pops: usize, push: bool) {
let new_len = self.vm_stack.len() - pops as usize;
self.vm_stack.truncate(new_len);
fn inst_base(&mut self, opcode: OpCode, pops: usize, push: bool) {
let current_stack = self.current_stack_mut();
let new_len = current_stack.len() - pops as usize;
current_stack.truncate(new_len);
if push {
self.vm_stack.push(Symbol::WASM_TMP);
current_stack.push(Symbol::WASM_TMP);
}
self.code.push(opcode as u8);
// println!("{:10}\t{:?}", format!("{:?}", opcode), &self.vm_stack);
}
fn inst_imm8(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u8) {
self.inst(opcode, pops, push);
self.code.push(immediate);
/// Plain instruction without any immediates
fn inst(&mut self, opcode: OpCode, pops: usize, push: bool) {
self.inst_base(opcode, pops, push);
log_instruction!(
"{:10}\t\t{:?}",
format!("{:?}", opcode),
self.current_stack()
);
}
// public for use in test code
pub fn inst_imm32(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u32) {
self.inst(opcode, pops, push);
/// Block instruction
fn inst_block(&mut self, opcode: OpCode, pops: usize, block_type: BlockType) {
self.inst_base(opcode, pops, false);
self.code.push(block_type.as_byte());
// Start a new block with a fresh value stack
self.vm_block_stack.push(VmBlock {
opcode,
value_stack: Vec::with_capacity_in(8, self.arena),
has_result: block_type != BlockType::NoResult,
});
log_instruction!(
"{:10} {:?}\t{:?}",
format!("{:?}", opcode),
block_type,
&self.vm_block_stack
);
}
fn inst_imm32(&mut self, opcode: OpCode, pops: usize, push: bool, immediate: u32) {
self.inst_base(opcode, pops, push);
self.code.encode_u32(immediate);
log_instruction!(
"{:10}\t{}\t{:?}",
format!("{:?}", opcode),
immediate,
self.current_stack()
);
}
fn inst_mem(&mut self, opcode: OpCode, pops: usize, push: bool, align: Align, offset: u32) {
self.inst(opcode, pops, push);
self.inst_base(opcode, pops, push);
self.code.push(align as u8);
self.code.encode_u32(offset);
log_instruction!(
"{:10} {:?} {}\t{:?}",
format!("{:?}", opcode),
align,
offset,
self.current_stack()
);
}
/// Insert a linker relocation for a memory address
@ -488,22 +596,38 @@ impl<'a> CodeBuilder<'a> {
instruction_no_args!(nop, NOP, 0, false);
pub fn block(&mut self, ty: BlockType) {
self.inst_imm8(BLOCK, 0, false, ty.as_byte());
self.inst_block(BLOCK, 0, ty);
}
pub fn loop_(&mut self, ty: BlockType) {
self.inst_imm8(LOOP, 0, false, ty.as_byte());
self.inst_block(LOOP, 0, ty);
}
pub fn if_(&mut self, ty: BlockType) {
self.inst_imm8(IF, 1, false, ty.as_byte());
self.inst_block(IF, 1, ty);
}
pub fn else_(&mut self) {
// Reuse the 'then' block but clear its value stack
self.current_stack_mut().clear();
self.inst(ELSE, 0, false);
}
instruction_no_args!(else_, ELSE, 0, false);
instruction_no_args!(end, END, 0, false);
pub fn end(&mut self) {
self.inst_base(END, 0, false);
let ended_block = self.vm_block_stack.pop().unwrap();
if ended_block.has_result {
let result = ended_block.value_stack.last().unwrap();
self.current_stack_mut().push(*result)
}
log_instruction!("END \t\t{:?}", &self.vm_block_stack);
}
pub fn br(&mut self, levels: u32) {
self.inst_imm32(BR, 0, false, levels);
}
pub fn br_if(&mut self, levels: u32) {
// In dynamic execution, br_if can pop 2 values if condition is true and the target block has a result.
// But our stack model is for *static* analysis and we need it to be correct at the next instruction,
// where the branch was not taken. So we only pop 1 value, the condition.
self.inst_imm32(BRIF, 1, false, levels);
}
#[allow(dead_code)]
@ -520,7 +644,7 @@ impl<'a> CodeBuilder<'a> {
n_args: usize,
has_return_val: bool,
) {
self.inst(CALL, n_args, has_return_val);
self.inst_base(CALL, n_args, has_return_val);
let offset = self.code.len() as u32;
self.code.encode_padded_u32(function_index);
@ -533,6 +657,13 @@ impl<'a> CodeBuilder<'a> {
offset,
symbol_index,
});
log_instruction!(
"{:10}\t{}\t{:?}",
format!("{:?}", CALL),
function_index,
self.current_stack()
);
}
#[allow(dead_code)]
@ -584,26 +715,44 @@ impl<'a> CodeBuilder<'a> {
instruction_memargs!(i64_store32, I64STORE32, 2, false);
pub fn memory_size(&mut self) {
self.inst_imm8(CURRENTMEMORY, 0, true, 0);
self.inst(CURRENTMEMORY, 0, true);
self.code.push(0);
}
pub fn memory_grow(&mut self) {
self.inst_imm8(GROWMEMORY, 1, true, 0);
self.inst(GROWMEMORY, 1, true);
self.code.push(0);
}
fn log_const<T>(&self, opcode: OpCode, x: T)
where
T: std::fmt::Debug + std::fmt::Display,
{
log_instruction!(
"{:10}\t{}\t{:?}",
format!("{:?}", opcode),
x,
self.current_stack()
);
}
pub fn i32_const(&mut self, x: i32) {
self.inst(I32CONST, 0, true);
self.inst_base(I32CONST, 0, true);
self.code.encode_i32(x);
self.log_const(I32CONST, x);
}
pub fn i64_const(&mut self, x: i64) {
self.inst(I64CONST, 0, true);
self.inst_base(I64CONST, 0, true);
self.code.encode_i64(x);
self.log_const(I64CONST, x);
}
pub fn f32_const(&mut self, x: f32) {
self.inst(F32CONST, 0, true);
self.inst_base(F32CONST, 0, true);
self.code.encode_f32(x);
self.log_const(F32CONST, x);
}
pub fn f64_const(&mut self, x: f64) {
self.inst(F64CONST, 0, true);
self.inst_base(F64CONST, 0, true);
self.code.encode_f64(x);
self.log_const(F64CONST, x);
}
// TODO: Consider creating unified methods for numerical ops like 'eq' and 'add',

View File

@ -4,8 +4,6 @@ pub mod opcodes;
pub mod sections;
pub mod serialize;
pub use code_builder::{
Align, BlockType, CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState,
};
pub use code_builder::{Align, BlockType, CodeBuilder, LocalId, ValueType, VmSymbolState};
pub use linking::{LinkingSubSection, SymInfo};
pub use sections::{ConstExpr, Export, ExportType, Global, GlobalType, Signature, WasmModule};

View File

@ -1,5 +1,5 @@
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OpCode {
UNREACHABLE = 0x00,
NOP = 0x01,

View File

@ -235,16 +235,12 @@ fn generate_entry_doc<'a>(
fn type_to_docs(in_func_type_ann: bool, type_annotation: ast::TypeAnnotation) -> TypeAnnotation {
match type_annotation {
ast::TypeAnnotation::TagUnion {
tags,
ext,
final_comments: _,
} => {
ast::TypeAnnotation::TagUnion { tags, ext } => {
let mut tags_to_render: Vec<Tag> = Vec::new();
let mut any_tags_are_private = false;
for tag in tags {
for tag in tags.iter() {
match tag_to_doc(in_func_type_ann, tag.value) {
None => {
any_tags_are_private = true;

View File

@ -2608,8 +2608,8 @@ fn parse_header<'a>(
opt_shorthand,
header_src,
packages: &[],
exposes: header.exposes.into_bump_slice(),
imports: header.imports.into_bump_slice(),
exposes: header.exposes.items,
imports: header.imports.items,
to_platform: None,
};
@ -2642,8 +2642,8 @@ fn parse_header<'a>(
opt_shorthand,
header_src,
packages,
exposes: header.provides.into_bump_slice(),
imports: header.imports.into_bump_slice(),
exposes: header.provides.items,
imports: header.imports.items,
to_platform: Some(header.to.value.clone()),
};
@ -3236,7 +3236,7 @@ fn send_header_two<'a>(
let extra = HeaderFor::PkgConfig {
config_shorthand: shorthand,
platform_main_type: requires[0].value.clone(),
platform_main_type: requires[0].value,
main_for_host,
};
@ -3409,8 +3409,7 @@ fn fabricate_pkg_config_module<'a>(
header_src: &'a str,
module_timing: ModuleTiming,
) -> (ModuleId, Msg<'a>) {
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] =
header.provides.clone().into_bump_slice();
let provides: &'a [Located<ExposesEntry<'a, &'a str>>] = header.provides.items;
let info = PlatformHeaderInfo {
filename,
@ -3420,8 +3419,8 @@ fn fabricate_pkg_config_module<'a>(
app_module_id,
packages: &[],
provides,
requires: arena.alloc([header.requires.signature.clone()]),
imports: header.imports.clone().into_bump_slice(),
requires: arena.alloc([header.requires.signature]),
imports: header.imports.items,
};
send_header_two(
@ -3467,7 +3466,7 @@ fn fabricate_effects_module<'a>(
{
let mut module_ids = (*module_ids).lock();
for exposed in header.exposes {
for exposed in header.exposes.iter() {
if let ExposesEntry::Exposed(module_name) = exposed.value {
module_ids.get_or_insert(&PQModuleName::Qualified(
shorthand,
@ -3886,7 +3885,7 @@ fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>
Module(module_name, exposes) => {
let mut exposed = Vec::with_capacity(exposes.len());
for loc_entry in exposes {
for loc_entry in exposes.iter() {
exposed.push(ident_from_exposed(&loc_entry.value));
}
@ -3901,7 +3900,7 @@ fn exposed_from_import<'a>(entry: &ImportsEntry<'a>) -> (QualifiedModuleName<'a>
Package(package_name, module_name, exposes) => {
let mut exposed = Vec::with_capacity(exposes.len());
for loc_entry in exposes {
for loc_entry in exposes.iter() {
exposed.push(ident_from_exposed(&loc_entry.value));
}

View File

@ -19,6 +19,7 @@ pub enum LowLevel {
StrFromFloat,
StrTrim,
StrTrimLeft,
StrTrimRight,
ListLen,
ListGetUnsafe,
ListSet,
@ -130,6 +131,7 @@ macro_rules! first_order {
| StrRepeat
| StrTrim
| StrTrimLeft
| StrTrimRight
| StrFromFloat
| ListLen
| ListGetUnsafe

View File

@ -1019,6 +1019,7 @@ define_builtins! {
19 STR_REPEAT: "repeat"
20 STR_TRIM: "trim"
21 STR_TRIM_LEFT: "trimLeft"
22 STR_TRIM_RIGHT: "trimRight"
}
4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias

View File

@ -941,6 +941,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
StrTrim => arena.alloc_slice_copy(&[owned]),
StrTrimLeft => arena.alloc_slice_copy(&[owned]),
StrTrimRight => arena.alloc_slice_copy(&[owned]),
StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListSingle => arena.alloc_slice_copy(&[irrelevant]),
ListRepeat => arena.alloc_slice_copy(&[irrelevant, borrowed]),

View File

@ -863,6 +863,16 @@ impl<'a> Layout<'a> {
false
}
pub fn is_passed_by_reference(&self) -> bool {
match self {
Layout::Union(UnionLayout::NonRecursive(_)) => true,
Layout::LambdaSet(lambda_set) => {
lambda_set.runtime_representation().is_passed_by_reference()
}
_ => false,
}
}
pub fn stack_size(&self, pointer_size: u32) -> u32 {
let width = self.stack_size_without_alignment(pointer_size);
let alignment = self.alignment_bytes(pointer_size);
@ -941,16 +951,16 @@ impl<'a> Layout<'a> {
})
.max();
let tag_id_builtin = variant.tag_id_builtin();
match max_alignment {
Some(align) => {
let tag_id_builtin = variant.tag_id_builtin();
round_up_to_alignment(
align,
tag_id_builtin.alignment_bytes(pointer_size),
)
Some(align) => round_up_to_alignment(
align.max(tag_id_builtin.alignment_bytes(pointer_size)),
tag_id_builtin.alignment_bytes(pointer_size),
),
None => {
// none of the tags had any payload, but the tag id still contains information
tag_id_builtin.alignment_bytes(pointer_size)
}
None => 0,
}
}
Recursive(_)
@ -2674,3 +2684,27 @@ impl<'a> std::convert::TryFrom<&Layout<'a>> for ListLayout<'a> {
}
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn width_and_alignment_union_empty_struct() {
let lambda_set = LambdaSet {
set: &[(Symbol::LIST_MAP, &[])],
representation: &Layout::Struct(&[]),
};
let a = &[Layout::Struct(&[])] as &[_];
let b = &[Layout::LambdaSet(lambda_set)] as &[_];
let tt = [a, b];
let layout = Layout::Union(UnionLayout::NonRecursive(&tt));
// at the moment, the tag id uses an I64, so
let ptr_width = 8;
assert_eq!(layout.stack_size(ptr_width), 8);
assert_eq!(layout.alignment_bytes(ptr_width), 8);
}
}

View File

@ -1,32 +1,12 @@
use std::fmt::Debug;
use crate::header::{AppHeader, ImportsEntry, InterfaceHeader, PlatformHeader, TypedIdent};
use crate::ident::Ident;
use bumpalo::collections::String;
use bumpalo::collections::{String, Vec};
use bumpalo::Bump;
use roc_module::operator::{BinOp, CalledVia, UnaryOp};
use roc_region::all::{Loc, Position, Region};
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Collection<'a, T> {
pub items: &'a [T],
pub final_comments: &'a [CommentOrNewline<'a>],
}
impl<'a, T> Collection<'a, T> {
pub fn empty() -> Collection<'a, T> {
Collection {
items: &[],
final_comments: &[],
}
}
pub fn with_items(items: &'a [T]) -> Collection<'a, T> {
Collection {
items,
final_comments: &[],
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Module<'a> {
Interface { header: InterfaceHeader<'a> },
@ -115,21 +95,14 @@ pub enum Expr<'a> {
AccessorFunction(&'a str),
// Collection Literals
List {
items: &'a [&'a Loc<Expr<'a>>],
final_comments: &'a [CommentOrNewline<'a>],
},
List(Collection<'a, &'a Loc<Expr<'a>>>),
RecordUpdate {
update: &'a Loc<Expr<'a>>,
fields: &'a [Loc<AssignedField<'a, Expr<'a>>>],
final_comments: &'a &'a [CommentOrNewline<'a>],
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
},
Record {
fields: &'a [Loc<AssignedField<'a, Expr<'a>>>],
final_comments: &'a [CommentOrNewline<'a>],
},
Record(Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>),
// Lookups
Var {
@ -257,16 +230,14 @@ pub enum TypeAnnotation<'a> {
/// The row type variable in an open record, e.g. the `r` in `{ name: Str }r`.
/// This is None if it's a closed record annotation like `{ name: Str }`.
ext: Option<&'a Loc<TypeAnnotation<'a>>>,
// final_comments: &'a [CommentOrNewline<'a>],
},
/// A tag union, e.g. `[
TagUnion {
tags: &'a [Loc<Tag<'a>>],
/// The row type variable in an open tag union, e.g. the `a` in `[ Foo, Bar ]a`.
/// This is None if it's a closed tag union like `[ Foo, Bar]`.
ext: Option<&'a Loc<TypeAnnotation<'a>>>,
final_comments: &'a [CommentOrNewline<'a>],
tags: Collection<'a, Loc<Tag<'a>>>,
},
/// The `*` type variable, e.g. in (List *)
@ -357,10 +328,11 @@ pub enum Pattern<'a> {
GlobalTag(&'a str),
PrivateTag(&'a str),
Apply(&'a Loc<Pattern<'a>>, &'a [Loc<Pattern<'a>>]),
/// This is Loc<Pattern> rather than Loc<str> so we can record comments
/// around the destructured names, e.g. { x ### x does stuff ###, y }
/// In practice, these patterns will always be Identifier
RecordDestructure(&'a [Loc<Pattern<'a>>]),
RecordDestructure(Collection<'a, Loc<Pattern<'a>>>),
/// A required field pattern, e.g. { x: Just 0 } -> ...
/// Can only occur inside of a RecordDestructure
@ -519,6 +491,126 @@ impl<'a> Pattern<'a> {
}
}
}
#[derive(Copy, Clone)]
pub struct Collection<'a, T> {
pub items: &'a [T],
// Use a pointer to a slice (rather than just a slice), in order to avoid bloating
// Ast variants. The final_comments field is rarely accessed in the hot path, so
// this shouldn't matter much for perf.
// Use an Option, so it's possible to initialize without allocating.
final_comments: Option<&'a &'a [CommentOrNewline<'a>]>,
}
impl<'a, T> Collection<'a, T> {
pub fn empty() -> Collection<'a, T> {
Collection {
items: &[],
final_comments: None,
}
}
pub fn with_items(items: &'a [T]) -> Collection<'a, T> {
Collection {
items,
final_comments: None,
}
}
pub fn with_items_and_comments(
arena: &'a Bump,
items: &'a [T],
comments: &'a [CommentOrNewline<'a>],
) -> Collection<'a, T> {
Collection {
items,
final_comments: if comments.is_empty() {
None
} else {
Some(arena.alloc(comments))
},
}
}
pub fn replace_items<V>(&self, new_items: &'a [V]) -> Collection<'a, V> {
Collection {
items: new_items,
final_comments: self.final_comments,
}
}
pub fn ptrify_items(&self, arena: &'a Bump) -> Collection<'a, &'a T> {
let mut allocated = Vec::with_capacity_in(self.len(), arena);
for parsed_elem in self.items {
allocated.push(parsed_elem);
}
self.replace_items(allocated.into_bump_slice())
}
pub fn map_items<V: 'a>(&self, arena: &'a Bump, f: impl Fn(&'a T) -> V) -> Collection<'a, V> {
let mut allocated = Vec::with_capacity_in(self.len(), arena);
for parsed_elem in self.items {
allocated.push(f(parsed_elem));
}
self.replace_items(allocated.into_bump_slice())
}
pub fn map_items_result<V: 'a, E>(
&self,
arena: &'a Bump,
f: impl Fn(&T) -> Result<V, E>,
) -> Result<Collection<'a, V>, E> {
let mut allocated = Vec::with_capacity_in(self.len(), arena);
for parsed_elem in self.items {
allocated.push(f(parsed_elem)?);
}
Ok(self.replace_items(allocated.into_bump_slice()))
}
pub fn final_comments(&self) -> &'a [CommentOrNewline<'a>] {
if let Some(final_comments) = self.final_comments {
*final_comments
} else {
&[]
}
}
pub fn iter(&self) -> impl Iterator<Item = &'a T> {
self.items.iter()
}
pub fn len(&self) -> usize {
self.items.len()
}
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
}
impl<'a, T: PartialEq> PartialEq for Collection<'a, T> {
fn eq(&self, other: &Self) -> bool {
self.items == other.items && self.final_comments() == other.final_comments()
}
}
impl<'a, T: Debug> Debug for Collection<'a, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.final_comments().is_empty() {
f.debug_list().entries(self.items.iter()).finish()
} else {
f.debug_struct("Collection")
.field("items", &self.items)
.field("final_comments", &self.final_comments())
.finish()
}
}
}
pub trait Spaceable<'a> {
fn before(&'a self, _: &'a [CommentOrNewline<'a>]) -> Self;

View File

@ -1,4 +1,6 @@
use crate::ast::{AssignedField, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation};
use crate::ast::{
AssignedField, Collection, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation,
};
use crate::blankspace::{space0_after_e, space0_around_ee, space0_before_e, space0_e};
use crate::ident::{lowercase_ident, parse_ident, Ident};
use crate::keyword;
@ -1446,20 +1448,14 @@ fn expr_to_pattern_help<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<
Expr::ParensAround(sub_expr) => expr_to_pattern_help(arena, sub_expr),
Expr::Record {
fields,
final_comments: _,
} => {
let mut loc_patterns = Vec::with_capacity_in(fields.len(), arena);
for loc_assigned_field in fields.iter() {
Expr::Record(fields) => {
let patterns = fields.map_items_result(arena, |loc_assigned_field| {
let region = loc_assigned_field.region;
let value = assigned_expr_field_to_pattern_help(arena, &loc_assigned_field.value)?;
Ok(Located { region, value })
})?;
loc_patterns.push(Located { region, value });
}
Ok(Pattern::RecordDestructure(loc_patterns.into_bump_slice()))
Ok(Pattern::RecordDestructure(patterns))
}
Expr::Float(string) => Ok(Pattern::FloatLiteral(string)),
@ -2165,16 +2161,8 @@ fn list_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EList<'a>
)
.parse(arena, state)?;
let mut allocated = Vec::with_capacity_in(elements.items.len(), arena);
for parsed_elem in elements.items {
allocated.push(parsed_elem);
}
let expr = Expr::List {
items: allocated.into_bump_slice(),
final_comments: elements.final_comments,
};
let elements = elements.ptrify_items(arena);
let expr = Expr::List(elements);
Ok((MadeProgress, expr, state))
}
@ -2312,13 +2300,17 @@ fn record_literal_help<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>, EExpr<'
let mut value = match opt_update {
Some(update) => Expr::RecordUpdate {
update: &*arena.alloc(update),
fields: loc_assigned_fields_with_comments.value.0.into_bump_slice(),
final_comments: arena.alloc(loc_assigned_fields_with_comments.value.1),
},
None => Expr::Record {
fields: loc_assigned_fields_with_comments.value.0.into_bump_slice(),
final_comments: loc_assigned_fields_with_comments.value.1,
fields: Collection::with_items_and_comments(
arena,
loc_assigned_fields_with_comments.value.0.into_bump_slice(),
arena.alloc(loc_assigned_fields_with_comments.value.1),
),
},
None => Expr::Record(Collection::with_items_and_comments(
arena,
loc_assigned_fields_with_comments.value.0.into_bump_slice(),
loc_assigned_fields_with_comments.value.1,
)),
};
// there can be field access, e.g. `{ x : 4 }.x`

View File

@ -38,7 +38,7 @@ pub enum PackageOrPath<'a> {
Path(StrLiteral<'a>),
}
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct ModuleName<'a>(&'a str);
impl<'a> From<ModuleName<'a>> for &'a str {
@ -60,8 +60,8 @@ impl<'a> ModuleName<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct InterfaceHeader<'a> {
pub name: Loc<ModuleName<'a>>,
pub exposes: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
pub exposes: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
// Potential comments and newlines - these will typically all be empty.
pub before_header: &'a [CommentOrNewline<'a>],
@ -82,8 +82,8 @@ pub enum To<'a> {
pub struct AppHeader<'a> {
pub name: Loc<StrLiteral<'a>>,
pub packages: Collection<'a, Loc<PackageEntry<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
pub provides: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
pub provides: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub to: Loc<To<'a>>,
// Potential comments and newlines - these will typically all be empty.
@ -117,7 +117,7 @@ pub struct PackageHeader<'a> {
pub after_imports: &'a [CommentOrNewline<'a>],
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PlatformRigid<'a> {
Entry { rigid: &'a str, alias: &'a str },
@ -137,7 +137,7 @@ impl<'a> Spaceable<'a> for PlatformRigid<'a> {
#[derive(Clone, Debug, PartialEq)]
pub struct PlatformRequires<'a> {
pub rigids: Vec<'a, Loc<PlatformRigid<'a>>>,
pub rigids: Collection<'a, Loc<PlatformRigid<'a>>>,
pub signature: Loc<TypedIdent<'a>>,
}
@ -145,10 +145,10 @@ pub struct PlatformRequires<'a> {
pub struct PlatformHeader<'a> {
pub name: Loc<PackageName<'a>>,
pub requires: PlatformRequires<'a>,
pub exposes: Vec<'a, Loc<ExposesEntry<'a, ModuleName<'a>>>>,
pub exposes: Collection<'a, Loc<ExposesEntry<'a, ModuleName<'a>>>>,
pub packages: Collection<'a, Loc<PackageEntry<'a>>>,
pub imports: Vec<'a, Loc<ImportsEntry<'a>>>,
pub provides: Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub imports: Collection<'a, Loc<ImportsEntry<'a>>>,
pub provides: Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
pub effects: Effects<'a>,
// Potential comments and newlines - these will typically all be empty.
@ -177,7 +177,7 @@ pub struct Effects<'a> {
pub entries: &'a [Loc<TypedIdent<'a>>],
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ExposesEntry<'a, T> {
/// e.g. `Task`
Exposed(T),
@ -196,16 +196,19 @@ impl<'a, T> Spaceable<'a> for ExposesEntry<'a, T> {
}
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum ImportsEntry<'a> {
/// e.g. `Task` or `Task.{ Task, after }`
Module(ModuleName<'a>, Vec<'a, Loc<ExposesEntry<'a, &'a str>>>),
Module(
ModuleName<'a>,
Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
),
/// e.g. `base.Task` or `base.Task.{ after }` or `base.{ Task.{ Task, after } }`
Package(
&'a str,
ModuleName<'a>,
Vec<'a, Loc<ExposesEntry<'a, &'a str>>>,
Collection<'a, Loc<ExposesEntry<'a, &'a str>>>,
),
// Spaces
@ -224,7 +227,7 @@ impl<'a> ExposesEntry<'a, &'a str> {
}
}
#[derive(Clone, Debug, PartialEq)]
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum TypedIdent<'a> {
/// e.g.
///

View File

@ -220,11 +220,11 @@ fn app_header<'a>() -> impl Parser<'a, AppHeader<'a>, EHeader<'a>> {
#[allow(clippy::type_complexity)]
let opt_imports: Option<(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ImportsEntry<'a>>>,
Collection<'a, Located<ImportsEntry<'a>>>,
)> = opt_imports;
let ((before_imports, after_imports), imports) =
opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Vec::new_in(arena)));
opt_imports.unwrap_or_else(|| ((&[] as _, &[] as _), Collection::empty()));
let provides: ProvidesTo<'a> = provides; // rustc must be told the type here
let header = AppHeader {
@ -303,7 +303,7 @@ fn platform_header<'a>() -> impl Parser<'a, PlatformHeader<'a>, EHeader<'a>> {
#[derive(Debug)]
struct ProvidesTo<'a> {
entries: Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
entries: Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
to: Located<To<'a>>,
before_provides_keyword: &'a [CommentOrNewline<'a>],
@ -362,7 +362,7 @@ fn provides_without_to<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
),
EProvides<'a>,
> {
@ -376,14 +376,16 @@ fn provides_without_to<'a>() -> impl Parser<
EProvides::IndentProvides,
EProvides::IndentListStart
),
collection_e!(
collection_trailing_sep_e!(
word1(b'[', EProvides::ListStart),
exposes_entry(EProvides::Identifier),
word1(b',', EProvides::ListEnd),
word1(b']', EProvides::ListEnd),
min_indent,
EProvides::Open,
EProvides::Space,
EProvides::IndentListEnd
EProvides::IndentListEnd,
ExposesEntry::SpaceBefore
)
)
}
@ -442,15 +444,17 @@ fn platform_requires<'a>() -> impl Parser<'a, PlatformRequires<'a>, ERequires<'a
#[inline(always)]
fn requires_rigids<'a>(
min_indent: u16,
) -> impl Parser<'a, Vec<'a, Located<PlatformRigid<'a>>>, ERequires<'a>> {
collection_e!(
) -> impl Parser<'a, Collection<'a, Located<PlatformRigid<'a>>>, ERequires<'a>> {
collection_trailing_sep_e!(
word1(b'{', ERequires::ListStart),
specialize(|_, r, c| ERequires::Rigid(r, c), loc!(requires_rigid())),
word1(b',', ERequires::ListEnd),
word1(b'}', ERequires::ListEnd),
min_indent,
ERequires::Open,
ERequires::Space,
ERequires::IndentListEnd
ERequires::IndentListEnd,
PlatformRigid::SpaceBefore
)
}
@ -487,7 +491,7 @@ fn exposes_values<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, &'a str>>>,
Collection<'a, Located<ExposesEntry<'a, &'a str>>>,
),
EExposes,
> {
@ -502,14 +506,16 @@ fn exposes_values<'a>() -> impl Parser<
EExposes::IndentExposes,
EExposes::IndentListStart
),
collection_e!(
collection_trailing_sep_e!(
word1(b'[', EExposes::ListStart),
exposes_entry(EExposes::Identifier),
word1(b',', EExposes::ListEnd),
word1(b']', EExposes::ListEnd),
min_indent,
EExposes::Open,
EExposes::Space,
EExposes::IndentListEnd
EExposes::IndentListEnd,
ExposesEntry::SpaceBefore
)
)
}
@ -539,7 +545,7 @@ fn exposes_modules<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>,
Collection<'a, Located<ExposesEntry<'a, ModuleName<'a>>>>,
),
EExposes,
> {
@ -554,14 +560,16 @@ fn exposes_modules<'a>() -> impl Parser<
EExposes::IndentExposes,
EExposes::IndentListStart
),
collection_e!(
collection_trailing_sep_e!(
word1(b'[', EExposes::ListStart),
exposes_module(EExposes::Identifier),
word1(b',', EExposes::ListEnd),
word1(b']', EExposes::ListEnd),
min_indent,
EExposes::Open,
EExposes::Space,
EExposes::IndentListEnd
EExposes::IndentListEnd,
ExposesEntry::SpaceBefore
)
)
}
@ -631,7 +639,7 @@ fn imports<'a>() -> impl Parser<
'a,
(
(&'a [CommentOrNewline<'a>], &'a [CommentOrNewline<'a>]),
Vec<'a, Located<ImportsEntry<'a>>>,
Collection<'a, Located<ImportsEntry<'a>>>,
),
EImports,
> {
@ -646,14 +654,16 @@ fn imports<'a>() -> impl Parser<
EImports::IndentImports,
EImports::IndentListStart
),
collection_e!(
collection_trailing_sep_e!(
word1(b'[', EImports::ListStart),
loc!(imports_entry()),
word1(b',', EImports::ListEnd),
word1(b']', EImports::ListEnd),
min_indent,
EImports::Open,
EImports::Space,
EImports::IndentListEnd
EImports::IndentListEnd,
ImportsEntry::SpaceBefore
)
)
}
@ -687,14 +697,16 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
space0_e(min_indent, EEffects::Space, EEffects::IndentListStart)
)
.parse(arena, state)?;
let (_, entries, state) = collection_e!(
let (_, entries, state) = collection_trailing_sep_e!(
word1(b'{', EEffects::ListStart),
specialize(EEffects::TypedIdent, loc!(typed_ident())),
word1(b',', EEffects::ListEnd),
word1(b'}', EEffects::ListEnd),
min_indent,
EEffects::Open,
EEffects::Space,
EEffects::IndentListEnd
EEffects::IndentListEnd,
TypedIdent::SpaceBefore
)
.parse(arena, state)?;
@ -706,7 +718,7 @@ fn effects<'a>() -> impl Parser<'a, Effects<'a>, EEffects<'a>> {
spaces_after_type_name,
effect_shortname: type_shortname,
effect_type_name: type_name,
entries: entries.into_bump_slice(),
entries: entries.items,
},
state,
))
@ -768,7 +780,7 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> {
type Temp<'a> = (
(Option<&'a str>, ModuleName<'a>),
Option<Vec<'a, Located<ExposesEntry<'a, &'a str>>>>,
Option<Collection<'a, Located<ExposesEntry<'a, &'a str>>>>,
);
map_with_arena!(
@ -785,19 +797,21 @@ fn imports_entry<'a>() -> impl Parser<'a, ImportsEntry<'a>, EImports> {
// e.g. `.{ Task, after}`
maybe!(skip_first!(
word1(b'.', EImports::ExposingDot),
collection_e!(
collection_trailing_sep_e!(
word1(b'{', EImports::SetStart),
exposes_entry(EImports::Identifier),
word1(b',', EImports::SetEnd),
word1(b'}', EImports::SetEnd),
min_indent,
EImports::Open,
EImports::Space,
EImports::IndentSetEnd
EImports::IndentSetEnd,
ExposesEntry::SpaceBefore
)
))
),
|arena, ((opt_shortname, module_name), opt_values): Temp<'a>| {
let exposed_values = opt_values.unwrap_or_else(|| Vec::new_in(arena));
|_arena, ((opt_shortname, module_name), opt_values): Temp<'a>| {
let exposed_values = opt_values.unwrap_or_else(Collection::empty);
match opt_shortname {
Some(shortname) => ImportsEntry::Package(shortname, module_name, exposed_values),

View File

@ -214,6 +214,7 @@ pub enum EHeader<'a> {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EProvides<'a> {
Provides(Row, Col),
Open(Row, Col),
To(Row, Col),
IndentProvides(Row, Col),
IndentTo(Row, Col),
@ -230,6 +231,7 @@ pub enum EProvides<'a> {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EExposes {
Exposes(Row, Col),
Open(Row, Col),
IndentExposes(Row, Col),
IndentListStart(Row, Col),
IndentListEnd(Row, Col),
@ -242,6 +244,7 @@ pub enum EExposes {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ERequires<'a> {
Requires(Row, Col),
Open(Row, Col),
IndentRequires(Row, Col),
IndentListStart(Row, Col),
IndentListEnd(Row, Col),
@ -302,6 +305,7 @@ pub enum EPackageEntry<'a> {
pub enum EEffects<'a> {
Space(BadInputError, Row, Col),
Effects(Row, Col),
Open(Row, Col),
IndentEffects(Row, Col),
ListStart(Row, Col),
ListEnd(Row, Col),
@ -315,6 +319,7 @@ pub enum EEffects<'a> {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EImports {
Open(Row, Col),
Imports(Row, Col),
IndentImports(Row, Col),
IndentListStart(Row, Col),
@ -1183,83 +1188,6 @@ macro_rules! collection {
};
}
#[macro_export]
macro_rules! collection_e {
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $space_problem:expr, $indent_problem:expr) => {
skip_first!(
$opening_brace,
skip_first!(
// We specifically allow space characters inside here, so that
// `[ ]` can be successfully parsed as an empty list, and then
// changed by the formatter back into `[]`.
//
// We don't allow newlines or comments in the middle of empty
// roc_collections because those are normally stored in an Expr,
// and there's no Expr in which to store them in an empty collection!
//
// We could change the AST to add extra storage specifically to
// support empty literals containing newlines or comments, but this
// does not seem worth even the tiniest regression in compiler performance.
zero_or_more!($crate::parser::word1(b' ', |row, col| $space_problem(
crate::parser::BadInputError::LineTooLong,
row,
col
))),
skip_second!(
$crate::parser::sep_by0(
$delimiter,
$crate::blankspace::space0_around_ee(
$elem,
$min_indent,
$space_problem,
$indent_problem,
$indent_problem
)
),
$closing_brace
)
)
)
};
}
/// Parse zero or more elements between two braces (e.g. square braces).
/// Elements can be optionally surrounded by spaces, and are separated by a
/// delimiter (e.g comma-separated) with optionally a trailing delimiter.
/// Braces and delimiters get discarded.
#[macro_export]
macro_rules! collection_trailing_sep {
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr) => {
skip_first!(
$opening_brace,
skip_first!(
// We specifically allow space characters inside here, so that
// `[ ]` can be successfully parsed as an empty list, and then
// changed by the formatter back into `[]`.
//
// We don't allow newlines or comments in the middle of empty
// roc_collections because those are normally stored in an Expr,
// and there's no Expr in which to store them in an empty collection!
//
// We could change the AST to add extra storage specifically to
// support empty literals containing newlines or comments, but this
// does not seem worth even the tiniest regression in compiler performance.
zero_or_more!($crate::parser::ascii_char(b' ')),
skip_second!(
and!(
$crate::parser::trailing_sep_by0(
$delimiter,
$crate::blankspace::space0_around($elem, $min_indent)
),
$crate::blankspace::space0($min_indent)
),
$closing_brace
)
)
)
};
}
#[macro_export]
macro_rules! collection_trailing_sep_e {
($opening_brace:expr, $elem:expr, $delimiter:expr, $closing_brace:expr, $min_indent:expr, $open_problem:expr, $space_problem:expr, $indent_problem:expr, $space_before:expr) => {
@ -1300,10 +1228,10 @@ macro_rules! collection_trailing_sep_e {
}
}
let collection = $crate::ast::Collection {
items: parsed_elems.into_bump_slice(),
final_comments,
};
let collection = $crate::ast::Collection::with_items_and_comments(
arena,
parsed_elems.into_bump_slice(),
final_comments);
Ok((MadeProgress, collection, state))
}

View File

@ -331,10 +331,7 @@ fn record_pattern_help<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>, PRec
)
.parse(arena, state)?;
// TODO
let _unused = fields.final_comments;
let result = Pattern::RecordDestructure(fields.items);
let result = Pattern::RecordDestructure(fields);
Ok((MadeProgress, result, state))
}

View File

@ -1,4 +1,4 @@
use crate::ast::{AssignedField, Collection, Tag, TypeAnnotation};
use crate::ast::{AssignedField, Tag, TypeAnnotation};
use crate::blankspace::{space0_around_ee, space0_before_e, space0_e};
use crate::keyword;
use crate::parser::{
@ -40,11 +40,7 @@ fn tag_union_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, ET
)))
.parse(arena, state)?;
let result = TypeAnnotation::TagUnion {
tags: tags.items,
ext,
final_comments: tags.final_comments,
};
let result = TypeAnnotation::TagUnion { tags, ext };
Ok((MadeProgress, result, state))
}
@ -295,13 +291,7 @@ fn record_type<'a>(min_indent: u16) -> impl Parser<'a, TypeAnnotation<'a>, EType
let field_term = specialize_ref(ETypeRecord::Type, term(min_indent));
let (_, ext, state) = optional(allocated(field_term)).parse(arena, state)?;
let result = Record {
fields: Collection {
items: fields.items,
final_comments: fields.final_comments,
},
ext,
};
let result = Record { fields, ext };
Ok((MadeProgress, result, state))
}

View File

@ -460,10 +460,7 @@ mod test_parse {
#[test]
fn empty_record() {
let arena = Bump::new();
let expected = Record {
fields: &[],
final_comments: &[],
};
let expected = Record(Collection::empty());
let actual = parse_expr_with(&arena, "{}");
assert_eq!(Ok(expected), actual);
@ -493,8 +490,7 @@ mod test_parse {
let update_target = Located::new(0, 0, 2, 13, var);
let expected = RecordUpdate {
update: &*arena.alloc(update_target),
fields,
final_comments: &(&[] as &[_]),
fields: Collection::with_items(fields),
};
let actual = parse_expr_with(&arena, "{ Foo.Bar.baz & x: 5, y: 0 }");
@ -526,10 +522,7 @@ mod test_parse {
Located::new(0, 0, 1, 26, label1),
Located::new(0, 0, 28, 32, label2),
];
let expected = Record {
fields,
final_comments: &[],
};
let expected = Record(Collection::with_items(fields));
let actual = parse_expr_with(&arena, "{x : if True then 1 else 2, y: 3 }");
assert_eq!(Ok(expected), actual);
@ -1173,10 +1166,7 @@ mod test_parse {
#[test]
fn empty_list() {
let arena = Bump::new();
let expected = List {
items: &[],
final_comments: &[],
};
let expected = List(Collection::empty());
let actual = parse_expr_with(&arena, "[]");
assert_eq!(Ok(expected), actual);
@ -1186,10 +1176,7 @@ mod test_parse {
fn spaces_inside_empty_list() {
// This is a regression test!
let arena = Bump::new();
let expected = List {
items: &[],
final_comments: &[],
};
let expected = List(Collection::empty());
let actual = parse_expr_with(&arena, "[ ]");
assert_eq!(Ok(expected), actual);
@ -1198,10 +1185,7 @@ mod test_parse {
#[test]
fn newline_inside_empty_list() {
let arena = Bump::new();
let expected = List {
items: &[],
final_comments: &[Newline],
};
let expected = List(Collection::with_items_and_comments(&arena, &[], &[Newline]));
let actual = parse_expr_with(&arena, "[\n]");
assert_eq!(Ok(expected), actual);
@ -1210,10 +1194,11 @@ mod test_parse {
#[test]
fn comment_inside_empty_list() {
let arena = Bump::new();
let expected = List {
items: &[],
final_comments: &[LineComment("comment")],
};
let expected = List(Collection::with_items_and_comments(
&arena,
&[],
&[LineComment("comment")],
));
let actual = parse_expr_with(&arena, "[#comment\n]");
assert_eq!(Ok(expected), actual);
@ -1223,10 +1208,7 @@ mod test_parse {
fn packed_singleton_list() {
let arena = Bump::new();
let items = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))];
let expected = List {
items,
final_comments: &[],
};
let expected = List(Collection::with_items(items));
let actual = parse_expr_with(&arena, "[1]");
assert_eq!(Ok(expected), actual);
@ -1236,10 +1218,7 @@ mod test_parse {
fn spaced_singleton_list() {
let arena = Bump::new();
let items = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))];
let expected = List {
items,
final_comments: &[],
};
let expected = List(Collection::with_items(items));
let actual = parse_expr_with(&arena, "[ 1 ]");
assert_eq!(Ok(expected), actual);
@ -1254,10 +1233,7 @@ mod test_parse {
));
let item = Expr::SpaceBefore(item, arena.alloc([Newline]));
let items = [&*arena.alloc(Located::new(1, 1, 0, 1, item))];
let expected = List {
items: &items,
final_comments: &[],
};
let expected = List(Collection::with_items(&items));
let actual = parse_expr_with(&arena, "[\n1\n]");
assert_eq!(Ok(expected), actual);
@ -1897,7 +1873,13 @@ mod test_parse {
Located::new(1, 1, 5, 7, Identifier("y"))
];
let def1 = Def::Body(
arena.alloc(Located::new(1, 1, 0, 8, RecordDestructure(&fields))),
arena.alloc(Located::new(
1,
1,
0,
8,
RecordDestructure(Collection::with_items(&fields)),
)),
arena.alloc(Located::new(1, 1, 11, 12, Num("5"))),
);
let loc_def1 = &*arena.alloc(Located::new(1, 1, 0, 12, def1));
@ -2028,10 +2010,7 @@ mod test_parse {
let inner_backpassing = {
let identifier_z = Located::new(2, 2, 0, 1, Identifier("z"));
let empty_record = Record {
fields: &[],
final_comments: &[],
};
let empty_record = Record(Collection::empty());
let loc_empty_record = Located::new(2, 2, 5, 7, empty_record);
let var_x = Var {
@ -2115,8 +2094,8 @@ mod test_parse {
let apply = Expr::Apply(
arena.alloc(Located::new(0, 0, 8, 17, var_list_map2)),
bumpalo::vec![ in &arena;
&*arena.alloc(Located::new(0, 0, 18, 20, Expr::List{ items: &[], final_comments: &[] })),
&*arena.alloc(Located::new(0, 0, 21, 23, Expr::List{ items: &[], final_comments: &[] })),
&*arena.alloc(Located::new(0, 0, 18, 20, Expr::List(Collection::empty()))),
&*arena.alloc(Located::new(0, 0, 21, 23, Expr::List(Collection::empty()))),
]
.into_bump_slice(),
CalledVia::Space,
@ -2950,7 +2929,10 @@ mod test_parse {
let arena = Bump::new();
let newlines = &[Newline];
let identifiers1 = &[Located::new(1, 1, 3, 4, Identifier("y"))];
let pattern1 = Pattern::SpaceBefore(arena.alloc(RecordDestructure(identifiers1)), newlines);
let pattern1 = Pattern::SpaceBefore(
arena.alloc(RecordDestructure(Collection::with_items(identifiers1))),
newlines,
);
let loc_pattern1 = Located::new(1, 1, 1, 6, pattern1);
let expr1 = Num("2");
let loc_expr1 = Located::new(1, 1, 10, 11, expr1);
@ -2964,7 +2946,10 @@ mod test_parse {
Located::new(2, 2, 3, 4, Identifier("z")),
Located::new(2, 2, 6, 7, Identifier("w")),
];
let pattern2 = Pattern::SpaceBefore(arena.alloc(RecordDestructure(identifiers2)), newlines);
let pattern2 = Pattern::SpaceBefore(
arena.alloc(RecordDestructure(Collection::with_items(identifiers2))),
newlines,
);
let loc_pattern2 = Located::new(2, 2, 1, 9, pattern2);
let expr2 = Num("4");
let loc_expr2 = Located::new(2, 2, 13, 14, expr2);
@ -3092,8 +3077,8 @@ mod test_parse {
fn empty_app_header() {
let arena = Bump::new();
let packages = Collection::empty();
let imports = Vec::new_in(&arena);
let provides = Vec::new_in(&arena);
let imports = Collection::empty();
let provides = Collection::empty();
let module_name = StrLiteral::PlainLine("test-app");
let header = AppHeader {
name: Located::new(0, 0, 4, 14, module_name),
@ -3132,8 +3117,8 @@ mod test_parse {
let arena = Bump::new();
let packages = Collection::empty();
let imports = Vec::new_in(&arena);
let provides = Vec::new_in(&arena);
let imports = Collection::empty();
let provides = Collection::empty();
let module_name = StrLiteral::PlainLine("test-app");
let header = AppHeader {
before_header: &[],
@ -3181,11 +3166,11 @@ mod test_parse {
let loc_pkg_entry = Located::new(1, 1, 15, 33, pkg_entry);
let arena = Bump::new();
let packages = Collection::with_items(arena.alloc([loc_pkg_entry]));
let import = ImportsEntry::Package("foo", ModuleName::new("Bar.Baz"), Vec::new_in(&arena));
let import = ImportsEntry::Package("foo", ModuleName::new("Bar.Baz"), Collection::empty());
let loc_import = Located::new(2, 2, 14, 25, import);
let imports = bumpalo::vec![in &arena; loc_import];
let imports = Collection::with_items(arena.alloc([loc_import]));
let provide_entry = Located::new(3, 3, 15, 24, Exposed("quicksort"));
let provides = bumpalo::vec![in &arena; provide_entry];
let provides = Collection::with_items(arena.alloc([provide_entry]));
let module_name = StrLiteral::PlainLine("quicksort");
let header = AppHeader {
@ -3237,11 +3222,40 @@ mod test_parse {
let loc_pkg_entry = Located::new(1, 1, 15, 33, pkg_entry);
let arena = Bump::new();
let packages = Collection::with_items(arena.alloc([loc_pkg_entry]));
let import = ImportsEntry::Package("foo", ModuleName::new("Bar.Baz"), Vec::new_in(&arena));
let loc_import = Located::new(2, 2, 14, 25, import);
let imports = bumpalo::vec![in &arena; loc_import];
let provide_entry = Located::new(3, 3, 15, 24, Exposed("quicksort"));
let provides = bumpalo::vec![in &arena; provide_entry];
let import = ImportsEntry::Package(
"foo",
ModuleName::new("Bar"),
Collection::with_items_and_comments(
&arena,
arena.alloc([
Located::new(
3,
3,
8,
11,
ExposesEntry::SpaceBefore(
arena.alloc(ExposesEntry::Exposed("Baz")),
arena.alloc([Newline]),
),
),
Located::new(
4,
4,
8,
17,
ExposesEntry::SpaceBefore(
arena.alloc(ExposesEntry::Exposed("FourtyTwo")),
arena.alloc([Newline]),
),
),
]),
arena.alloc([Newline, LineComment(" I'm a happy comment")]),
),
);
let loc_import = Located::new(2, 6, 14, 5, import);
let imports = Collection::with_items(arena.alloc([loc_import]));
let provide_entry = Located::new(7, 7, 15, 24, Exposed("quicksort"));
let provides = Collection::with_items(arena.alloc([provide_entry]));
let module_name = StrLiteral::PlainLine("quicksort");
let header = AppHeader {
@ -3250,7 +3264,7 @@ mod test_parse {
packages,
imports,
provides,
to: Located::new(3, 3, 30, 34, To::ExistingPackage("base")),
to: Located::new(7, 7, 31, 35, To::ExistingPackage("base")),
after_app_keyword: &[],
before_packages: newlines,
after_packages: &[],
@ -3268,8 +3282,12 @@ mod test_parse {
r#"
app "quicksort"
packages { base: "./platform", }
imports [ foo.Bar.Baz ]
provides [ quicksort ] to base
imports [ foo.Bar.{
Baz,
FourtyTwo,
# I'm a happy comment
} ]
provides [ quicksort, ] to base
"#
);
@ -3300,7 +3318,7 @@ mod test_parse {
let region2 = Region::new(0, 0, 45, 47);
PlatformRequires {
rigids: Vec::new_in(&arena),
rigids: Collection::empty(),
signature: Located::at(
region1,
TypedIdent::Entry {
@ -3322,10 +3340,10 @@ mod test_parse {
before_header: &[],
name: Located::new(0, 0, 9, 23, pkg_name),
requires,
exposes: Vec::new_in(&arena),
exposes: Collection::empty(),
packages: Collection::empty(),
imports: Vec::new_in(&arena),
provides: Vec::new_in(&arena),
imports: Collection::empty(),
provides: Collection::empty(),
effects,
after_platform_keyword: &[],
before_requires: &[],
@ -3367,9 +3385,9 @@ mod test_parse {
let loc_pkg_entry = Located::new(3, 3, 15, 27, pkg_entry);
let arena = Bump::new();
let packages = Collection::with_items(arena.alloc([loc_pkg_entry]));
let imports = Vec::new_in(&arena);
let imports = Collection::empty();
let provide_entry = Located::new(5, 5, 15, 26, Exposed("mainForHost"));
let provides = bumpalo::vec![in &arena; provide_entry];
let provides = Collection::with_items(arena.alloc([provide_entry]));
let effects = Effects {
effect_type_name: "Effect",
effect_shortname: "fx",
@ -3385,7 +3403,13 @@ mod test_parse {
let region3 = Region::new(1, 1, 14, 26);
PlatformRequires {
rigids: bumpalo::vec![ in &arena; Located::at(region3, PlatformRigid::Entry { alias: "Model", rigid: "model" }) ],
rigids: Collection::with_items(arena.alloc([Located::at(
region3,
PlatformRigid::Entry {
alias: "Model",
rigid: "model",
},
)])),
signature: Located::at(
region1,
TypedIdent::Entry {
@ -3407,7 +3431,7 @@ mod test_parse {
before_header: &[],
name: Located::new(0, 0, 9, 19, pkg_name),
requires,
exposes: Vec::new_in(&arena),
exposes: Collection::empty(),
packages,
imports,
provides,
@ -3447,8 +3471,8 @@ mod test_parse {
#[test]
fn empty_interface_header() {
let arena = Bump::new();
let exposes = Vec::new_in(&arena);
let imports = Vec::new_in(&arena);
let exposes = Collection::empty();
let imports = Collection::empty();
let module_name = ModuleName::new("Foo");
let header = InterfaceHeader {
before_header: &[],
@ -3479,8 +3503,8 @@ mod test_parse {
#[test]
fn nested_module() {
let arena = Bump::new();
let exposes = Vec::new_in(&arena);
let imports = Vec::new_in(&arena);
let exposes = Collection::empty();
let imports = Collection::empty();
let module_name = ModuleName::new("Foo.Bar.Baz");
let header = InterfaceHeader {
before_header: &[],

View File

@ -3093,6 +3093,7 @@ fn to_provides_report<'a>(
use roc_parse::parser::EProvides;
match *parse_problem {
EProvides::ListEnd(row, col) | // TODO: give this its own error message
EProvides::Identifier(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);
@ -3158,6 +3159,7 @@ fn to_exposes_report<'a>(
use roc_parse::parser::EExposes;
match *parse_problem {
EExposes::ListEnd(row, col) | // TODO: give this its own error message
EExposes::Identifier(row, col) => {
let surroundings = Region::from_rows_cols(start_row, start_col, row, col);
let region = Region::from_row_col(row, col);

View File

@ -385,7 +385,7 @@ fn gen_basic_def() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn gen_multiple_defs() {
assert_evals_to!(
indoc!(

View File

@ -11,7 +11,7 @@ use crate::helpers::wasm::assert_evals_to;
use indoc::indoc;
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn basic_record() {
assert_evals_to!(
indoc!(
@ -45,7 +45,7 @@ fn basic_record() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn f64_record() {
assert_evals_to!(
indoc!(
@ -137,7 +137,7 @@ fn fn_record() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn def_record() {
assert_evals_to!(
indoc!(
@ -192,7 +192,7 @@ fn when_on_record() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn when_record_with_guard_pattern() {
assert_evals_to!(
indoc!(
@ -207,7 +207,7 @@ fn when_record_with_guard_pattern() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn let_with_record_pattern() {
assert_evals_to!(
indoc!(
@ -223,7 +223,7 @@ fn let_with_record_pattern() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn record_guard_pattern() {
assert_evals_to!(
indoc!(
@ -239,7 +239,7 @@ fn record_guard_pattern() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn twice_record_access() {
assert_evals_to!(
indoc!(
@ -254,7 +254,7 @@ fn twice_record_access() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
fn empty_record() {
assert_evals_to!(
indoc!(
@ -873,7 +873,7 @@ fn update_single_element_record() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn booleans_in_record() {
assert_evals_to!(
indoc!("{ x: 1 == 1, y: 1 == 1 }"),
@ -908,7 +908,7 @@ fn alignment_in_record() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn blue_and_present() {
assert_evals_to!(
indoc!(
@ -927,7 +927,7 @@ fn blue_and_present() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn blue_and_absent() {
assert_evals_to!(
indoc!(

View File

@ -1244,3 +1244,96 @@ fn str_trim_left_small_to_small_shared() {
(RocStr, RocStr)
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_small_blank_string() {
assert_evals_to!(indoc!(r#"Str.trimRight " ""#), RocStr::from(""), RocStr);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_small_to_small() {
assert_evals_to!(
indoc!(r#"Str.trimRight " hello world ""#),
RocStr::from(" hello world"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_large_to_large_unique() {
assert_evals_to!(
indoc!(r#"Str.trimRight (Str.concat " hello world from a large string" " ")"#),
RocStr::from(" hello world from a large string"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_large_to_small_unique() {
assert_evals_to!(
indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#),
RocStr::from(" hello world"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_large_to_large_shared() {
assert_evals_to!(
indoc!(
r#"
original : Str
original = " hello world world "
{ trimmed: Str.trimRight original, original: original }
"#
),
(
RocStr::from(" hello world world "),
RocStr::from(" hello world world"),
),
(RocStr, RocStr)
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_large_to_small_shared() {
assert_evals_to!(
indoc!(
r#"
original : Str
original = " hello world "
{ trimmed: Str.trimRight original, original: original }
"#
),
(
RocStr::from(" hello world "),
RocStr::from(" hello world"),
),
(RocStr, RocStr)
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_small_to_small_shared() {
assert_evals_to!(
indoc!(
r#"
original : Str
original = " hello world "
{ trimmed: Str.trimRight original, original: original }
"#
),
(RocStr::from(" hello world "), RocStr::from(" hello world"),),
(RocStr, RocStr)
);
}

View File

@ -453,7 +453,7 @@ fn result_with_guard_pattern() {
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn maybe_is_just() {
fn maybe_is_just_not_nested() {
assert_evals_to!(
indoc!(
r#"

View File

@ -198,6 +198,10 @@ fn create_llvm_module<'a>(
if name.starts_with("roc_builtins.dict") {
function.add_attribute(AttributeLoc::Function, attr);
}
if name.starts_with("roc_builtins.list") {
function.add_attribute(AttributeLoc::Function, attr);
}
}
// Compile and add all the Procs before adding main