1
-use bumpalo::collections::{CollectIn, String};
1
+use bumpalo::collections::{String, Vec};
2
use estree::{
3
AssignmentTarget, BinaryExpression, ExpressionLike, ForInit, ForStatement, FunctionDeclaration,
4
IfStatement, Literal, LiteralValue, Pattern, Statement, VariableDeclarationKind,
9
PrimitiveValue, TerminalValue,
10
};
11
12
-use crate::builder::{Binding, Builder, LoopScope};
12
+use crate::{
13
+ builder::{Binding, Builder, LoopScope},
14
+ error::DiagnosticError,
15
+ BuildDiagnostic, ErrorSeverity,
16
+};
17
18
/// Converts a React function in ESTree format into HIR. Returns the HIR
19
/// if it was constructed sucessfully, otherwise a list of diagnostics
24
pub fn build<'a>(
25
environment: &'a Environment<'a>,
26
fun: FunctionDeclaration,
23
-) -> Result<Function<'a>, Diagnostic> {
27
+) -> Result<Function<'a>, BuildDiagnostic> {
28
let mut builder = Builder::new(environment);
29
30
lower_statement(environment, &mut builder, fun.body.unwrap(), None)?;
61
builder: &mut Builder<'a>,
62
stmt: Statement,
63
label: Option<String<'a>>,
60
-) -> Result<(), Diagnostic> {
64
+) -> Result<(), BuildDiagnostic> {
65
match stmt {
66
Statement::BlockStatement(stmt) => {
67
for stmt in stmt.body {
90
}
91
Statement::ReturnStatement(stmt) => {
92
let value = match stmt.argument {
89
- Some(argument) => lower_expression_to_temporary(env, builder, argument),
93
+ Some(argument) => lower_expression_to_temporary(env, builder, argument)?,
94
None => lower_value_to_temporary(
95
env,
96
builder,
105
);
106
}
107
Statement::ExpressionStatement(stmt) => {
104
- lower_expression_to_temporary(env, builder, stmt.expression);
108
+ lower_expression_to_temporary(env, builder, stmt.expression)?;
109
}
110
Statement::EmptyStatement(_) => {
111
// no-op
114
let kind = match stmt.kind {
115
VariableDeclarationKind::Const => InstructionKind::Const,
116
VariableDeclarationKind::Let => InstructionKind::Let,
113
- VariableDeclarationKind::Var => panic!("`var` declarations are not supported"),
117
+ VariableDeclarationKind::Var => {
118
+ return Err(BuildDiagnostic::new(
119
+ DiagnosticError::VariableDeclarationKindIsVar,
120
+ ErrorSeverity::Unsupported,
121
+ stmt.range,
122
+ ));
123
+ }
124
};
125
for declaration in stmt.declarations {
126
if let Some(init) = declaration.init {
117
- let value = lower_expression_to_temporary(env, builder, init);
127
+ let value = lower_expression_to_temporary(env, builder, init)?;
128
lower_assignment(
129
env,
130
builder,
131
kind,
132
AssignmentTarget::Pattern(declaration.id.into()),
133
value,
124
- );
134
+ )?;
135
} else {
136
if let Pattern::Identifier(id) = declaration.id {
137
// TODO: handle unbound variables
128
- let binding = builder.resolve_binding(&id).unwrap();
138
+ let binding = builder.resolve_binding(&id)?;
139
let identifier = match binding {
140
Binding::Local(identifier) => identifier,
131
- _ => panic!("Expected variable declaration to be a local binding"),
141
+ _ => {
142
+ return Err(BuildDiagnostic::new(
143
+ DiagnosticError::VariableDeclarationBindingIsNonLocal,
144
+ ErrorSeverity::Invariant,
145
+ id.range,
146
+ ));
147
+ }
148
};
149
let place = Place {
150
effect: None,
174
} = *stmt;
175
176
let consequent_block = builder.enter(BlockKind::Block, |builder| {
161
- lower_statement(env, builder, consequent, None).unwrap();
162
- TerminalValue::Goto(hir::GotoTerminal {
177
+ lower_statement(env, builder, consequent, None)?;
178
+ Ok(TerminalValue::Goto(hir::GotoTerminal {
179
block: fallthrough_block.id,
180
kind: GotoKind::Break,
165
- })
166
- });
181
+ }))
182
+ })?;
183
184
let alternate_block = builder.enter(BlockKind::Block, |builder| {
185
if let Some(alternate) = alternate {
170
- lower_statement(env, builder, alternate, None).unwrap();
186
+ lower_statement(env, builder, alternate, None)?;
187
}
172
- TerminalValue::Goto(hir::GotoTerminal {
188
+ Ok(TerminalValue::Goto(hir::GotoTerminal {
189
block: fallthrough_block.id,
190
kind: GotoKind::Break,
175
- })
176
- });
191
+ }))
192
+ })?;
193
178
- let test = lower_expression_to_temporary(env, builder, test);
194
+ let test = lower_expression_to_temporary(env, builder, test)?;
195
let terminal = TerminalValue::If(hir::IfTerminal {
196
test,
197
consequent: consequent_block,
217
218
let init_block = builder.enter(BlockKind::Loop, |builder| {
219
if let Some(ForInit::VariableDeclaration(decl)) = init {
204
- lower_statement(env, builder, Statement::VariableDeclaration(decl), None)
205
- .unwrap();
206
- TerminalValue::Goto(hir::GotoTerminal {
220
+ lower_statement(env, builder, Statement::VariableDeclaration(decl), None)?;
221
+ Ok(TerminalValue::Goto(hir::GotoTerminal {
222
block: test_block.id,
223
kind: GotoKind::Break,
209
- })
224
+ }))
225
} else {
211
- panic!("Expected for statement to have a variable declaration initializer")
226
+ Err(BuildDiagnostic::new(
227
+ DiagnosticError::ForStatementIsMissingInitializer,
228
+ ErrorSeverity::Todo,
229
+ None,
230
+ ))
231
}
213
- });
232
+ })?;
233
215
- let update_block = update.map(|update| {
216
- builder.enter(BlockKind::Loop, |builder| {
217
- lower_expression_to_temporary(env, builder, update);
218
- TerminalValue::Goto(hir::GotoTerminal {
219
- block: test_block.id,
220
- kind: GotoKind::Break,
234
+ let update_block = update
235
+ .map(|update| {
236
+ builder.enter(BlockKind::Loop, |builder| {
237
+ lower_expression_to_temporary(env, builder, update)?;
238
+ Ok(TerminalValue::Goto(hir::GotoTerminal {
239
+ block: test_block.id,
240
+ kind: GotoKind::Break,
241
+ }))
242
})
243
})
223
- });
244
+ .transpose()?;
245
246
let body_block = builder.enter(BlockKind::Block, |builder| {
247
let loop_ = LoopScope {
250
break_block: fallthrough_block.id,
251
};
252
builder.enter_loop(loop_, |builder| {
232
- lower_statement(env, builder, body, None).unwrap();
233
- TerminalValue::Goto(hir::GotoTerminal {
253
+ lower_statement(env, builder, body, None)?;
254
+ Ok(TerminalValue::Goto(hir::GotoTerminal {
255
block: update_block.unwrap_or(test_block.id),
256
kind: GotoKind::Continue,
236
- })
257
+ }))
258
})
238
- });
259
+ })?;
260
261
let terminal = TerminalValue::For(ForTerminal {
262
body: body_block,
268
builder.terminate_with_fallthrough(terminal, test_block);
269
270
if let Some(test) = test {
250
- let test_value = lower_expression_to_temporary(env, builder, test);
271
+ let test_value = lower_expression_to_temporary(env, builder, test)?;
272
let terminal = TerminalValue::Branch(BranchTerminal {
273
test: test_value,
274
consequent: body_block,
276
});
277
builder.terminate_with_fallthrough(terminal, fallthrough_block);
278
} else {
258
- panic!("Expected for statement to have a tesst block");
279
+ return Err(BuildDiagnostic::new(
280
+ DiagnosticError::ForStatementIsMissingTest,
281
+ ErrorSeverity::Todo,
282
+ stmt.range,
283
+ ));
284
}
285
}
286
_ => todo!("Lower {stmt:#?}"),
293
env: &'a Environment<'a>,
294
builder: &mut Builder<'a>,
295
expr: ExpressionLike,
271
-) -> Place<'a> {
272
- let value = lower_expression(env, builder, expr);
273
- lower_value_to_temporary(env, builder, value)
296
+) -> Result<Place<'a>, BuildDiagnostic> {
297
+ let value = lower_expression(env, builder, expr)?;
298
+ Ok(lower_value_to_temporary(env, builder, value))
299
}
300
301
/// Converts an ESTree Expression into an HIR InstructionValue. Note that while only a single
306
env: &'a Environment<'a>,
307
builder: &mut Builder<'a>,
308
expr: ExpressionLike,
284
-) -> InstructionValue<'a> {
285
- match expr {
309
+) -> Result<InstructionValue<'a>, BuildDiagnostic> {
310
+ Ok(match expr {
311
ExpressionLike::Identifier(expr) => {
312
// TODO: handle unbound variables
288
- let binding = builder.resolve_binding(&expr).unwrap();
313
+ let binding = builder.resolve_binding(&expr)?;
314
match binding {
315
Binding::Local(identifier) => {
316
let place = Place {
328
value: lower_primitive(env, builder, *expr),
329
}),
330
ExpressionLike::ArrayExpression(expr) => {
306
- let elements = expr
307
- .elements
308
- .into_iter()
309
- .map(|expr| match expr {
331
+ let mut elements = Vec::with_capacity_in(expr.elements.len(), &env.allocator);
332
+ for expr in expr.elements {
333
+ let element = match expr {
334
ExpressionLike::SpreadElement(expr) => ArrayElement::Spread(
311
- lower_expression_to_temporary(env, builder, expr.argument),
335
+ lower_expression_to_temporary(env, builder, expr.argument)?,
336
),
313
- _ => ArrayElement::Place(lower_expression_to_temporary(env, builder, expr)),
314
- })
315
- .collect_in(env.allocator);
337
+ _ => ArrayElement::Place(lower_expression_to_temporary(env, builder, expr)?),
338
+ };
339
+ elements.push(element);
340
+ }
341
InstructionValue::Array(hir::Array { elements })
342
}
343
344
ExpressionLike::AssignmentExpression(expr) => match expr.operator {
345
estree::AssignmentOperator::Equals => {
321
- let right = lower_expression_to_temporary(env, builder, expr.right);
322
- lower_assignment(env, builder, InstructionKind::Reassign, expr.left, right)
346
+ let right = lower_expression_to_temporary(env, builder, expr.right)?;
347
+ lower_assignment(env, builder, InstructionKind::Reassign, expr.left, right)?
348
}
349
_ => todo!("lower assignment expr {:#?}", expr),
350
},
356
right,
357
..
358
} = *expr;
334
- let left = lower_expression_to_temporary(env, builder, left);
335
- let right = lower_expression_to_temporary(env, builder, right);
359
+ let left = lower_expression_to_temporary(env, builder, left)?;
360
+ let right = lower_expression_to_temporary(env, builder, right)?;
361
InstructionValue::Binary(hir::Binary {
362
left,
363
operator,
367
368
// Cases that cannot appear in expression position but which are included in ExpressionLike
369
// to make serialization easier
345
- ExpressionLike::SpreadElement(_) => {
346
- panic!("SpreadElement may not appear in normal expression position")
370
+ ExpressionLike::SpreadElement(expr) => {
371
+ return Err(BuildDiagnostic::new(
372
+ DiagnosticError::NonExpressionInExpressionPosition,
373
+ ErrorSeverity::Invariant,
374
+ expr.range,
375
+ ));
376
}
377
_ => todo!("Lower expr {expr:#?}"),
349
- }
378
+ })
379
}
380
381
fn lower_assignment<'a>(
384
kind: InstructionKind,
385
lvalue: AssignmentTarget,
386
value: Place<'a>,
358
-) -> InstructionValue<'a> {
359
- match lvalue {
387
+) -> Result<InstructionValue<'a>, BuildDiagnostic> {
388
+ Ok(match lvalue {
389
AssignmentTarget::Pattern(lvalue) => match *lvalue {
390
Pattern::Identifier(lvalue) => {
362
- let place = lower_identifier_for_assignment(env, builder, kind, *lvalue).unwrap();
391
+ let place = lower_identifier_for_assignment(env, builder, kind, *lvalue)?;
392
let temporary = lower_value_to_temporary(
393
env,
394
builder,
402
_ => todo!("lower assignment pattern for {:#?}", lvalue),
403
},
404
_ => todo!("lower assignment for {:#?}", lvalue),
376
- }
405
+ })
406
}
407
408
fn lower_identifier_for_assignment<'a>(
410
builder: &mut Builder<'a>,
411
_kind: InstructionKind,
412
identifier: estree::Identifier,
384
-) -> Option<Place<'a>> {
413
+) -> Result<Place<'a>, BuildDiagnostic> {
414
let binding = builder.resolve_binding(&identifier)?;
415
match binding {
387
- Binding::Module(..) | Binding::Global => panic!("Cannot reassign a global"),
388
- Binding::Local(id) => Some(Place {
416
+ Binding::Module(..) | Binding::Global => Err(BuildDiagnostic::new(
417
+ DiagnosticError::ReassignedGlobal,
418
+ ErrorSeverity::InvalidReact,
419
+ identifier.range,
420
+ )),
421
+ Binding::Local(id) => Ok(Place {
422
identifier: id,
423
effect: None,
424
}),
473
_ => todo!("Lower literal {literal:#?}"),
474
}
475
}
443
-
444
-type Diagnostic = ();