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

[rust] Data structures for semantic analysis

This is two things: * A toy semantic analysis that handles a tiny subset of JS, including labeled statements, labeled break/continue, and variable declaration/reference/reassignment. This only exists as a way to prove out the API for the more important bit: * More importantly, this defines a data model for the semantic analysis results and an API for building up the semantic analysis. Subsequent diffs will replace the first bit (toy analysis impl), while keeping the second part.

Joe Savona committed Aug 3, 2023 at 12:41 UTC a67fefdebd12be50f61339f57cdf2a39a05237fc
11 files changed +1350 -87
compiler/forget/Cargo.lock
+5
@@ -566,8 +566,13 @@ dependencies = [
566 name = "forget_semantic_analysis"
567 version = "0.1.0"
568 dependencies = [
569 + "forget_diagnostics",
570 "forget_estree",
571 + "forget_hermes_parser",
572 "forget_utils",
573 + "indexmap 2.0.0",
574 + "insta",
575 + "serde_json",
576 ]
577
578 [[package]]
compiler/forget/Cargo.toml
+1
@@ -22,6 +22,7 @@ forget_fixtures = { path = "crates/forget_fixtures" }
22 forget_hermes_parser = { path = "crates/forget_hermes_parser" }
23 forget_hir = { path = "crates/forget_hir" }
24 forget_optimization = { path = "crates/forget_optimization" }
25 +forget_semantic_analysis = { path = "crates/forget_semantic_analysis" }
26 forget_ssa = { path = "crates/forget_ssa" }
27 forget_swc_demo = { path = "crates/forget_swc_demo" }
28 forget_utils = { path = "crates/forget_utils" }
compiler/forget/crates/forget_estree/src/visit.rs
+60 -32
@@ -2,10 +2,10 @@ use crate::{
2 AssignmentPropertyOrRestElement, AssignmentTarget, Class, ClassItem, ClassPrivateProperty,
3 ClassProperty, Declaration, DeclarationOrExpression, ExportAllDeclaration,
4 ExportDefaultDeclaration, ExportNamedDeclaration, Expression, ExpressionOrPrivateIdentifier,
5 - ExpressionOrSpread, ExpressionOrSuper, ForInInit, ForInit, Function, FunctionBody, Identifier,
6 - ImportDeclaration, ImportDeclarationSpecifier, ImportOrExportDeclaration, Literal,
7 - MethodDefinition, ModuleItem, Pattern, PrivateIdentifier, PrivateName, Program, Statement,
8 - StaticBlock, Super, SwitchCase, VariableDeclarator, _Literal,
5 + ExpressionOrSpread, ExpressionOrSuper, ForInInit, ForInit, Function, FunctionBody,
6 + FunctionDeclaration, Identifier, ImportDeclaration, ImportDeclarationSpecifier,
7 + ImportOrExportDeclaration, Literal, MethodDefinition, ModuleItem, Pattern, PrivateIdentifier,
8 + PrivateName, Program, Statement, StaticBlock, Super, SwitchCase, VariableDeclarator, _Literal,
9 };
10
11 /// Trait for visiting an estree
@@ -17,7 +17,18 @@ pub trait Visitor<'ast> {
17 f(self);
18 }
19
20 + fn visit_rvalue<F>(&mut self, f: F)
21 + where
22 + F: FnOnce(&mut Self) -> (),
23 + {
24 + f(self);
25 + }
26 +
27 fn visit_program(&mut self, program: &'ast Program) {
28 + self.default_visit_program(program)
29 + }
30 +
31 + fn default_visit_program(&mut self, program: &'ast Program) {
32 for item in &program.body {
33 self.visit_module_item(item);
34 }
@@ -73,7 +84,7 @@ pub trait Visitor<'ast> {
84 fn visit_import_declaration(&mut self, declaration: &'ast ImportDeclaration) {
85 self.visit_lvalue(|visitor| {
86 for specifier in &declaration.specifiers {
76 - visitor.visit_import_declaration_specifier(specifier, &declaration.source)
87 + visitor.visit_import_declaration_specifier(specifier);
88 }
89 });
90 self.visit_import_source(&declaration.source);
@@ -101,11 +112,7 @@ pub trait Visitor<'ast> {
112 }
113 }
114
104 - fn visit_import_declaration_specifier(
105 - &mut self,
106 - specifier: &'ast ImportDeclarationSpecifier,
107 - _source: &'ast _Literal,
108 - ) {
115 + fn visit_import_declaration_specifier(&mut self, specifier: &'ast ImportDeclarationSpecifier) {
116 match specifier {
117 ImportDeclarationSpecifier::ImportSpecifier(specifier) => {
118 self.visit_identifier(&specifier.local);
@@ -129,7 +136,7 @@ pub trait Visitor<'ast> {
136 self.visit_class(&declaration.class);
137 }
138 Declaration::FunctionDeclaration(declaration) => {
132 - self.visit_function(&declaration.function);
139 + self.visit_function_declaration(declaration);
140 }
141 Declaration::VariableDeclaration(declaration) => {
142 for declarator in &declaration.declarations {
@@ -142,6 +149,10 @@ pub trait Visitor<'ast> {
149 }
150 }
151
152 + fn visit_function_declaration(&mut self, declaration: &'ast FunctionDeclaration) {
153 + self.visit_function(&declaration.function);
154 + }
155 +
156 fn visit_statement(&mut self, stmt: &'ast Statement) {
157 self.default_visit_statement(stmt);
158 }
@@ -198,7 +209,7 @@ pub trait Visitor<'ast> {
209 self.visit_statement(&stmt.body);
210 }
211 Statement::FunctionDeclaration(stmt) => {
201 - self.visit_function(&stmt.function);
212 + self.visit_function_declaration(stmt);
213 }
214 Statement::IfStatement(stmt) => {
215 self.visit_expression(&stmt.test);
@@ -270,32 +281,28 @@ pub trait Visitor<'ast> {
281 }
282 for item in &class.body.body {
283 match item {
273 - ClassItem::MethodDefinition(item) => self.visit_method_definition(class, item),
284 + ClassItem::MethodDefinition(item) => self.visit_method_definition(item),
285 ClassItem::ClassProperty(item) => {
275 - self.visit_class_property(class, item);
286 + self.visit_class_property(item);
287 }
288 ClassItem::ClassPrivateProperty(item) => {
278 - self.visit_class_private_property(class, item);
289 + self.visit_class_private_property(item);
290 }
291 ClassItem::StaticBlock(item) => {
281 - self.visit_static_block(class, item);
292 + self.visit_static_block(item);
293 }
294 }
295 }
296 }
297
287 - fn visit_class_property(&mut self, _class: &'ast Class, property: &'ast ClassProperty) {
298 + fn visit_class_property(&mut self, property: &'ast ClassProperty) {
299 self.visit_expression(&property.key);
300 if let Some(value) = &property.value {
301 self.visit_expression(value)
302 }
303 }
304
294 - fn visit_class_private_property(
295 - &mut self,
296 - _class: &'ast Class,
297 - property: &'ast ClassPrivateProperty,
298 - ) {
305 + fn visit_class_private_property(&mut self, property: &'ast ClassPrivateProperty) {
306 match &property.key {
307 ExpressionOrPrivateIdentifier::Expression(key) => self.visit_expression(key),
308 ExpressionOrPrivateIdentifier::PrivateIdentifier(key) => {
@@ -308,21 +315,17 @@ pub trait Visitor<'ast> {
315 }
316 }
317
311 - fn visit_static_block(&mut self, _class: &'ast Class, property: &'ast StaticBlock) {
318 + fn visit_static_block(&mut self, property: &'ast StaticBlock) {
319 for stmt in &property.body {
320 self.visit_statement(stmt)
321 }
322 }
323
317 - fn visit_method_definition(&mut self, class: &'ast Class, method: &'ast MethodDefinition) {
318 - self.default_visit_method_definition(class, method);
324 + fn visit_method_definition(&mut self, method: &'ast MethodDefinition) {
325 + self.default_visit_method_definition(method);
326 }
327
321 - fn default_visit_method_definition(
322 - &mut self,
323 - _class: &'ast Class,
324 - method: &'ast MethodDefinition,
325 - ) {
328 + fn default_visit_method_definition(&mut self, method: &'ast MethodDefinition) {
329 self.visit_expression(&method.key);
330 self.visit_function(&method.value.function);
331 }
@@ -386,8 +389,10 @@ pub trait Visitor<'ast> {
389 }
390 Pattern::RestElement(pattern) => self.visit_pattern(&pattern.argument),
391 Pattern::AssignmentPattern(pattern) => {
389 - self.visit_expression(&pattern.right);
392 self.visit_pattern(&pattern.left);
393 + self.visit_rvalue(|visitor| {
394 + visitor.visit_expression(&pattern.right);
395 + });
396 }
397 }
398 }
@@ -460,8 +465,31 @@ pub trait Visitor<'ast> {
465 }
466 }
467 }
468 + Expression::CallExpression(expr) => {
469 + match &expr.callee {
470 + ExpressionOrSuper::Expression(callee) => self.visit_expression(callee),
471 + ExpressionOrSuper::Super(callee) => self.visit_super(callee),
472 + }
473 + for arg in &expr.arguments {
474 + match arg {
475 + ExpressionOrSpread::Expression(arg) => self.visit_expression(arg),
476 + ExpressionOrSpread::SpreadElement(arg) => {
477 + self.visit_expression(&arg.argument)
478 + }
479 + }
480 + }
481 + }
482 + Expression::UpdateExpression(expr) => {
483 + self.visit_expression(&expr.argument);
484 + }
485 + Expression::BooleanLiteral(_)
486 + | Expression::NullLiteral(_)
487 + | Expression::StringLiteral(_)
488 + | Expression::NumericLiteral(_) => {
489 + // no-op
490 + }
491 _ => {
464 - todo!("more expression types")
492 + todo!("{:#?}", expr)
493 }
494 }
495 }
compiler/forget/crates/forget_estree_swc/src/lib.rs
+26 -2
@@ -11,8 +11,8 @@ use swc_core::ecma::ast::{
11 AssignOp, BinaryOp, BlockStmt, BlockStmtOrExpr, Callee, Decl, EsVersion, Expr, ExprOrSpread,
12 Function, Ident, JSXAttr, JSXAttrName, JSXAttrOrSpread, JSXAttrValue, JSXElement,
13 JSXElementChild, JSXElementName, JSXExpr, JSXMemberExpr, JSXObject, Lit, MemberExpr,
14 - MemberProp, ModuleItem, OptChainBase, Pat, PatOrExpr, Program, Stmt, UnaryOp, VarDecl,
15 - VarDeclKind, VarDeclOrExpr,
14 + MemberProp, ModuleItem, OptChainBase, Pat, PatOrExpr, Program, Stmt, UnaryOp, UpdateOp,
15 + VarDecl, VarDeclKind, VarDeclOrExpr,
16 };
17 use swc_core::ecma::parser::{Syntax, TsConfig};
18 use swc_core::ecma::transforms::base::resolver;
@@ -271,6 +271,14 @@ fn convert_statement(cx: &Context, stmt: &Stmt) -> forget_estree::Statement {
271 range: convert_span(&item.span),
272 }))
273 }
274 + Stmt::Labeled(item) => {
275 + forget_estree::Statement::LabeledStatement(Box::new(forget_estree::LabeledStatement {
276 + label: convert_identifier(cx, &item.label),
277 + body: convert_statement(cx, &item.body),
278 + loc: None,
279 + range: convert_span(&item.span),
280 + }))
281 + }
282 _ => todo!("translate statement {:#?}", stmt),
283 }
284 }
@@ -461,6 +469,15 @@ fn convert_expression(cx: &Context, expr: &Expr) -> forget_estree::Expression {
469 forget_estree::Expression::JSXElement(Box::new(convert_jsx_element(cx, expr)))
470 }
471 Expr::Paren(expr) => convert_expression(cx, &expr.expr),
472 + Expr::Update(expr) => {
473 + forget_estree::Expression::UpdateExpression(Box::new(forget_estree::UpdateExpression {
474 + operator: convert_update_operator(expr.op),
475 + argument: convert_expression(cx, &expr.arg),
476 + prefix: expr.prefix,
477 + loc: None,
478 + range: convert_span(&expr.span),
479 + }))
480 + }
481 _ => todo!("translate expression {:#?}", expr),
482 }
483 }
@@ -885,6 +902,13 @@ fn convert_binary_operator(op: BinaryOp) -> Operator {
902 }
903 }
904
905 +fn convert_update_operator(op: UpdateOp) -> forget_estree::UpdateOperator {
906 + match op {
907 + UpdateOp::MinusMinus => forget_estree::UpdateOperator::Decrement,
908 + UpdateOp::PlusPlus => forget_estree::UpdateOperator::Increment,
909 + }
910 +}
911 +
912 fn convert_pattern(cx: &Context, pat: &Pat) -> forget_estree::Pattern {
913 match pat {
914 Pat::Ident(pat) => {
compiler/forget/crates/forget_fixtures/Cargo.toml
+1 -1
@@ -12,7 +12,7 @@ repository.workspace = true
12
13 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
14
15 -[dependencies]
15 +[dev-dependencies]
16 insta = { workspace = true }
17 forget_estree = { workspace = true }
18 forget_estree_swc = { workspace = true }
compiler/forget/crates/forget_semantic_analysis/Cargo.toml
+8 -1
@@ -12,5 +12,12 @@ repository.workspace = true
12 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
13
14 [dependencies]
15 +forget_diagnostics = { workspace = true }
16 forget_estree = { workspace = true }
16 -forget_utils = { workspace = true }
\ No newline at end of file
17 +forget_utils = { workspace = true }
18 +indexmap = { workspace = true }
19 +
20 +[dev-dependencies]
21 +forget_hermes_parser = { workspace = true }
22 +insta = { workspace = true }
23 +serde_json = { workspace = true }
\ No newline at end of file
compiler/forget/crates/forget_semantic_analysis/src/analyze.rs new
+500
@@ -0,0 +1,500 @@
1 +use forget_diagnostics::Diagnostic;
2 +use forget_estree::{
3 + BreakStatement, ContinueStatement, ESTreeNode, Identifier, LabeledStatement, Program,
4 + Statement, Visitor,
5 +};
6 +use forget_utils::PointerAddress;
7 +use indexmap::IndexMap;
8 +
9 +pub fn analyze(ast: &Program) -> SemanticAnalysis {
10 + let mut analyzer = Analyzer::new();
11 + analyzer.visit_program(ast);
12 + analyzer.results
13 +}
14 +
15 +pub struct SemanticAnalysis {
16 + root: ScopeId,
17 +
18 + // Storage of the semantic information
19 + scopes: Vec<Scope>,
20 + labels: Vec<Label>,
21 + declarations: Vec<Declaration>,
22 + references: Vec<Reference>,
23 +
24 + // Mapping of AST nodes (by pointer address) to semantic information
25 + // Not all nodes will have all types of information available
26 + node_scopes: IndexMap<AstNode, ScopeId>,
27 + node_labels: IndexMap<AstNode, LabelId>,
28 + node_declarations: IndexMap<AstNode, DeclarationId>,
29 + node_references: IndexMap<AstNode, ReferenceId>,
30 + diagnostics: Vec<Diagnostic>,
31 +}
32 +
33 +#[derive(Debug)]
34 +#[allow(dead_code)]
35 +pub struct SemanticAnalysisDebug<'a> {
36 + root: ScopeId,
37 +
38 + // Storage of the semantic information
39 + scopes: &'a Vec<Scope>,
40 + labels: &'a Vec<Label>,
41 + declarations: &'a Vec<Declaration>,
42 + references: &'a Vec<Reference>,
43 +}
44 +
45 +impl SemanticAnalysis {
46 + fn new() -> Self {
47 + let root_id = ScopeId(0);
48 + Self {
49 + root: root_id,
50 + scopes: vec![Scope {
51 + id: root_id,
52 + kind: ScopeKind::Global,
53 + parent: None,
54 + labels: Default::default(),
55 + declarations: Default::default(),
56 + references: Default::default(),
57 + children: Default::default(),
58 + }],
59 + labels: Default::default(),
60 + declarations: Default::default(),
61 + references: Default::default(),
62 + node_scopes: Default::default(),
63 + node_labels: Default::default(),
64 + node_declarations: Default::default(),
65 + node_references: Default::default(),
66 + diagnostics: Default::default(),
67 + }
68 + }
69 +
70 + pub fn debug(&self) -> SemanticAnalysisDebug<'_> {
71 + SemanticAnalysisDebug {
72 + root: self.root,
73 + scopes: &self.scopes,
74 + labels: &self.labels,
75 + declarations: &self.declarations,
76 + references: &self.references,
77 + }
78 + }
79 +
80 + pub fn root(&self) -> &Scope {
81 + &self.scopes[self.root.0]
82 + }
83 +
84 + pub fn scope(&self, id: ScopeId) -> &Scope {
85 + &self.scopes[id.0]
86 + }
87 +
88 + pub fn label(&self, id: LabelId) -> &Label {
89 + &self.labels[id.0]
90 + }
91 +
92 + pub fn declaration(&self, id: DeclarationId) -> &Declaration {
93 + &self.declarations[id.0]
94 + }
95 +
96 + pub fn reference(&self, id: ScopeId) -> &Reference {
97 + &self.references[id.0]
98 + }
99 +
100 + pub fn node_scope<T: ESTreeNode>(&self, node: &T) -> Option<&Scope> {
101 + self.node_scopes
102 + .get(&AstNode::from(node))
103 + .map(|id| &self.scopes[id.0])
104 + }
105 +
106 + pub fn node_label(&self, node: &LabeledStatement) -> Option<&Label> {
107 + self.node_labels
108 + .get(&AstNode::from(node))
109 + .map(|id| &self.labels[id.0])
110 + }
111 +
112 + pub fn break_label(&self, node: &BreakStatement) -> Option<&Label> {
113 + self.node_labels
114 + .get(&AstNode::from(node))
115 + .map(|id| &self.labels[id.0])
116 + }
117 +
118 + pub fn continue_label(&self, node: &ContinueStatement) -> Option<&Label> {
119 + self.node_labels
120 + .get(&AstNode::from(node))
121 + .map(|id| &self.labels[id.0])
122 + }
123 +
124 + pub fn node_declaration(&self, node: &Identifier) -> Option<&Declaration> {
125 + self.node_declarations
126 + .get(&AstNode::from(node))
127 + .map(|id| &self.declarations[id.0])
128 + }
129 +
130 + pub fn node_reference(&self, node: &Identifier) -> Option<&Reference> {
131 + self.node_references
132 + .get(&AstNode::from(node))
133 + .map(|id| &self.references[id.0])
134 + }
135 +
136 + pub fn lookup_label(&self, scope: ScopeId, name: &str) -> Option<&Label> {
137 + let mut current = &self.scopes[scope.0];
138 + loop {
139 + if let Some(id) = current.labels.get(name) {
140 + return Some(&self.labels[id.0]);
141 + }
142 + if let Some(parent) = current.parent {
143 + current = &self.scopes[parent.0];
144 + } else {
145 + return None;
146 + }
147 + }
148 + }
149 +
150 + pub fn lookup_declaration(&self, scope: ScopeId, name: &str) -> Option<&Declaration> {
151 + let mut current = &self.scopes[scope.0];
152 + loop {
153 + if let Some(id) = current.declarations.get(name) {
154 + return Some(&self.declarations[id.0]);
155 + }
156 + if let Some(parent) = current.parent {
157 + current = &self.scopes[parent.0];
158 + } else {
159 + return None;
160 + }
161 + }
162 + }
163 +
164 + pub(crate) fn root_id(&self) -> ScopeId {
165 + self.root
166 + }
167 +
168 + pub(crate) fn add_scope(&mut self, parent: ScopeId, kind: ScopeKind) -> ScopeId {
169 + let id = ScopeId(self.scopes.len());
170 + self.scopes.push(Scope {
171 + id,
172 + kind,
173 + parent: Some(parent),
174 + labels: Default::default(),
175 + declarations: Default::default(),
176 + references: Default::default(),
177 + children: Default::default(),
178 + });
179 + self.scopes[parent.0].children.push(id);
180 + id
181 + }
182 +
183 + pub(crate) fn add_label(&mut self, scope: ScopeId, kind: LabelKind, name: String) -> LabelId {
184 + let id = LabelId(self.labels.len());
185 + self.labels.push(Label { id, kind, scope });
186 + self.scopes[scope.0].labels.insert(name, id);
187 + id
188 + }
189 +
190 + pub(crate) fn add_declaration(
191 + &mut self,
192 + scope: ScopeId,
193 + name: String,
194 + kind: DeclarationKind,
195 + ) -> DeclarationId {
196 + let id = DeclarationId(self.declarations.len());
197 + self.declarations.push(Declaration { id, kind, scope });
198 + self.scopes[scope.0].declarations.insert(name, id);
199 + id
200 + }
201 +
202 + pub(crate) fn add_reference(
203 + &mut self,
204 + scope: ScopeId,
205 + kind: ReferenceKind,
206 + declaration: DeclarationId,
207 + ) -> ReferenceId {
208 + let id = ReferenceId(self.references.len());
209 + self.references.push(Reference {
210 + id,
211 + kind,
212 + declaration,
213 + scope,
214 + });
215 + self.scopes[scope.0].references.push(id);
216 + id
217 + }
218 +}
219 +
220 +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
221 +pub struct ScopeId(usize);
222 +
223 +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
224 +pub struct DeclarationId(usize);
225 +
226 +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
227 +pub struct ReferenceId(usize);
228 +
229 +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
230 +pub struct LabelId(usize);
231 +
232 +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
233 +pub enum ScopeKind {
234 + Global,
235 + Function,
236 + Class,
237 + Block,
238 +}
239 +
240 +#[derive(Debug, Clone)]
241 +pub struct Scope {
242 + pub id: ScopeId,
243 + pub kind: ScopeKind,
244 + pub parent: Option<ScopeId>,
245 + pub labels: IndexMap<String, LabelId>,
246 + pub declarations: IndexMap<String, DeclarationId>,
247 + pub references: Vec<ReferenceId>,
248 + pub children: Vec<ScopeId>,
249 +}
250 +
251 +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
252 +pub enum LabelKind {
253 + Loop,
254 + Other,
255 +}
256 +
257 +#[derive(Debug, Clone)]
258 +pub struct Label {
259 + pub id: LabelId,
260 + pub kind: LabelKind,
261 + pub scope: ScopeId,
262 +}
263 +
264 +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
265 +pub enum DeclarationKind {
266 + Const,
267 + Var,
268 + Let,
269 +}
270 +
271 +#[derive(Debug, Clone)]
272 +pub struct Declaration {
273 + pub id: DeclarationId,
274 + pub kind: DeclarationKind,
275 + pub scope: ScopeId,
276 +}
277 +
278 +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
279 +pub enum ReferenceKind {
280 + Read,
281 + Write,
282 + ReadWrite,
283 +}
284 +
285 +#[derive(Debug, Clone)]
286 +pub struct Reference {
287 + pub id: ReferenceId,
288 + pub kind: ReferenceKind,
289 + pub declaration: DeclarationId,
290 + pub scope: ScopeId,
291 +}
292 +
293 +#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
294 +struct AstNode(PointerAddress);
295 +
296 +impl AstNode {
297 + fn new<T: ESTreeNode>(node: &T) -> Self {
298 + Self(PointerAddress::new(node))
299 + }
300 +}
301 +
302 +impl<T> From<&T> for AstNode
303 +where
304 + T: ESTreeNode,
305 +{
306 + fn from(value: &T) -> Self {
307 + Self::new(value)
308 + }
309 +}
310 +
311 +impl<T> From<&mut T> for AstNode
312 +where
313 + T: ESTreeNode,
314 +{
315 + fn from(value: &mut T) -> Self {
316 + Self::new(value)
317 + }
318 +}
319 +
320 +struct Analyzer {
321 + results: SemanticAnalysis,
322 + current: ScopeId,
323 + is_lvalue: bool,
324 +}
325 +
326 +impl Analyzer {
327 + fn new() -> Self {
328 + let results = SemanticAnalysis::new();
329 + let current = results.root_id();
330 + Self {
331 + results,
332 + current,
333 + is_lvalue: false,
334 + }
335 + }
336 +
337 + fn enter<F>(&mut self, kind: ScopeKind, mut f: F) -> ScopeId
338 + where
339 + F: FnMut(&mut Self) -> (),
340 + {
341 + let scope = self.results.add_scope(self.current, kind);
342 + let previous = std::mem::replace(&mut self.current, scope);
343 + f(self);
344 + let scope = std::mem::replace(&mut self.current, previous);
345 + scope
346 + }
347 +}
348 +
349 +impl<'ast> Visitor<'ast> for Analyzer {
350 + fn visit_function_declaration(
351 + &mut self,
352 + declaration: &'ast forget_estree::FunctionDeclaration,
353 + ) {
354 + let scope = self.enter(ScopeKind::Function, |visitor| {
355 + visitor.visit_function(&declaration.function);
356 + });
357 + self.results
358 + .node_scopes
359 + .insert(AstNode::from(declaration), scope);
360 + }
361 +
362 + fn visit_statement(&mut self, stmt: &'ast forget_estree::Statement) {
363 + match stmt {
364 + Statement::LabeledStatement(stmt) => {
365 + let inner = &stmt.body;
366 + let kind = match inner {
367 + Statement::ForStatement(_)
368 + | Statement::ForInStatement(_)
369 + | Statement::ForOfStatement(_)
370 + | Statement::WhileStatement(_)
371 + | Statement::DoWhileStatement(_) => LabelKind::Loop,
372 + _ => LabelKind::Other,
373 + };
374 + let id = self
375 + .results
376 + .add_label(self.current, kind, stmt.label.name.clone());
377 + self.results
378 + .node_labels
379 + .insert(AstNode::from(stmt.as_ref()), id);
380 + self.visit_statement(&stmt.body);
381 + }
382 + Statement::BreakStatement(stmt) => {
383 + if let Some(label) = &stmt.label {
384 + if let Some(label) = self.results.lookup_label(self.current, &label.name) {
385 + self.results
386 + .node_labels
387 + .insert(AstNode::from(stmt.as_ref()), label.id);
388 + } else {
389 + self.results.diagnostics.push(Diagnostic::invalid_syntax(
390 + "Undefined break label",
391 + label.range,
392 + ));
393 + }
394 + }
395 + }
396 + Statement::ContinueStatement(stmt) => {
397 + if let Some(label_node) = &stmt.label {
398 + if let Some(label) = self.results.lookup_label(self.current, &label_node.name) {
399 + if label.kind == LabelKind::Loop {
400 + self.results
401 + .node_labels
402 + .insert(AstNode::from(stmt.as_ref()), label.id);
403 + } else {
404 + self.results.diagnostics.push(Diagnostic::invalid_syntax(
405 + "Invalid continue statement, can only continue to a label associated with a loop statement (for, for..in, for..of, etc)",
406 + label_node.range,
407 + ));
408 + }
409 + } else {
410 + self.results.diagnostics.push(Diagnostic::invalid_syntax(
411 + "Undefined continue label",
412 + label_node.range,
413 + ));
414 + }
415 + }
416 + }
417 + Statement::BlockStatement(stmt) => {
418 + let scope = self.enter(ScopeKind::Block, |visitor| {
419 + for item in &stmt.body {
420 + visitor.visit_statement(item);
421 + }
422 + });
423 + self.results
424 + .node_scopes
425 + .insert(AstNode::from(stmt.as_ref()), scope);
426 + }
427 + _ => {
428 + self.default_visit_statement(stmt);
429 + }
430 + }
431 + }
432 +
433 + fn visit_identifier(&mut self, identifier: &'ast Identifier) {
434 + if self.is_lvalue {
435 + let declaration = self
436 + .results
437 + .lookup_declaration(self.current, &identifier.name);
438 + if let Some(declaration) = declaration {
439 + let id = self.results.add_reference(
440 + self.current,
441 + ReferenceKind::ReadWrite,
442 + declaration.id,
443 + );
444 + self.results
445 + .node_references
446 + .insert(AstNode::from(identifier), id);
447 + } else {
448 + let id = self.results.add_declaration(
449 + self.current,
450 + identifier.name.clone(),
451 + DeclarationKind::Let,
452 + ); // TODO: determine the correct kind!
453 + self.results
454 + .node_declarations
455 + .insert(AstNode::from(identifier), id);
456 + }
457 + } else {
458 + let declaration = self
459 + .results
460 + .lookup_declaration(self.current, &identifier.name);
461 + if let Some(declaration) = declaration {
462 + let declaration_id = declaration.id;
463 + let id =
464 + self.results
465 + .add_reference(self.current, ReferenceKind::Read, declaration.id);
466 + self.results
467 + .node_references
468 + .insert(AstNode::from(identifier), id);
469 + } else {
470 + // Oops, undefined variable
471 + self.results.diagnostics.push(Diagnostic::invalid_syntax(
472 + "Undefined variable",
473 + identifier.range,
474 + ));
475 + }
476 + }
477 + }
478 +
479 + fn visit_literal(&mut self, _literal: &'ast forget_estree::Literal) {}
480 +
481 + fn visit_lvalue<F>(&mut self, f: F)
482 + where
483 + F: FnOnce(&mut Self) -> (),
484 + {
485 + let prev = self.is_lvalue;
486 + self.is_lvalue = true;
487 + f(self);
488 + self.is_lvalue = prev;
489 + }
490 +
491 + fn visit_rvalue<F>(&mut self, f: F)
492 + where
493 + F: FnOnce(&mut Self) -> (),
494 + {
495 + let prev = self.is_lvalue;
496 + self.is_lvalue = false;
497 + f(self);
498 + self.is_lvalue = prev;
499 + }
500 +}
compiler/forget/crates/forget_semantic_analysis/src/lib.rs
+2 -51
@@ -1,52 +1,3 @@
1 -use std::collections::HashMap;
1 +mod analyze;
2
3 -use forget_estree::{ESTreeNode, Identifier, Program};
4 -use forget_utils::PointerAddress;
5 -
6 -pub fn analyze<'ast>(ast: &'ast Program) -> SemanticAnalysis {
7 - todo!("Actually analyze code")
8 -}
9 -
10 -#[derive(Default, Debug)]
11 -pub struct SemanticAnalysis {
12 - scopes: HashMap<AstNode, Scope>,
13 - references: HashMap<AstNode, Reference>,
14 -}
15 -
16 -impl SemanticAnalysis {
17 - pub(crate) fn new() -> Self {
18 - Default::default()
19 - }
20 -
21 - fn scope<T: ESTreeNode>(&self, node: &T) -> Option<&Scope> {
22 - self.scopes.get(&node.into())
23 - }
24 -
25 - fn reference(&self, identifier: &Identifier) -> Option<&Reference> {
26 - self.references.get(&identifier.into())
27 - }
28 -}
29 -
30 -#[derive(Debug)]
31 -pub struct Scope {}
32 -
33 -#[derive(Debug)]
34 -pub struct Reference {}
35 -
36 -#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
37 -struct AstNode(PointerAddress);
38 -
39 -impl AstNode {
40 - fn new<T: ESTreeNode>(node: &T) -> Self {
41 - Self(PointerAddress::new(node))
42 - }
43 -}
44 -
45 -impl<T> From<&T> for AstNode
46 -where
47 - T: ESTreeNode,
48 -{
49 - fn from(value: &T) -> Self {
50 - Self::new(value)
51 - }
52 -}
3 +pub use analyze::analyze;
compiler/forget/crates/forget_semantic_analysis/tests/analysis_test.rs new
+19
@@ -0,0 +1,19 @@
1 +use forget_hermes_parser::parse;
2 +use forget_semantic_analysis::analyze;
3 +use insta::{assert_snapshot, glob};
4 +
5 +#[test]
6 +fn fixtures() {
7 + glob!("fixtures/**.js", |path| {
8 + println!("fixture {}", path.to_str().unwrap());
9 + let input = std::fs::read_to_string(path).unwrap();
10 + let ast = parse(&input, path.to_str().unwrap()).unwrap();
11 + let analysis = analyze(&ast);
12 +
13 + let ast_output = serde_json::to_string_pretty(&ast).unwrap();
14 + let analysis_output = format!("{:#?}", analysis.debug());
15 + assert_snapshot!(format!(
16 + "Input:\n{input}\n\nAST:\n{ast_output}\n\nAnalysis:\n{analysis_output}"
17 + ));
18 + });
19 +}
compiler/forget/crates/forget_semantic_analysis/tests/fixtures/labels.js new
+13
@@ -0,0 +1,13 @@
1 +function Component(props) {
2 + let y = 0;
3 + foo: for (let x = 0; x < 10; x++) {
4 + if (x == 7) {
5 + break foo;
6 + }
7 + y = x + y;
8 + continue foo;
9 + }
10 + bar: if (props) {
11 + break bar;
12 + }
13 +}
compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures.snap new
+715
@@ -0,0 +1,715 @@
1 +---
2 +source: crates/forget_semantic_analysis/tests/analysis_test.rs
3 +expression: "format!(\"Input:\\n{input}\\n\\nAST:\\n{ast_output}\\n\\nAnalysis:\\n{analysis_output}\")"
4 +input_file: crates/forget_semantic_analysis/tests/fixtures/labels.js
5 +---
6 +Input:
7 +function Component(props) {
8 + let y = 0;
9 + foo: for (let x = 0; x < 10; x++) {
10 + if (x == 7) {
11 + break foo;
12 + }
13 + y = x + y;
14 + continue foo;
15 + }
16 + bar: if (props) {
17 + break bar;
18 + }
19 +}
20 +
21 +
22 +AST:
23 +{
24 + "type": "Program",
25 + "body": [
26 + {
27 + "type": "FunctionDeclaration",
28 + "id": {
29 + "type": "Identifier",
30 + "name": "Component",
31 + "typeAnnotation": null,
32 + "loc": null,
33 + "range": {
34 + "start": 0,
35 + "end": 1
36 + }
37 + },
38 + "params": [
39 + {
40 + "type": "Identifier",
41 + "name": "props",
42 + "typeAnnotation": null,
43 + "loc": null,
44 + "range": {
45 + "start": 0,
46 + "end": 1
47 + }
48 + }
49 + ],
50 + "body": {
51 + "type": "BlockStatement",
52 + "body": [
53 + {
54 + "type": "VariableDeclaration",
55 + "kind": "let",
56 + "declarations": [
57 + {
58 + "type": "VariableDeclarator",
59 + "id": {
60 + "type": "Identifier",
61 + "name": "y",
62 + "typeAnnotation": null,
63 + "loc": null,
64 + "range": {
65 + "start": 0,
66 + "end": 1
67 + }
68 + },
69 + "init": {
70 + "type": "NumericLiteral",
71 + "value": 0,
72 + "loc": null,
73 + "range": {
74 + "start": 0,
75 + "end": 1
76 + }
77 + },
78 + "loc": null,
79 + "range": {
80 + "start": 0,
81 + "end": 1
82 + }
83 + }
84 + ],
85 + "loc": null,
86 + "range": {
87 + "start": 0,
88 + "end": 1
89 + }
90 + },
91 + {
92 + "type": "LabeledStatement",
93 + "label": {
94 + "type": "Identifier",
95 + "name": "foo",
96 + "typeAnnotation": null,
97 + "loc": null,
98 + "range": {
99 + "start": 0,
100 + "end": 1
101 + }
102 + },
103 + "body": {
104 + "type": "ForStatement",
105 + "init": {
106 + "type": "VariableDeclaration",
107 + "kind": "let",
108 + "declarations": [
109 + {
110 + "type": "VariableDeclarator",
111 + "id": {
112 + "type": "Identifier",
113 + "name": "x",
114 + "typeAnnotation": null,
115 + "loc": null,
116 + "range": {
117 + "start": 0,
118 + "end": 1
119 + }
120 + },
121 + "init": {
122 + "type": "NumericLiteral",
123 + "value": 0,
124 + "loc": null,
125 + "range": {
126 + "start": 0,
127 + "end": 1
128 + }
129 + },
130 + "loc": null,
131 + "range": {
132 + "start": 0,
133 + "end": 1
134 + }
135 + }
136 + ],
137 + "loc": null,
138 + "range": {
139 + "start": 0,
140 + "end": 1
141 + }
142 + },
143 + "test": {
144 + "type": "BinaryExpression",
145 + "left": {
146 + "type": "Identifier",
147 + "name": "x",
148 + "typeAnnotation": null,
149 + "loc": null,
150 + "range": {
151 + "start": 0,
152 + "end": 1
153 + }
154 + },
155 + "operator": "<",
156 + "right": {
157 + "type": "NumericLiteral",
158 + "value": 4621819117588971520,
159 + "loc": null,
160 + "range": {
161 + "start": 0,
162 + "end": 1
163 + }
164 + },
165 + "loc": null,
166 + "range": {
167 + "start": 0,
168 + "end": 1
169 + }
170 + },
171 + "update": {
172 + "type": "UpdateExpression",
173 + "operator": "++",
174 + "argument": {
175 + "type": "Identifier",
176 + "name": "x",
177 + "typeAnnotation": null,
178 + "loc": null,
179 + "range": {
180 + "start": 0,
181 + "end": 1
182 + }
183 + },
184 + "prefix": false,
185 + "loc": null,
186 + "range": {
187 + "start": 0,
188 + "end": 1
189 + }
190 + },
191 + "body": {
192 + "type": "BlockStatement",
193 + "body": [
194 + {
195 + "type": "IfStatement",
196 + "test": {
197 + "type": "BinaryExpression",
198 + "left": {
199 + "type": "Identifier",
200 + "name": "x",
201 + "typeAnnotation": null,
202 + "loc": null,
203 + "range": {
204 + "start": 0,
205 + "end": 1
206 + }
207 + },
208 + "operator": "==",
209 + "right": {
210 + "type": "NumericLiteral",
211 + "value": 4619567317775286272,
212 + "loc": null,
213 + "range": {
214 + "start": 0,
215 + "end": 1
216 + }
217 + },
218 + "loc": null,
219 + "range": {
220 + "start": 0,
221 + "end": 1
222 + }
223 + },
224 + "consequent": {
225 + "type": "BlockStatement",
226 + "body": [
227 + {
228 + "type": "BreakStatement",
229 + "label": {
230 + "type": "Identifier",
231 + "name": "foo",
232 + "typeAnnotation": null,
233 + "loc": null,
234 + "range": {
235 + "start": 0,
236 + "end": 1
237 + }
238 + },
239 + "loc": null,
240 + "range": {
241 + "start": 0,
242 + "end": 1
243 + }
244 + }
245 + ],
246 + "loc": null,
247 + "range": {
248 + "start": 0,
249 + "end": 1
250 + }
251 + },
252 + "alternate": null,
253 + "loc": null,
254 + "range": {
255 + "start": 0,
256 + "end": 1
257 + }
258 + },
259 + {
260 + "type": "ExpressionStatement",
261 + "expression": {
262 + "type": "AssignmentExpression",
263 + "operator": "=",
264 + "left": {
265 + "type": "Identifier",
266 + "name": "y",
267 + "typeAnnotation": null,
268 + "loc": null,
269 + "range": {
270 + "start": 0,
271 + "end": 1
272 + }
273 + },
274 + "right": {
275 + "type": "BinaryExpression",
276 + "left": {
277 + "type": "Identifier",
278 + "name": "x",
279 + "typeAnnotation": null,
280 + "loc": null,
281 + "range": {
282 + "start": 0,
283 + "end": 1
284 + }
285 + },
286 + "operator": "+",
287 + "right": {
288 + "type": "Identifier",
289 + "name": "y",
290 + "typeAnnotation": null,
291 + "loc": null,
292 + "range": {
293 + "start": 0,
294 + "end": 1
295 + }
296 + },
297 + "loc": null,
298 + "range": {
299 + "start": 0,
300 + "end": 1
301 + }
302 + },
303 + "loc": null,
304 + "range": {
305 + "start": 0,
306 + "end": 1
307 + }
308 + },
309 + "directive": null,
310 + "loc": null,
311 + "range": {
312 + "start": 0,
313 + "end": 1
314 + }
315 + },
316 + {
317 + "type": "ContinueStatement",
318 + "label": {
319 + "type": "Identifier",
320 + "name": "foo",
321 + "typeAnnotation": null,
322 + "loc": null,
323 + "range": {
324 + "start": 0,
325 + "end": 1
326 + }
327 + },
328 + "loc": null,
329 + "range": {
330 + "start": 0,
331 + "end": 1
332 + }
333 + }
334 + ],
335 + "loc": null,
336 + "range": {
337 + "start": 0,
338 + "end": 1
339 + }
340 + },
341 + "loc": null,
342 + "range": {
343 + "start": 0,
344 + "end": 1
345 + }
346 + },
347 + "loc": null,
348 + "range": {
349 + "start": 0,
350 + "end": 1
351 + }
352 + },
353 + {
354 + "type": "LabeledStatement",
355 + "label": {
356 + "type": "Identifier",
357 + "name": "bar",
358 + "typeAnnotation": null,
359 + "loc": null,
360 + "range": {
361 + "start": 0,
362 + "end": 1
363 + }
364 + },
365 + "body": {
366 + "type": "IfStatement",
367 + "test": {
368 + "type": "Identifier",
369 + "name": "props",
370 + "typeAnnotation": null,
371 + "loc": null,
372 + "range": {
373 + "start": 0,
374 + "end": 1
375 + }
376 + },
377 + "consequent": {
378 + "type": "BlockStatement",
379 + "body": [
380 + {
381 + "type": "BreakStatement",
382 + "label": {
383 + "type": "Identifier",
384 + "name": "bar",
385 + "typeAnnotation": null,
386 + "loc": null,
387 + "range": {
388 + "start": 0,
389 + "end": 1
390 + }
391 + },
392 + "loc": null,
393 + "range": {
394 + "start": 0,
395 + "end": 1
396 + }
397 + }
398 + ],
399 + "loc": null,
400 + "range": {
401 + "start": 0,
402 + "end": 1
403 + }
404 + },
405 + "alternate": null,
406 + "loc": null,
407 + "range": {
408 + "start": 0,
409 + "end": 1
410 + }
411 + },
412 + "loc": null,
413 + "range": {
414 + "start": 0,
415 + "end": 1
416 + }
417 + }
418 + ],
419 + "loc": null,
420 + "range": {
421 + "start": 0,
422 + "end": 1
423 + }
424 + },
425 + "generator": false,
426 + "async": false,
427 + "loc": null,
428 + "range": {
429 + "start": 0,
430 + "end": 1
431 + },
432 + "loc": null,
433 + "range": {
434 + "start": 0,
435 + "end": 1
436 + }
437 + }
438 + ],
439 + "sourceType": "script",
440 + "loc": null,
441 + "range": {
442 + "start": 0,
443 + "end": 1
444 + }
445 +}
446 +
447 +Analysis:
448 +SemanticAnalysisDebug {
449 + root: ScopeId(
450 + 0,
451 + ),
452 + scopes: [
453 + Scope {
454 + id: ScopeId(
455 + 0,
456 + ),
457 + kind: Global,
458 + parent: None,
459 + labels: {},
460 + declarations: {},
461 + references: [],
462 + children: [
463 + ScopeId(
464 + 1,
465 + ),
466 + ],
467 + },
468 + Scope {
469 + id: ScopeId(
470 + 1,
471 + ),
472 + kind: Function,
473 + parent: Some(
474 + ScopeId(
475 + 0,
476 + ),
477 + ),
478 + labels: {
479 + "foo": LabelId(
480 + 0,
481 + ),
482 + "bar": LabelId(
483 + 1,
484 + ),
485 + },
486 + declarations: {
487 + "props": DeclarationId(
488 + 0,
489 + ),
490 + "y": DeclarationId(
491 + 1,
492 + ),
493 + "x": DeclarationId(
494 + 2,
495 + ),
496 + },
497 + references: [
498 + ReferenceId(
499 + 0,
500 + ),
501 + ReferenceId(
502 + 1,
503 + ),
504 + ReferenceId(
505 + 6,
506 + ),
507 + ],
508 + children: [
509 + ScopeId(
510 + 2,
511 + ),
512 + ScopeId(
513 + 4,
514 + ),
515 + ],
516 + },
517 + Scope {
518 + id: ScopeId(
519 + 2,
520 + ),
521 + kind: Block,
522 + parent: Some(
523 + ScopeId(
524 + 1,
525 + ),
526 + ),
527 + labels: {},
528 + declarations: {},
529 + references: [
530 + ReferenceId(
531 + 2,
532 + ),
533 + ReferenceId(
534 + 3,
535 + ),
536 + ReferenceId(
537 + 4,
538 + ),
539 + ReferenceId(
540 + 5,
541 + ),
542 + ],
543 + children: [
544 + ScopeId(
545 + 3,
546 + ),
547 + ],
548 + },
549 + Scope {
550 + id: ScopeId(
551 + 3,
552 + ),
553 + kind: Block,
554 + parent: Some(
555 + ScopeId(
556 + 2,
557 + ),
558 + ),
559 + labels: {},
560 + declarations: {},
561 + references: [],
562 + children: [],
563 + },
564 + Scope {
565 + id: ScopeId(
566 + 4,
567 + ),
568 + kind: Block,
569 + parent: Some(
570 + ScopeId(
571 + 1,
572 + ),
573 + ),
574 + labels: {},
575 + declarations: {},
576 + references: [],
577 + children: [],
578 + },
579 + ],
580 + labels: [
581 + Label {
582 + id: LabelId(
583 + 0,
584 + ),
585 + kind: Loop,
586 + scope: ScopeId(
587 + 1,
588 + ),
589 + },
590 + Label {
591 + id: LabelId(
592 + 1,
593 + ),
594 + kind: Other,
595 + scope: ScopeId(
596 + 1,
597 + ),
598 + },
599 + ],
600 + declarations: [
601 + Declaration {
602 + id: DeclarationId(
603 + 0,
604 + ),
605 + kind: Let,
606 + scope: ScopeId(
607 + 1,
608 + ),
609 + },
610 + Declaration {
611 + id: DeclarationId(
612 + 1,
613 + ),
614 + kind: Let,
615 + scope: ScopeId(
616 + 1,
617 + ),
618 + },
619 + Declaration {
620 + id: DeclarationId(
621 + 2,
622 + ),
623 + kind: Let,
624 + scope: ScopeId(
625 + 1,
626 + ),
627 + },
628 + ],
629 + references: [
630 + Reference {
631 + id: ReferenceId(
632 + 0,
633 + ),
634 + kind: Read,
635 + declaration: DeclarationId(
636 + 2,
637 + ),
638 + scope: ScopeId(
639 + 1,
640 + ),
641 + },
642 + Reference {
643 + id: ReferenceId(
644 + 1,
645 + ),
646 + kind: Read,
647 + declaration: DeclarationId(
648 + 2,
649 + ),
650 + scope: ScopeId(
651 + 1,
652 + ),
653 + },
654 + Reference {
655 + id: ReferenceId(
656 + 2,
657 + ),
658 + kind: Read,
659 + declaration: DeclarationId(
660 + 2,
661 + ),
662 + scope: ScopeId(
663 + 2,
664 + ),
665 + },
666 + Reference {
667 + id: ReferenceId(
668 + 3,
669 + ),
670 + kind: ReadWrite,
671 + declaration: DeclarationId(
672 + 1,
673 + ),
674 + scope: ScopeId(
675 + 2,
676 + ),
677 + },
678 + Reference {
679 + id: ReferenceId(
680 + 4,
681 + ),
682 + kind: Read,
683 + declaration: DeclarationId(
684 + 2,
685 + ),
686 + scope: ScopeId(
687 + 2,
688 + ),
689 + },
690 + Reference {
691 + id: ReferenceId(
692 + 5,
693 + ),
694 + kind: Read,
695 + declaration: DeclarationId(
696 + 1,
697 + ),
698 + scope: ScopeId(
699 + 2,
700 + ),
701 + },
702 + Reference {
703 + id: ReferenceId(
704 + 6,
705 + ),
706 + kind: Read,
707 + declaration: DeclarationId(
708 + 0,
709 + ),
710 + scope: ScopeId(
711 + 1,
712 + ),
713 + },
714 + ],
715 +}