Skip to content

Commit f30651d

Browse files
committed
Ensure JS exceptions always have information
1 parent 8cf3d64 commit f30651d

File tree

5 files changed

+104
-16
lines changed

5 files changed

+104
-16
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@
4646
generated on the JavaScript target.
4747
([Surya Rose](https://github.com/GearsDatapacks))
4848

49+
- Fixed a bug where exceptions on JavaScript could be sometimes missing the
50+
function name metadata.
51+
([Louis Pilfold](https://github.com/lpil))
52+
4953
## v1.11.0-rc1 - 2025-05-15
5054

5155
### Compiler

compiler-core/src/javascript/expression.rs

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,60 @@ pub enum Ordering {
7676
Loose,
7777
}
7878

79+
/// Tracking where the current function is a module function or an anonymous function.
80+
#[derive(Debug)]
81+
enum CurrentFunction {
82+
/// The current function is a module function
83+
///
84+
/// ```gleam
85+
/// pub fn main() -> Nil {
86+
/// // we are here
87+
/// }
88+
/// ```
89+
Module,
90+
91+
/// The current function is a module function, but one of its arguments shadows
92+
/// the reference to itself so it cannot recurse.
93+
///
94+
/// ```gleam
95+
/// pub fn main(main: fn() -> Nil) -> Nil {
96+
/// // we are here
97+
/// }
98+
/// ```
99+
ModuleWithShadowingArgument,
100+
101+
/// The current function is an anonymous function
102+
///
103+
/// ```gleam
104+
/// pub fn main() -> Nil {
105+
/// fn() {
106+
/// // we are here
107+
/// }
108+
/// }
109+
/// ```
110+
Anonymous,
111+
}
112+
113+
impl CurrentFunction {
114+
#[inline]
115+
fn can_recurse(&self) -> bool {
116+
match self {
117+
CurrentFunction::Module => true,
118+
CurrentFunction::ModuleWithShadowingArgument => false,
119+
CurrentFunction::Anonymous => false,
120+
}
121+
}
122+
}
123+
79124
#[derive(Debug)]
80125
pub(crate) struct Generator<'module, 'ast> {
81126
module_name: EcoString,
82127
src_path: &'module Utf8Path,
83128
project_root: &'module Utf8Path,
84129
line_numbers: &'module LineNumbers,
85-
function_name: Option<EcoString>,
130+
function_name: EcoString,
86131
function_arguments: Vec<Option<&'module EcoString>>,
132+
current_function: CurrentFunction,
87133
pub current_scope_vars: im::HashMap<EcoString, usize>,
88134
pub function_position: Position,
89135
pub scope_position: Position,
@@ -137,15 +183,15 @@ impl<'module, 'a> Generator<'module, 'a> {
137183
tracker: &'module mut UsageTracker,
138184
mut current_scope_vars: im::HashMap<EcoString, usize>,
139185
) -> Self {
140-
let mut function_name = Some(function_name);
186+
let mut current_function = CurrentFunction::Module;
141187
for &name in function_arguments.iter().flatten() {
142188
// Initialise the function arguments
143189
let _ = current_scope_vars.insert(name.clone(), 0);
144190

145191
// If any of the function arguments shadow the current function then
146192
// recursion is no longer possible.
147-
if function_name.as_ref() == Some(name) {
148-
function_name = None;
193+
if function_name.as_ref() == name {
194+
current_function = CurrentFunction::ModuleWithShadowingArgument;
149195
}
150196
}
151197
Self {
@@ -158,6 +204,7 @@ impl<'module, 'a> Generator<'module, 'a> {
158204
function_arguments,
159205
tail_recursion_used: false,
160206
current_scope_vars,
207+
current_function,
161208
function_position: Position::Tail,
162209
scope_position: Position::Tail,
163210
statement_level: Vec::new(),
@@ -1307,7 +1354,8 @@ impl<'module, 'a> Generator<'module, 'a> {
13071354
// and we are in tail position we can avoid creating a new stack
13081355
// frame, enabling recursion with constant memory usage.
13091356
TypedExpr::Var { name, .. }
1310-
if self.function_name.as_ref() == Some(name)
1357+
if self.function_name == *name
1358+
&& self.current_function.can_recurse()
13111359
&& self.function_position.is_tail()
13121360
&& self.current_scope_vars.get(name) == Some(&0) =>
13131361
{
@@ -1366,10 +1414,10 @@ impl<'module, 'a> Generator<'module, 'a> {
13661414
let _ = self.current_scope_vars.insert(name.clone(), 0);
13671415
}
13681416

1369-
// This is a new function so unset the recorded name so that we don't
1417+
// This is a new function so track that so that we don't
13701418
// mistakenly trigger tail call optimisation
1371-
let mut name = None;
1372-
std::mem::swap(&mut self.function_name, &mut name);
1419+
let mut current_function = CurrentFunction::Anonymous;
1420+
std::mem::swap(&mut self.current_function, &mut current_function);
13731421

13741422
// Generate the function body
13751423
let result = self.statements(body);
@@ -1378,7 +1426,7 @@ impl<'module, 'a> Generator<'module, 'a> {
13781426
self.function_position = function_position;
13791427
self.scope_position = scope_position;
13801428
self.current_scope_vars = scope;
1381-
std::mem::swap(&mut self.function_name, &mut name);
1429+
std::mem::swap(&mut self.current_function, &mut current_function);
13821430

13831431
Ok(docvec![
13841432
docvec![
@@ -1610,12 +1658,7 @@ impl<'module, 'a> Generator<'module, 'a> {
16101658
{
16111659
self.tracker.make_error_used = true;
16121660
let module = self.module_name.clone().to_doc().surround('"', '"');
1613-
let function = self
1614-
.function_name
1615-
.clone()
1616-
.unwrap_or_default()
1617-
.to_doc()
1618-
.surround("\"", "\"");
1661+
let function = self.function_name.clone().to_doc().surround("\"", "\"");
16191662
let line = self.line_numbers.line_number(location.start).to_doc();
16201663
let fields = wrap_object(fields.into_iter().map(|(k, v)| (k.to_doc(), Some(v))));
16211664

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
source: compiler-core/src/javascript/tests/todo.rs
3+
assertion_line: 78
4+
expression: "\npub fn main() {\n fn() { todo }\n}\n"
5+
snapshot_kind: text
6+
---
7+
----- SOURCE CODE
8+
9+
pub fn main() {
10+
fn() { todo }
11+
}
12+
13+
14+
----- COMPILED JAVASCRIPT
15+
import { makeError } from "../gleam.mjs";
16+
17+
export function main() {
18+
return () => {
19+
throw makeError(
20+
"todo",
21+
"my/mod",
22+
3,
23+
"main",
24+
"`todo` expression evaluated. This code has not yet been implemented.",
25+
{}
26+
)
27+
};
28+
}

compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__use___no_callback_body.snap

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
---
22
source: compiler-core/src/javascript/tests/use_.rs
3+
assertion_line: 56
34
expression: "\npub fn main() {\n let thingy = fn(f) { f() }\n use <- thingy()\n}\n"
5+
snapshot_kind: text
46
---
57
----- SOURCE CODE
68

@@ -21,7 +23,7 @@ export function main() {
2123
"todo",
2224
"my/mod",
2325
4,
24-
"",
26+
"main",
2527
"`todo` expression evaluated. This code has not yet been implemented.",
2628
{}
2729
)

compiler-core/src/javascript/tests/todo.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,14 @@ pub fn unimplemented(message) {
7272
"#
7373
);
7474
}
75+
76+
#[test]
77+
fn inside_fn() {
78+
assert_js!(
79+
r#"
80+
pub fn main() {
81+
fn() { todo }
82+
}
83+
"#
84+
);
85+
}

0 commit comments

Comments
 (0)