mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00

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.
609 lines
12 KiB
Rust
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));
|
|
}
|
|
}
|