[rust] basic VariableDeclaration support
Joe Savona committed
Jul 6, 2023 at 09:24 UTC
d99da0169827ff01fc49a08c27223f1fc969e1a2
12 files changed
+222
-82
compiler/forget/Cargo.lock
+1
@@ -587,6 +587,7 @@ name = "hir"
587
version = "0.1.0"
588
dependencies = [
589
"bumpalo",
590
+ "estree",
591
"indexmap 2.0.0",
592
"serde",
593
]
compiler/forget/crates/build-hir/src/build.rs
+79
-3
@@ -1,8 +1,11 @@
1
use bumpalo::collections::{CollectIn, String};
2
-use estree::{ExpressionLike, FunctionDeclaration, Literal, LiteralValue, Statement};
2
+use estree::{
3
+ ExpressionLike, FunctionDeclaration, Literal, LiteralValue, Pattern, Statement,
4
+ VariableDeclarationKind,
5
+};
6
use hir::{
4
- ArrayElement, BlockKind, Environment, Function, GotoKind, Identifier, InstructionValue,
5
- LoadGlobal, LoadLocal, Place, PrimitiveValue, TerminalValue,
7
+ ArrayElement, BlockKind, Environment, Function, GotoKind, Identifier, InstructionKind,
8
+ InstructionValue, LValue, LoadGlobal, LoadLocal, Place, PrimitiveValue, TerminalValue,
9
};
10
11
use crate::builder::{Binding, Builder};
@@ -102,6 +105,39 @@ fn lower_statement<'a>(
105
Statement::EmptyStatement(_) => {
106
// no-op
107
}
108
+ Statement::VariableDeclaration(stmt) => {
109
+ let kind = match stmt.kind {
110
+ VariableDeclarationKind::Const => InstructionKind::Const,
111
+ VariableDeclarationKind::Let => InstructionKind::Let,
112
+ VariableDeclarationKind::Var => panic!("`var` declarations are not supported"),
113
+ };
114
+ for declaration in stmt.declarations {
115
+ if let Some(init) = declaration.init {
116
+ let value = lower_expression_to_temporary(env, builder, init);
117
+ lower_assignment(env, builder, kind, declaration.id, value);
118
+ } else {
119
+ if let Pattern::Identifier(id) = declaration.id {
120
+ // TODO: handle unbound variables
121
+ let binding = builder.resolve_binding(&id).unwrap();
122
+ let identifier = match binding {
123
+ Binding::Local(identifier) => identifier,
124
+ _ => panic!("Expected variable declaration to be a local binding"),
125
+ };
126
+ let place = Place {
127
+ effect: None,
128
+ identifier,
129
+ };
130
+ lower_value_to_temporary(
131
+ env,
132
+ builder,
133
+ InstructionValue::DeclareLocal(hir::DeclareLocal {
134
+ lvalue: LValue { place, kind },
135
+ }),
136
+ );
137
+ }
138
+ }
139
+ }
140
+ }
141
_ => todo!("Lower {stmt:#?}"),
142
}
143
Ok(())
@@ -168,6 +204,46 @@ fn lower_expression<'a>(
204
}
205
}
206
207
+fn lower_assignment<'a>(
208
+ env: &'a Environment<'a>,
209
+ builder: &mut Builder<'a>,
210
+ kind: InstructionKind,
211
+ lvalue: Pattern,
212
+ value: Place<'a>,
213
+) -> InstructionValue<'a> {
214
+ match lvalue {
215
+ Pattern::Identifier(lvalue) => {
216
+ let place = lower_identifier_for_assignment(env, builder, kind, *lvalue).unwrap();
217
+ let temporary = lower_value_to_temporary(
218
+ env,
219
+ builder,
220
+ InstructionValue::StoreLocal(hir::StoreLocal {
221
+ lvalue: LValue { place, kind },
222
+ value,
223
+ }),
224
+ );
225
+ InstructionValue::LoadLocal(LoadLocal { place: temporary })
226
+ }
227
+ _ => todo!("lower assignment for {:#?}", lvalue),
228
+ }
229
+}
230
+
231
+fn lower_identifier_for_assignment<'a>(
232
+ env: &'a Environment<'a>,
233
+ builder: &mut Builder<'a>,
234
+ kind: InstructionKind,
235
+ identifier: estree::Identifier,
236
+) -> Option<Place<'a>> {
237
+ let binding = builder.resolve_binding(&identifier)?;
238
+ match binding {
239
+ Binding::Module(..) | Binding::Global => panic!("Cannot reassign a global"),
240
+ Binding::Local(id) => Some(Place {
241
+ identifier: id,
242
+ effect: None,
243
+ }),
244
+ }
245
+}
246
+
247
/// Given an already lowered InstructionValue:
248
/// - if the instruction is a LoadLocal for a temporary location, avoid the indirection
249
/// and return the place that the LoadLocal loads from
compiler/forget/crates/build-hir/src/builder.rs
+14
-36
@@ -21,7 +21,7 @@ use indexmap::IndexMap;
21
/// generally involves driving calls to enter/exit blocks, resolve
22
/// labels and variables, and then calling `build()` when the HIR
23
/// is complete.
24
-pub struct Builder<'a> {
24
+pub(crate) struct Builder<'a> {
25
#[allow(dead_code)]
26
environment: &'a Environment<'a>,
27
@@ -32,8 +32,12 @@ pub struct Builder<'a> {
32
wip: WipBlock<'a>,
33
34
id_gen: InstructionIdGenerator,
35
+}
36
36
- bindings: HashMap<(bumpalo::collections::String<'a>, BindingId), Identifier<'a>>,
37
+pub(crate) struct WipBlock<'a> {
38
+ pub id: BlockId,
39
+ pub kind: BlockKind,
40
+ pub instructions: Vec<'a, Instruction<'a>>,
41
}
42
43
impl<'a> Builder<'a> {
@@ -50,7 +54,6 @@ impl<'a> Builder<'a> {
54
entry,
55
wip: current,
56
id_gen: InstructionIdGenerator::new(),
53
- bindings: Default::default(),
57
}
58
}
59
@@ -148,35 +151,16 @@ impl<'a> Builder<'a> {
151
) -> Option<Binding<'a>> {
152
identifier.binding.as_ref().map(|binding| match binding {
153
estree::Binding::Global => Binding::Global,
151
- estree::Binding::Local(id) => {
152
- Binding::Local(self.resolve_binding_identifier(&identifier.name, *id))
153
- }
154
- estree::Binding::Module(id) => {
155
- Binding::Module(self.resolve_binding_identifier(&identifier.name, *id))
156
- }
154
+ estree::Binding::Local(id) => Binding::Local(
155
+ self.environment
156
+ .resolve_binding_identifier(&identifier.name, *id),
157
+ ),
158
+ estree::Binding::Module(id) => Binding::Module(
159
+ self.environment
160
+ .resolve_binding_identifier(&identifier.name, *id),
161
+ ),
162
})
163
}
159
-
160
- fn resolve_binding_identifier(&mut self, name: &str, binding_id: BindingId) -> Identifier<'a> {
161
- let key_name = bumpalo::collections::String::from_str_in(name, &self.environment.allocator);
162
- if let Some(identifier) = self.bindings.get(&(key_name.clone(), binding_id)) {
163
- identifier.clone()
164
- } else {
165
- let id = self.environment.next_identifier_id();
166
- let identifier = Identifier {
167
- id,
168
- name: Some(key_name.clone()),
169
- data: Rc::new(RefCell::new(IdentifierData {
170
- mutable_range: Default::default(),
171
- scope: None,
172
- type_: Type::Var(self.environment.next_type_var_id()),
173
- })),
174
- };
175
- self.bindings
176
- .insert((key_name, binding_id), identifier.clone());
177
- identifier
178
- }
179
- }
164
}
165
166
pub(crate) enum Binding<'a> {
@@ -231,12 +215,6 @@ fn reverse_postorder_blocks<'a>(hir: &mut HIR<'a>) {
215
hir.blocks = blocks;
216
}
217
234
-pub(crate) struct WipBlock<'a> {
235
- pub id: BlockId,
236
- pub kind: BlockKind,
237
- pub instructions: Vec<'a, Instruction<'a>>,
238
-}
239
-
218
/// Prunes ForTerminal.update values (sets to None) if they are unreachable
219
fn remove_unreachable_for_updates<'a>(hir: &mut HIR<'a>) {
220
let block_ids: HashSet<BlockId> = hir.blocks.keys().cloned().collect();
compiler/forget/crates/estree-swc/src/lib.rs
+34
-19
@@ -7,7 +7,7 @@ use swc_core::common::source_map::Pos;
7
use swc_core::common::{FileName, FilePathMapping, Mark, SourceMap, Span, SyntaxContext, GLOBALS};
8
use swc_core::ecma::ast::{
9
AssignOp, BinaryOp, BlockStmt, Decl, EsVersion, Expr, Ident, Lit, ModuleItem, Pat, PatOrExpr,
10
- Program, Stmt, UnaryOp, VarDeclKind, VarDeclOrExpr,
10
+ Program, Stmt, UnaryOp, VarDecl, VarDeclKind, VarDeclOrExpr,
11
};
12
use swc_core::ecma::parser::Syntax;
13
use swc_core::ecma::transforms::base::resolver;
@@ -95,7 +95,7 @@ fn convert_module_item(cx: &Context, item: &ModuleItem) -> estree::ModuleItem {
95
ModuleItem::Stmt(item) => {
96
estree::ModuleItem::Statement(Box::new(convert_statement(cx, item)))
97
}
98
- _ => todo!("Convert {:#?}", item),
98
+ _ => todo!("translate module item {:#?}", item),
99
}
100
}
101
@@ -152,6 +152,9 @@ fn convert_statement(cx: &Context, stmt: &Stmt) -> estree::Statement {
152
range: convert_span(&item.function.span),
153
}))
154
}
155
+ Stmt::Decl(Decl::Var(item)) => {
156
+ estree::Statement::VariableDeclaration(Box::new(convert_variable_declaration(cx, item)))
157
+ }
158
Stmt::Block(item) => {
159
estree::Statement::BlockStatement(Box::new(convert_block_statement(cx, item)))
160
}
@@ -207,18 +210,9 @@ fn convert_statement(cx: &Context, stmt: &Stmt) -> estree::Statement {
210
}
211
VarDeclOrExpr::VarDecl(init) => {
212
assert_eq!(init.decls.len(), 1);
210
- let decl = &init.decls[0];
211
- estree::ForInit::VariableDeclaration(Box::new(estree::VariableDeclaration {
212
- kind: convert_decl_kind(&init.kind),
213
- declarations: vec![estree::VariableDeclarator {
214
- id: convert_pattern(cx, &decl.name),
215
- init: decl.init.as_ref().map(|init| convert_expression(cx, init)),
216
- loc: None,
217
- range: convert_span(&decl.span),
218
- }],
219
- loc: None,
220
- range: convert_span(&init.span),
221
- }))
213
+ estree::ForInit::VariableDeclaration(Box::new(convert_variable_declaration(
214
+ cx, init,
215
+ )))
216
}
217
}),
218
test: item.test.as_ref().map(|test| convert_expression(cx, test)),
@@ -249,7 +243,28 @@ fn convert_statement(cx: &Context, stmt: &Stmt) -> estree::Statement {
243
loc: None,
244
range: convert_span(&item.span),
245
})),
252
- _ => todo!(),
246
+ _ => todo!("translate statement {:#?}", stmt),
247
+ }
248
+}
249
+
250
+fn convert_variable_declaration(cx: &Context, decl: &VarDecl) -> estree::VariableDeclaration {
251
+ estree::VariableDeclaration {
252
+ kind: convert_decl_kind(&decl.kind),
253
+ declarations: decl
254
+ .decls
255
+ .iter()
256
+ .map(|declarator| estree::VariableDeclarator {
257
+ id: convert_pattern(cx, &declarator.name),
258
+ init: declarator
259
+ .init
260
+ .as_ref()
261
+ .map(|init| convert_expression(cx, init)),
262
+ loc: None,
263
+ range: convert_span(&decl.span),
264
+ })
265
+ .collect(),
266
+ loc: None,
267
+ range: convert_span(&decl.span),
268
}
269
}
270
@@ -351,12 +366,12 @@ fn convert_expression(cx: &Context, expr: &Expr) -> estree::ExpressionLike {
366
range: convert_span(&expr.span),
367
}))
368
}
354
- _ => todo!(),
369
+ _ => todo!("translate expression {:#?}", expr),
370
}
371
}
372
373
fn convert_assignment_target(_target: &PatOrExpr) -> estree::AssignmentTarget {
359
- todo!()
374
+ todo!("translate assignment target")
375
}
376
377
fn convert_unary_operator(op: UnaryOp) -> estree::UnaryOperator {
@@ -374,7 +389,7 @@ fn convert_unary_operator(op: UnaryOp) -> estree::UnaryOperator {
389
fn convert_assignment_operator(op: AssignOp) -> estree::AssignmentOperator {
390
match op {
391
AssignOp::AddAssign => estree::AssignmentOperator::PlusEquals,
377
- _ => todo!(),
392
+ _ => todo!("translate assignment operator"),
393
}
394
}
395
@@ -422,7 +437,7 @@ fn convert_pattern(cx: &Context, pat: &Pat) -> estree::Pattern {
437
loc: None,
438
range: convert_span(&pat.span),
439
})),
425
- _ => todo!(),
440
+ _ => todo!("translate pattern {:#?}", pat),
441
}
442
}
443
compiler/forget/crates/estree/src/lib.rs
+16
-16
@@ -1038,6 +1038,22 @@ pub enum Binding {
1038
Global,
1039
}
1040
1041
+#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
1042
+#[serde(transparent)]
1043
+pub struct BindingId(NonZeroU32);
1044
+
1045
+impl BindingId {
1046
+ pub fn new(id: NonZeroU32) -> Self {
1047
+ Self(id)
1048
+ }
1049
+}
1050
+
1051
+impl From<BindingId> for u32 {
1052
+ fn from(value: BindingId) -> Self {
1053
+ value.0.into()
1054
+ }
1055
+}
1056
+
1057
#[derive(Serialize, Deserialize, Debug)]
1058
pub struct MemberExpression {
1059
pub object: ExpressionLike,
@@ -1253,22 +1269,6 @@ pub struct JSXText {
1269
pub range: Option<SourceRange>,
1270
}
1271
1256
-#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)]
1257
-#[serde(transparent)]
1258
-pub struct BindingId(NonZeroU32);
1259
-
1260
-impl BindingId {
1261
- pub fn new(id: NonZeroU32) -> Self {
1262
- Self(id)
1263
- }
1264
-}
1265
-
1266
-impl From<BindingId> for u32 {
1267
- fn from(value: BindingId) -> Self {
1268
- value.0.into()
1269
- }
1270
-}
1271
-
1272
#[cfg(test)]
1273
mod tests {
1274
use super::*;
compiler/forget/crates/fixtures/tests/fixtures/identifiers.js
+4
@@ -7,5 +7,9 @@ function id(x) {
7
// FOO;
8
Math;
9
id;
10
+ let y = true;
11
+ y;
12
+ let z;
13
+ z;
14
return x;
15
}
compiler/forget/crates/fixtures/tests/fixtures_test.rs
-2
@@ -11,8 +11,6 @@ fn fixtures() {
11
let input = std::fs::read_to_string(path).unwrap();
12
let ast = parse(&input, path.to_str().unwrap()).unwrap();
13
14
- println!("{:#?}", &ast);
15
-
14
let mut output = String::new();
15
16
for (ix, item) in ast.body.into_iter().enumerate() {
compiler/forget/crates/fixtures/tests/snapshots/fixtures_test__fixtures@identifiers.js.snap
+11
-2
@@ -13,6 +13,10 @@ function id(x) {
13
// FOO;
14
Math;
15
id;
16
+ let y = true;
17
+ y;
18
+ let z;
19
+ z;
20
return x;
21
}
22
@@ -22,6 +26,11 @@ entry bb0
26
bb0
27
[0] unknown $0 = LoadGlobal Math
28
[1] unknown $2 = LoadGlobal id
25
- [2] unknown $4 = LoadLocal unknown x$3
26
- [3] Return unknown $4
29
+ [2] unknown $3 = true
30
+ [3] unknown $5 = StoreLocal Let unknown y$4 = unknown $3
31
+ [4] unknown $6 = LoadLocal unknown y$4
32
+ [5] unknown $8 = DeclareLocal Let unknown z$7
33
+ [6] unknown $9 = LoadLocal unknown z$7
34
+ [7] unknown $11 = LoadLocal unknown x$10
35
+ [8] Return unknown $11
36
compiler/forget/crates/hir/Cargo.toml
+1
@@ -7,5 +7,6 @@ edition = "2021"
7
8
[dependencies]
9
bumpalo = { version = "3.13.0", features = ["boxed", "collections"] }
10
+estree = { path = "../estree" }
11
indexmap = "2.0.0"
12
serde = "1.0.164"
compiler/forget/crates/hir/src/environment.rs
+33
-2
@@ -1,8 +1,15 @@
1
-use std::cell::Cell;
1
+use std::{
2
+ cell::{Cell, RefCell},
3
+ collections::HashMap,
4
+ rc::Rc,
5
+};
6
7
use bumpalo::Bump;
8
+use estree::BindingId;
9
5
-use crate::{BlockId, Features, IdentifierId, Registry, TypeVarId};
10
+use crate::{
11
+ BlockId, Features, Identifier, IdentifierData, IdentifierId, Registry, Type, TypeVarId,
12
+};
13
14
/// Stores all the contextual information about the top-level React function being
15
/// compiled. Environments may not be reused between React functions, but *are*
@@ -28,6 +35,8 @@ pub struct Environment<'a> {
35
next_identifier_id: Cell<IdentifierId>,
36
37
next_type_var_id: Cell<TypeVarId>,
38
+
39
+ bindings: Rc<RefCell<HashMap<(bumpalo::collections::String<'a>, BindingId), Identifier<'a>>>>,
40
}
41
42
impl<'a> Environment<'a> {
@@ -39,6 +48,7 @@ impl<'a> Environment<'a> {
48
next_block_id: Cell::new(BlockId(0)),
49
next_identifier_id: Cell::new(IdentifierId(0)),
50
next_type_var_id: Cell::new(TypeVarId(0)),
51
+ bindings: Default::default(),
52
}
53
}
54
@@ -67,4 +77,25 @@ impl<'a> Environment<'a> {
77
self.next_type_var_id.set(id.next());
78
id
79
}
80
+
81
+ pub fn resolve_binding_identifier(&self, name: &str, binding_id: BindingId) -> Identifier<'a> {
82
+ let key_name = bumpalo::collections::String::from_str_in(name, &self.allocator);
83
+ let mut bindings = self.bindings.borrow_mut();
84
+ if let Some(identifier) = bindings.get(&(key_name.clone(), binding_id)) {
85
+ identifier.clone()
86
+ } else {
87
+ let id = self.next_identifier_id();
88
+ let identifier = Identifier {
89
+ id,
90
+ name: Some(key_name.clone()),
91
+ data: Rc::new(RefCell::new(IdentifierData {
92
+ mutable_range: Default::default(),
93
+ scope: None,
94
+ type_: Type::Var(self.next_type_var_id()),
95
+ })),
96
+ };
97
+ bindings.insert((key_name, binding_id), identifier.clone());
98
+ identifier
99
+ }
100
+ }
101
}
compiler/forget/crates/hir/src/instruction.rs
+10
@@ -145,6 +145,16 @@ pub enum InstructionKind {
145
Reassign,
146
}
147
148
+impl Display for InstructionKind {
149
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
150
+ match self {
151
+ Self::Const => f.write_str("Const"),
152
+ Self::Let => f.write_str("Let"),
153
+ Self::Reassign => f.write_str("Reassign"),
154
+ }
155
+ }
156
+}
157
+
158
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
159
pub enum Effect {
160
/// This reference freezes the value (corresponds to a place where codegen should emit a freeze instruction)
compiler/forget/crates/hir/src/print.rs
+19
-2
@@ -1,8 +1,8 @@
1
use std::fmt::{Result, Write};
2
3
use crate::{
4
- ArrayElement, BasicBlock, Function, Instruction, InstructionValue, Place, PrimitiveValue,
5
- Terminal, TerminalValue,
4
+ ArrayElement, BasicBlock, Function, Instruction, InstructionValue, LValue, Place,
5
+ PrimitiveValue, Terminal, TerminalValue,
6
};
7
8
/// Trait for HIR types to describe how they print themselves.
@@ -80,6 +80,16 @@ impl<'a> Print for InstructionValue<'a> {
80
PrimitiveValue::Undefined => write!(out, "<undefined>")?,
81
};
82
}
83
+ InstructionValue::StoreLocal(value) => {
84
+ write!(out, "StoreLocal ")?;
85
+ value.lvalue.print(out)?;
86
+ write!(out, " = ")?;
87
+ value.value.print(out)?;
88
+ }
89
+ InstructionValue::DeclareLocal(value) => {
90
+ write!(out, "DeclareLocal ")?;
91
+ value.lvalue.print(out)?;
92
+ }
93
_ => write!(out, "{:?}", self)?,
94
}
95
Ok(())
@@ -117,6 +127,13 @@ impl<'a> Print for Place<'a> {
127
}
128
}
129
130
+impl<'a> Print for LValue<'a> {
131
+ fn print(&self, out: &mut impl Write) -> Result {
132
+ write!(out, "{} ", self.kind)?;
133
+ self.place.print(out)
134
+ }
135
+}
136
+
137
impl<'a> Print for Terminal<'a> {
138
fn print(&self, out: &mut impl Write) -> Result {
139
write!(out, " {} ", self.id)?;