Skip to content

Commit

Permalink
Merge pull request #7009 from roc-lang/lower-module-params
Browse files Browse the repository at this point in the history
Lower module params
  • Loading branch information
smores56 authored Aug 30, 2024
2 parents e619f60 + 3c679f6 commit f6c969a
Show file tree
Hide file tree
Showing 33 changed files with 1,547 additions and 178 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

189 changes: 182 additions & 7 deletions crates/cli/tests/cli_run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ extern crate roc_module;
#[cfg(test)]
mod cli_run {
use cli_utils::helpers::{
extract_valgrind_errors, file_path_from_root, fixture_file, fixtures_dir, has_error,
known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError,
cli_testing_dir, extract_valgrind_errors, file_path_from_root, fixture_file, fixtures_dir,
has_error, known_bad_file, run_cmd, run_roc, run_with_valgrind, Out, ValgrindError,
ValgrindErrorXWhat,
};
use const_format::concatcp;
Expand Down Expand Up @@ -86,11 +86,11 @@ mod cli_run {
}

fn check_compile_error(file: &Path, flags: &[&str], expected: &str) {
let compile_out = run_roc(
[CMD_CHECK, file.to_str().unwrap()].iter().chain(flags),
&[],
&[],
);
check_compile_error_with(CMD_CHECK, file, flags, expected);
}

fn check_compile_error_with(cmd: &str, file: &Path, flags: &[&str], expected: &str) {
let compile_out = run_roc([cmd, file.to_str().unwrap()].iter().chain(flags), &[], &[]);
let err = compile_out.stdout.trim();
let err = strip_colors(err);

Expand Down Expand Up @@ -728,6 +728,181 @@ mod cli_run {
)
}

#[test]
#[cfg_attr(windows, ignore)]
fn module_params() {
test_roc_app(
"crates/cli/tests/module_params",
"app.roc",
&[],
&[],
&[],
indoc!(
r#"
App1.baseUrl: https://api.example.com/one
App2.baseUrl: http://api.example.com/two
App3.baseUrl: https://api.example.com/three
App1.getUser 1: https://api.example.com/one/users/1
App2.getUser 2: http://api.example.com/two/users/2
App3.getUser 3: https://api.example.com/three/users/3
App1.getPost 1: https://api.example.com/one/posts/1
App2.getPost 2: http://api.example.com/two/posts/2
App3.getPost 3: https://api.example.com/three/posts/3
App1.getPosts [1, 2]: ["https://api.example.com/one/posts/1", "https://api.example.com/one/posts/2"]
App2.getPosts [3, 4]: ["http://api.example.com/two/posts/3", "http://api.example.com/two/posts/4"]
App2.getPosts [5, 6]: ["http://api.example.com/two/posts/5", "http://api.example.com/two/posts/6"]
App1.getPostComments 1: https://api.example.com/one/posts/1/comments
App2.getPostComments 2: http://api.example.com/two/posts/2/comments
App2.getPostComments 3: http://api.example.com/two/posts/3/comments
App1.getCompanies [1, 2]: ["https://api.example.com/one/companies/1", "https://api.example.com/one/companies/2"]
App2.getCompanies [3, 4]: ["http://api.example.com/two/companies/3", "http://api.example.com/two/companies/4"]
App2.getCompanies [5, 6]: ["http://api.example.com/two/companies/5", "http://api.example.com/two/companies/6"]
App1.getPostAliased 1: https://api.example.com/one/posts/1
App2.getPostAliased 2: http://api.example.com/two/posts/2
App3.getPostAliased 3: https://api.example.com/three/posts/3
App1.baseUrlAliased: https://api.example.com/one
App2.baseUrlAliased: http://api.example.com/two
App3.baseUrlAliased: https://api.example.com/three
App1.getUserSafe 1: https://api.example.com/one/users/1
Prod.getUserSafe 2: http://api.example.com/prod_1/users/2?safe=true
usersApp1: ["https://api.example.com/one/users/1", "https://api.example.com/one/users/2", "https://api.example.com/one/users/3"]
getUserApp3Nested 3: https://api.example.com/three/users/3
usersApp3Passed: ["https://api.example.com/three/users/1", "https://api.example.com/three/users/2", "https://api.example.com/three/users/3"]
"#
),
UseValgrind::No,
TestCliCommands::Run,
);
}

#[test]
#[cfg_attr(windows, ignore)]
fn module_params_arity_mismatch() {
check_compile_error_with(
CMD_DEV,
&cli_testing_dir("/module_params/arity_mismatch.roc"),
&[],
indoc!(
r#"
── TOO MANY ARGS in tests/module_params/arity_mismatch.roc ─────────────────────
The getUser function expects 1 argument, but it got 2 instead:
12│ $(Api.getUser 1 2)
^^^^^^^^^^^
Are there any missing commas? Or missing parentheses?
── TOO MANY ARGS in tests/module_params/arity_mismatch.roc ─────────────────────
This value is not a function, but it was given 1 argument:
13│ $(Api.baseUrl 1)
^^^^^^^^^^^
Are there any missing commas? Or missing parentheses?
── TOO FEW ARGS in tests/module_params/arity_mismatch.roc ──────────────────────
The getPostComment function expects 2 arguments, but it got only 1:
16│ $(Api.getPostComment 1)
^^^^^^^^^^^^^^^^^^
Roc does not allow functions to be partially applied. Use a closure to
make partial application explicit.
────────────────────────────────────────────────────────────────────────────────
3 errors and 0 warnings found in <ignored for test> ms."#
),
);
}

#[test]
#[cfg_attr(windows, ignore)]
fn module_params_unexpected_fn() {
check_compile_error_with(
CMD_DEV,
&cli_testing_dir("/module_params/unexpected_fn.roc"),
&[],
indoc!(
r#"
── TYPE MISMATCH in tests/module_params/unexpected_fn.roc ──────────────────────
This argument to this string interpolation has an unexpected type:
11│ $(Api.getPost)
^^^^^^^^^^^
The argument is an anonymous function of type:
U32 -> Str
But this string interpolation needs its argument to be:
Str
────────────────────────────────────────────────────────────────────────────────
1 error and 0 warnings found in <ignored for test> ms."#
),
)
}

#[test]
#[cfg_attr(windows, ignore)]
fn module_params_bad_ann() {
check_compile_error_with(
CMD_DEV,
&cli_testing_dir("/module_params/bad_ann.roc"),
&[],
indoc!(
r#"
── TYPE MISMATCH in tests/module_params/BadAnn.roc ─────────────────────────────
Something is off with the body of the fnAnnotatedAsValue definition:
3│ fnAnnotatedAsValue : Str
4│> fnAnnotatedAsValue = /postId, commentId ->
5│> "/posts/$(postId)/comments/$(Num.toStr commentId)"
The body is an anonymous function of type:
Str, Num * -> Str
But the type annotation on fnAnnotatedAsValue says it should be:
Str
── TYPE MISMATCH in tests/module_params/BadAnn.roc ─────────────────────────────
Something is off with the body of the missingArg definition:
7│ missingArg : Str -> Str
8│> missingArg = /postId, _ ->
9│> "/posts/$(postId)/comments"
The body is an anonymous function of type:
(Str, ? -> Str)
But the type annotation on missingArg says it should be:
(Str -> Str)
Tip: It looks like it takes too many arguments. I'm seeing 1 extra.
────────────────────────────────────────────────────────────────────────────────
2 errors and 1 warning found in <ignored for test> ms."#
),
);
}

#[test]
#[cfg_attr(windows, ignore)]
fn transitive_expects() {
Expand Down
69 changes: 69 additions & 0 deletions crates/cli/tests/module_params/Api.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
module { appId, protocol } -> [
baseUrl,
getUser,
getPost,
getPosts,
getPostComments,
getCompanies,
baseUrlAliased,
getPostAliased,
getUserSafe,
getPostComment,
]

## value def referencing params
baseUrl : Str
baseUrl =
protocol "api.example.com/$(appId)"

## function def referencing params
getUser : U32 -> Str
getUser = \userId ->
# purposefully not using baseUrl to test top-level fn referencing param
protocol "api.example.com/$(appId)/users/$(Num.toStr userId)"

## function def referencing top-level value
getPost : U32 -> Str
getPost = \postId ->
"$(baseUrl)/posts/$(Num.toStr postId)"

## function def passing top-level function
getPosts : List U32 -> List Str
getPosts = \ids ->
List.map ids getPost

## function def calling top-level function
getPostComments : U32 -> Str
getPostComments = \postId ->
"$(getPost postId)/comments"

## function def passing nested function
getCompanies : List U32 -> List Str
getCompanies = \ids ->
getCompany = \id ->
protocol "api.example.com/$(appId)/companies/$(Num.toStr id)"

List.map ids getCompany

## aliasing top-level value
baseUrlAliased : Str
baseUrlAliased =
baseUrl

## aliasing top-level fn
getPostAliased : U32 -> Str
getPostAliased =
getPost

## top-level value returning functions
getUserSafe : U32 -> Str
getUserSafe =
if Str.startsWith appId "prod_" then
\id -> "$(getUser id)?safe=true"
else
getUser

## two-argument function
getPostComment : U32, U32 -> Str
getPostComment = \postId, commentId ->
"$(getPost postId)/comments/$(Num.toStr commentId)"
9 changes: 9 additions & 0 deletions crates/cli/tests/module_params/BadAnn.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module { appId } -> [fnAnnotatedAsValue, missingArg]

fnAnnotatedAsValue : Str
fnAnnotatedAsValue = \postId, commentId ->
"/posts/$(postId)/comments/$(Num.toStr commentId)"

missingArg : Str -> Str
missingArg = \postId, _ ->
"/posts/$(postId)/comments"
59 changes: 59 additions & 0 deletions crates/cli/tests/module_params/app.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
app [main] {
pf: platform "../fixtures/multi-dep-str/platform/main.roc",
}

import Api { appId: "one", protocol: https } as App1
import Api { appId: "two", protocol: http } as App2
import Api { appId: "prod_1", protocol: http } as Prod

https = \url -> "https://$(url)"
http = \url -> "http://$(url)"

usersApp1 =
# pass top-level fn in a module with params
List.map [1, 2, 3] App1.getUser

main =
app3Id = "three"

import Api { appId: app3Id, protocol: https } as App3

getUserApp3Nested = \userId ->
# use captured params def
App3.getUser userId

usersApp3Passed =
# pass top-level fn in a nested def
List.map [1, 2, 3] App3.getUser

"""
App1.baseUrl: $(App1.baseUrl)
App2.baseUrl: $(App2.baseUrl)
App3.baseUrl: $(App3.baseUrl)
App1.getUser 1: $(App1.getUser 1)
App2.getUser 2: $(App2.getUser 2)
App3.getUser 3: $(App3.getUser 3)
App1.getPost 1: $(App1.getPost 1)
App2.getPost 2: $(App2.getPost 2)
App3.getPost 3: $(App3.getPost 3)
App1.getPosts [1, 2]: $(Inspect.toStr (App1.getPosts [1, 2]))
App2.getPosts [3, 4]: $(Inspect.toStr (App2.getPosts [3, 4]))
App2.getPosts [5, 6]: $(Inspect.toStr (App2.getPosts [5, 6]))
App1.getPostComments 1: $(App1.getPostComments 1)
App2.getPostComments 2: $(App2.getPostComments 2)
App2.getPostComments 3: $(App2.getPostComments 3)
App1.getCompanies [1, 2]: $(Inspect.toStr (App1.getCompanies [1, 2]))
App2.getCompanies [3, 4]: $(Inspect.toStr (App2.getCompanies [3, 4]))
App2.getCompanies [5, 6]: $(Inspect.toStr (App2.getCompanies [5, 6]))
App1.getPostAliased 1: $(App1.getPostAliased 1)
App2.getPostAliased 2: $(App2.getPostAliased 2)
App3.getPostAliased 3: $(App3.getPostAliased 3)
App1.baseUrlAliased: $(App1.baseUrlAliased)
App2.baseUrlAliased: $(App2.baseUrlAliased)
App3.baseUrlAliased: $(App3.baseUrlAliased)
App1.getUserSafe 1: $(App1.getUserSafe 1)
Prod.getUserSafe 2: $(Prod.getUserSafe 2)
usersApp1: $(Inspect.toStr usersApp1)
getUserApp3Nested 3: $(getUserApp3Nested 3)
usersApp3Passed: $(Inspect.toStr usersApp3Passed)
"""
17 changes: 17 additions & 0 deletions crates/cli/tests/module_params/arity_mismatch.roc
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
app [main] {
pf: platform "../fixtures/multi-dep-str/platform/main.roc",
}

import Api { appId: "one", protocol: https }

https = \url -> "https://$(url)"

main =
"""
# too many args
$(Api.getUser 1 2)
$(Api.baseUrl 1)
# too few args
$(Api.getPostComment 1)
"""
Loading

0 comments on commit f6c969a

Please sign in to comment.