@samitouri / QOS-React-2 / commits / d99da01698

[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)?;