Merge branch 'trunk' of ssh://github.com/rtfeldman/roc into text_highlighting

This commit is contained in:
Anton-4 2020-12-30 13:40:42 +01:00
commit 7f69e57d30
20 changed files with 944 additions and 287 deletions

View file

@ -93,7 +93,10 @@ fn jit_to_ast_help<'a>(
), ),
Layout::Builtin(Builtin::EmptyList) => { Layout::Builtin(Builtin::EmptyList) => {
Ok(run_jit_function!(lib, main_fn_name, &'static str, |_| { Ok(run_jit_function!(lib, main_fn_name, &'static str, |_| {
Expr::List(&[]) Expr::List {
items: &[],
final_comments: &[],
}
})) }))
} }
Layout::Builtin(Builtin::List(_, elem_layout)) => Ok(run_jit_function!( Layout::Builtin(Builtin::List(_, elem_layout)) => Ok(run_jit_function!(
@ -251,7 +254,10 @@ fn ptr_to_ast<'a>(
num_to_ast(env, f64_to_ast(env.arena, num), content) num_to_ast(env, f64_to_ast(env.arena, num), content)
} }
Layout::Builtin(Builtin::EmptyList) => Expr::List(&[]), Layout::Builtin(Builtin::EmptyList) => Expr::List {
items: &[],
final_comments: &[],
},
Layout::Builtin(Builtin::List(_, elem_layout)) => { Layout::Builtin(Builtin::List(_, elem_layout)) => {
// Turn the (ptr, len) wrapper struct into actual ptr and len values. // 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) }; let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
@ -331,7 +337,10 @@ fn list_to_ast<'a>(
let output = output.into_bump_slice(); let output = output.into_bump_slice();
Expr::List(output) Expr::List {
items: output,
final_comments: &[],
}
} }
fn single_tag_union_to_ast<'a>( fn single_tag_union_to_ast<'a>(

View file

@ -285,7 +285,9 @@ pub fn canonicalize_expr<'a>(
} }
} }
ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal), ast::Expr::Str(literal) => flatten_str_literal(env, var_store, scope, literal),
ast::Expr::List(loc_elems) => { ast::Expr::List {
items: loc_elems, ..
} => {
if loc_elems.is_empty() { if loc_elems.is_empty() {
( (
List { List {

View file

@ -109,14 +109,24 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
arena.alloc(Located { region, value }) arena.alloc(Located { region, value })
} }
List(elems) | Nested(List(elems)) => { List {
let mut new_elems = Vec::with_capacity_in(elems.len(), arena); items,
final_comments,
}
| Nested(List {
items,
final_comments,
}) => {
let mut new_items = Vec::with_capacity_in(items.len(), arena);
for elem in elems.iter() { for item in items.iter() {
new_elems.push(desugar_expr(arena, elem)); new_items.push(desugar_expr(arena, item));
} }
let new_elems = new_elems.into_bump_slice(); let new_items = new_items.into_bump_slice();
let value: Expr<'a> = List(new_elems); let value: Expr<'a> = List {
items: new_items,
final_comments,
};
arena.alloc(Located { arena.alloc(Located {
region: loc_expr.region, region: loc_expr.region,

View file

@ -122,7 +122,7 @@ pub fn fmt_body<'a>(
Expr::SpaceBefore(_, _) => { Expr::SpaceBefore(_, _) => {
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
} }
Expr::Record { .. } | Expr::List(_) => { Expr::Record { .. } | Expr::List { .. } => {
newline(buf, indent + INDENT); newline(buf, indent + INDENT);
body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT); body.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
} }

View file

@ -38,7 +38,7 @@ impl<'a> Formattable<'a> for Expr<'a> {
// These expressions always have newlines // These expressions always have newlines
Defs(_, _) | When(_, _) => true, Defs(_, _) | When(_, _) => true,
List(elems) => elems.iter().any(|loc_expr| loc_expr.is_multiline()), List { items, .. } => items.iter().any(|loc_expr| loc_expr.is_multiline()),
Str(literal) => { Str(literal) => {
use roc_parse::ast::StrLiteral::*; use roc_parse::ast::StrLiteral::*;
@ -261,8 +261,11 @@ impl<'a> Formattable<'a> for Expr<'a> {
fmt_if(buf, loc_condition, loc_then, loc_else, indent); fmt_if(buf, loc_condition, loc_then, loc_else, indent);
} }
When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent), When(loc_condition, branches) => fmt_when(buf, loc_condition, branches, indent),
List(loc_items) => { List {
fmt_list(buf, &loc_items, indent); items,
final_comments,
} => {
fmt_list(buf, &items, final_comments, indent);
} }
BinOp((loc_left_side, bin_op, loc_right_side)) => fmt_bin_op( BinOp((loc_left_side, bin_op, loc_right_side)) => fmt_bin_op(
buf, buf,
@ -396,92 +399,88 @@ fn fmt_bin_op<'a>(
} }
} }
pub fn fmt_list<'a>(buf: &mut String<'a>, loc_items: &[&Located<Expr<'a>>], indent: u16) { pub fn fmt_list<'a>(
buf.push('['); buf: &mut String<'a>,
loc_items: &[&Located<Expr<'a>>],
let mut iter = loc_items.iter().peekable(); final_comments: &'a [CommentOrNewline<'a>],
indent: u16,
let is_multiline = loc_items.iter().any(|item| (&item.value).is_multiline()); ) {
if loc_items.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
let item_indent = if is_multiline { buf.push_str("[]");
indent + INDENT
} else { } else {
indent buf.push('[');
}; let is_multiline = loc_items.iter().any(|item| (&item.value).is_multiline());
while let Some(item) = iter.next() {
if is_multiline { if is_multiline {
match &item.value { let item_indent = indent + INDENT;
Expr::SpaceBefore(expr_below, spaces_above_expr) => { for item in loc_items.iter() {
newline(buf, item_indent); match &item.value {
fmt_comments_only( // TODO?? These SpaceAfter/SpaceBefore litany seems overcomplicated
buf, // Can we simplify this? Or at least move this in a separate function.
spaces_above_expr.iter(), Expr::SpaceBefore(expr_below, spaces_above_expr) => {
NewlineAt::Bottom, newline(buf, item_indent);
item_indent, fmt_comments_only(
); buf,
spaces_above_expr.iter(),
NewlineAt::Bottom,
item_indent,
);
match &expr_below { match &expr_below {
Expr::SpaceAfter(expr_above, spaces_below_expr) => { Expr::SpaceAfter(expr_above, spaces_below_expr) => {
expr_above.format(buf, item_indent); expr_above.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(','); buf.push(',');
}
fmt_comments_only( fmt_comments_only(
buf, buf,
spaces_below_expr.iter(), spaces_below_expr.iter(),
NewlineAt::Top, NewlineAt::Top,
item_indent, item_indent,
); );
} }
_ => { _ => {
expr_below.format(buf, item_indent); expr_below.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(','); buf.push(',');
} }
} }
} }
}
Expr::SpaceAfter(sub_expr, spaces) => { Expr::SpaceAfter(sub_expr, spaces) => {
newline(buf, item_indent); newline(buf, item_indent);
sub_expr.format(buf, item_indent); sub_expr.format(buf, item_indent);
if iter.peek().is_some() {
buf.push(','); buf.push(',');
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, item_indent);
} }
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, item_indent); _ => {
} newline(buf, item_indent);
item.format_with_options(
_ => { buf,
newline(buf, item_indent); Parens::NotNeeded,
item.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, item_indent); Newlines::Yes,
if iter.peek().is_some() { item_indent,
);
buf.push(','); buf.push(',');
} }
} }
} }
fmt_comments_only(buf, final_comments.iter(), NewlineAt::Top, item_indent);
newline(buf, indent);
buf.push(']');
} else { } else {
buf.push(' '); // is_multiline == false
item.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, item_indent); let mut iter = loc_items.iter().peekable();
if iter.peek().is_some() { while let Some(item) = iter.next() {
buf.push(','); buf.push(' ');
item.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
if iter.peek().is_some() {
buf.push(',');
}
} }
buf.push_str(" ]");
} }
} }
if is_multiline {
newline(buf, indent);
}
if !loc_items.is_empty() && !is_multiline {
buf.push(' ');
}
buf.push(']');
} }
pub fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool { pub fn empty_line_before_expr<'a>(expr: &'a Expr<'a>) -> bool {
@ -809,7 +808,7 @@ pub fn fmt_record<'a>(
final_comments: &'a [CommentOrNewline<'a>], final_comments: &'a [CommentOrNewline<'a>],
indent: u16, indent: u16,
) { ) {
if loc_fields.is_empty() { if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
buf.push_str("{}"); buf.push_str("{}");
} else { } else {
buf.push('{'); buf.push('{');
@ -846,7 +845,7 @@ pub fn fmt_record<'a>(
newline(buf, indent); newline(buf, indent);
} else { } else {
// is_multiline == false */ // is_multiline == false
buf.push(' '); buf.push(' ');
let field_indent = indent; let field_indent = indent;
let mut iter = loc_fields.iter().peekable(); let mut iter = loc_fields.iter().peekable();

View file

@ -1022,7 +1022,7 @@ mod test_fmt {
[ [
7, 7,
8, 8,
9 9,
] ]
"# "#
)); ));
@ -1041,7 +1041,7 @@ mod test_fmt {
[ [
17, 17,
18, 18,
19 19,
] ]
"# "#
), ),
@ -1064,7 +1064,7 @@ mod test_fmt {
[ [
27, 27,
28, 28,
29 29,
] ]
"# "#
), ),
@ -1084,7 +1084,7 @@ mod test_fmt {
[ [
157, 157,
158, 158,
159 159,
] ]
"# "#
), ),
@ -1105,7 +1105,7 @@ mod test_fmt {
557, 557,
648, 648,
759, 759,
837 837,
] ]
"# "#
), ),
@ -1127,7 +1127,7 @@ mod test_fmt {
257, 257,
358, 358,
# Hey! # Hey!
459 459,
] ]
"# "#
), ),
@ -1155,7 +1155,7 @@ mod test_fmt {
37, 37,
# Thirty Eight # Thirty Eight
38, 38,
39 39,
] ]
"# "#
), ),
@ -1189,7 +1189,32 @@ mod test_fmt {
48, 48,
# Bottom 48 # Bottom 48
# Top 49 # Top 49
49 49,
# Bottom 49
# 49!
]
"#
),
);
}
#[test]
fn ending_comments_in_list() {
expr_formats_to(
indoc!(
r#"
[ # Top 49
49
# Bottom 49
,
# 49!
]
"#
),
indoc!(
r#"
[
# Top 49
49,
# Bottom 49 # Bottom 49
# 49! # 49!
] ]
@ -1197,7 +1222,6 @@ mod test_fmt {
), ),
); );
} }
#[test] #[test]
fn multi_line_list_def() { fn multi_line_list_def() {
// expr_formats_same(indoc!( // expr_formats_same(indoc!(
@ -1228,7 +1252,7 @@ mod test_fmt {
results = results =
[ [
Ok 4, Ok 4,
Ok 5 Ok 5,
] ]
allOks results allOks results
@ -1271,6 +1295,27 @@ mod test_fmt {
expr_formats_same("{}"); expr_formats_same("{}");
} }
#[test]
fn empty_record_with_comment() {
expr_formats_same(indoc!(
r#"
{
# comment
}"#
));
}
#[test]
fn empty_record_with_newline() {
expr_formats_to(
indoc!(
r#"
{
}"#
),
"{}",
);
}
#[test] #[test]
fn one_field() { fn one_field() {
expr_formats_same("{ x: 4 }"); expr_formats_same("{ x: 4 }");
@ -2407,22 +2452,54 @@ mod test_fmt {
)); ));
} }
// TODO This raises a parse error: #[test]
// NotYetImplemented("TODO the : in this declaration seems outdented") fn multiline_tag_union_annotation() {
// #[test] expr_formats_same(indoc!(
// fn multiline_tag_union_annotation() { r#"
// expr_formats_same(indoc!( b :
// r#" [
// b : True,
// [ False,
// True, ]
// False,
// ]
// b b
// "# "#
// )); ));
// } }
#[test]
fn multiline_tag_union_annotation_with_final_comment() {
expr_formats_to(
indoc!(
r#"
b :
[
True,
# comment 1
False # comment 2
,
# comment 3
]
b
"#
),
indoc!(
r#"
b :
[
True,
# comment 1
False,
# comment 2
# comment 3
]
b
"#
),
);
}
#[test] #[test]
fn tag_union() { fn tag_union() {
@ -2436,6 +2513,28 @@ mod test_fmt {
)); ));
} }
// TODO: the current formatting seems a bit odd for multiline function annotations
// (beside weird indentation, note the trailing space after the "->")
// #[test]
// fn multiline_tag_union_function_annotation() {
// expr_formats_same(indoc!(
// r#"
// f :
// [
// True,
// False,
// ] ->
// [
// True,
// False,
// ]
// f = \x -> x
// a
// "#
// ));
// }
#[test] #[test]
fn recursive_tag_union() { fn recursive_tag_union() {
expr_formats_same(indoc!( expr_formats_same(indoc!(

View file

@ -29,7 +29,7 @@ impl<T: Sized> Into<Result<T, String>> for RocCallResult<T> {
#[macro_export] #[macro_export]
macro_rules! run_jit_function { macro_rules! run_jit_function {
($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{ ($lib: expr, $main_fn_name: expr, $ty:ty, $transform:expr) => {{
let v: std::vec::Vec<roc_problem::can::Problem> = std::vec::Vec::new(); let v: String = String::new();
run_jit_function!($lib, $main_fn_name, $ty, $transform, v) run_jit_function!($lib, $main_fn_name, $ty, $transform, v)
}}; }};
@ -52,12 +52,7 @@ macro_rules! run_jit_function {
match result.assume_init().into() { match result.assume_init().into() {
Ok(success) => { Ok(success) => {
// only if there are no exceptions thrown, check for errors // only if there are no exceptions thrown, check for errors
assert_eq!( assert!($errors.is_empty(), "Encountered errors:\n{}", $errors);
$errors,
std::vec::Vec::new(),
"Encountered errors: {:?}",
$errors
);
$transform(success) $transform(success)
} }
@ -73,7 +68,7 @@ macro_rules! run_jit_function {
#[macro_export] #[macro_export]
macro_rules! run_jit_function_dynamic_type { macro_rules! run_jit_function_dynamic_type {
($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr) => {{ ($lib: expr, $main_fn_name: expr, $bytes:expr, $transform:expr) => {{
let v: std::vec::Vec<roc_problem::can::Problem> = std::vec::Vec::new(); let v: String = String::new();
run_jit_function_dynamic_type!($lib, $main_fn_name, $bytes, $transform, v) run_jit_function_dynamic_type!($lib, $main_fn_name, $bytes, $transform, v)
}}; }};

View file

@ -1748,4 +1748,59 @@ mod gen_primitives {
u8 u8
); );
} }
#[test]
#[should_panic(
expected = "Roc failed with message: \"Shadowing { original_region: |L 3-3, C 4-5|, shadow: |L 6-6, C 8-9| Ident(\\\"x\\\") }\""
)]
fn pattern_shadowing() {
assert_evals_to!(
indoc!(
r#"
x = 4
when 4 is
x -> x
"#
),
0,
i64
);
}
#[test]
#[should_panic(expected = "TODO non-exhaustive pattern")]
fn non_exhaustive_pattern_let() {
assert_evals_to!(
indoc!(
r#"
x : Result I64 F64
x = Ok 4
(Ok y) = x
y
"#
),
0,
i64
);
}
#[test]
#[ignore]
#[should_panic(expected = "")]
fn unsupported_pattern_str_interp() {
assert_evals_to!(
indoc!(
r#"
{ x: 4 } = { x : 4 }
x
"#
),
0,
i64
);
}
} }

View file

@ -561,7 +561,9 @@ mod gen_records {
} }
#[test] #[test]
#[ignore]
fn optional_field_function_no_use_default() { fn optional_field_function_no_use_default() {
// blocked on https://github.com/rtfeldman/roc/issues/786
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
@ -579,7 +581,9 @@ mod gen_records {
} }
#[test] #[test]
#[ignore]
fn optional_field_function_no_use_default_nested() { fn optional_field_function_no_use_default_nested() {
// blocked on https://github.com/rtfeldman/roc/issues/786
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"

View file

@ -22,7 +22,7 @@ pub fn helper<'a>(
stdlib: roc_builtins::std::StdLib, stdlib: roc_builtins::std::StdLib,
leak: bool, leak: bool,
context: &'a inkwell::context::Context, context: &'a inkwell::context::Context,
) -> (&'static str, Vec<roc_problem::can::Problem>, Library) { ) -> (&'static str, String, Library) {
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope}; use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -64,11 +64,14 @@ pub fn helper<'a>(
debug_assert_eq!(exposed_to_host.len(), 1); debug_assert_eq!(exposed_to_host.len(), 1);
let main_fn_symbol = exposed_to_host.keys().copied().nth(0).unwrap(); let main_fn_symbol = exposed_to_host.keys().copied().nth(0).unwrap();
let (_, main_fn_layout) = procedures let (_, main_fn_layout) = match procedures.keys().find(|(s, _)| *s == main_fn_symbol) {
.keys() Some(found) => found.clone(),
.find(|(s, _)| *s == main_fn_symbol) None => panic!(
.unwrap() "The main function symbol {:?} does not have a procedure in {:?}",
.clone(); main_fn_symbol,
&procedures.keys()
),
};
let target = target_lexicon::Triple::host(); let target = target_lexicon::Triple::host();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
@ -106,14 +109,14 @@ pub fn helper<'a>(
| UnusedArgument(_, _, _) | UnusedArgument(_, _, _)
| UnusedImport(_, _) | UnusedImport(_, _)
| RuntimeError(_) | RuntimeError(_)
| UnsupportedPattern(_, _)
| ExposedButNotDefined(_) => { | ExposedButNotDefined(_) => {
delayed_errors.push(problem.clone());
let report = can_problem(&alloc, module_path.clone(), problem); let report = can_problem(&alloc, module_path.clone(), problem);
let mut buf = String::new(); let mut buf = String::new();
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
delayed_errors.push(buf.clone());
lines.push(buf); lines.push(buf);
} }
_ => { _ => {
@ -142,6 +145,7 @@ pub fn helper<'a>(
report.render_color_terminal(&mut buf, &alloc, &palette); report.render_color_terminal(&mut buf, &alloc, &palette);
delayed_errors.push(buf.clone());
lines.push(buf); lines.push(buf);
} }
} }
@ -283,7 +287,7 @@ pub fn helper<'a>(
let lib = module_to_dylib(&env.module, &target, opt_level) let lib = module_to_dylib(&env.module, &target, opt_level)
.expect("Error loading compiled dylib for test"); .expect("Error loading compiled dylib for test");
(main_fn_name, delayed_errors, lib) (main_fn_name, delayed_errors.join("\n"), lib)
} }
// TODO this is almost all code duplication with assert_llvm_evals_to // TODO this is almost all code duplication with assert_llvm_evals_to

View file

@ -379,7 +379,7 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V
match pattern { match pattern {
// TODO use guard! // TODO use guard!
Identifier(_) | Underscore | Shadowed(_, _) | UnsupportedPattern(_) => { Identifier(_) | Underscore => {
if let Guard::Guard { symbol, id, stmt } = guard { if let Guard::Guard { symbol, id, stmt } = guard {
all_tests.push(Guarded { all_tests.push(Guarded {
opt_test: None, opt_test: None,
@ -408,12 +408,9 @@ fn test_at_path<'a>(selected_path: &Path, branch: &Branch<'a>, all_tests: &mut V
DestructType::Guard(guard) => { DestructType::Guard(guard) => {
arguments.push((guard.clone(), destruct.layout.clone())); arguments.push((guard.clone(), destruct.layout.clone()));
} }
DestructType::Required => { DestructType::Required(_) => {
arguments.push((Pattern::Underscore, destruct.layout.clone())); arguments.push((Pattern::Underscore, destruct.layout.clone()));
} }
DestructType::Optional(_expr) => {
// do nothing
}
} }
} }
@ -531,7 +528,7 @@ fn to_relevant_branch_help<'a>(
use Test::*; use Test::*;
match pattern { match pattern {
Identifier(_) | Underscore | Shadowed(_, _) | UnsupportedPattern(_) => Some(branch.clone()), Identifier(_) | Underscore => Some(branch.clone()),
RecordDestructure(destructs, _) => match test { RecordDestructure(destructs, _) => match test {
IsCtor { IsCtor {
@ -540,27 +537,22 @@ fn to_relevant_branch_help<'a>(
.. ..
} => { } => {
debug_assert!(test_name == &TagName::Global(RECORD_TAG_NAME.into())); debug_assert!(test_name == &TagName::Global(RECORD_TAG_NAME.into()));
let sub_positions = destructs let sub_positions = destructs.into_iter().enumerate().map(|(index, destruct)| {
.into_iter() let pattern = match destruct.typ {
.filter(|destruct| !matches!(destruct.typ, DestructType::Optional(_))) DestructType::Guard(guard) => guard.clone(),
.enumerate() DestructType::Required(_) => Pattern::Underscore,
.map(|(index, destruct)| { };
let pattern = match destruct.typ {
DestructType::Guard(guard) => guard.clone(),
DestructType::Required => Pattern::Underscore,
DestructType::Optional(_expr) => unreachable!("because of the filter"),
};
( (
Path::Index { Path::Index {
index: index as u64, index: index as u64,
tag_id: *tag_id, tag_id: *tag_id,
path: Box::new(path.clone()), path: Box::new(path.clone()),
}, },
Guard::NoGuard, Guard::NoGuard,
pattern, pattern,
) )
}); });
start.extend(sub_positions); start.extend(sub_positions);
start.extend(end); start.extend(end);
@ -742,7 +734,7 @@ fn needs_tests<'a>(pattern: &Pattern<'a>) -> bool {
use Pattern::*; use Pattern::*;
match pattern { match pattern {
Identifier(_) | Underscore | Shadowed(_, _) | UnsupportedPattern(_) => false, Identifier(_) | Underscore => false,
RecordDestructure(_, _) RecordDestructure(_, _)
| AppliedTag { .. } | AppliedTag { .. }

View file

@ -67,7 +67,7 @@ fn simplify<'a>(pattern: &crate::ir::Pattern<'a>) -> Pattern {
field_names.push(destruct.label.clone()); field_names.push(destruct.label.clone());
match &destruct.typ { match &destruct.typ {
DestructType::Required | DestructType::Optional(_) => patterns.push(Anything), DestructType::Required(_) => patterns.push(Anything),
DestructType::Guard(guard) => patterns.push(simplify(guard)), DestructType::Guard(guard) => patterns.push(simplify(guard)),
} }
} }
@ -84,17 +84,6 @@ fn simplify<'a>(pattern: &crate::ir::Pattern<'a>) -> Pattern {
Ctor(union, tag_id, patterns) Ctor(union, tag_id, patterns)
} }
Shadowed(_region, _ident) => {
// Treat as an Anything
// code-gen will make a runtime error out of the branch
Anything
}
UnsupportedPattern(_region) => {
// Treat as an Anything
// code-gen will make a runtime error out of the branch
Anything
}
AppliedTag { AppliedTag {
tag_id, tag_id,
arguments, arguments,

View file

@ -4,7 +4,7 @@ use crate::layout::{Builtin, ClosureLayout, Layout, LayoutCache, LayoutProblem};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use bumpalo::Bump; use bumpalo::Bump;
use roc_collections::all::{default_hasher, MutMap, MutSet}; use roc_collections::all::{default_hasher, MutMap, MutSet};
use roc_module::ident::{ForeignSymbol, Ident, Lowercase, TagName}; use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError; use roc_problem::can::RuntimeError;
@ -1264,7 +1264,50 @@ fn patterns_to_when<'a>(
// this must be fixed when moving exhaustiveness checking to the new canonical AST // this must be fixed when moving exhaustiveness checking to the new canonical AST
for (pattern_var, pattern) in patterns.into_iter() { for (pattern_var, pattern) in patterns.into_iter() {
let context = crate::exhaustive::Context::BadArg; let context = crate::exhaustive::Context::BadArg;
let mono_pattern = from_can_pattern(env, layout_cache, &pattern.value); let mono_pattern = match from_can_pattern(env, layout_cache, &pattern.value) {
Ok((pat, assignments)) => {
for (symbol, variable, expr) in assignments.into_iter().rev() {
if let Ok(old_body) = body {
let def = roc_can::def::Def {
annotation: None,
expr_var: variable,
loc_expr: Located::at(pattern.region, expr),
loc_pattern: Located::at(
pattern.region,
roc_can::pattern::Pattern::Identifier(symbol),
),
pattern_vars: std::iter::once((symbol, variable)).collect(),
};
let new_expr = roc_can::expr::Expr::LetNonRec(
Box::new(def),
Box::new(old_body),
variable,
);
let new_body = Located {
region: pattern.region,
value: new_expr,
};
body = Ok(new_body);
}
}
pat
}
Err(runtime_error) => {
// Even if the body was Ok, replace it with this Err.
// If it was already an Err, leave it at that Err, so the first
// RuntimeError we encountered remains the first.
body = body.and({
Err(Located {
region: pattern.region,
value: runtime_error,
})
});
continue;
}
};
match crate::exhaustive::check( match crate::exhaustive::check(
pattern.region, pattern.region,
@ -2392,7 +2435,14 @@ pub fn with_hole<'a>(
} }
} else { } else {
// this may be a destructure pattern // this may be a destructure pattern
let mono_pattern = from_can_pattern(env, layout_cache, &def.loc_pattern.value); let (mono_pattern, assignments) =
match from_can_pattern(env, layout_cache, &def.loc_pattern.value) {
Ok(v) => v,
Err(_runtime_error) => {
// todo
panic!();
}
};
let context = crate::exhaustive::Context::BadDestruct; let context = crate::exhaustive::Context::BadDestruct;
match crate::exhaustive::check( match crate::exhaustive::check(
@ -2412,6 +2462,14 @@ pub fn with_hole<'a>(
// return Stmt::RuntimeError("TODO non-exhaustive pattern"); // return Stmt::RuntimeError("TODO non-exhaustive pattern");
} }
let mut hole = hole;
for (symbol, variable, expr) in assignments {
let stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole);
hole = env.arena.alloc(stmt);
}
// convert the continuation // convert the continuation
let mut stmt = with_hole( let mut stmt = with_hole(
env, env,
@ -3923,13 +3981,23 @@ pub fn from_can<'a>(
} }
// this may be a destructure pattern // this may be a destructure pattern
let mono_pattern = from_can_pattern(env, layout_cache, &def.loc_pattern.value); let (mono_pattern, assignments) =
match from_can_pattern(env, layout_cache, &def.loc_pattern.value) {
Ok(v) => v,
Err(_) => todo!(),
};
if let Pattern::Identifier(symbol) = mono_pattern { if let Pattern::Identifier(symbol) = mono_pattern {
let hole = let mut hole =
env.arena env.arena
.alloc(from_can(env, variable, cont.value, procs, layout_cache)); .alloc(from_can(env, variable, cont.value, procs, layout_cache));
for (symbol, variable, expr) in assignments {
let stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole);
hole = env.arena.alloc(stmt);
}
with_hole( with_hole(
env, env,
def.loc_expr.value, def.loc_expr.value,
@ -3954,13 +4022,20 @@ pub fn from_can<'a>(
for error in errors { for error in errors {
env.problems.push(MonoProblem::PatternProblem(error)) env.problems.push(MonoProblem::PatternProblem(error))
} }
} // TODO make all variables bound in the pattern evaluate to a runtime error
// return Stmt::RuntimeError("TODO non-exhaustive pattern"); return Stmt::RuntimeError("TODO non-exhaustive pattern");
}
} }
// convert the continuation // convert the continuation
let mut stmt = from_can(env, variable, cont.value, procs, layout_cache); let mut stmt = from_can(env, variable, cont.value, procs, layout_cache);
// layer on any default record fields
for (symbol, variable, expr) in assignments {
let hole = env.arena.alloc(stmt);
stmt = with_hole(env, expr, variable, procs, layout_cache, symbol, hole);
}
if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value { if let roc_can::expr::Expr::Var(outer_symbol) = def.loc_expr.value {
store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt) store_pattern(env, procs, layout_cache, &mono_pattern, outer_symbol, stmt)
.unwrap() .unwrap()
@ -4015,19 +4090,51 @@ fn to_opt_branches<'a>(
}; };
for loc_pattern in when_branch.patterns { for loc_pattern in when_branch.patterns {
let mono_pattern = from_can_pattern(env, layout_cache, &loc_pattern.value); match from_can_pattern(env, layout_cache, &loc_pattern.value) {
Ok((mono_pattern, assignments)) => {
loc_branches.push((
Located::at(loc_pattern.region, mono_pattern.clone()),
exhaustive_guard.clone(),
));
loc_branches.push(( let mut loc_expr = when_branch.value.clone();
Located::at(loc_pattern.region, mono_pattern.clone()), let region = loc_pattern.region;
exhaustive_guard.clone(), for (symbol, variable, expr) in assignments.into_iter().rev() {
)); let def = roc_can::def::Def {
annotation: None,
expr_var: variable,
loc_expr: Located::at(region, expr),
loc_pattern: Located::at(
region,
roc_can::pattern::Pattern::Identifier(symbol),
),
pattern_vars: std::iter::once((symbol, variable)).collect(),
};
let new_expr = roc_can::expr::Expr::LetNonRec(
Box::new(def),
Box::new(loc_expr),
variable,
);
loc_expr = Located::at(region, new_expr);
}
// TODO remove clone? // TODO remove clone?
opt_branches.push(( opt_branches.push((mono_pattern, when_branch.guard.clone(), loc_expr.value));
mono_pattern, }
when_branch.guard.clone(), Err(runtime_error) => {
when_branch.value.value.clone(), loc_branches.push((
)); Located::at(loc_pattern.region, Pattern::Underscore),
exhaustive_guard.clone(),
));
// TODO remove clone?
opt_branches.push((
Pattern::Underscore,
when_branch.guard.clone(),
roc_can::expr::Expr::RuntimeError(runtime_error),
));
}
}
} }
} }
@ -4658,14 +4765,6 @@ fn store_pattern<'a>(
RecordDestructure(_, _) => { RecordDestructure(_, _) => {
unreachable!("a record destructure must always occur on a struct layout"); unreachable!("a record destructure must always occur on a struct layout");
} }
Shadowed(_region, _ident) => {
return Err(&"shadowed");
}
UnsupportedPattern(_region) => {
return Err(&"unsupported pattern");
}
} }
Ok(stmt) Ok(stmt)
@ -4695,25 +4794,14 @@ fn store_record_destruct<'a>(
}; };
match &destruct.typ { match &destruct.typ {
DestructType::Required => { DestructType::Required(symbol) => {
stmt = Stmt::Let( stmt = Stmt::Let(
destruct.symbol, *symbol,
load, load,
destruct.layout.clone(), destruct.layout.clone(),
env.arena.alloc(stmt), env.arena.alloc(stmt),
); );
} }
DestructType::Optional(expr) => {
stmt = with_hole(
env,
expr.clone(),
destruct.variable,
procs,
layout_cache,
destruct.symbol,
env.arena.alloc(stmt),
);
}
DestructType::Guard(guard_pattern) => match &guard_pattern { DestructType::Guard(guard_pattern) => match &guard_pattern {
Identifier(symbol) => { Identifier(symbol) => {
stmt = Stmt::Let( stmt = Stmt::Let(
@ -5429,11 +5517,6 @@ pub enum Pattern<'a> {
layout: Layout<'a>, layout: Layout<'a>,
union: crate::exhaustive::Union, union: crate::exhaustive::Union,
}, },
// Runtime Exceptions
Shadowed(Region, Located<Ident>),
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
UnsupportedPattern(Region),
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
@ -5441,14 +5524,12 @@ pub struct RecordDestruct<'a> {
pub label: Lowercase, pub label: Lowercase,
pub variable: Variable, pub variable: Variable,
pub layout: Layout<'a>, pub layout: Layout<'a>,
pub symbol: Symbol,
pub typ: DestructType<'a>, pub typ: DestructType<'a>,
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub enum DestructType<'a> { pub enum DestructType<'a> {
Required, Required(Symbol),
Optional(roc_can::expr::Expr),
Guard(Pattern<'a>), Guard(Pattern<'a>),
} }
@ -5459,27 +5540,51 @@ pub struct WhenBranch<'a> {
pub guard: Option<Stmt<'a>>, pub guard: Option<Stmt<'a>>,
} }
pub fn from_can_pattern<'a>( #[allow(clippy::type_complexity)]
fn from_can_pattern<'a>(
env: &mut Env<'a, '_>, env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
can_pattern: &roc_can::pattern::Pattern, can_pattern: &roc_can::pattern::Pattern,
) -> Pattern<'a> { ) -> Result<
(
Pattern<'a>,
Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
),
RuntimeError,
> {
let mut assignments = Vec::new_in(env.arena);
let pattern = from_can_pattern_help(env, layout_cache, can_pattern, &mut assignments)?;
Ok((pattern, assignments))
}
fn from_can_pattern_help<'a>(
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
can_pattern: &roc_can::pattern::Pattern,
assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
) -> Result<Pattern<'a>, RuntimeError> {
use roc_can::pattern::Pattern::*; use roc_can::pattern::Pattern::*;
match can_pattern { match can_pattern {
Underscore => Pattern::Underscore, Underscore => Ok(Pattern::Underscore),
Identifier(symbol) => Pattern::Identifier(*symbol), Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
IntLiteral(v) => Pattern::IntLiteral(*v), IntLiteral(v) => Ok(Pattern::IntLiteral(*v)),
FloatLiteral(v) => Pattern::FloatLiteral(f64::to_bits(*v)), FloatLiteral(v) => Ok(Pattern::FloatLiteral(f64::to_bits(*v))),
StrLiteral(v) => Pattern::StrLiteral(v.clone()), StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
Shadowed(region, ident) => Pattern::Shadowed(*region, ident.clone()), Shadowed(region, ident) => Err(RuntimeError::Shadowing {
UnsupportedPattern(region) => Pattern::UnsupportedPattern(*region), original_region: *region,
shadow: ident.clone(),
}),
UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)),
MalformedPattern(_problem, region) => { MalformedPattern(_problem, region) => {
// TODO preserve malformed problem information here? // TODO preserve malformed problem information here?
Pattern::UnsupportedPattern(*region) Err(RuntimeError::UnsupportedPattern(*region))
} }
NumLiteral(var, num) => match num_argument_to_int_or_float(env.subs, *var) { NumLiteral(var, num) => match num_argument_to_int_or_float(env.subs, *var) {
IntOrFloat::IntType => Pattern::IntLiteral(*num), IntOrFloat::IntType => Ok(Pattern::IntLiteral(*num)),
IntOrFloat::FloatType => Pattern::FloatLiteral(*num as u64), IntOrFloat::FloatType => Ok(Pattern::FloatLiteral(*num as u64)),
}, },
AppliedTag { AppliedTag {
@ -5493,7 +5598,7 @@ pub fn from_can_pattern<'a>(
let variant = crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs); let variant = crate::layout::union_sorted_tags(env.arena, *whole_var, env.subs);
match variant { let result = match variant {
Never => unreachable!( Never => unreachable!(
"there is no pattern of type `[]`, union var {:?}", "there is no pattern of type `[]`, union var {:?}",
*whole_var *whole_var
@ -5582,7 +5687,7 @@ pub fn from_can_pattern<'a>(
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) { for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) {
mono_args.push(( mono_args.push((
from_can_pattern(env, layout_cache, &loc_pat.value), from_can_pattern_help(env, layout_cache, &loc_pat.value, assignments)?,
layout.clone(), layout.clone(),
)); ));
} }
@ -5642,7 +5747,7 @@ pub fn from_can_pattern<'a>(
let it = argument_layouts[1..].iter(); let it = argument_layouts[1..].iter();
for ((_, loc_pat), layout) in arguments.iter().zip(it) { for ((_, loc_pat), layout) in arguments.iter().zip(it) {
mono_args.push(( mono_args.push((
from_can_pattern(env, layout_cache, &loc_pat.value), from_can_pattern_help(env, layout_cache, &loc_pat.value, assignments)?,
layout.clone(), layout.clone(),
)); ));
} }
@ -5664,7 +5769,9 @@ pub fn from_can_pattern<'a>(
layout, layout,
} }
} }
} };
Ok(result)
} }
RecordDestructure { RecordDestructure {
@ -5704,14 +5811,14 @@ pub fn from_can_pattern<'a>(
layout_cache, layout_cache,
&destruct.value, &destruct.value,
field_layout.clone(), field_layout.clone(),
)); assignments,
)?);
} }
None => { None => {
// this field is not destructured by the pattern // this field is not destructured by the pattern
// put in an underscore // put in an underscore
mono_destructs.push(RecordDestruct { mono_destructs.push(RecordDestruct {
label: label.clone(), label: label.clone(),
symbol: env.unique_symbol(),
variable, variable,
layout: field_layout.clone(), layout: field_layout.clone(),
typ: DestructType::Guard(Pattern::Underscore), typ: DestructType::Guard(Pattern::Underscore),
@ -5727,29 +5834,26 @@ pub fn from_can_pattern<'a>(
match destructs_by_label.remove(&label) { match destructs_by_label.remove(&label) {
Some(destruct) => { Some(destruct) => {
// this field is destructured by the pattern // this field is destructured by the pattern
mono_destructs.push(RecordDestruct { match &destruct.value.typ {
label: destruct.value.label.clone(), roc_can::pattern::DestructType::Optional(_, loc_expr) => {
symbol: destruct.value.symbol, // if we reach this stage, the optional field is not present
layout: field_layout, // so we push the default assignment into the branch
variable, assignments.push((
typ: match &destruct.value.typ { destruct.value.symbol,
roc_can::pattern::DestructType::Optional(_, loc_expr) => { variable,
// if we reach this stage, the optional field is not present loc_expr.value.clone(),
// so use the default ));
DestructType::Optional(loc_expr.value.clone()) }
} _ => unreachable!(
_ => unreachable!( "only optional destructs can be optional fields"
"only optional destructs can be optional fields" ),
), };
},
});
} }
None => { None => {
// this field is not destructured by the pattern // this field is not destructured by the pattern
// put in an underscore // put in an underscore
mono_destructs.push(RecordDestruct { mono_destructs.push(RecordDestruct {
label: label.clone(), label: label.clone(),
symbol: env.unique_symbol(),
variable, variable,
layout: field_layout.clone(), layout: field_layout.clone(),
typ: DestructType::Guard(Pattern::Underscore), typ: DestructType::Guard(Pattern::Underscore),
@ -5765,28 +5869,30 @@ pub fn from_can_pattern<'a>(
// it must be an optional field, and we will use the default // it must be an optional field, and we will use the default
match &destruct.value.typ { match &destruct.value.typ {
roc_can::pattern::DestructType::Optional(field_var, loc_expr) => { roc_can::pattern::DestructType::Optional(field_var, loc_expr) => {
let field_layout = layout_cache // TODO these don't match up in the uniqueness inference; when we remove
.from_var(env.arena, *field_var, env.subs) // that, reinstate this assert!
.unwrap_or_else(|err| { //
panic!("TODO turn fn_var into a RuntimeError {:?}", err) // dbg!(&env.subs.get_without_compacting(*field_var).content);
}); // dbg!(&env.subs.get_without_compacting(destruct.value.var).content);
// debug_assert_eq!(
mono_destructs.push(RecordDestruct { // env.subs.get_root_key_without_compacting(*field_var),
label: destruct.value.label.clone(), // env.subs.get_root_key_without_compacting(destruct.value.var)
symbol: destruct.value.symbol, // );
variable: destruct.value.var, assignments.push((
layout: field_layout, destruct.value.symbol,
typ: DestructType::Optional(loc_expr.value.clone()), // destruct.value.var,
}) *field_var,
loc_expr.value.clone(),
));
} }
_ => unreachable!("only optional destructs can be optional fields"), _ => unreachable!("only optional destructs can be optional fields"),
} }
} }
Pattern::RecordDestructure( Ok(Pattern::RecordDestructure(
mono_destructs, mono_destructs,
Layout::Struct(field_layouts.into_bump_slice()), Layout::Struct(field_layouts.into_bump_slice()),
) ))
} }
} }
} }
@ -5796,24 +5902,23 @@ fn from_can_record_destruct<'a>(
layout_cache: &mut LayoutCache<'a>, layout_cache: &mut LayoutCache<'a>,
can_rd: &roc_can::pattern::RecordDestruct, can_rd: &roc_can::pattern::RecordDestruct,
field_layout: Layout<'a>, field_layout: Layout<'a>,
) -> RecordDestruct<'a> { assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
RecordDestruct { ) -> Result<RecordDestruct<'a>, RuntimeError> {
Ok(RecordDestruct {
label: can_rd.label.clone(), label: can_rd.label.clone(),
symbol: can_rd.symbol,
variable: can_rd.var, variable: can_rd.var,
layout: field_layout, layout: field_layout,
typ: match &can_rd.typ { typ: match &can_rd.typ {
roc_can::pattern::DestructType::Required => DestructType::Required, roc_can::pattern::DestructType::Required => DestructType::Required(can_rd.symbol),
roc_can::pattern::DestructType::Optional(_, _) => { roc_can::pattern::DestructType::Optional(_, _) => {
// if we reach this stage, the optional field is present // if we reach this stage, the optional field is present
// DestructType::Optional(loc_expr.value.clone()) DestructType::Required(can_rd.symbol)
DestructType::Required
}
roc_can::pattern::DestructType::Guard(_, loc_pattern) => {
DestructType::Guard(from_can_pattern(env, layout_cache, &loc_pattern.value))
} }
roc_can::pattern::DestructType::Guard(_, loc_pattern) => DestructType::Guard(
from_can_pattern_help(env, layout_cache, &loc_pattern.value, assignments)?,
),
}, },
} })
} }
/// Potentially translate LowLevel operations into more efficient ones based on /// Potentially translate LowLevel operations into more efficient ones based on

View file

@ -93,7 +93,10 @@ pub enum Expr<'a> {
AccessorFunction(&'a str), AccessorFunction(&'a str),
// Collection Literals // Collection Literals
List(&'a [&'a Loc<Expr<'a>>]), List {
items: &'a [&'a Loc<Expr<'a>>],
final_comments: &'a [CommentOrNewline<'a>],
},
Record { Record {
update: Option<&'a Loc<Expr<'a>>>, update: Option<&'a Loc<Expr<'a>>>,
@ -300,6 +303,15 @@ impl<'a> CommentOrNewline<'a> {
DocComment(_) => true, DocComment(_) => true,
} }
} }
pub fn is_newline(&self) -> bool {
use CommentOrNewline::*;
match self {
Newline => true,
LineComment(_) => false,
DocComment(_) => false,
}
}
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]

View file

@ -306,7 +306,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
// These would not have parsed as patterns // These would not have parsed as patterns
Expr::AccessorFunction(_) Expr::AccessorFunction(_)
| Expr::Access(_, _) | Expr::Access(_, _)
| Expr::List(_) | Expr::List { .. }
| Expr::Closure(_, _) | Expr::Closure(_, _)
| Expr::BinOp(_) | Expr::BinOp(_)
| Expr::Defs(_, _) | Expr::Defs(_, _)
@ -1848,7 +1848,7 @@ fn binop<'a>() -> impl Parser<'a, BinOp> {
} }
pub fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> { pub fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
let elems = collection!( let elems = collection_trailing_sep!(
ascii_char(b'['), ascii_char(b'['),
loc!(expr(min_indent)), loc!(expr(min_indent)),
ascii_char(b','), ascii_char(b','),
@ -1858,14 +1858,21 @@ pub fn list_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
parser::attempt( parser::attempt(
Attempting::List, Attempting::List,
map_with_arena!(elems, |arena, parsed_elems: Vec<'a, Located<Expr<'a>>>| { map_with_arena!(elems, |arena,
(parsed_elems, final_comments): (
Vec<'a, Located<Expr<'a>>>,
&'a [CommentOrNewline<'a>]
)| {
let mut allocated = Vec::with_capacity_in(parsed_elems.len(), arena); let mut allocated = Vec::with_capacity_in(parsed_elems.len(), arena);
for parsed_elem in parsed_elems { for parsed_elem in parsed_elems {
allocated.push(&*arena.alloc(parsed_elem)); allocated.push(&*arena.alloc(parsed_elem));
} }
Expr::List(allocated.into_bump_slice()) Expr::List {
items: allocated.into_bump_slice(),
final_comments,
}
}), }),
) )
} }

View file

@ -1017,8 +1017,10 @@ mod test_parse {
#[test] #[test]
fn empty_list() { fn empty_list() {
let arena = Bump::new(); let arena = Bump::new();
let elems = &[]; let expected = List {
let expected = List(elems); items: &[],
final_comments: &[],
};
let actual = parse_expr_with(&arena, "[]"); let actual = parse_expr_with(&arena, "[]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
@ -1028,8 +1030,10 @@ mod test_parse {
fn spaces_inside_empty_list() { fn spaces_inside_empty_list() {
// This is a regression test! // This is a regression test!
let arena = Bump::new(); let arena = Bump::new();
let elems = &[]; let expected = List {
let expected = List(elems); items: &[],
final_comments: &[],
};
let actual = parse_expr_with(&arena, "[ ]"); let actual = parse_expr_with(&arena, "[ ]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
@ -1038,8 +1042,11 @@ mod test_parse {
#[test] #[test]
fn packed_singleton_list() { fn packed_singleton_list() {
let arena = Bump::new(); let arena = Bump::new();
let elems = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))]; let items = &[&*arena.alloc(Located::new(0, 0, 1, 2, Num("1")))];
let expected = List(elems); let expected = List {
items,
final_comments: &[],
};
let actual = parse_expr_with(&arena, "[1]"); let actual = parse_expr_with(&arena, "[1]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);
@ -1048,8 +1055,11 @@ mod test_parse {
#[test] #[test]
fn spaced_singleton_list() { fn spaced_singleton_list() {
let arena = Bump::new(); let arena = Bump::new();
let elems = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))]; let items = &[&*arena.alloc(Located::new(0, 0, 2, 3, Num("1")))];
let expected = List(elems); let expected = List {
items,
final_comments: &[],
};
let actual = parse_expr_with(&arena, "[ 1 ]"); let actual = parse_expr_with(&arena, "[ 1 ]");
assert_eq!(Ok(expected), actual); assert_eq!(Ok(expected), actual);

View file

@ -43,6 +43,28 @@ pub enum Def {
Function(FunctionDef), Function(FunctionDef),
} }
impl Def {
pub fn symbols(&self, pool: &Pool) -> MutSet<Symbol> {
let mut output = MutSet::default();
match self {
Def::AnnotationOnly { .. } => todo!("lost pattern information here ... "),
Def::Value(ValueDef { pattern, .. }) => {
let pattern2 = &pool[*pattern];
output.extend(symbols_from_pattern(pool, pattern2));
}
Def::Function(function_def) => match function_def {
FunctionDef::NoAnnotation { name, .. }
| FunctionDef::WithAnnotation { name, .. } => {
output.insert(*name);
}
},
}
output
}
}
impl ShallowClone for Def { impl ShallowClone for Def {
fn shallow_clone(&self) -> Self { fn shallow_clone(&self) -> Self {
match self { match self {
@ -787,7 +809,6 @@ pub struct CanDefs {
pub fn canonicalize_defs<'a>( pub fn canonicalize_defs<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
mut output: Output, mut output: Output,
var_store: &mut VarStore,
original_scope: &Scope, original_scope: &Scope,
loc_defs: &'a [&'a Located<ast::Def<'a>>], loc_defs: &'a [&'a Located<ast::Def<'a>>],
pattern_type: PatternType, pattern_type: PatternType,

View file

@ -43,7 +43,7 @@ impl Output {
pub struct Env<'a> { pub struct Env<'a> {
pub home: ModuleId, pub home: ModuleId,
pub var_store: VarStore, pub var_store: &'a mut VarStore,
pub pool: &'a mut Pool, pub pool: &'a mut Pool,
pub arena: &'a Bump, pub arena: &'a Bump,
@ -63,6 +63,32 @@ pub struct Env<'a> {
} }
impl<'a> Env<'a> { impl<'a> Env<'a> {
pub fn new(
home: ModuleId,
arena: &'a Bump,
pool: &'a mut Pool,
var_store: &'a mut VarStore,
dep_idents: MutMap<ModuleId, IdentIds>,
module_ids: &'a ModuleIds,
exposed_ident_ids: IdentIds,
) -> Env<'a> {
Env {
home,
arena,
pool,
var_store,
dep_idents,
module_ids,
ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later
exposed_ident_ids,
closures: MutMap::default(),
qualified_lookups: MutSet::default(),
tailcallable_symbol: None,
closure_name_symbol: None,
top_level_symbols: MutSet::default(),
}
}
pub fn add<T>(&mut self, item: T, region: Region) -> NodeId<T> { pub fn add<T>(&mut self, item: T, region: Region) -> NodeId<T> {
let id = self.pool.add(item); let id = self.pool.add(item);
self.set_region(id, region); self.set_region(id, region);
@ -285,14 +311,14 @@ pub fn to_expr2<'a>(
Str(literal) => flatten_str_literal(env, scope, &literal), Str(literal) => flatten_str_literal(env, scope, &literal),
List(elements) => { List { items, .. } => {
let mut output = Output::default(); let mut output = Output::default();
let output_ref = &mut output; let output_ref = &mut output;
let elems = PoolVec::with_capacity(elements.len() as u32, env.pool); let elems = PoolVec::with_capacity(items.len() as u32, env.pool);
for (node_id, element) in elems.iter_node_ids().zip(elements.iter()) { for (node_id, item) in elems.iter_node_ids().zip(items.iter()) {
let (expr, sub_output) = to_expr2(env, scope, &element.value, element.region); let (expr, sub_output) = to_expr2(env, scope, &item.value, item.region);
output_ref.union(sub_output); output_ref.union(sub_output);

View file

@ -34,6 +34,7 @@ pub mod error;
pub mod expr; pub mod expr;
pub mod file; pub mod file;
mod keyboard_input; mod keyboard_input;
mod module;
mod ortho; mod ortho;
mod pattern; mod pattern;
pub mod pool; pub mod pool;

317
editor/src/module.rs Normal file
View file

@ -0,0 +1,317 @@
#![allow(clippy::all)]
#![allow(dead_code)]
#![allow(unused_imports)]
#![allow(unused_variables)]
use crate::ast::{FunctionDef, ValueDef};
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::expr::Env;
use crate::expr::Output;
use crate::pattern::Pattern2;
use crate::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone};
use crate::scope::Scope;
use crate::types::Alias;
use bumpalo::Bump;
use roc_can::operator::desugar_def;
use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap};
use roc_module::ident::Ident;
use roc_module::ident::Lowercase;
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_parse::ast;
use roc_parse::pattern::PatternType;
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
pub struct ModuleOutput {
pub aliases: MutMap<Symbol, NodeId<Alias>>,
pub rigid_variables: MutMap<Variable, Lowercase>,
pub declarations: Vec<Declaration>,
pub exposed_imports: MutMap<Symbol, Variable>,
pub lookups: Vec<(Symbol, Variable, Region)>,
pub problems: Vec<Problem>,
pub ident_ids: IdentIds,
pub references: MutSet<Symbol>,
}
// TODO trim these down
#[allow(clippy::too_many_arguments)]
pub fn canonicalize_module_defs<'a>(
arena: &Bump,
loc_defs: &'a [Located<ast::Def<'a>>],
home: ModuleId,
module_ids: &ModuleIds,
exposed_ident_ids: IdentIds,
dep_idents: MutMap<ModuleId, IdentIds>,
aliases: MutMap<Symbol, Alias>,
exposed_imports: MutMap<Ident, (Symbol, Region)>,
mut exposed_symbols: MutSet<Symbol>,
var_store: &mut VarStore,
) -> Result<ModuleOutput, RuntimeError> {
let mut pool = Pool::with_capacity(1 << 10);
let mut can_exposed_imports = MutMap::default();
let mut scope = Scope::new(home, &mut pool, var_store);
let num_deps = dep_idents.len();
for (name, alias) in aliases.into_iter() {
let vars = PoolVec::with_capacity(alias.targs.len() as u32, &mut pool);
for (node_id, targ_id) in vars.iter_node_ids().zip(alias.targs.iter_node_ids()) {
let (poolstr, var) = &pool[targ_id];
pool[node_id] = (poolstr.shallow_clone(), *var);
}
scope.add_alias(&mut pool, name, vars, alias.actual);
}
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let mut desugared =
bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena);
for loc_def in loc_defs.iter() {
desugared.push(&*arena.alloc(Located {
value: desugar_def(arena, &loc_def.value),
region: loc_def.region,
}));
}
let mut env = Env::new(
home,
arena,
&mut pool,
var_store,
dep_idents,
module_ids,
exposed_ident_ids,
);
let mut lookups = Vec::with_capacity(num_deps);
let rigid_variables = MutMap::default();
// Exposed values are treated like defs that appear before any others, e.g.
//
// imports [ Foo.{ bar, baz } ]
//
// ...is basically the same as if we'd added these extra defs at the start of the module:
//
// bar = Foo.bar
// baz = Foo.baz
//
// Here we essentially add those "defs" to "the beginning of the module"
// by canonicalizing them right before we canonicalize the actual ast::Def nodes.
for (ident, (symbol, region)) in exposed_imports {
let first_char = ident.as_inline_str().chars().next().unwrap();
if first_char.is_lowercase() {
// this is a value definition
let expr_var = env.var_store.fresh();
match scope.import(ident, symbol, region) {
Ok(()) => {
// Add an entry to exposed_imports using the current module's name
// as the key; e.g. if this is the Foo module and we have
// exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
can_exposed_imports.insert(symbol, expr_var);
// This will be used during constraint generation,
// to add the usual Lookup constraint as if this were a normal def.
lookups.push((symbol, expr_var, region));
}
Err((_shadowed_symbol, _region)) => {
panic!("TODO gracefully handle shadowing in imports.")
}
}
} else {
// This is a type alias
// the should already be added to the scope when this module is canonicalized
debug_assert!(scope.contains_alias(symbol));
}
}
let (defs, _scope, output, symbols_introduced) = canonicalize_defs(
&mut env,
Output::default(),
&scope,
&desugared,
PatternType::TopLevelDef,
);
// See if any of the new idents we defined went unused.
// If any were unused and also not exposed, report it.
for (symbol, region) in symbols_introduced {
if !output.references.has_lookup(symbol) && !exposed_symbols.contains(&symbol) {
env.problem(Problem::UnusedDef(symbol, region));
}
}
// TODO register rigids
// for (var, lowercase) in output.introduced_variables.name_by_var.clone() {
// rigid_variables.insert(var, lowercase);
// }
let mut references = MutSet::default();
// Gather up all the symbols that were referenced across all the defs' lookups.
for symbol in output.references.lookups.iter() {
references.insert(*symbol);
}
// Gather up all the symbols that were referenced across all the defs' calls.
for symbol in output.references.calls.iter() {
references.insert(*symbol);
}
// Gather up all the symbols that were referenced from other modules.
for symbol in env.qualified_lookups.iter() {
references.insert(*symbol);
}
// NOTE previously we inserted builtin defs into the list of defs here
// this is now done later, in file.rs.
match sort_can_defs(&mut env, defs, Output::default()) {
(Ok(mut declarations), output) => {
use crate::def::Declaration::*;
for decl in declarations.iter() {
match decl {
Declare(def) => {
for symbol in def.symbols(env.pool) {
if exposed_symbols.contains(&symbol) {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
// exposed symbols which did not have
// corresponding defs.
exposed_symbols.remove(&symbol);
}
}
}
DeclareRec(defs) => {
for def in defs {
for symbol in def.symbols(env.pool) {
if exposed_symbols.contains(&symbol) {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
// exposed symbols which did not have
// corresponding defs.
exposed_symbols.remove(&symbol);
}
}
}
}
InvalidCycle(identifiers, _) => {
panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers);
}
Builtin(def) => {
// Builtins cannot be exposed in module declarations.
// This should never happen!
debug_assert!(def
.symbols(env.pool)
.iter()
.all(|symbol| !exposed_symbols.contains(symbol)));
}
}
}
let mut aliases = MutMap::default();
for (symbol, alias) in output.aliases {
// Remove this from exposed_symbols,
// so that at the end of the process,
// we can see if there were any
// exposed symbols which did not have
// corresponding defs.
exposed_symbols.remove(&symbol);
aliases.insert(symbol, alias);
}
// By this point, all exposed symbols should have been removed from
// exposed_symbols and added to exposed_vars_by_symbol. If any were
// not, that means they were declared as exposed but there was
// no actual declaration with that name!
for symbol in exposed_symbols {
env.problem(Problem::ExposedButNotDefined(symbol));
// In case this exposed value is referenced by other modules,
// create a decl for it whose implementation is a runtime error.
let mut pattern_vars = SendMap::default();
pattern_vars.insert(symbol, env.var_store.fresh());
let runtime_error = RuntimeError::ExposedButNotDefined(symbol);
let value_def = {
let pattern = env.pool.add(Pattern2::Identifier(symbol));
ValueDef {
pattern,
expr_type: None,
expr_var: env.var_store.fresh(),
}
};
let def = Def::Value(value_def);
declarations.push(Declaration::Declare(def));
}
// Incorporate any remaining output.lookups entries into references.
for symbol in output.references.lookups {
references.insert(symbol);
}
// Incorporate any remaining output.calls entries into references.
for symbol in output.references.calls {
references.insert(symbol);
}
// Gather up all the symbols that were referenced from other modules.
for symbol in env.qualified_lookups.iter() {
references.insert(*symbol);
}
// TODO find captured variables
// for declaration in declarations.iter_mut() {
// match declaration {
// Declare(def) => fix_values_captured_in_closure_def(def, &mut MutSet::default()),
// DeclareRec(defs) => {
// fix_values_captured_in_closure_defs(defs, &mut MutSet::default())
// }
// InvalidCycle(_, _) | Builtin(_) => {}
// }
// }
// TODO this loops over all symbols in the module, we can speed it up by having an
// iterator over all builtin symbols
// TODO move over the builtins
// for symbol in references.iter() {
// if symbol.is_builtin() {
// // this can fail when the symbol is for builtin types, or has no implementation yet
// if let Some(def) = builtins::builtin_defs_map(*symbol, var_store) {
// declarations.push(Declaration::Builtin(def));
// }
// }
// }
Ok(ModuleOutput {
aliases,
rigid_variables,
declarations,
references,
exposed_imports: can_exposed_imports,
problems: vec![], // TODO env.problems,
lookups,
ident_ids: env.ident_ids,
})
}
(Err(runtime_error), _) => Err(runtime_error),
}
}