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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#### :bug: Bug fix

- Make rewatch compile independent modules after an unrelated failure and recompile blocked dependents when a changed interface survives a failed implementation, including across full watcher rebuilds. https://github.com/rescript-lang/rescript/pull/8667
- Fix invalid JavaScript that exported names left unbound when a module's toplevel always throws. https://github.com/rescript-lang/rescript/pull/8692
- Make module inclusion error messages independent of the length of the source file path. https://github.com/rescript-lang/rescript/pull/8691

#### :memo: Documentation
Expand Down
28 changes: 26 additions & 2 deletions compiler/core/lam_compile_main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,24 @@ let compile_group output_prefix (meta : Lam_stats.t) (x : Lam_group.t) :
}
lam

(* When a toplevel group always throws, [Js_output.concat] drops every later
group, and a [let] whose right-hand side throws compiles to the [throw]
alone. The identifiers those groups bind are then undeclared, and an export
list naming them is a JavaScript syntax error. This appends a declaration
for each such exported identifier after the [throw]. *)
let declare_undeclared_exports (exports : Ident.t list) (block : J.block) :

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add the required DCO sign-off

The message for commit c00d4f6c38e4239dcb5d2ed117c2b7b0b7e598cd has no Signed-Off-By trailer, so the contribution does not meet the repository's required DCO commit standard and cannot be accepted as submitted.

AGENTS.md reference: AGENTS.md:L68-L70

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Every commit on this branch carries a Signed-off-by trailer (git log --format='%(trailers:key=Signed-off-by)' origin/master..HEAD lists one per commit), so there is nothing to add.

J.block =
let declared =
Ext_list.fold_left block Set_ident.empty (fun acc (stmt : J.statement) ->
match stmt.statement_desc with
| Variable {ident} -> Set_ident.add acc ident
| _ -> acc)
in
block
@ Ext_list.filter_map exports (fun id ->
if Set_ident.mem declared id then None
else Some (Js_stmt_make.declare_variable ~kind:Strict id))

(** Also need analyze its depenency is pure or not *)
let no_side_effects (rest : Lam_group.t list) : string option =
Ext_list.find_opt rest (fun x ->
Expand Down Expand Up @@ -355,8 +373,14 @@ let compile (output_prefix : string) export_idents hoisted (lam : Lambda.t) =
(Sys.time () *. 1000.)
in
let body =
Ext_list.map groups (fun group -> compile_group output_prefix meta group)
|> Js_output.concat |> Js_output.output_as_block
let output =
Ext_list.map groups (fun group -> compile_group output_prefix meta group)
|> Js_output.concat
in
let block = Js_output.output_as_block output in
if output.output_finished = True then
declare_undeclared_exports meta.exports block
Comment on lines +381 to +382

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add the required changelog entry

This changes compiler output for affected user programs from invalid to valid JavaScript, making it a user-facing bug fix, but the commit does not add the required entry under the current Unreleased bug-fix section of CHANGELOG.md; without it, the fix will be omitted from the release notes.

AGENTS.md reference: AGENTS.md:L93-L95

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Added in 59571ed, under the Unreleased section with the PR link.

else block
in
let () =
if debug_ir then
Expand Down
2 changes: 2 additions & 0 deletions tests/tests/src/conditional/cond_a.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ throw {
Error: new Error()
};

let u;

export {
u,
}
Expand Down
44 changes: 44 additions & 0 deletions tests/tests/src/conditional/cond_a_load_test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Generated by ReScript, PLEASE EDIT WITH CARE

import * as Mocha from "mocha";
import * as Pervasives from "@rescript/runtime/lib/es6/Pervasives.mjs";
import * as Primitive_exceptions from "@rescript/runtime/lib/es6/Primitive_exceptions.mjs";

async function loadCondANone() {
let M = await import("./cond_a_none.mjs");
return M.A.u;
}

async function raisesAssertFailure(load) {
try {
await load();
return false;
} catch (raw_exn) {
let exn = Primitive_exceptions.internalToException(raw_exn);
if (exn.RE_EXN_ID === "Assert_failure") {
return true;
}
throw exn;
}
}

Mocha.describe("Cond_a_load_test", () => {
Mocha.test("cond_a loads as a module whose body throws Assert_failure", async () => {
let ok = await raisesAssertFailure(() => import("./cond_a.mjs").then(m => m.u));
if (!ok) {
return Pervasives.failwith("cond_a did not throw Assert_failure on load");
}
});
Mocha.test("cond_a_none loads as a module whose body throws Assert_failure", async () => {
let ok = await raisesAssertFailure(loadCondANone);
if (!ok) {
return Pervasives.failwith("cond_a_none did not throw Assert_failure on load");
}
});
});

export {
loadCondANone,
raisesAssertFailure,
}
/* Not a pure module */
30 changes: 30 additions & 0 deletions tests/tests/src/conditional/cond_a_load_test.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
open Mocha

@module("mocha")
external testAsync: (string, unit => promise<unit>) => unit = "test"

let loadCondANone = async () => {
module M = await Cond_a_none
M.A.u
}

let raisesAssertFailure = async load =>
switch await load() {
| _ => false
| exception Assert_failure(_) => true
}

describe(__MODULE__, () => {
testAsync("cond_a loads as a module whose body throws Assert_failure", async () => {
let ok = await raisesAssertFailure(() => import(Cond_a.u))
if !ok {
failwith("cond_a did not throw Assert_failure on load")
}
})
testAsync("cond_a_none loads as a module whose body throws Assert_failure", async () => {
let ok = await raisesAssertFailure(loadCondANone)
if !ok {
failwith("cond_a_none did not throw Assert_failure on load")
}
})
})
2 changes: 2 additions & 0 deletions tests/tests/src/conditional/cond_a_none.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ throw {
Error: new Error()
};

let A;

export {
A,
}
Expand Down
Loading