Fork of daniellemaywood.uk/gleam — Wasm codegen work
6.7 kB
431 lines
1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 The Gleam contributors
3
4use crate::assert_js;
5
6const BOOL_MODULE: &str = "
7pub fn guard(
8 when condition: Bool,
9 return value: a,
10 otherwise callback: fn() -> a,
11) -> a {
12 case condition {
13 True -> value
14 False -> callback()
15 }
16}
17
18pub fn lazy_guard(
19 when condition: Bool,
20 return consequence: fn() -> a,
21 otherwise alternative: fn() -> a,
22) -> a {
23 case condition {
24 True -> consequence()
25 False -> alternative()
26 }
27}
28";
29
30const RESULT_MODULE: &str = "
31pub fn try(result: Result(a, e), apply f: fn(a) -> Result(b, e)) -> Result(b, e) {
32 case result {
33 Ok(value) -> f(value)
34 Error(error) -> Error(error)
35 }
36}
37
38pub fn map(over result: Result(a, e), with f: fn(a) -> b) -> Result(b, e) {
39 case result {
40 Ok(value) -> Ok(f(value))
41 Error(error) -> Error(error)
42 }
43}
44";
45
46#[test]
47fn inline_higher_order_function() {
48 assert_js!(
49 ("gleam_stdlib", "gleam/result", RESULT_MODULE),
50 "
51import gleam/result
52
53pub fn main() {
54 result.map(over: Ok(10), with: double)
55}
56
57fn double(x) { x + x }
58"
59 );
60}
61
62#[test]
63fn inline_higher_order_function_with_capture() {
64 assert_js!(
65 ("gleam_stdlib", "gleam/result", RESULT_MODULE),
66 "
67import gleam/result
68
69pub fn main() {
70 result.try(Ok(10), divide(_, 2))
71}
72
73fn divide(a: Int, b: Int) -> Result(Int, Nil) {
74 case a % b {
75 0 -> Ok(a / b)
76 _ -> Error(Nil)
77 }
78}
79"
80 );
81}
82
83#[test]
84fn inline_higher_order_function_anonymous() {
85 assert_js!(
86 ("gleam_stdlib", "gleam/result", RESULT_MODULE),
87 "
88import gleam/result
89
90pub fn main() {
91 result.try(Ok(10), fn(value) {
92 Ok({ value + 2 } * 4)
93 })
94}
95"
96 );
97}
98
99#[test]
100fn inline_function_which_calls_other_function() {
101 // This function calls `result.try`, meaning this must be inlined twice to
102 // achieve the desired result.
103 assert_js!(
104 ("gleam_stdlib", "gleam/result", RESULT_MODULE),
105 (
106 "gleam_stdlib",
107 "testing",
108 "
109import gleam/result.{try}
110
111pub fn always_inline(result, f) -> Result(b, e) {
112 try(result, f)
113}
114"
115 ),
116 "
117import testing
118
119pub fn main() {
120 testing.always_inline(Ok(10), Error)
121}
122"
123 );
124}
125
126#[test]
127fn inline_function_with_use() {
128 assert_js!(
129 ("gleam_stdlib", "gleam/bool", BOOL_MODULE),
130 "
131import gleam/bool
132
133pub fn divide(a, b) {
134 use <- bool.guard(when: b == 0, return: 0)
135 a / b
136}
137"
138 );
139}
140
141#[test]
142fn inline_function_with_use_and_anonymous() {
143 assert_js!(
144 ("gleam_stdlib", "gleam/bool", BOOL_MODULE),
145 r#"
146import gleam/bool
147
148pub fn divide(a, b) {
149 use <- bool.lazy_guard(b == 0, fn() { panic as "Cannot divide by 0" })
150 a / b
151}
152"#
153 );
154}
155
156#[test]
157fn inline_function_with_use_becomes_tail_recursive() {
158 assert_js!(
159 ("gleam_stdlib", "gleam/bool", BOOL_MODULE),
160 "
161import gleam/bool
162
163pub fn count(from: Int, to: Int) -> Int {
164 use <- bool.guard(when: from >= to, return: from)
165 echo from
166 count(from + 1, to)
167}
168"
169 );
170}
171
172#[test]
173fn do_not_inline_parameters_used_more_than_once() {
174 // Since the `something` parameter is used more than once in the body of the
175 // function, it should not be inlined, and should be assigned once at the
176 // beginning of the function.
177 assert_js!(
178 (
179 "gleam_stdlib",
180 "testing",
181 "
182pub fn always_inline(something) {
183 case something {
184 True -> something
185 False -> False
186 }
187}
188"
189 ),
190 "
191import testing
192
193pub fn main() {
194 testing.always_inline(True)
195}
196"
197 );
198}
199
200#[test]
201fn do_not_inline_parameters_that_have_side_effects() {
202 assert_js!(
203 ("gleam_stdlib", "gleam/result", RESULT_MODULE),
204 r#"
205import gleam/result
206
207pub fn main() {
208 result.map(Ok(10), do_side_effects())
209}
210
211fn do_side_effects() {
212 let function = fn(x) { x + 1 }
213 panic as "Side effects"
214 function
215}
216"#
217 );
218}
219
220#[test]
221fn inline_anonymous_function_call() {
222 assert_js!(
223 "
224pub fn main() {
225 fn(a, b) { #(a, b) }(42, False)
226}
227"
228 );
229}
230
231#[test]
232fn inline_anonymous_function_in_pipe() {
233 assert_js!(
234 "
235pub fn main() {
236 1 |> fn(x) { x + 1 } |> fn(y) { y * y }
237}
238"
239 );
240}
241
242#[test]
243fn inline_function_capture_in_pipe() {
244 // The function capture is desugared to an anonymous function, so it should
245 // be turned into a direct call to `add`
246 assert_js!(
247 "
248pub fn main() {
249 1 |> add(4, _)
250}
251
252fn add(a, b) { a + b }
253"
254 );
255}
256
257#[test]
258fn inlining_works_through_blocks() {
259 assert_js!(
260 "
261pub fn main() {
262 { fn(x) { Ok(x + 1) } }(41)
263}
264"
265 );
266}
267
268#[test]
269fn blocks_get_preserved_when_needed() {
270 assert_js!(
271 "
272pub fn main() {
273 { 4 |> make_adder }(6)
274}
275
276fn make_adder(a) {
277 fn(b) { a + b }
278}
279"
280 );
281}
282
283#[test]
284fn blocks_get_preserved_when_needed2() {
285 assert_js!(
286 "
287pub fn main() {
288 fn(x) { 1 + x }(2) * 3
289}
290"
291 );
292}
293
294#[test]
295fn parameters_from_nested_functions_are_correctly_inlined() {
296 assert_js!(
297 ("gleam_stdlib", "gleam/result", RESULT_MODULE),
298 "
299import gleam/result
300
301pub fn halve_all(a, b, c) {
302 use x <- result.try(divide(a, 2))
303 use y <- result.try(divide(b, 2))
304 use z <- result.map(divide(c, 2))
305
306 #(x, y, z)
307}
308
309fn divide(a, b) {
310 case a % b {
311 0 -> Ok(a / b)
312 _ -> Error(Nil)
313 }
314}
315"
316 );
317}
318
319// https://github.com/gleam-lang/gleam/issues/4852
320#[test]
321fn inlining_works_properly_with_record_updates() {
322 assert_js!(
323 ("gleam_stdlib", "gleam/result", RESULT_MODULE),
324 "
325import gleam/result
326
327pub type Wibble {
328 Wibble(a: Int, b: Int)
329}
330
331pub fn main() {
332 let w = Wibble(1, 2)
333 use b <- result.map(Ok(3))
334 Wibble(..w, b:)
335}
336"
337 );
338}
339
340// https://github.com/gleam-lang/gleam/issues/4877
341#[test]
342fn inline_shadowed_variable() {
343 assert_js!(
344 "
345pub fn main() {
346 let a = 10
347 let b = 20
348
349 fn(x) {
350 let a = 7
351 x + a
352 }(a + b)
353
354 a
355}
356"
357 );
358}
359
360#[test]
361fn inline_variable_shadowing_parameter() {
362 assert_js!(
363 "
364pub fn sum(a, b) {
365 fn(x) {
366 let a = 7
367 x + a
368 }(a + b)
369
370 a
371}
372"
373 );
374}
375
376#[test]
377fn inline_shadowed_variable_nested() {
378 assert_js!(
379 "
380pub fn sum(a, b) {
381 fn(x) {
382 let a = 7
383 fn(y) {
384 let a = 10
385 y - a
386 }(x + a)
387
388 a
389 }(a + b)
390
391 a
392}
393"
394 );
395}
396
397#[test]
398fn inline_variable_shadowed_in_case_pattern() {
399 assert_js!(
400 "
401pub fn sum() {
402 let a = 10
403 let b = 20
404
405 fn(x) {
406 case 7, 8 {
407 a, b -> a + b + x
408 }
409 }(a + b)
410
411 a + b
412}
413"
414 );
415}
416
417#[test]
418fn inline_variable_shadowing_case_pattern() {
419 assert_js!(
420 "
421pub fn sum() {
422 case 1, 2 {
423 a, b -> fn(x) {
424 let a = 7
425 x + a
426 }(a + b)
427 }
428}
429"
430 );
431}