From 6bca9fb7a3f991b39c14a3f1c8a7bb00b1510a9a Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Thu, 10 Aug 2023 10:59:48 -0400 Subject: [PATCH] [rust] Update HIR builder to use new semantic analysis Updates HIR builder to rely on the new semantic analysis instead of assuming that the ast nodes will already have binding info attached. That was a stopgap until we had our own name resolution :-) Once this lands we can remove SWC and switch everything to forget_hermes_parser, and also remove the non-spec Identifier.binding field (which stored the temporary name resolution data). --- compiler/forget/Cargo.lock | 3 + .../forget/crates/forget_build_hir/Cargo.toml | 1 + .../crates/forget_build_hir/src/build.rs | 399 ++++----- .../crates/forget_build_hir/src/builder.rs | 23 - .../crates/forget_build_hir/src/context.rs | 108 ++- .../forget_estree/src/generated_extensions.rs | 52 +- .../forget/crates/forget_estree/src/lib.rs | 2 +- .../forget/crates/forget_fixtures/Cargo.toml | 1 + .../tests/fixtures/simple-function.js | 9 + .../forget_fixtures/tests/fixtures_test.rs | 20 +- ...est__fixtures@function-expressions.js.snap | 44 +- ...ixtures_test__fixtures@identifiers.js.snap | 12 +- ...res_test__fixtures@simple-function.js.snap | 46 + .../fixtures_test__fixtures@use-memo.js.snap | 12 +- compiler/forget/crates/forget_hir/Cargo.toml | 1 + .../crates/forget_hir/src/environment.rs | 47 +- .../forget_semantic_analysis/src/analyzer.rs | 19 +- .../forget_semantic_analysis/src/lib.rs | 1 + .../src/scope_manager.rs | 61 +- .../src/scope_view.rs | 139 +++- .../tests/analysis_test.rs | 5 +- .../tests/fixtures/simple-function.js | 9 + ...test__fixtures@globals-and-imports.js.snap | 787 +----------------- .../analysis_test__fixtures@labels.js.snap | 427 +--------- ...sis_test__fixtures@simple-function.js.snap | 223 +++++ 25 files changed, 876 insertions(+), 1575 deletions(-) create mode 100644 compiler/forget/crates/forget_fixtures/tests/fixtures/simple-function.js create mode 100644 compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@simple-function.js.snap create mode 100644 compiler/forget/crates/forget_semantic_analysis/tests/fixtures/simple-function.js create mode 100644 compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@simple-function.js.snap diff --git a/compiler/forget/Cargo.lock b/compiler/forget/Cargo.lock index 572e7b2e9f..5c2b5456cd 100644 --- a/compiler/forget/Cargo.lock +++ b/compiler/forget/Cargo.lock @@ -462,6 +462,7 @@ dependencies = [ "forget_diagnostics", "forget_estree", "forget_hir", + "forget_semantic_analysis", "indexmap 2.0.0", "thiserror", ] @@ -517,6 +518,7 @@ dependencies = [ "forget_estree_swc", "forget_hir", "forget_optimization", + "forget_semantic_analysis", "forget_ssa", "insta", "miette 5.9.0", @@ -541,6 +543,7 @@ version = "0.1.0" dependencies = [ "forget_diagnostics", "forget_estree", + "forget_semantic_analysis", "forget_utils", "indexmap 2.0.0", "serde", diff --git a/compiler/forget/crates/forget_build_hir/Cargo.toml b/compiler/forget/crates/forget_build_hir/Cargo.toml index 2a3ffcbd52..f0334cff7e 100644 --- a/compiler/forget/crates/forget_build_hir/Cargo.toml +++ b/compiler/forget/crates/forget_build_hir/Cargo.toml @@ -17,4 +17,5 @@ forget_hir = { workspace = true } forget_estree = { workspace = true} indexmap = { workspace = true } forget_diagnostics = { workspace = true } +forget_semantic_analysis = { workspace = true } thiserror = { workspace = true } diff --git a/compiler/forget/crates/forget_build_hir/src/build.rs b/compiler/forget/crates/forget_build_hir/src/build.rs index ce0f2282fb..db67841706 100644 --- a/compiler/forget/crates/forget_build_hir/src/build.rs +++ b/compiler/forget/crates/forget_build_hir/src/build.rs @@ -3,8 +3,9 @@ use std::collections::HashSet; use forget_diagnostics::Diagnostic; use forget_estree::{ AssignmentTarget, BinaryExpression, BlockStatement, Expression, ExpressionOrSpread, - ExpressionOrSuper, ForInit, ForStatement, Function, IfStatement, JsValue, Literal, Pattern, - Statement, VariableDeclarationKind, + ExpressionOrSuper, ForInit, ForStatement, Function, FunctionExpression, IfStatement, + IntoFunction, JsValue, Literal, Pattern, Statement, VariableDeclaration, + VariableDeclarationKind, }; use forget_hir::{ BlockKind, BranchTerminal, Environment, ForTerminal, GotoKind, IdentifierOperand, InstrIx, @@ -22,33 +23,18 @@ use crate::error::BuildHIRError; /// /// Failures generally include nonsensical input (`delete 1`) or syntax /// that is not yet supported. -pub fn build(env: &Environment, fun: Function) -> Result, Diagnostic> { +pub fn build(env: &Environment, fun: &Function) -> Result, Diagnostic> { let mut builder = Builder::new(env); - match fun.body { - Some(forget_estree::FunctionBody::BlockStatement(body)) => { - lower_block_statement(env, &mut builder, *body)? - } - Some(forget_estree::FunctionBody::Expression(body)) => { - lower_expression(env, &mut builder, body)?; - } - None => { - return Err(Diagnostic::invalid_syntax( - BuildHIRError::EmptyFunction, - fun.range, - )); - } - } - let mut params = Vec::with_capacity(fun.params.len()); - for param in fun.params { + for param in &fun.params { match param { Pattern::Identifier(param) => { let identifier = lower_identifier_for_assignment( env, &mut builder, InstructionKind::Let, - *param, + param, )?; params.push(identifier); } @@ -61,6 +47,21 @@ pub fn build(env: &Environment, fun: Function) -> Result { + lower_block_statement(env, &mut builder, body)? + } + Some(forget_estree::FunctionBody::Expression(body)) => { + lower_expression(env, &mut builder, body)?; + } + None => { + return Err(Diagnostic::invalid_syntax( + BuildHIRError::EmptyFunction, + fun.range, + )); + } + } + // In case the function did not explicitly return, terminate the final // block with an explicit `return undefined`. If the function *did* return, // this will be unreachable and get pruned later. @@ -79,7 +80,7 @@ pub fn build(env: &Environment, fun: Function) -> Result Result Result<(), Diagnostic> { - for stmt in stmt.body { + for stmt in &stmt.body { lower_statement(env, builder, stmt, None)?; } Ok(()) @@ -105,12 +106,12 @@ fn lower_block_statement( fn lower_statement( env: &Environment, builder: &mut Builder, - stmt: Statement, + stmt: &Statement, label: Option, ) -> Result<(), Diagnostic> { match stmt { Statement::BlockStatement(stmt) => { - lower_block_statement(env, builder, *stmt)?; + lower_block_statement(env, builder, stmt)?; } Statement::BreakStatement(stmt) => { let block = builder.resolve_break(stmt.label.as_ref())?; @@ -133,7 +134,7 @@ fn lower_statement( ); } Statement::ReturnStatement(stmt) => { - let ix = match stmt.argument { + let ix = match &stmt.argument { Some(argument) => lower_expression(env, builder, argument)?, None => builder.push(InstructionValue::Primitive(forget_hir::Primitive { value: PrimitiveValue::Undefined, @@ -147,82 +148,21 @@ fn lower_statement( ); } Statement::ExpressionStatement(stmt) => { - lower_expression(env, builder, stmt.expression)?; + lower_expression(env, builder, &stmt.expression)?; } Statement::EmptyStatement(_) => { // no-op } Statement::VariableDeclaration(stmt) => { - let kind = match stmt.kind { - VariableDeclarationKind::Const => InstructionKind::Const, - VariableDeclarationKind::Let => InstructionKind::Let, - VariableDeclarationKind::Var => { - return Err(Diagnostic::unsupported( - BuildHIRError::VariableDeclarationKindIsVar, - stmt.range, - )); - } - }; - for declaration in stmt.declarations { - if let Some(init) = declaration.init { - let value = lower_expression(env, builder, init)?; - lower_assignment( - env, - builder, - kind, - AssignmentTarget::Pattern(declaration.id.into()), - value, - )?; - } else { - match declaration.id { - Pattern::Identifier(id) => { - // TODO: handle unbound variables - let binding = builder.resolve_identifier(&id)?; - let identifier = match binding { - Binding::Local(identifier) => identifier, - _ => { - return Err(Diagnostic::invariant( - BuildHIRError::VariableDeclarationBindingIsNonLocal, - id.range, - )); - } - }; - builder.push(InstructionValue::DeclareLocal( - forget_hir::DeclareLocal { - lvalue: LValue { - identifier: IdentifierOperand { - identifier, - effect: None, - }, - kind, - }, - }, - )); - } - _ => { - return Err(Diagnostic::todo( - "Handle non-identifier variable declarations", - declaration.range, - )); - } - } - } - } + lower_variable_declaration(env, builder, stmt)?; } Statement::IfStatement(stmt) => { // block for what follows the if statement, though this may // not be reachable let fallthrough_block = builder.reserve(BlockKind::Block); - let IfStatement { - test, - consequent, - alternate, - .. - } = *stmt; - let consequent_block = builder.enter(BlockKind::Block, |builder| { - lower_statement(env, builder, consequent, None)?; + lower_statement(env, builder, &stmt.consequent, None)?; Ok(TerminalValue::Goto(forget_hir::GotoTerminal { block: fallthrough_block.id, kind: GotoKind::Break, @@ -230,7 +170,7 @@ fn lower_statement( })?; let alternate_block = builder.enter(BlockKind::Block, |builder| { - if let Some(alternate) = alternate { + if let Some(alternate) = &stmt.alternate { lower_statement(env, builder, alternate, None)?; } Ok(TerminalValue::Goto(forget_hir::GotoTerminal { @@ -239,7 +179,7 @@ fn lower_statement( })) })?; - let test = lower_expression(env, builder, test)?; + let test = lower_expression(env, builder, &stmt.test)?; let terminal = TerminalValue::If(forget_hir::IfTerminal { test: Operand { ix: test, @@ -252,14 +192,6 @@ fn lower_statement( builder.terminate_with_fallthrough(terminal, fallthrough_block); } Statement::ForStatement(stmt) => { - let ForStatement { - init, - test, - update, - body, - .. - } = *stmt; - // Block for the loop's test condition let test_block = builder.reserve(BlockKind::Loop); @@ -267,8 +199,8 @@ fn lower_statement( let fallthrough_block = builder.reserve(BlockKind::Block); let init_block = builder.enter(BlockKind::Loop, |builder| { - if let Some(ForInit::VariableDeclaration(decl)) = init { - lower_statement(env, builder, Statement::VariableDeclaration(decl), None)?; + if let Some(ForInit::VariableDeclaration(decl)) = &stmt.init { + lower_variable_declaration(env, builder, decl)?; Ok(TerminalValue::Goto(forget_hir::GotoTerminal { block: test_block.id, kind: GotoKind::Break, @@ -281,7 +213,9 @@ fn lower_statement( } })?; - let update_block = update + let update_block = stmt + .update + .as_ref() .map(|update| { builder.enter(BlockKind::Loop, |builder| { lower_expression(env, builder, update)?; @@ -300,7 +234,7 @@ fn lower_statement( break_block: fallthrough_block.id, }; builder.enter_loop(loop_, |builder| { - lower_statement(env, builder, body, None)?; + lower_statement(env, builder, &stmt.body, None)?; Ok(TerminalValue::Goto(forget_hir::GotoTerminal { block: update_block.unwrap_or(test_block.id), kind: GotoKind::Continue, @@ -317,7 +251,7 @@ fn lower_statement( }); builder.terminate_with_fallthrough(terminal, test_block); - if let Some(test) = test { + if let Some(test) = &stmt.test { let test_value = lower_expression(env, builder, test)?; let terminal = TerminalValue::Branch(BranchTerminal { test: Operand { @@ -340,6 +274,58 @@ fn lower_statement( Ok(()) } +fn lower_variable_declaration( + env: &Environment, + builder: &mut Builder, + stmt: &VariableDeclaration, +) -> Result<(), Diagnostic> { + let kind = match stmt.kind { + VariableDeclarationKind::Const => InstructionKind::Const, + VariableDeclarationKind::Let => InstructionKind::Let, + VariableDeclarationKind::Var => { + return Err(Diagnostic::unsupported( + BuildHIRError::VariableDeclarationKindIsVar, + stmt.range, + )); + } + }; + for declaration in &stmt.declarations { + if let Some(init) = &declaration.init { + let value = lower_expression(env, builder, init)?; + lower_assignment_pattern(env, builder, kind, &declaration.id, value)?; + } else { + match &declaration.id { + Pattern::Identifier(id) => { + let identifier = env.resolve_variable_declaration(id.as_ref(), &id.name); + if let Some(identifier) = identifier { + builder.push(InstructionValue::DeclareLocal(forget_hir::DeclareLocal { + lvalue: LValue { + identifier: IdentifierOperand { + identifier, + effect: None, + }, + kind, + }, + })); + } else { + return Err(Diagnostic::invariant( + BuildHIRError::VariableDeclarationBindingIsNonLocal, + id.range, + )); + } + } + _ => { + return Err(Diagnostic::todo( + "Handle non-identifier variable declarations", + declaration.range, + )); + } + } + } + } + Ok(()) +} + /// Converts an ESTree Expression into an HIR InstructionValue. Note that while only a single /// InstructionValue is returned, this function is recursive and may cause multiple instructions /// to be emitted, possibly across multiple basic blocks (in the case of expressions with control @@ -347,35 +333,33 @@ fn lower_statement( fn lower_expression( env: &Environment, builder: &mut Builder, - expr: Expression, + expr: &Expression, ) -> Result { let value = match expr { Expression::Identifier(expr) => { - // TODO: handle unbound variables - let binding = builder.resolve_identifier(&expr)?; - match binding { - Binding::Local(identifier) => { - let place = IdentifierOperand { - effect: None, - identifier, - }; - InstructionValue::LoadLocal(LoadLocal { place }) - } - Binding::Module(..) | Binding::Global => { - InstructionValue::LoadGlobal(LoadGlobal { name: expr.name }) - } + let identifier = env.resolve_variable_reference(expr.as_ref()); + if let Some(identifier) = identifier { + let place = IdentifierOperand { + effect: None, + identifier, + }; + InstructionValue::LoadLocal(LoadLocal { place }) + } else { + InstructionValue::LoadGlobal(LoadGlobal { + name: expr.name.clone(), + }) } } Expression::Literal(expr) => InstructionValue::Primitive(forget_hir::Primitive { - value: lower_primitive(env, builder, *expr), + value: lower_primitive(env, builder, expr), }), Expression::ArrayExpression(expr) => { let mut elements = Vec::with_capacity(expr.elements.len()); - for expr in expr.elements { + for expr in &expr.elements { let element = match expr { Some(forget_estree::ExpressionOrSpread::SpreadElement(expr)) => { Some(PlaceOrSpread::Spread(Operand { - ix: lower_expression(env, builder, expr.argument)?, + ix: lower_expression(env, builder, &expr.argument)?, effect: None, })) } @@ -394,12 +378,12 @@ fn lower_expression( Expression::AssignmentExpression(expr) => match expr.operator { forget_estree::AssignmentOperator::Equals => { - let right = lower_expression(env, builder, expr.right)?; + let right = lower_expression(env, builder, &expr.right)?; return Ok(lower_assignment( env, builder, InstructionKind::Reassign, - expr.left, + &expr.left, right, )?); } @@ -407,20 +391,14 @@ fn lower_expression( }, Expression::BinaryExpression(expr) => { - let BinaryExpression { - left, - operator, - right, - .. - } = *expr; - let left = lower_expression(env, builder, left)?; - let right = lower_expression(env, builder, right)?; + let left = lower_expression(env, builder, &expr.left)?; + let right = lower_expression(env, builder, &expr.right)?; InstructionValue::Binary(forget_hir::Binary { left: Operand { ix: left, effect: None, }, - operator, + operator: expr.operator, right: Operand { ix: right, effect: None, @@ -429,15 +407,15 @@ fn lower_expression( } Expression::FunctionExpression(expr) => { - InstructionValue::Function(lower_function(env, builder, expr.function)?) + InstructionValue::Function(lower_function(env, builder, expr.as_ref())?) } Expression::ArrowFunctionExpression(expr) => { - InstructionValue::Function(lower_function(env, builder, expr.function)?) + InstructionValue::Function(lower_function(env, builder, expr.as_ref())?) } Expression::CallExpression(expr) => { - let callee_expr = match expr.callee { + let callee_expr = match &expr.callee { ExpressionOrSuper::Super(callee) => { return Err(Diagnostic::unsupported( BuildHIRError::UnsupportedSuperExpression, @@ -451,8 +429,8 @@ fn lower_expression( return Err(Diagnostic::todo("Support method calls", expr.range)); } - let callee = lower_expression(env, builder, callee_expr)?; - let arguments = lower_arguments(env, builder, expr.arguments)?; + let callee = lower_expression(env, builder, &callee_expr)?; + let arguments = lower_arguments(env, builder, &expr.arguments)?; InstructionValue::Call(forget_hir::Call { callee: Operand { ix: callee, @@ -463,7 +441,7 @@ fn lower_expression( } Expression::JSXElement(expr) => { - InstructionValue::JSXElement(lower_jsx_element(env, builder, *expr)?) + InstructionValue::JSXElement(lower_jsx_element(env, builder, expr)?) } _ => todo!("Lower expr {expr:#?}"), @@ -474,14 +452,14 @@ fn lower_expression( fn lower_arguments( env: &Environment, builder: &mut Builder, - args: Vec, + args: &[ExpressionOrSpread], ) -> Result, Diagnostic> { let mut arguments = Vec::with_capacity(args.len()); for arg in args { let element = match arg { forget_estree::ExpressionOrSpread::SpreadElement(arg) => { PlaceOrSpread::Spread(Operand { - ix: lower_expression(env, builder, arg.argument)?, + ix: lower_expression(env, builder, &arg.argument)?, effect: None, }) } @@ -495,31 +473,28 @@ fn lower_arguments( Ok(arguments) } -fn lower_function( +fn lower_function( env: &Environment, - builder: &mut Builder, - function: forget_estree::Function, + _builder: &mut Builder, + function: &T, ) -> Result { println!("get_context_identifiers() ..."); - let context_identifiers = get_context_identifiers(env, &function); + let context_identifiers = get_context_identifiers(env, function); println!("ok"); let mut context = Vec::new(); let mut seen = HashSet::new(); - for identifier in context_identifiers { - match builder.resolve_identifier(identifier)? { - Binding::Local(identifier) => { - if !seen.insert(identifier.id) { - continue; - } - context.push(IdentifierOperand { - effect: None, - identifier, - }); + for declaration_id in context_identifiers { + if let Some(identifier) = env.resolve_declaration_id(declaration_id) { + if !seen.insert(identifier.id) { + continue; } - _ => {} + context.push(IdentifierOperand { + effect: None, + identifier, + }); } } - let mut fun = build(env, function)?; + let mut fun = build(env, function.function())?; fun.context = context; Ok(forget_hir::FunctionExpression { // TODO: collect dependencies! @@ -531,18 +506,18 @@ fn lower_function( fn lower_jsx_element( env: &Environment, builder: &mut Builder, - expr: forget_estree::JSXElement, + expr: &forget_estree::JSXElement, ) -> Result { let props: Result, Diagnostic> = expr .opening_element .attributes - .into_iter() + .iter() .map(|attr| lower_jsx_attribute(env, builder, attr)) .collect(); let props = props?; let children: Result, Diagnostic> = expr .children - .into_iter() + .iter() .map(|child| { let ix = lower_jsx_child(env, builder, child)?; Ok(Operand { effect: None, ix }) @@ -564,7 +539,7 @@ fn lower_jsx_element( fn lower_jsx_attribute( env: &Environment, builder: &mut Builder, - attr: forget_estree::JSXAttributeOrSpread, + attr: &forget_estree::JSXAttributeOrSpread, ) -> Result { todo!("lower jsx attribute") } @@ -572,7 +547,7 @@ fn lower_jsx_attribute( fn lower_jsx_child( env: &Environment, builder: &mut Builder, - child: forget_estree::JSXChildItem, + child: &forget_estree::JSXChildItem, ) -> Result { todo!("lower jsx child") } @@ -581,57 +556,83 @@ fn lower_assignment( env: &Environment, builder: &mut Builder, kind: InstructionKind, - lvalue: AssignmentTarget, + lvalue: &AssignmentTarget, value: InstrIx, ) -> Result { Ok(match lvalue { - AssignmentTarget::Pattern(lvalue) => match lvalue { - Pattern::Identifier(lvalue) => { - let identifier = lower_identifier_for_assignment(env, builder, kind, *lvalue)?; - builder.push(InstructionValue::StoreLocal(forget_hir::StoreLocal { - lvalue: LValue { identifier, kind }, - value: Operand { - ix: value, - effect: None, - }, - })) - } - _ => todo!("lower assignment pattern for {:#?}", lvalue), - }, + AssignmentTarget::Pattern(lvalue) => { + lower_assignment_pattern(env, builder, kind, lvalue, value)? + } _ => todo!("lower assignment for {:#?}", lvalue), }) } -fn lower_identifier_for_assignment( - _env: &Environment, +fn lower_assignment_pattern( + env: &Environment, builder: &mut Builder, - _kind: InstructionKind, - identifier: forget_estree::Identifier, + kind: InstructionKind, + lvalue: &Pattern, + value: InstrIx, +) -> Result { + Ok(match lvalue { + Pattern::Identifier(lvalue) => { + let identifier = lower_identifier_for_assignment(env, builder, kind, lvalue)?; + builder.push(InstructionValue::StoreLocal(forget_hir::StoreLocal { + lvalue: LValue { identifier, kind }, + value: Operand { + ix: value, + effect: None, + }, + })) + } + _ => todo!("lower assignment pattern for {:#?}", lvalue), + }) +} + +fn lower_identifier_for_assignment( + env: &Environment, + builder: &mut Builder, + kind: InstructionKind, + node: &forget_estree::Identifier, ) -> Result { - let binding = builder.resolve_identifier(&identifier)?; - match binding { - Binding::Module(..) | Binding::Global => Err(Diagnostic::invalid_react( - BuildHIRError::ReassignedGlobal, - identifier.range, - ) - .annotate( - format!("Cannot reassign `{}`", &identifier.name), - identifier.range, - )), - Binding::Local(id) => Ok(IdentifierOperand { - identifier: id, - effect: None, - }), + match kind { + InstructionKind::Reassign => { + let identifier = env.resolve_variable_reference(node); + if let Some(identifier) = identifier { + Ok(IdentifierOperand { + identifier, + effect: None, + }) + } else { + // Reassigning a global + Err( + Diagnostic::invalid_react(BuildHIRError::ReassignedGlobal, node.range) + .annotate(format!("Cannot reassign `{}`", &node.name), node.range), + ) + } + } + _ => { + // Declaration + let identifier = env.resolve_variable_declaration(node, &node.name).unwrap(); + Ok(IdentifierOperand { + identifier, + effect: None, + }) + } } } /// Converts an ESTree literal into a HIR primitive -fn lower_primitive(_env: &Environment, _builder: &mut Builder, literal: Literal) -> PrimitiveValue { - match literal.value { - JsValue::Bool(bool) => PrimitiveValue::Boolean(bool), +fn lower_primitive( + _env: &Environment, + _builder: &mut Builder, + literal: &Literal, +) -> PrimitiveValue { + match &literal.value { + JsValue::Bool(bool) => PrimitiveValue::Boolean(*bool), JsValue::Null => PrimitiveValue::Null, - JsValue::Number(value) => PrimitiveValue::Number(f64::from(value).into()), - JsValue::String(s) => PrimitiveValue::String(s), + JsValue::Number(value) => PrimitiveValue::Number(f64::from(*value).into()), + JsValue::String(s) => PrimitiveValue::String(s.clone()), _ => todo!("Lower literal {literal:#?}"), } } diff --git a/compiler/forget/crates/forget_build_hir/src/builder.rs b/compiler/forget/crates/forget_build_hir/src/builder.rs index cd78735e0f..cf035976bf 100644 --- a/compiler/forget/crates/forget_build_hir/src/builder.rs +++ b/compiler/forget/crates/forget_build_hir/src/builder.rs @@ -313,27 +313,4 @@ impl<'e> Builder<'e> { None, )) } - - pub(crate) fn resolve_identifier( - &mut self, - identifier: &forget_estree::Identifier, - ) -> Result { - match &identifier.binding { - Some(binding) => Ok(match binding { - forget_estree::Binding::Global => Binding::Global, - forget_estree::Binding::Local(id) => Binding::Local( - self.environment - .resolve_binding_identifier(&identifier.name, *id), - ), - forget_estree::Binding::Module(id) => Binding::Module( - self.environment - .resolve_binding_identifier(&identifier.name, *id), - ), - }), - _ => Err(Diagnostic::invariant( - BuildHIRError::UnknownIdentifier, - identifier.range.clone(), - )), - } - } } diff --git a/compiler/forget/crates/forget_build_hir/src/context.rs b/compiler/forget/crates/forget_build_hir/src/context.rs index c753944693..f23ed9b76c 100644 --- a/compiler/forget/crates/forget_build_hir/src/context.rs +++ b/compiler/forget/crates/forget_build_hir/src/context.rs @@ -1,72 +1,58 @@ use std::collections::HashSet; -use forget_estree::{Binding, BindingId, Function, Identifier, Visitor_DEPRECATED}; +use forget_estree::IntoFunction; use forget_hir::Environment; +use forget_semantic_analysis::{DeclarationId, ScopeView}; -pub(crate) fn get_context_identifiers<'ast>( - _env: &Environment, - function: &'ast Function, -) -> Vec<&'ast Identifier> { - let mut visitor = ContextVisitor::new(); - visitor.visit_function(function); - let ContextVisitor { - free_variables, - defined, - .. - } = visitor; - free_variables - .into_iter() - .filter(|identifier| match &identifier.binding { - Some(Binding::Local(id)) if !defined.contains(id) => true, - _ => false, - }) - .collect() +pub(crate) fn get_context_identifiers( + env: &Environment, + node: &T, +) -> Vec { + let function_scope = env.scope(node.function()).unwrap(); + println!( + "get_context_identifiers for function scope {:?}", + function_scope.id() + ); + let mut free = FreeVariables::default(); + let mut seen = HashSet::new(); + populate_free_variable_references(&mut free, &mut seen, function_scope); + free } -struct ContextVisitor<'ast> { - free_variables: Vec<&'ast Identifier>, - defined: HashSet, - lvalue: bool, -} +type FreeVariables = Vec; -impl<'ast> ContextVisitor<'ast> { - fn new() -> Self { - Self { - free_variables: Default::default(), - defined: Default::default(), - lvalue: false, +fn populate_free_variable_references( + free: &mut FreeVariables, + seen: &mut HashSet, + scope: ScopeView<'_>, +) { + for reference in scope.references() { + if !seen.insert(reference.declaration().id()) { + println!( + "skip {}${:?}", + reference.declaration().name(), + reference.declaration().id() + ); + continue; + } + let declaration_scope = reference.declaration().scope(); + if !declaration_scope.is_descendant_of(scope) { + println!( + "free variable: not descendant {}${:?}", + reference.declaration().name(), + reference.declaration().id() + ); + free.push(reference.declaration().id()) + } else { + println!( + "local variable: descendant {}${:?} scope={:?}", + reference.declaration().name(), + reference.declaration().id(), + reference.declaration().scope().id() + ); } } -} - -impl<'ast> Visitor_DEPRECATED<'ast> for ContextVisitor<'ast> { - fn visit_lvalue(&mut self, f: F) - where - F: FnOnce(&mut Self) -> (), - { - let prev_lvalue = self.lvalue; - self.lvalue = true; - f(self); - self.lvalue = prev_lvalue; - } - - fn visit_identifier(&mut self, identifier: &'ast Identifier) { - let binding = identifier.binding.unwrap(); - match binding { - Binding::Local(binding_id) => { - if self.lvalue { - // println!("lvalue {identifier:?}"); - self.defined.insert(binding_id); - } else { - // println!("rvalue {identifier:?}"); - self.free_variables.push(identifier); - } - } - _ => {} - } - } - - fn visit_literal(&mut self, _literal: &'ast forget_estree::Literal) { - // no-op + for child in scope.children() { + populate_free_variable_references(free, seen, child); } } diff --git a/compiler/forget/crates/forget_estree/src/generated_extensions.rs b/compiler/forget/crates/forget_estree/src/generated_extensions.rs index b436a55518..da7c7a98d8 100644 --- a/compiler/forget/crates/forget_estree/src/generated_extensions.rs +++ b/compiler/forget/crates/forget_estree/src/generated_extensions.rs @@ -1,7 +1,7 @@ // Manual extensions to generated types use crate::{ - JSXElementName, JSXMemberExpression, JSXMemberExpressionOrIdentifier, Pattern, SourceRange, - SourceType, + ArrowFunctionExpression, Function, FunctionDeclaration, FunctionExpression, JSXElementName, + JSXMemberExpression, JSXMemberExpressionOrIdentifier, Pattern, SourceRange, SourceType, }; /// Sentinel trait to distinguish AST *node* types @@ -43,3 +43,51 @@ impl JSXMemberExpression { } } } + +pub trait IntoFunction: ESTreeNode { + fn function(&self) -> &Function; + + fn into_function(self) -> Function; +} + +impl IntoFunction for FunctionDeclaration { + fn function(&self) -> &Function { + &self.function + } + + fn into_function(self) -> Function { + self.function + } +} + +impl IntoFunction for FunctionExpression { + fn function(&self) -> &Function { + &self.function + } + + fn into_function(self) -> Function { + self.function + } +} + +impl IntoFunction for ArrowFunctionExpression { + fn function(&self) -> &Function { + &self.function + } + + fn into_function(self) -> Function { + self.function + } +} + +impl ESTreeNode for Function {} + +impl IntoFunction for Function { + fn function(&self) -> &Function { + self + } + + fn into_function(self) -> Function { + self + } +} diff --git a/compiler/forget/crates/forget_estree/src/lib.rs b/compiler/forget/crates/forget_estree/src/lib.rs index e02ddcf9f3..8f98f0fed5 100644 --- a/compiler/forget/crates/forget_estree/src/lib.rs +++ b/compiler/forget/crates/forget_estree/src/lib.rs @@ -7,7 +7,7 @@ mod visit; pub use binding::{Binding, BindingId}; pub use generated::*; -pub use generated_extensions::ESTreeNode; +pub use generated_extensions::*; pub use js_value::{JsValue, Number}; pub use range::SourceRange; pub use visit::*; diff --git a/compiler/forget/crates/forget_fixtures/Cargo.toml b/compiler/forget/crates/forget_fixtures/Cargo.toml index 9d0435ee9b..41fd185a8b 100644 --- a/compiler/forget/crates/forget_fixtures/Cargo.toml +++ b/compiler/forget/crates/forget_fixtures/Cargo.toml @@ -18,6 +18,7 @@ forget_estree = { workspace = true } forget_estree_swc = { workspace = true } forget_hir = { workspace = true } forget_optimization = { workspace = true } +forget_semantic_analysis = { workspace = true } forget_ssa = { workspace = true } forget_build_hir = { workspace = true } miette = { workspace = true, features = ["backtrace", "fancy"] } diff --git a/compiler/forget/crates/forget_fixtures/tests/fixtures/simple-function.js b/compiler/forget/crates/forget_fixtures/tests/fixtures/simple-function.js new file mode 100644 index 0000000000..ef9d16bb14 --- /dev/null +++ b/compiler/forget/crates/forget_fixtures/tests/fixtures/simple-function.js @@ -0,0 +1,9 @@ +function Component(a) { + Math; + let b = 0; + const foo = function foo_(c) { + let d = 1; + return a + b + c + d; + }; + return foo(); +} diff --git a/compiler/forget/crates/forget_fixtures/tests/fixtures_test.rs b/compiler/forget/crates/forget_fixtures/tests/fixtures_test.rs index 92bbb1952b..6059863c3b 100644 --- a/compiler/forget/crates/forget_fixtures/tests/fixtures_test.rs +++ b/compiler/forget/crates/forget_fixtures/tests/fixtures_test.rs @@ -6,6 +6,7 @@ use forget_estree::{ModuleItem, Statement}; use forget_estree_swc::parse; use forget_hir::{inline_use_memo, Environment, Features, Print, Registry}; use forget_optimization::constant_propagation; +use forget_semantic_analysis::analyze; use forget_ssa::{eliminate_redundant_phis, enter_ssa}; use insta::{assert_snapshot, glob}; use miette::{NamedSource, Report}; @@ -20,19 +21,22 @@ fn fixtures() { let mut output = String::new(); - for (ix, item) in ast.body.into_iter().enumerate() { + let analysis = analyze(&ast); + // println!("{:#?}", analysis.debug()); + let environment = Environment::new( + Features { + validate_frozen_lambdas: true, + }, + Registry, + analysis, + ); + for (ix, item) in ast.body.iter().enumerate() { if let ModuleItem::Statement(stmt) = item { if let Statement::FunctionDeclaration(fun) = stmt { - let environment = Environment::new( - Features { - validate_frozen_lambdas: true, - }, - Registry, - ); if ix != 0 { output.push_str("\n\n"); } - match build(&environment, fun.function) { + match build(&environment, &fun.function) { Ok(mut fun) => { // println!("{fun:#?}"); // let mut out = String::new(); diff --git a/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@function-expressions.js.snap b/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@function-expressions.js.snap index a9f0a277a6..d0141fc7c0 100644 --- a/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@function-expressions.js.snap +++ b/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@function-expressions.js.snap @@ -29,54 +29,54 @@ function Component(props) { Output: function Component( - unknown props$11, + unknown props$10, ) entry bb0 bb0 (block) [0] #0 = 2 - [1] #1 = StoreLocal Const unknown x$12 = unknown #0 - [2] #2 = Function @deps[] @context[unknown x$12, unknown foo$1]: + [1] #1 = StoreLocal Const unknown x$11 = unknown #0 + [2] #2 = Function @deps[] @context[unknown x$11]: function foo( - unknown y$13, + unknown y$12, ) entry bb1 bb1 (block) [0] #0 = 1 - [1] #1 = StoreLocal Let unknown a$14 = unknown #0 - [2] #2 = DeclareLocal Let unknown b$15 + [1] #1 = StoreLocal Let unknown a$13 = unknown #0 + [2] #2 = DeclareLocal Let unknown b$14 [3] #9 = 1 [4] #10 = 1 [5] #11 = true [6] #3 = 5 [7] #4 = 3 [8] #5 = 8 - [9] #6 = StoreLocal Reassign unknown b$16 = unknown #5 + [9] #6 = StoreLocal Reassign unknown b$15 = unknown #5 [10] #12 = 2 - [11] #13 = LoadLocal unknown y$13 + [11] #13 = LoadLocal unknown y$12 [12] #14 = Binary unknown #12 + unknown #13 [13] #15 = 1 [14] #16 = Binary unknown #14 + unknown #15 [15] #17 = 8 [16] #18 = Binary unknown #16 + unknown #17 - [17] #19 = Function @deps[] @context[unknown x$18, unknown y$19, unknown a$20, unknown b$21]: + [17] #19 = Function @deps[] @context[unknown x$17, unknown y$18, unknown a$19, unknown b$20]: function bar( - unknown z$22, + unknown z$21, ) entry bb5 bb5 (block) [0] #0 = 2 - [1] #1 = StoreLocal Let unknown c$23 = unknown #0 - [2] #2 = DeclareLocal Let unknown d$24 + [1] #1 = StoreLocal Let unknown c$22 = unknown #0 + [2] #2 = DeclareLocal Let unknown d$23 [3] #3 = 3 - [4] #4 = StoreLocal Reassign unknown d$25 = unknown #3 - [5] #5 = LoadLocal unknown x$18 - [6] #6 = LoadLocal unknown y$19 + [4] #4 = StoreLocal Reassign unknown d$24 = unknown #3 + [5] #5 = LoadLocal unknown x$17 + [6] #6 = LoadLocal unknown y$18 [7] #7 = Binary unknown #5 + unknown #6 - [8] #8 = LoadLocal unknown a$20 + [8] #8 = LoadLocal unknown a$19 [9] #9 = Binary unknown #7 + unknown #8 - [10] #10 = LoadLocal unknown b$21 + [10] #10 = LoadLocal unknown b$20 [11] #11 = Binary unknown #9 + unknown #10 - [12] #12 = LoadLocal unknown z$22 + [12] #12 = LoadLocal unknown z$21 [13] #13 = Binary unknown #11 + unknown #12 [14] #14 = 2 [15] #15 = Binary unknown #13 + unknown #14 @@ -84,11 +84,11 @@ bb0 (block) [17] #17 = Binary unknown #15 + unknown #16 [18] #18 = [19] Return unknown #18 - [18] #20 = StoreLocal Const unknown bar$26 = unknown #19 - [19] #21 = LoadLocal unknown bar$26 - [20] #22 = LoadLocal unknown foo$1 + [18] #20 = StoreLocal Const unknown bar$25 = unknown #19 + [19] #21 = LoadLocal unknown bar$25 + [20] #22 = LoadGlobal foo [21] #23 = [22] Return unknown #23 - [3] #3 = StoreLocal Const unknown foo$28 = unknown #2 + [3] #3 = StoreLocal Const unknown foo$26 = unknown #2 [4] #4 = [5] Return unknown #4 diff --git a/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@identifiers.js.snap b/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@identifiers.js.snap index 96ed5924e9..f2ee912ae9 100644 --- a/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@identifiers.js.snap +++ b/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@identifiers.js.snap @@ -24,18 +24,18 @@ function id(x) { Output: function id( - unknown x$4, + unknown x$3, ) entry bb0 bb0 (block) [0] #0 = LoadGlobal Math [1] #1 = LoadGlobal id [2] #2 = true - [3] #3 = StoreLocal Let unknown y$5 = unknown #2 + [3] #3 = StoreLocal Let unknown y$4 = unknown #2 [4] #4 = false - [5] #5 = StoreLocal Reassign unknown y$6 = unknown #4 + [5] #5 = StoreLocal Reassign unknown y$5 = unknown #4 [6] #6 = false - [7] #7 = DeclareLocal Let unknown z$7 - [8] #8 = LoadLocal unknown z$7 - [9] #9 = LoadLocal unknown x$4 + [7] #7 = DeclareLocal Let unknown z$6 + [8] #8 = LoadLocal unknown z$6 + [9] #9 = LoadLocal unknown x$3 [10] Return unknown #9 diff --git a/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@simple-function.js.snap b/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@simple-function.js.snap new file mode 100644 index 0000000000..09a3b99966 --- /dev/null +++ b/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@simple-function.js.snap @@ -0,0 +1,46 @@ +--- +source: crates/forget_fixtures/tests/fixtures_test.rs +expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")" +input_file: crates/forget_fixtures/tests/fixtures/simple-function.js +--- +Input: +function Component(a) { + Math; + let b = 0; + const foo = function foo_(c) { + let d = 1; + return a + b + c + d; + }; + return foo(); +} + + +Output: +function Component( + unknown a$5, +) +entry bb0 +bb0 (block) + [0] #0 = LoadGlobal Math + [1] #1 = 0 + [2] #2 = StoreLocal Let unknown b$6 = unknown #1 + [3] #3 = Function @deps[] @context[unknown a$5, unknown b$6]: + function foo_( + unknown c$7, + ) + entry bb1 + bb1 (block) + [0] #0 = 1 + [1] #1 = StoreLocal Let unknown d$8 = unknown #0 + [2] #2 = LoadLocal unknown a$5 + [3] #3 = 0 + [4] #4 = Binary unknown #2 + unknown #3 + [5] #5 = LoadLocal unknown c$7 + [6] #6 = Binary unknown #4 + unknown #5 + [7] #7 = 1 + [8] #8 = Binary unknown #6 + unknown #7 + [9] Return unknown #8 + [4] #4 = StoreLocal Const unknown foo$9 = unknown #3 + [5] #5 = LoadLocal unknown foo$9 + [6] #6 = Call unknown #5() + [7] Return unknown #6 diff --git a/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@use-memo.js.snap b/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@use-memo.js.snap index ac440caa5d..add5facf77 100644 --- a/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@use-memo.js.snap +++ b/compiler/forget/crates/forget_fixtures/tests/snapshots/fixtures_test__fixtures@use-memo.js.snap @@ -14,21 +14,21 @@ function Component(x) { Output: function Component( - unknown x$1, + unknown x$2, ) entry bb0 bb0 (block) [0] #0 = LoadGlobal useMemo - [1] #6 = DeclareLocal Let unknown t$3 + [1] #6 = DeclareLocal Let unknown t$4 [2] Label block=bb1 fallthrough=bb6 bb1 (block) predecessors: bb0 [3] #7 = LoadGlobal y - [4] #9 = StoreLocal Reassign unknown t$3 = unknown #7 + [4] #9 = StoreLocal Reassign unknown t$4 = unknown #7 [5] Goto bb6 bb6 (block) predecessors: bb1 - [6] #2 = LoadLocal unknown t$3 - [7] #3 = StoreLocal Const unknown x$2 = unknown #2 - [8] #4 = LoadLocal unknown x$2 + [6] #2 = LoadLocal unknown t$4 + [7] #3 = StoreLocal Const unknown x$3 = unknown #2 + [8] #4 = LoadLocal unknown x$3 [9] Return unknown #4 diff --git a/compiler/forget/crates/forget_hir/Cargo.toml b/compiler/forget/crates/forget_hir/Cargo.toml index 6bf0437cda..cde35b9517 100644 --- a/compiler/forget/crates/forget_hir/Cargo.toml +++ b/compiler/forget/crates/forget_hir/Cargo.toml @@ -18,4 +18,5 @@ indexmap = { workspace = true } serde = { workspace = true } forget_utils = { workspace = true } forget_diagnostics = { workspace = true } +forget_semantic_analysis = { workspace = true } thiserror = { workspace = true } diff --git a/compiler/forget/crates/forget_hir/src/environment.rs b/compiler/forget/crates/forget_hir/src/environment.rs index ec7f85fe38..4ecc4939d9 100644 --- a/compiler/forget/crates/forget_hir/src/environment.rs +++ b/compiler/forget/crates/forget_hir/src/environment.rs @@ -1,8 +1,10 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; +use std::process::id; use std::rc::Rc; -use forget_estree::BindingId; +use forget_estree::{BindingId, ESTreeNode}; +use forget_semantic_analysis::{AstNode, DeclarationId, ScopeManager, ScopeView}; use crate::{ BlockId, Features, Identifier, IdentifierData, IdentifierId, Registry, Type, TypeVarId, @@ -27,16 +29,19 @@ pub struct Environment { /// The next available identifier id next_identifier_id: Cell, + analysis: ScopeManager, + next_type_var_id: Cell, - bindings: Rc>>, + bindings: Rc>>, } impl Environment { - pub fn new(features: Features, registry: Registry) -> Self { + pub fn new(features: Features, registry: Registry, analysis: ScopeManager) -> Self { Self { features, registry, + analysis, next_block_id: Cell::new(BlockId(0)), next_identifier_id: Cell::new(IdentifierId(0)), next_type_var_id: Cell::new(TypeVarId(0)), @@ -65,24 +70,46 @@ impl Environment { id } - pub fn resolve_binding_identifier(&self, name: &str, binding_id: BindingId) -> Identifier { - let key_name = name.to_string(); + pub fn resolve_variable_declaration( + &self, + node: &T, + name: &str, + ) -> Option { + let declaration = self.analysis.node_declaration(node)?; let mut bindings = self.bindings.borrow_mut(); - if let Some(identifier) = bindings.get(&(key_name.clone(), binding_id)) { - identifier.clone() + if let Some(identifier) = bindings.get(&declaration.id) { + Some(identifier.clone()) } else { let id = self.next_identifier_id(); let identifier = Identifier { id, - name: Some(key_name.clone()), + name: Some(name.to_string()), data: Rc::new(RefCell::new(IdentifierData { mutable_range: Default::default(), scope: None, type_: Type::Var(self.next_type_var_id()), })), }; - bindings.insert((key_name, binding_id), identifier.clone()); - identifier + bindings.insert(declaration.id, identifier.clone()); + Some(identifier) } } + + pub fn resolve_variable_reference(&self, node: &T) -> Option { + let reference = self.analysis.node_reference(node)?; + let bindings = self.bindings.borrow(); + let declaration = self.analysis.declaration(reference.declaration); + let identifier = bindings.get(&declaration.id)?; + Some(identifier.clone()) + } + + pub fn resolve_declaration_id(&self, id: DeclarationId) -> Option { + let bindings = self.bindings.borrow(); + let identifier = bindings.get(&id)?; + Some(identifier.clone()) + } + + pub fn scope(&self, node: &T) -> Option> { + self.analysis.node_scope_view(node) + } } diff --git a/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs b/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs index b65c850bf5..6a06909637 100644 --- a/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs +++ b/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs @@ -1,8 +1,9 @@ use forget_diagnostics::Diagnostic; use forget_estree::{ AssignmentOperator, AssignmentPropertyOrRestElement, AssignmentTarget, Expression, - ExpressionOrSuper, ForInInit, ForInit, Function, FunctionBody, Identifier, JSXElementName, - Pattern, Program, SourceRange, SourceType, Statement, VariableDeclarationKind, Visitor, + ExpressionOrSuper, ForInInit, ForInit, Function, FunctionBody, Identifier, IntoFunction, + JSXElementName, Pattern, Program, SourceRange, SourceType, Statement, VariableDeclarationKind, + Visitor, }; use crate::{AstNode, DeclarationKind, LabelKind, ReferenceKind, ScopeId, ScopeKind, ScopeManager}; @@ -48,8 +49,9 @@ impl Analyzer { self.current = scope.parent.unwrap(); } - fn visit_function(&mut self, function: &Function) { - self.enter(ScopeKind::Function, |visitor| { + fn visit_function(&mut self, node: &T) { + let function = node.function(); + let scope = self.enter(ScopeKind::Function, |visitor| { for param in &function.params { // `this` parameters don't declare variables, nor can they have // default values @@ -80,6 +82,9 @@ impl Analyzer { } } }); + self.manager + .node_scopes + .insert(AstNode::from(function), scope); } fn visit_reference_identifier( @@ -230,7 +235,7 @@ impl Visitor for Analyzer { .node_declarations .insert(AstNode::from(id), declaration); } - Analyzer::visit_function(self, &ast.function); + Analyzer::visit_function(self, ast); } fn visit_function_expression(&mut self, ast: &forget_estree::FunctionExpression) { @@ -247,14 +252,14 @@ impl Visitor for Analyzer { .insert(AstNode::from(id), declaration); } - Analyzer::visit_function(self, &ast.function); + Analyzer::visit_function(self, ast); if let Some(function_scope) = function_scope { self.close_scope(function_scope); } } fn visit_arrow_function_expression(&mut self, ast: &forget_estree::ArrowFunctionExpression) { - Analyzer::visit_function(self, &ast.function); + Analyzer::visit_function(self, ast); } fn visit_assignment_expression(&mut self, ast: &forget_estree::AssignmentExpression) { diff --git a/compiler/forget/crates/forget_semantic_analysis/src/lib.rs b/compiler/forget/crates/forget_semantic_analysis/src/lib.rs index f2849d4890..30f0758c19 100644 --- a/compiler/forget/crates/forget_semantic_analysis/src/lib.rs +++ b/compiler/forget/crates/forget_semantic_analysis/src/lib.rs @@ -4,3 +4,4 @@ mod scope_view; pub use analyzer::analyze; pub use scope_manager::*; +pub use scope_view::*; diff --git a/compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs b/compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs index d4d43635d8..7711dfa363 100644 --- a/compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs +++ b/compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs @@ -1,12 +1,11 @@ use forget_diagnostics::Diagnostic; use forget_estree::{ - BreakStatement, ContinueStatement, ESTreeNode, Identifier, LabeledStatement, - VariableDeclarationKind, + BreakStatement, ContinueStatement, ESTreeNode, LabeledStatement, VariableDeclarationKind, }; use forget_utils::PointerAddress; use indexmap::IndexMap; -use crate::scope_view::ScopeView; +use crate::scope_view::{DeclarationView, ReferenceView, ScopeView}; pub struct ScopeManager { root: ScopeId, @@ -26,6 +25,12 @@ pub struct ScopeManager { pub(crate) diagnostics: Vec, } +impl std::fmt::Debug for ScopeManager { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + std::fmt::Debug::fmt(&self.debug(), f) + } +} + impl ScopeManager { pub(crate) fn new() -> Self { let root_id = ScopeId(0); @@ -71,6 +76,21 @@ impl ScopeManager { &self.scopes[id.0] } + pub fn is_descendant_of(&self, maybe_descendant: ScopeId, maybe_ancestor: ScopeId) -> bool { + let mut current = maybe_descendant; + loop { + if current == maybe_ancestor { + return true; + } + let scope = self.scope(current); + if let Some(parent) = scope.parent { + current = parent; + } else { + return false; + } + } + } + pub fn label(&self, id: LabelId) -> &Label { &self.labels[id.0] } @@ -89,6 +109,15 @@ impl ScopeManager { .map(|id| &self.scopes[id.0]) } + pub fn node_scope_view(&self, node: &T) -> Option> { + self.node_scopes + .get(&AstNode::from(node)) + .map(|id| ScopeView { + manager: self, + scope: &self.scopes[id.0], + }) + } + pub fn node_label(&self, node: &LabeledStatement) -> Option<&Label> { self.node_labels .get(&AstNode::from(node)) @@ -107,18 +136,36 @@ impl ScopeManager { .map(|id| &self.labels[id.0]) } - pub fn node_declaration(&self, node: &Identifier) -> Option<&Declaration> { + pub fn node_declaration(&self, node: &T) -> Option<&Declaration> { self.node_declarations .get(&AstNode::from(node)) .map(|id| &self.declarations[id.0]) } - pub fn node_reference(&self, node: &Identifier) -> Option<&Reference> { + pub fn node_declaration_view(&self, node: &T) -> Option> { + self.node_declarations + .get(&AstNode::from(node)) + .map(|id| DeclarationView { + manager: self, + declaration: &self.declarations[id.0], + }) + } + + pub fn node_reference(&self, node: &T) -> Option<&Reference> { self.node_references .get(&AstNode::from(node)) .map(|id| &self.references[id.0]) } + pub fn node_reference_view(&self, node: &T) -> Option> { + self.node_references + .get(&AstNode::from(node)) + .map(|id| ReferenceView { + manager: self, + reference: &self.references[id.0], + }) + } + pub fn lookup_label(&self, scope: ScopeId, name: &str) -> Option<&Label> { let mut current = &self.scopes[scope.0]; loop { @@ -316,10 +363,10 @@ pub struct Reference { } #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)] -pub(crate) struct AstNode(PointerAddress); +pub struct AstNode(PointerAddress); impl AstNode { - fn new(node: &T) -> Self { + pub fn new(node: &T) -> Self { Self(PointerAddress::new(node)) } } diff --git a/compiler/forget/crates/forget_semantic_analysis/src/scope_view.rs b/compiler/forget/crates/forget_semantic_analysis/src/scope_view.rs index 4b6e15f686..7a137218e9 100644 --- a/compiler/forget/crates/forget_semantic_analysis/src/scope_view.rs +++ b/compiler/forget/crates/forget_semantic_analysis/src/scope_view.rs @@ -1,12 +1,86 @@ use indexmap::IndexMap; -use crate::{Declaration, Label, Reference, Scope, ScopeManager}; +use crate::{ + Declaration, DeclarationId, DeclarationKind, Label, Reference, ReferenceId, ReferenceKind, + Scope, ScopeId, ScopeKind, ScopeManager, +}; +#[derive(Clone, Copy)] pub struct ScopeView<'m> { pub(crate) manager: &'m ScopeManager, pub(crate) scope: &'m Scope, } +impl<'m> ScopeView<'m> { + pub fn id(&self) -> ScopeId { + self.scope.id + } + + pub fn kind(&self) -> ScopeKind { + self.scope.kind + } + + pub fn parent(&self) -> Option> { + self.scope.parent.map(|id| { + let scope = self.manager.scope(id); + ScopeView { + manager: &self.manager, + scope, + } + }) + } + + pub fn declarations(&self) -> Vec> { + self.scope + .declarations + .values() + .cloned() + .map(|id| { + let declaration = self.manager.declaration(id); + DeclarationView { + manager: &self.manager, + declaration, + } + }) + .collect() + } + + pub fn references(&self) -> Vec> { + self.scope + .references + .iter() + .cloned() + .map(|id| { + let reference = self.manager.reference(id); + ReferenceView { + manager: &self.manager, + reference, + } + }) + .collect() + } + + pub fn children(&self) -> Vec> { + self.scope + .children + .iter() + .cloned() + .map(|id| { + let scope = self.manager.scope(id); + ScopeView { + manager: &self.manager, + scope, + } + }) + .collect() + } + + pub fn is_descendant_of(&self, maybe_ancestor: Self) -> bool { + self.manager + .is_descendant_of(self.scope.id, maybe_ancestor.scope.id) + } +} + impl<'m> std::fmt::Debug for ScopeView<'m> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let labels: IndexMap<_, _> = self @@ -66,9 +140,10 @@ impl<'m> std::fmt::Debug for ScopeView<'m> { } } +#[derive(Clone, Copy)] pub struct LabelView<'m> { - manager: &'m ScopeManager, - label: &'m Label, + pub(crate) manager: &'m ScopeManager, + pub(crate) label: &'m Label, } impl<'m> std::fmt::Debug for LabelView<'m> { @@ -81,9 +156,32 @@ impl<'m> std::fmt::Debug for LabelView<'m> { } } +#[derive(Clone, Copy)] pub struct DeclarationView<'m> { - manager: &'m ScopeManager, - declaration: &'m Declaration, + pub(crate) manager: &'m ScopeManager, + pub(crate) declaration: &'m Declaration, +} + +impl<'m> DeclarationView<'m> { + pub fn id(&self) -> DeclarationId { + self.declaration.id + } + + pub fn name(&self) -> &str { + &self.declaration.name + } + + pub fn kind(&self) -> DeclarationKind { + self.declaration.kind + } + + pub fn scope(&self) -> ScopeView<'m> { + let scope = self.manager.scope(self.declaration.scope); + ScopeView { + manager: &self.manager, + scope, + } + } } impl<'m> std::fmt::Debug for DeclarationView<'m> { @@ -96,9 +194,36 @@ impl<'m> std::fmt::Debug for DeclarationView<'m> { } } +#[derive(Clone, Copy)] pub struct ReferenceView<'m> { - manager: &'m ScopeManager, - reference: &'m Reference, + pub(crate) manager: &'m ScopeManager, + pub(crate) reference: &'m Reference, +} + +impl<'m> ReferenceView<'m> { + pub fn id(&self) -> ReferenceId { + self.reference.id + } + + pub fn kind(&self) -> ReferenceKind { + self.reference.kind + } + + pub fn scope(&self) -> ScopeView<'m> { + let scope = self.manager.scope(self.reference.scope); + ScopeView { + manager: &self.manager, + scope, + } + } + + pub fn declaration(&self) -> DeclarationView<'m> { + let declaration = self.manager.declaration(self.reference.declaration); + DeclarationView { + manager: &self.manager, + declaration, + } + } } impl<'m> std::fmt::Debug for ReferenceView<'m> { diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/analysis_test.rs b/compiler/forget/crates/forget_semantic_analysis/tests/analysis_test.rs index 37975e127c..11708f8ba5 100644 --- a/compiler/forget/crates/forget_semantic_analysis/tests/analysis_test.rs +++ b/compiler/forget/crates/forget_semantic_analysis/tests/analysis_test.rs @@ -12,15 +12,12 @@ fn fixtures() { let ast = parse(&input, path.to_str().unwrap()).unwrap(); let mut analysis = analyze(&ast); - let ast_output = serde_json::to_string_pretty(&ast).unwrap(); let mut output = String::new(); writeln!(&mut output, "{:#?}", analysis.debug()).unwrap(); let diagnostics = analysis.diagnostics(); for diagnostic in diagnostics { writeln!(&mut output, "{:#?}", diagnostic).unwrap(); } - assert_snapshot!(format!( - "Input:\n{input}\n\nAST:\n{ast_output}\n\nAnalysis:\n{output}" - )); + assert_snapshot!(format!("Input:\n{input}\n\nAnalysis:\n{output}")); }); } diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/simple-function.js b/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/simple-function.js new file mode 100644 index 0000000000..ef9d16bb14 --- /dev/null +++ b/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/simple-function.js @@ -0,0 +1,9 @@ +function Component(a) { + Math; + let b = 0; + const foo = function foo_(c) { + let d = 1; + return a + b + c + d; + }; + return foo(); +} diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@globals-and-imports.js.snap b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@globals-and-imports.js.snap index d1b94090ca..10e8be739d 100644 --- a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@globals-and-imports.js.snap +++ b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@globals-and-imports.js.snap @@ -1,6 +1,6 @@ --- source: crates/forget_semantic_analysis/tests/analysis_test.rs -expression: "format!(\"Input:\\n{input}\\n\\nAST:\\n{ast_output}\\n\\nAnalysis:\\n{output}\")" +expression: "format!(\"Input:\\n{input}\\n\\nAnalysis:\\n{output}\")" input_file: crates/forget_semantic_analysis/tests/fixtures/globals-and-imports.js --- Input: @@ -23,791 +23,6 @@ function Component(props) { } -AST: -{ - "type": "Program", - "body": [ - { - "type": "ImportDeclaration", - "specifiers": [ - { - "type": "ImportDefaultSpecifier", - "local": { - "type": "Identifier", - "name": "Foo", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "source": { - "type": "StringLiteral", - "value": "foo", - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "ImportDeclaration", - "specifiers": [ - { - "type": "ImportNamespaceSpecifier", - "local": { - "type": "Identifier", - "name": "Bar", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "source": { - "type": "StringLiteral", - "value": "bar", - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "ImportDeclaration", - "specifiers": [ - { - "type": "ImportSpecifier", - "imported": { - "type": "Identifier", - "name": "Baz", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "local": { - "type": "Identifier", - "name": "Baz", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "source": { - "type": "StringLiteral", - "value": "baz", - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "FunctionDeclaration", - "id": { - "type": "Identifier", - "name": "Component", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "params": [ - { - "type": "Identifier", - "name": "props", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "body": { - "type": "BlockStatement", - "body": [ - { - "type": "VariableDeclaration", - "kind": "let", - "declarations": [ - { - "type": "VariableDeclarator", - "id": { - "type": "Identifier", - "name": "g", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "init": { - "type": "Identifier", - "name": "global", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "VariableDeclaration", - "kind": "let", - "declarations": [ - { - "type": "VariableDeclarator", - "id": { - "type": "Identifier", - "name": "y", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "init": { - "type": "NewExpression", - "callee": { - "type": "Identifier", - "name": "Array", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "arguments": [ - { - "type": "MemberExpression", - "object": { - "type": "Identifier", - "name": "props", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "property": { - "type": "Identifier", - "name": "count", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "computed": false, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "VariableDeclaration", - "kind": "let", - "declarations": [ - { - "type": "VariableDeclarator", - "id": { - "type": "Identifier", - "name": "s", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "init": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "String", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "arguments": [ - { - "type": "StringLiteral", - "value": "hello", - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "VariableDeclaration", - "kind": "let", - "declarations": [ - { - "type": "VariableDeclarator", - "id": { - "type": "Identifier", - "name": "b", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "init": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "Boolean", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "arguments": [ - { - "type": "BooleanLiteral", - "value": true, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "VariableDeclaration", - "kind": "let", - "declarations": [ - { - "type": "VariableDeclarator", - "id": { - "type": "Identifier", - "name": "n", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "init": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "Number", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "arguments": [ - { - "type": "NumericLiteral", - "value": 0, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "VariableDeclaration", - "kind": "let", - "declarations": [ - { - "type": "VariableDeclarator", - "id": { - "type": "Identifier", - "name": "x", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "init": { - "type": "CallExpression", - "callee": { - "type": "MemberExpression", - "object": { - "type": "Identifier", - "name": "Math", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "property": { - "type": "Identifier", - "name": "min", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "computed": false, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "arguments": [ - { - "type": "MemberExpression", - "object": { - "type": "Identifier", - "name": "props", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "property": { - "type": "Identifier", - "name": "x", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "computed": false, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "MemberExpression", - "object": { - "type": "Identifier", - "name": "props", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "property": { - "type": "Identifier", - "name": "y", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "computed": false, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "ExpressionStatement", - "expression": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "setTimeout", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "arguments": [ - { - "type": "ArrowFunctionExpression", - "id": null, - "params": [], - "body": { - "type": "BlockStatement", - "body": [], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "generator": false, - "async": false, - "loc": null, - "range": { - "start": 0, - "end": 1 - }, - "expression": false, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "NumericLiteral", - "value": 0, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "directive": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "ExpressionStatement", - "expression": { - "type": "CallExpression", - "callee": { - "type": "Identifier", - "name": "setInterval", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "arguments": [ - { - "type": "ArrowFunctionExpression", - "id": null, - "params": [], - "body": { - "type": "BlockStatement", - "body": [], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "generator": false, - "async": false, - "loc": null, - "range": { - "start": 0, - "end": 1 - }, - "expression": false, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "NumericLiteral", - "value": 0, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "directive": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "ExpressionStatement", - "expression": { - "type": "Identifier", - "name": "Foo", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "directive": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "ExpressionStatement", - "expression": { - "type": "Identifier", - "name": "Bar", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "directive": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "ExpressionStatement", - "expression": { - "type": "Identifier", - "name": "Baz", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "directive": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "generator": false, - "async": false, - "loc": null, - "range": { - "start": 0, - "end": 1 - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "sourceType": "module", - "loc": null, - "range": { - "start": 0, - "end": 1 - } -} - Analysis: Scope { id: ScopeId( diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@labels.js.snap b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@labels.js.snap index fb540061c3..3d90fd2649 100644 --- a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@labels.js.snap +++ b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@labels.js.snap @@ -1,6 +1,6 @@ --- source: crates/forget_semantic_analysis/tests/analysis_test.rs -expression: "format!(\"Input:\\n{input}\\n\\nAST:\\n{ast_output}\\n\\nAnalysis:\\n{output}\")" +expression: "format!(\"Input:\\n{input}\\n\\nAnalysis:\\n{output}\")" input_file: crates/forget_semantic_analysis/tests/fixtures/labels.js --- Input: @@ -19,431 +19,6 @@ function Component(props) { } -AST: -{ - "type": "Program", - "body": [ - { - "type": "FunctionDeclaration", - "id": { - "type": "Identifier", - "name": "Component", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "params": [ - { - "type": "Identifier", - "name": "props", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "body": { - "type": "BlockStatement", - "body": [ - { - "type": "VariableDeclaration", - "kind": "let", - "declarations": [ - { - "type": "VariableDeclarator", - "id": { - "type": "Identifier", - "name": "y", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "init": { - "type": "NumericLiteral", - "value": 0, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "LabeledStatement", - "label": { - "type": "Identifier", - "name": "foo", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "body": { - "type": "ForStatement", - "init": { - "type": "VariableDeclaration", - "kind": "let", - "declarations": [ - { - "type": "VariableDeclarator", - "id": { - "type": "Identifier", - "name": "x", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "init": { - "type": "NumericLiteral", - "value": 0, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "test": { - "type": "BinaryExpression", - "left": { - "type": "Identifier", - "name": "x", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "operator": "<", - "right": { - "type": "NumericLiteral", - "value": 4621819117588971520, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "update": { - "type": "UpdateExpression", - "operator": "++", - "argument": { - "type": "Identifier", - "name": "x", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "prefix": false, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "body": { - "type": "BlockStatement", - "body": [ - { - "type": "IfStatement", - "test": { - "type": "BinaryExpression", - "left": { - "type": "Identifier", - "name": "x", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "operator": "==", - "right": { - "type": "NumericLiteral", - "value": 4619567317775286272, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "consequent": { - "type": "BlockStatement", - "body": [ - { - "type": "BreakStatement", - "label": { - "type": "Identifier", - "name": "foo", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "alternate": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "ExpressionStatement", - "expression": { - "type": "AssignmentExpression", - "operator": "=", - "left": { - "type": "Identifier", - "name": "y", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "right": { - "type": "BinaryExpression", - "left": { - "type": "Identifier", - "name": "x", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "operator": "+", - "right": { - "type": "Identifier", - "name": "y", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "directive": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "ContinueStatement", - "label": { - "type": "Identifier", - "name": "foo", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - { - "type": "LabeledStatement", - "label": { - "type": "Identifier", - "name": "bar", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "body": { - "type": "IfStatement", - "test": { - "type": "Identifier", - "name": "props", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "consequent": { - "type": "BlockStatement", - "body": [ - { - "type": "BreakStatement", - "label": { - "type": "Identifier", - "name": "bar", - "typeAnnotation": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "alternate": null, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "loc": null, - "range": { - "start": 0, - "end": 1 - } - }, - "generator": false, - "async": false, - "loc": null, - "range": { - "start": 0, - "end": 1 - }, - "loc": null, - "range": { - "start": 0, - "end": 1 - } - } - ], - "sourceType": "module", - "loc": null, - "range": { - "start": 0, - "end": 1 - } -} - Analysis: Scope { id: ScopeId( diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@simple-function.js.snap b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@simple-function.js.snap new file mode 100644 index 0000000000..f03f5c92e6 --- /dev/null +++ b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@simple-function.js.snap @@ -0,0 +1,223 @@ +--- +source: crates/forget_semantic_analysis/tests/analysis_test.rs +expression: "format!(\"Input:\\n{input}\\n\\nAnalysis:\\n{output}\")" +input_file: crates/forget_semantic_analysis/tests/fixtures/simple-function.js +--- +Input: +function Component(a) { + Math; + let b = 0; + const foo = function foo_(c) { + let d = 1; + return a + b + c + d; + }; + return foo(); +} + + +Analysis: +Scope { + id: ScopeId( + 0, + ), + kind: Global, + labels: {}, + declarations: {}, + references: [], + children: [ + Scope { + id: ScopeId( + 1, + ), + kind: Module, + labels: {}, + declarations: { + "Component": Declaration { + id: DeclarationId( + 0, + ), + kind: FunctionDeclaration, + scope: ScopeId( + 1, + ), + }, + }, + references: [], + children: [ + Scope { + id: ScopeId( + 2, + ), + kind: Function, + labels: {}, + declarations: { + "a": Declaration { + id: DeclarationId( + 1, + ), + kind: FunctionDeclaration, + scope: ScopeId( + 2, + ), + }, + "b": Declaration { + id: DeclarationId( + 2, + ), + kind: Let, + scope: ScopeId( + 2, + ), + }, + "foo": Declaration { + id: DeclarationId( + 3, + ), + kind: Const, + scope: ScopeId( + 2, + ), + }, + }, + references: [ + Reference { + id: ReferenceId( + 4, + ), + kind: Read, + declaration: DeclarationId( + 3, + ), + declaration (name): "foo", + scope: ScopeId( + 2, + ), + }, + ], + children: [ + Scope { + id: ScopeId( + 3, + ), + kind: Function, + labels: {}, + declarations: { + "foo_": Declaration { + id: DeclarationId( + 4, + ), + kind: FunctionDeclaration, + scope: ScopeId( + 3, + ), + }, + }, + references: [], + children: [ + Scope { + id: ScopeId( + 4, + ), + kind: Function, + labels: {}, + declarations: { + "c": Declaration { + id: DeclarationId( + 5, + ), + kind: FunctionDeclaration, + scope: ScopeId( + 4, + ), + }, + "d": Declaration { + id: DeclarationId( + 6, + ), + kind: Let, + scope: ScopeId( + 4, + ), + }, + }, + references: [ + Reference { + id: ReferenceId( + 0, + ), + kind: Read, + declaration: DeclarationId( + 1, + ), + declaration (name): "a", + scope: ScopeId( + 4, + ), + }, + Reference { + id: ReferenceId( + 1, + ), + kind: Read, + declaration: DeclarationId( + 2, + ), + declaration (name): "b", + scope: ScopeId( + 4, + ), + }, + Reference { + id: ReferenceId( + 2, + ), + kind: Read, + declaration: DeclarationId( + 5, + ), + declaration (name): "c", + scope: ScopeId( + 4, + ), + }, + Reference { + id: ReferenceId( + 3, + ), + kind: Read, + declaration: DeclarationId( + 6, + ), + declaration (name): "d", + scope: ScopeId( + 4, + ), + }, + ], + children: [], + }, + ], + }, + ], + }, + ], + }, + ], +} +Diagnostic( + DiagnosticData { + message: "Undefined variable", + span: Some( + SourceSpan { + offset: SourceOffset( + 0, + ), + length: 1, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +