Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ jobs:
run: yarn ci:generated
- name: Test examples and hrefs
run: yarn test
- name: Check unused ReScript values and exports
run: yarn check:dead-code
# Disable for now. Reanalyze report wrong dead code.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for the v13 branch for now.

# - name: Check unused ReScript values and exports
# run: yarn check:dead-code
- name: Test dead-code diagnostics
run: node --test scripts/__tests__/check-dead-code.test.mjs
# Check the built Docs Pages routes and the Guide local dev server.
Expand Down
8 changes: 4 additions & 4 deletions apps/docs/markdown-pages/docs/manual/array-and-list.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,15 +102,15 @@ let x3 = [...y]
```

```javascript
import * as Belt_Array from "@rescript/runtime/lib/es6/Belt_Array.js";
import * as Primitive_array from "@rescript/runtime/lib/es6/Primitive_array.mjs";

let y = [1, 2];

let x = Belt_Array.concatMany([[4, 5], y]);
let x = Primitive_array.spread([[4, 5], y]);

let x2 = Belt_Array.concatMany([[4, 5], y, [7], y]);
let x2 = Primitive_array.spread([[4, 5], y, [7], y]);

let x3 = Belt_Array.concatMany([y]);
let x3 = Primitive_array.spread([y]);

export { y, x, x2, x3 };
```
Expand Down
36 changes: 32 additions & 4 deletions apps/docs/markdown-pages/docs/manual/async-await.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ let logUserDetails = async (userId: string) => {
```js
async function logUserDetails(userId) {
let email = await GlobalAPI.fetchUserMail(userId);
await GlobalAPI.sendAnalytics(`User details have been logged for ` + userId);
console.log(`Email address for user ` + userId + `: ` + email);
await GlobalAPI.sendAnalytics(`User details have been logged for ${userId}`);
console.log(`Email address for user ${userId}: ${email}`);
}

export { logUserDetails };
Expand Down Expand Up @@ -126,6 +126,34 @@ let fetchData = async (userId: string): string => {
}
```

## For Await Of

Inside an `async` function, use `for await of` to consume an `asyncIterable<'a>`. Each iteration waits for the next value before running the loop body:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
let printMessages = async (messages: asyncIterable<string>) => {
for await message of messages {
Console.log(message)
}
}
```

```js
async function printMessages(messages) {
for await (let message of messages) {
console.log(message);
}
}

export { printMessages };
```

</CodeTab>

Use [`for of`](./control-flow.mdx#forof-loops) for ordinary arrays and synchronous iterables; `for await of` expects an async iterable. You can use [`break` and `continue`](./control-flow.mdx#break--continue) to exit the loop early or skip to the next iteration.

## Error handling

You may use `try / catch` or `switch` to handle exceptions during async execution.
Expand Down Expand Up @@ -191,7 +219,7 @@ let fetchData = async () => {
```js
async function fetchData() {
let mail = (await GlobalAPI.fetchUserMail("1234")).toUpperCase();
console.log(`All upper-cased mail: ` + mail);
console.log(`All upper-cased mail: ${mail}`);
}

export { fetchData };
Expand Down Expand Up @@ -223,7 +251,7 @@ let fetchData = async () => {
```

```js
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.js";
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.mjs";

async function fetchData() {
let val;
Expand Down
37 changes: 16 additions & 21 deletions apps/docs/markdown-pages/docs/manual/bind-to-global-js-values.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -104,25 +104,24 @@ This binds to `window.location.ancestorOrigins.length`.

## Special Global Values

Global values like `__filename` and `__DEV__` don't always exist; you can't even model them as an `option`, since the mere act of referring to them in ReScript (then compiled into JS) would trigger the usual `Uncaught ReferenceError: __filename is not defined` error in e.g. the browser environment.
Application globals like `__DEV__` don't always exist. Referring to an undeclared identifier directly would trigger a JavaScript `ReferenceError`.

For these troublesome global values, ReScript provides a special approach: `%external(a_single_identifier)`.
Bind optional properties of `globalThis` instead. Reading a missing property returns `undefined`, which ReScript represents as `None`:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
switch %external(__DEV__) {
@scope("globalThis")
external dev: option<bool> = "__DEV__"

switch dev {
| Some(_) => Console.log("dev mode")
| None => Console.log("production mode")
}
```

```js
import * as Js from "@rescript/runtime/lib/es6/Js.js";

let match = Js.undefinedToOption(
typeof __DEV__ === "undefined" ? undefined : __DEV__,
);
let match = globalThis.__DEV__;

if (match !== undefined) {
console.log("dev mode");
Expand All @@ -133,34 +132,30 @@ if (match !== undefined) {

</CodeTab>

That first line's `typeof` check won't trigger a JS ReferenceError.
This approach works for properties set on the global object. Module-scoped values, such as CommonJS's `__filename`, and identifiers replaced by a bundler are not necessarily properties of `globalThis`.

Another example:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
switch %external(__filename) {
@scope("globalThis")
external appVersion: option<string> = "__APP_VERSION__"

switch appVersion {
| Some(f) => Console.log(f)
| None => Console.log("non-node environment")
| None => Console.log("application version not provided")
};
```

```js
import * as Js from "@rescript/runtime/lib/es6/Js.js";
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";

let f = Js.undefinedToOption(
typeof __filename === "undefined" ? undefined : __filename,
);
let f = globalThis.__APP_VERSION__;

if (f !== undefined) {
console.log(Primitive_option.valFromOption(f));
console.log(f);
} else {
console.log("non-node environment");
console.log("application version not provided");
}
```

</CodeTab>

{/* TODO: revamp this page. Not good. Tell to use globalThis["foo"], and look in our stdlib */}
12 changes: 5 additions & 7 deletions apps/docs/markdown-pages/docs/manual/bind-to-js-function.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ let session = stripe->createCheckoutSession({})
```js
import Stripe from "stripe";

let stripe = new Stripe("sk\_...");
let stripe = new Stripe("sk_...");

let session = stripe.checkout.sessions.create({});

Expand Down Expand Up @@ -536,19 +536,17 @@ Currently 4 directives are supported: `null_to_opt`, `undefined_to_opt`, `nullab

**Since 11.1**

**Experimental** You can easily bind to [JS tagged template functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates).
You can bind to [JS tagged template functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates).
Tag functions in JS expect as input an array of strings and variadic parameters for the arguments of the interpolation.
To bind to those functions in ReScript, the binding signature must have two arrays as arguments,
the first one being an array of strings and the second can be an array of anything.
You add the `@taggedTemplate` annotation and you're good to go!
Use the built-in `taggedTemplate<'param, 'output>` type. The first type parameter describes the interpolated values, and the second describes the return value. This replaces the `@taggedTemplate` decorator removed in v13.

<CodeTab labels={["ReScript", "JS Output"]}>

```res
// see https://bun.sh/docs/runtime/shell
type result = {exitCode: int}
@module("bun") @taggedTemplate
external sh: (array<string>, array<string>) => promise<result> = "$"
@module("bun")
external sh: taggedTemplate<string, promise<result>> = "$"

let filename = "index.res"
let result = await sh`ls ${filename}`
Expand Down
121 changes: 101 additions & 20 deletions apps/docs/markdown-pages/docs/manual/control-flow.mdx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: "If-Else & Loops"
description: "If, else, ternary, for, and while"
description: "If, else, ternary, for, for...of, while, break, and continue"
canonical: "/docs/manual/control-flow"
section: "Language Features"
order: 14
Expand All @@ -21,7 +21,7 @@ order: 14

</div>

ReScript supports `if`, `else`, ternary expression (`a ? b : c`), `for` and `while`.
ReScript supports `if`, `else`, ternary expressions (`a ? b : c`), numeric `for` loops, `for of`, and `while`. Use `break` to exit a loop early and `continue` to skip to its next iteration.

The `switch` pattern supports a default case. Read more about [pattern-matching & destructuring](./pattern-matching-destructuring.mdx).

Expand Down Expand Up @@ -115,7 +115,7 @@ export { message };

## For Loops

For loops iterate from a starting value up to (and including) the ending value.
Numeric `for` loops iterate over integers from a starting value up to (and including) the ending value. The loop variable is local to the loop body.

<CodeTab labels={["ReScript", "JS Output"]}>

Expand Down Expand Up @@ -185,9 +185,98 @@ for (let x = 3; x >= 1; --x) {

</CodeTab>

## For Of Loops

Use `for of` to visit each value in an array or an `iterable<'a>`, without managing an index:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
let names = ["Ada", "Grace", "Margaret"]

for name of names {
Console.log(name)
}
```

```js
let names = ["Ada", "Grace", "Margaret"];

for (let name of names) {
console.log(name);
}

export { names };
```

</CodeTab>

The loop binding must be a variable name or `_`. To destructure a value, do so inside the loop body:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
for entry of [("Ada", 36), ("Grace", 85)] {
let (name, age) = entry
Console.log(`${name}: ${age->Int.toString}`)
}
```

```js
for (let entry of [
["Ada", 36],
["Grace", 85],
]) {
console.log(`${entry[0]}: ${entry[1].toString()}`);
}
```

</CodeTab>

For asynchronous iteration, see [For Await Of](./async-await.mdx#for-awaitof).

## Break & Continue

`break` exits the nearest enclosing loop immediately. `continue` skips the rest of the current iteration and starts the next one. Both work in numeric `for`, `for of`, `for await of`, and `while` loops.

<CodeTab labels={["ReScript", "JS Output"]}>

```res
// Prints 1 and 3
for number in 1 to 5 {
if number == 2 {
continue
}
if number == 4 {
break
}
Console.log(number)
}
```

```js
for (let number = 1; number <= 5; ++number) {
if (number === 2) {
continue;
}
if (number === 4) {
break;
}
console.log(number);
}
```

</CodeTab>

In a numeric `for` loop, `continue` still advances the loop variable. In a `while` loop, it rechecks the condition, so make sure any necessary updates happen before `continue`.

You can also use `break` and `continue` inside a `switch` within a loop: they target the loop, not the `switch`. They cannot be used outside a loop or from a nested function to control an outer loop.

`break` and `continue` are reserved keywords, so they cannot be used as ordinary variable names.

## While Loops

While loops execute its body code block while its condition is true.
While loops execute their body while the condition is true. The condition is checked before each iteration, so the body may never run.

<CodeTab labels={["ReScript", "JS Output"]}>

Expand All @@ -205,36 +294,28 @@ while (testCondition) {

</CodeTab>

### Tips & Tricks
### Exiting Early

There's no loop-breaking `break` keyword (nor early `return` from functions, for that matter) in ReScript. However, we can break out of a while loop easily through using a [mutable binding](./mutation.mdx).
Use `break` when the stopping condition is discovered inside the loop. There is no need for a separate mutable stop flag:

<CodeTab labels={["ReScript", "JS Output"]}>

```res
let break = ref(false)

while !break.contents {
while true {
if Math.random() > 0.3 {
break := true
} else {
Console.log("Still running")
break
}
Console.log("Still running")
}
```

```js
let $$break = { contents: false };

while (!$$break.contents) {
while (true) {
if (Math.random() > 0.3) {
$$break.contents = true;
} else {
console.log("Still running");
break;
}
console.log("Still running");
}

export { $$break };
```

</CodeTab>
Loading