From cb2ba7118f6b826a4847db687e752b2815b5cb54 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Mon, 14 Aug 2023 12:24:05 -0700 Subject: [PATCH] [rust][sema] Support hoisting semantics Adds basic support for hoisting semantics: * Resolution of variable references is _always_ deferred in case the correct binding hasn't been seet yet due to hoisting. * Var and function declarations bubble to the appropriate scope There are lots of subtleties that aren't implemented yet but these rules cover a lot. --- .../forget_semantic_analysis/src/analyzer.rs | 39 +-- .../src/scope_manager.rs | 63 ++++- .../tests/fixtures/var-hoisting.js | 19 ++ .../analysis_test__fixtures@labels.js.snap | 14 +- ...alysis_test__fixtures@var-hoisting.js.snap | 251 ++++++++++++++++++ 5 files changed, 343 insertions(+), 43 deletions(-) create mode 100644 compiler/forget/crates/forget_semantic_analysis/tests/fixtures/var-hoisting.js create mode 100644 compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@var-hoisting.js.snap diff --git a/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs b/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs index cb8129bd0d..5d77889df9 100644 --- a/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs +++ b/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs @@ -167,16 +167,8 @@ impl Analyzer { kind: ReferenceKind, range: Option, ) { - let declaration = self.manager.lookup_declaration(self.current, name); - if let Some(declaration) = declaration { - let id = self - .manager - .add_reference(self.current, kind, declaration.id); - self.manager.node_references.insert(ast, id); - } else { - self.manager - .add_unresolved_reference(self.current, ast, name.to_string(), kind, range); - } + self.manager + .add_unresolved_reference(self.current, ast, name.to_string(), kind, range); } fn visit_declaration_identifier( @@ -205,25 +197,14 @@ impl Analyzer { .node_declarations .insert(AstNode::from(ast), id); } else { - // Referencing an existing variable, it should be defined - if let Some(declaration) = self.manager.lookup_declaration(self.current, &ast.name) { - let reference = self.manager.add_reference( - self.current, - ReferenceKind::ReadWrite, - declaration.id, - ); - self.manager - .node_references - .insert(AstNode::from(ast), reference); - } else { - self.manager.add_unresolved_reference( - self.current, - AstNode::from(ast), - ast.name.clone(), - ReferenceKind::ReadWrite, - ast.range, - ); - } + // Re-assigning a variable + self.manager.add_unresolved_reference( + self.current, + AstNode::from(ast), + ast.name.to_string(), + ReferenceKind::Write, + ast.range, + ); } } 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 e293059f09..8ffb8ec650 100644 --- a/compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs +++ b/compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs @@ -232,17 +232,65 @@ impl ScopeManager { name: String, kind: DeclarationKind, ) -> DeclarationId { + let hoisted_scope = self.get_scope_for_declaration(scope, kind); + let id = DeclarationId(self.declarations.len()); self.declarations.push(Declaration { id, kind, name: name.clone(), - scope, + scope: hoisted_scope, }); - self.scopes[scope.0].declarations.insert(name, id); + self.scopes[hoisted_scope.0].declarations.insert(name, id); id } + fn get_scope_for_declaration(&self, scope: ScopeId, kind: DeclarationKind) -> ScopeId { + match kind { + DeclarationKind::Let + | DeclarationKind::Const + | DeclarationKind::CatchClause + | DeclarationKind::For => scope, + DeclarationKind::Var => { + let mut current = scope; + loop { + let scope = self.scope(current); + match scope.kind { + ScopeKind::Function | ScopeKind::Global | ScopeKind::StaticBlock => { + return current; + } + _ => { /* no-op */ } + } + if let Some(parent) = &scope.parent { + current = *parent + } else { + unreachable!("Expected scope without a parent to be a Global scope"); + } + } + } + DeclarationKind::FunctionDeclaration => { + let mut current = scope; + loop { + let scope = self.scope(current); + match scope.kind { + ScopeKind::Function + | ScopeKind::Module + | ScopeKind::Global + | ScopeKind::StaticBlock => { + return current; + } + _ => { /* no-op */ } + } + if let Some(parent) = &scope.parent { + current = *parent + } else { + unreachable!("Expected scope without a parent to be a Global scope"); + } + } + } + } + } + pub(crate) fn add_reference( &mut self, scope: ScopeId, @@ -300,14 +348,15 @@ pub struct LabelId(usize); #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub enum ScopeKind { + Block, + CatchClause, + Class, + For, + Function, Global, Module, - Function, - Class, - Block, - For, + StaticBlock, Switch, - CatchClause, } #[derive(Debug, Clone)] diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/var-hoisting.js b/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/var-hoisting.js new file mode 100644 index 0000000000..9dac162f78 --- /dev/null +++ b/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/var-hoisting.js @@ -0,0 +1,19 @@ +function Component(props) { + bar; + bar = true; + baz; + baz = false; + function foo() { + bar; + bar = true; + if (props) { + var bar; + } + } + if (props) { + // nest inside a block statement to test that the declaration hoists + var bar; + } +} + +var baz; 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 2fe1e40bc6..2a42463302 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 @@ -106,7 +106,7 @@ Scope { references: [ Reference { id: ReferenceId( - 0, + 4, ), kind: Read, declaration: DeclarationId( @@ -119,7 +119,7 @@ Scope { }, Reference { id: ReferenceId( - 1, + 5, ), kind: Read, declaration: DeclarationId( @@ -141,7 +141,7 @@ Scope { references: [ Reference { id: ReferenceId( - 2, + 0, ), kind: Read, declaration: DeclarationId( @@ -154,9 +154,9 @@ Scope { }, Reference { id: ReferenceId( - 3, + 1, ), - kind: ReadWrite, + kind: Write, declaration: DeclarationId( 2, ), @@ -167,7 +167,7 @@ Scope { }, Reference { id: ReferenceId( - 4, + 2, ), kind: Read, declaration: DeclarationId( @@ -180,7 +180,7 @@ Scope { }, Reference { id: ReferenceId( - 5, + 3, ), kind: Read, declaration: DeclarationId( diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@var-hoisting.js.snap b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@var-hoisting.js.snap new file mode 100644 index 0000000000..74d599a008 --- /dev/null +++ b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@var-hoisting.js.snap @@ -0,0 +1,251 @@ +--- +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/var-hoisting.js +--- +Input: +function Component(props) { + bar; + bar = true; + baz; + baz = false; + function foo() { + bar; + bar = true; + if (props) { + var bar; + } + } + if (props) { + // nest inside a block statement to test that the declaration hoists + var bar; + } +} + +var baz; + + +Analysis: +Scope { + id: ScopeId( + 0, + ), + kind: Global, + declarations: { + "baz": Declaration { + id: DeclarationId( + 5, + ), + kind: Var, + scope: ScopeId( + 0, + ), + }, + }, + references: [], + children: [ + Scope { + id: ScopeId( + 1, + ), + kind: Module, + declarations: { + "Component": Declaration { + id: DeclarationId( + 0, + ), + kind: FunctionDeclaration, + scope: ScopeId( + 1, + ), + }, + }, + references: [], + children: [ + Scope { + id: ScopeId( + 2, + ), + kind: Function, + declarations: { + "props": Declaration { + id: DeclarationId( + 1, + ), + kind: FunctionDeclaration, + scope: ScopeId( + 2, + ), + }, + "foo": Declaration { + id: DeclarationId( + 2, + ), + kind: FunctionDeclaration, + scope: ScopeId( + 2, + ), + }, + "bar": Declaration { + id: DeclarationId( + 4, + ), + kind: Var, + scope: ScopeId( + 2, + ), + }, + }, + references: [ + Reference { + id: ReferenceId( + 3, + ), + kind: Read, + declaration: DeclarationId( + 4, + ), + declaration (name): "bar", + scope: ScopeId( + 2, + ), + }, + Reference { + id: ReferenceId( + 4, + ), + kind: Write, + declaration: DeclarationId( + 4, + ), + declaration (name): "bar", + scope: ScopeId( + 2, + ), + }, + Reference { + id: ReferenceId( + 5, + ), + kind: Read, + declaration: DeclarationId( + 1, + ), + declaration (name): "props", + scope: ScopeId( + 2, + ), + }, + Reference { + id: ReferenceId( + 6, + ), + kind: Read, + declaration: DeclarationId( + 5, + ), + declaration (name): "baz", + scope: ScopeId( + 2, + ), + }, + Reference { + id: ReferenceId( + 7, + ), + kind: Write, + declaration: DeclarationId( + 5, + ), + declaration (name): "baz", + scope: ScopeId( + 2, + ), + }, + ], + children: [ + Scope { + id: ScopeId( + 3, + ), + kind: Function, + declarations: { + "bar": Declaration { + id: DeclarationId( + 3, + ), + kind: Var, + scope: ScopeId( + 3, + ), + }, + }, + references: [ + Reference { + id: ReferenceId( + 0, + ), + kind: Read, + declaration: DeclarationId( + 3, + ), + declaration (name): "bar", + scope: ScopeId( + 3, + ), + }, + Reference { + id: ReferenceId( + 1, + ), + kind: Write, + declaration: DeclarationId( + 3, + ), + declaration (name): "bar", + scope: ScopeId( + 3, + ), + }, + Reference { + id: ReferenceId( + 2, + ), + kind: Read, + declaration: DeclarationId( + 1, + ), + declaration (name): "props", + scope: ScopeId( + 3, + ), + }, + ], + children: [ + Scope { + id: ScopeId( + 4, + ), + kind: Block, + declarations: {}, + references: [], + children: [], + }, + ], + }, + Scope { + id: ScopeId( + 5, + ), + kind: Block, + declarations: {}, + references: [], + children: [], + }, + ], + }, + ], + }, + ], +} +