roc/crates/compiler/can/tests/test_suffixed.rs
Elias Mulhall 82d0566041
Use module scope instead of var store to generate idents in dbg desugar
Fix a bug in `dbg` expression desugaring by using the module scope to
generate unique identifiers instead of the variable store.

In the initial implementation of `dbg` expressions we used the
`VarStore` to generate unique identifiers for new variables created
during desugaring. We should have instead used the current module's
`Scope`, which handles identifiers within the module. Each scope has its
own incrementing variable count which is independent of the shared
variable store. The scope is used to generate new identifiers at other
points in canonicalization, such as when assigning a global identifier
to closures and `expect`s. It's possible that the identifier generated
for `dbg` could conflict with an identifier generated by the scope,
resulting in a confusing error.
2024-09-03 14:00:39 -04:00

609 lines
12 KiB
Rust

#[macro_use]
extern crate indoc;
#[cfg(test)]
mod suffixed_tests {
use bumpalo::Bump;
use insta::assert_snapshot;
use roc_can::desugar::desugar_defs_node_values;
use roc_can::scope::Scope;
use roc_module::symbol::{IdentIds, ModuleIds};
use roc_parse::test_helpers::parse_defs_with;
macro_rules! run_test {
($src:expr) => {{
let arena = &Bump::new();
let home = ModuleIds::default().get_or_insert(&"Test".into());
let mut scope = Scope::new(
home,
"TestPath".into(),
IdentIds::default(),
Default::default(),
);
let mut defs = parse_defs_with(arena, indoc!($src)).unwrap();
desugar_defs_node_values(
arena,
&mut scope,
&mut defs,
$src,
&mut None,
"test.roc",
true,
&mut Default::default(),
);
let snapshot = format!("{:#?}", &defs);
println!("{}", snapshot);
assert_snapshot!(snapshot);
}};
}
/**
* This example tests a suffixed statement, followed
* by a Body with an empty record pattern.
*
* The def final expression is explicitly provided.
*/
#[test]
fn multi_defs_stmts() {
run_test!(
r#"
main =
line! "Ahoy"
{} = "There" |> Stdout.line!
Task.ok {}
"#
);
}
/**
* The most simple suffixed example. A single statement
* without arguments and a final expression.
*/
#[test]
fn basic() {
run_test!(
r#"
main =
foo!
ok {}
"#
);
}
/**
* A single suffixed statement with arguments applied.
* Note there is no final expression.
*/
#[test]
fn last_suffixed_single() {
run_test!(
r#"
main = foo! "bar" {} "baz"
"#
);
}
/**
* Multiple suffixed statements with no
* arguments, and no final expression.
*/
#[test]
fn last_suffixed_multiple() {
run_test!(
r#"
main =
foo!
bar!
baz!
"#
);
}
/**
* A definition with a closure that contains a Defs node, which also
* contains a suffixed binops statement.
*/
#[test]
fn closure_simple() {
run_test!(
r#"
main =
x = \msg ->
msg |> line!
ok {}
x "hi"
"#
);
}
/**
* Example of unwrapping a pipline statement
*
* Note pipelines are desugared into Apply functions,
* however this also tests the parser.
*
*/
#[test]
fn simple_pizza() {
run_test!(
r#"
main =
"hello"
|> Str.concat "world"
|> line!
Task.ok {}
"#
);
}
/**
* Example with a parens suffixed sub-expression
* in the function part of an Apply.
*
* Note how the parens unwraps into an intermediate answer #!0_arg instead of
* unwrapping the def `do`.
*
*/
#[test]
fn body_parens_apply() {
run_test!(
r#"
main =
do = (sayMultiple!) "hi"
do
"#
);
}
/**
* Example of unwrapping mixed Body defs with
* Var's of both single and multiple suffixes
*/
#[test]
fn var_suffixes() {
run_test!(
r#"
main =
a = foo!
b = bar!!
baz a b
"#
);
}
/**
* Example with a multiple suffixed Var
*
* Note it unwraps into an intermediate answer `#!0_arg`
*
*/
#[test]
fn multiple_suffix() {
run_test!(
r#"
main =
foo!!
bar
"#
);
}
/**
* A suffixed expression in the function part of the Apply
*/
#[test]
fn apply_function_suffixed() {
run_test!(
r#"
main =
x = (foo! "bar") "hello"
baz x
"#
);
}
/**
* A suffixed expression in an Apply argument position.
*/
#[test]
fn apply_argument_suffixed() {
run_test!(
r#"
main =
x = bar (foo! "hello")
baz x
"#
);
}
/**
* Example where the suffixed def is not the first def
*/
#[test]
fn multiple_def_first_suffixed() {
run_test!(
r#"
main =
msg = "hello"
x = foo! msg
bar x
"#
);
}
/**
* Annotated defs and a suffixed expression
* with annotations inside a closure
*/
#[test]
fn closure_with_annotations() {
run_test!(
r#"
main =
x : Str -> Task _ _
x = \msg ->
y : Task {} _
y = line! msg
y
x "foo"
"#
);
}
/**
* Nested suffixed expressions
*/
#[test]
fn nested_simple() {
run_test!(
r#"
run = line! (nextMsg!)
"#
);
}
/**
* Nested suffixed expressions
*/
#[test]
fn nested_complex() {
run_test!(
r#"
main =
z = foo! (bar! baz) (blah stuff)
doSomething z
"#
);
}
/**
* A closure that contains a Defs node
*/
#[test]
fn closure_with_defs() {
run_test!(
r#"
main =
foo : Str, {}, Str -> Task {} I32
foo = \a, _, b ->
line! a
line! b
Task.ok {}
foo "bar" {} "baz"
"#
);
}
/**
* Test when the suffixed def being unwrapped is not the first or last
*/
#[test]
fn defs_suffixed_middle() {
run_test!(
r#"
main =
a = "Foo"
Stdout.line! a
printBar!
printBar =
b = "Bar"
Stdout.line b
"#
);
}
/**
* A simple if-then-else statement which is split
*/
#[test]
fn if_simple() {
run_test!(
r#"
main =
isTrue = Task.ok Bool.true
isFalse = Task.ok Bool.false
if isFalse! then
line "fail"
else if isTrue! then
line "success"
else
line "fail"
"#
);
}
/**
* A more complex example including the use of nested Defs nodes
*/
#[test]
fn if_complex() {
run_test!(
r#"
main =
isTrue = Task.ok Bool.true
isFalsey = \x -> Task.ok x
msg : Task {} I32
msg =
if !(isTrue!) then
line! "fail"
err 1
else if (isFalsey! Bool.false) then
line! "nope"
ok {}
else
line! "success"
msg
"#
);
}
/**
* Unwrap a trailing binops
*/
#[test]
fn trailing_binops() {
run_test!(
r#"
copy = \a,b ->
line! "FOO"
CMD.new "cp"
|> mapErr! ERR
"#
);
}
/**
* Unwrap a when expression
*/
#[test]
fn when_simple() {
run_test!(
r#"
list =
when getList! is
[] -> "empty"
_ -> "non-empty"
"#
);
}
/**
* Unwrap a when expression
*/
#[test]
fn when_branches() {
run_test!(
r#"
list =
when getList! is
[] ->
line! "foo"
line! "bar"
_ ->
ok {}
"#
);
}
#[test]
fn trailing_suffix_inside_when() {
run_test!(
r#"
main =
result = Stdin.line!
when result is
End ->
Task.ok {}
Input name ->
Stdout.line! "Hello, $(name)"
"#
);
}
#[test]
fn dbg_simple() {
run_test!(
r#"
main =
foo = getFoo!
dbg foo
bar! foo
"#
);
}
#[test]
fn dbg_expr() {
run_test!(
r#"
main =
dbg (dbg 1 + 1)
"#
);
}
#[test]
fn apply_argument_single() {
run_test!(
r#"
main =
c = b a!
c
"#
);
}
#[test]
fn apply_argument_multiple() {
run_test!(
r#"
main =
c = b a! x!
c
"#
);
}
#[test]
fn bang_in_pipe_root() {
run_test!(
r#"
main =
c = a! |> b
c
"#
);
}
#[test]
fn expect_then_bang() {
run_test!(
r#"
main =
expect 1 == 2
x!
"#
);
}
#[test]
fn deep_when() {
run_test!(
r#"
main =
when a is
0 ->
when b is
1 ->
c!
"#
);
}
#[test]
fn deps_final_expr() {
run_test!(
r#"
main =
when x is
A ->
y = 42
if a then
b!
else
c!
B ->
d!
"#
);
}
#[test]
fn dbg_stmt_arg() {
run_test!(
r#"
main =
dbg a!
b
"#
)
}
#[test]
fn last_stmt_not_top_level_suffixed() {
run_test!(
r#"
main =
x = 42
a b!
"#
);
}
#[test]
fn nested_defs() {
run_test!(
r##"
main =
x =
a = b!
c! a
x
"##
);
}
#[test]
fn type_annotation() {
run_test!(
r##"
f = \x ->
r : A
r = x!
Task.ok r
"##
);
}
}
#[cfg(test)]
mod test_suffixed_helpers {
use roc_can::suffixed::is_matching_intermediate_answer;
use roc_parse::ast::Expr;
use roc_parse::ast::Pattern;
use roc_region::all::Loc;
#[test]
fn test_matching_answer() {
let loc_pat = Loc::at_zero(Pattern::Identifier { ident: "#!0_arg" });
let loc_new = Loc::at_zero(Expr::Var {
module_name: "",
ident: "#!0_arg",
});
std::assert!(is_matching_intermediate_answer(&loc_pat, &loc_new));
}
}