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: [], + }, +) +