From bfb7d2bfbd5a8fe2906cf42a5a4a448406c741d7 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Wed, 16 Aug 2023 12:26:04 -0700 Subject: [PATCH] [rust][sema] Handle var/let/const redeclaration JavaScript has ~sane~ fun rules around where variables can be redeclared or not, and which kinds of variables this applies to. Actually the rules are pretty straightforward: * `var` can be redeclared any number of times. * In strict mode, other declarations cannot be redeclared within the same scope. This implies that a `var` declaration cannot conflict with these other forms, which must take into account hoisting. So you can't have a `var a; let a` in the same scope, but you also can't have a `let a` at a scope and then a `var a` which will hoist to that same scope. --- .../tests/fixtures/use-memo.js | 8 +- .../forget_fixtures/tests/fixtures_test.rs | 12 +- .../fixtures_test__fixtures@use-memo.js.snap | 14 +- .../forget_semantic_analysis/src/analyzer.rs | 27 +- .../src/scope_manager.rs | 110 ++++-- .../tests/fixtures/block-item-duplication.js | 25 ++ .../tests/fixtures/var-duplication.js | 29 ++ ...t__fixtures@block-item-duplication.js.snap | 308 +++++++++++++++++ ...s_test__fixtures@function-hoisting.js.snap | 8 +- ...test__fixtures@globals-and-imports.js.snap | 4 +- .../analysis_test__fixtures@labels.js.snap | 4 +- ..._test__fixtures@let-const-hoisting.js.snap | 4 +- ...sis_test__fixtures@simple-function.js.snap | 8 +- .../analysis_test__fixtures@tdz.js.snap | 4 +- ...sis_test__fixtures@var-duplication.js.snap | 323 ++++++++++++++++++ ...alysis_test__fixtures@var-hoisting.js.snap | 6 +- 16 files changed, 823 insertions(+), 71 deletions(-) create mode 100644 compiler/forget/crates/forget_semantic_analysis/tests/fixtures/block-item-duplication.js create mode 100644 compiler/forget/crates/forget_semantic_analysis/tests/fixtures/var-duplication.js create mode 100644 compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@block-item-duplication.js.snap create mode 100644 compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@var-duplication.js.snap diff --git a/compiler/forget/crates/forget_fixtures/tests/fixtures/use-memo.js b/compiler/forget/crates/forget_fixtures/tests/fixtures/use-memo.js index c991dfb337..27bc81a2f5 100644 --- a/compiler/forget/crates/forget_fixtures/tests/fixtures/use-memo.js +++ b/compiler/forget/crates/forget_fixtures/tests/fixtures/use-memo.js @@ -1,6 +1,8 @@ +import { useMemo } from "react"; + function Component(x) { - const x = useMemo(() => { - return y; + const y = useMemo(() => { + return x; }); - return x; + return y; } diff --git a/compiler/forget/crates/forget_fixtures/tests/fixtures_test.rs b/compiler/forget/crates/forget_fixtures/tests/fixtures_test.rs index 964fccda30..81ee33d59c 100644 --- a/compiler/forget/crates/forget_fixtures/tests/fixtures_test.rs +++ b/compiler/forget/crates/forget_fixtures/tests/fixtures_test.rs @@ -21,7 +21,17 @@ fn fixtures() { let mut output = String::new(); - let analysis = analyze(&ast); + let mut analysis = analyze(&ast); + let diagnostics = analysis.diagnostics(); + if !diagnostics.is_empty() { + for diagnostic in diagnostics { + eprintln!( + "{:?}", + Report::new(diagnostic) + .with_source_code(NamedSource::new(path.to_string_lossy(), input.clone(),)) + ); + } + } let environment = Environment::new( Features { validate_frozen_lambdas: true, 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 d0f5c5a001..a09b1162e5 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 @@ -4,11 +4,13 @@ expression: "format!(\"Input:\\n{input}\\n\\nOutput:\\n{output}\")" input_file: crates/forget_fixtures/tests/fixtures/use-memo.js --- Input: +import { useMemo } from "react"; + function Component(x) { - const x = useMemo(() => { - return y; + const y = useMemo(() => { + return x; }); - return x; + return y; } @@ -23,12 +25,12 @@ bb0 (block) [2] Label block=bb1 fallthrough=bb6 bb1 (block) predecessors: bb0 - [3] unknown $13 = LoadGlobal y + [3] unknown $13 = LoadLocal unknown x$10 [4] unknown $19 = StoreLocal Reassign unknown t$18 = unknown $13 [5] Goto bb6 bb6 (block) predecessors: bb1 [6] unknown $14 = LoadLocal unknown t$18 - [7] unknown $16 = StoreLocal Const unknown x$15 = unknown $14 - [8] unknown $17 = LoadLocal unknown x$15 + [7] unknown $16 = StoreLocal Const unknown y$15 = unknown $14 + [8] unknown $17 = LoadLocal unknown y$15 [9] Return unknown $17 diff --git a/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs b/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs index 03af4146ab..d04989a9e9 100644 --- a/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs +++ b/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs @@ -157,7 +157,7 @@ impl Analyzer { Analyzer::visit_declaration_pattern( visitor, param, - Some(DeclarationKind::FunctionDeclaration), + Some(DeclarationKind::Function), ); } @@ -204,22 +204,9 @@ impl Analyzer { decl_kind: Option, ) { if let Some(decl_kind) = decl_kind { - // Declaring a "new" variable, report an error if this is a duplicate - // definition. In either case, we create a new declaration. Ie we - // act as if shadowing is allowed in the language - let previous_declaration = self.manager.lookup_declaration(self.current, &ast.name); - if let Some(previous_declaration) = previous_declaration { - if previous_declaration.scope == self.current { - // duplicate definition in the same scope - self.manager.diagnostics.push(Diagnostic::invalid_syntax( - "Duplicate declaration", - ast.range, - )); - } - } - let id = self - .manager - .add_declaration(self.current, ast.name.clone(), decl_kind); + let id = + self.manager + .add_declaration(self.current, ast.name.clone(), decl_kind, ast.range); self.manager .node_declarations .insert(AstNode::from(ast), id); @@ -350,7 +337,8 @@ impl Visitor for Analyzer { let declaration = self.manager.add_declaration( self.current, id.name.clone(), - DeclarationKind::FunctionDeclaration, + DeclarationKind::Function, + id.range, ); self.manager .node_declarations @@ -366,7 +354,8 @@ impl Visitor for Analyzer { let declaration = self.manager.add_declaration( self.current, id.name.clone(), - DeclarationKind::FunctionDeclaration, + DeclarationKind::Function, + id.range, ); self.manager .node_declarations 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 d55e0c3747..8a247e43f6 100644 --- a/compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs +++ b/compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs @@ -1,6 +1,6 @@ use forget_diagnostics::Diagnostic; use forget_estree::{ - BreakStatement, ContinueStatement, ESTreeNode, LabeledStatement, SourceType, + BreakStatement, ContinueStatement, ESTreeNode, LabeledStatement, SourceRange, SourceType, VariableDeclarationKind, }; use forget_utils::PointerAddress; @@ -174,20 +174,6 @@ impl ScopeManager { }) } - pub fn lookup_declaration(&self, scope: ScopeId, name: &str) -> Option<&Declaration> { - let mut current = &self.scopes[scope.0]; - loop { - if let Some(id) = current.declarations.get(name) { - return Some(&self.declarations[id.0]); - } - if let Some(parent) = current.parent { - current = &self.scopes[parent.0]; - } else { - return None; - } - } - } - pub fn lookup_reference( &self, scope: ScopeId, @@ -271,20 +257,86 @@ impl ScopeManager { pub(crate) fn add_declaration( &mut self, - scope: ScopeId, + scope_id: ScopeId, name: String, kind: DeclarationKind, + range: Option, ) -> DeclarationId { - let hoisted_scope = self.get_scope_for_declaration(scope, kind); + let scope = self.scope(scope_id); + // Determine the scope to which this declaration should be hoisted. This mainly applies to var declarations + let hoisted_scope_id = self.get_scope_for_declaration(scope_id, kind); + // Check for redeclaration. The rules are roughly: + // * `var` can be redeclared any number of times in a given scope. These redeclarations have no effect, + // subsequent declarations are equivalent to just reassigning a value to the original declaration. + // ie `var a = 1; var a = 2;` is equivalent to `var a; a = 1; a = 2`. + // * Other forms (in strict mode) may not be redeclared in a given scope. + // * This implies that `var` cannot conflict with other types of declarations, either in the scope + // at which they are declared or the scope to which the var will hoist: + // * `function() { {let a; var a;} }` conflicts at the declaration scope, even though the var will hoise above. + // * `function() { let a; { var a; } }` conflicts bc the var hoists to the scope w a conflicting let. + match kind { + DeclarationKind::Var => { + if let Some(declaration) = scope.declarations.get(&name) { + let declaration = self.declaration(*declaration); + if is_block_scoped_declaration(declaration.kind) { + // Var cannot be declared in the same scope as let/const/class/import/etc + self.diagnostics + .push(Diagnostic::invalid_syntax("Duplicate declaration", range)); + } + } else if hoisted_scope_id != scope_id { + if let Some(declaration) = self.scope(hoisted_scope_id).declarations.get(&name) + { + let declaration = self.declaration(*declaration); + if is_block_scoped_declaration(declaration.kind) { + // Var cannot *hoist* to the same scope as let/const/class/import/etc + self.diagnostics + .push(Diagnostic::invalid_syntax("Duplicate declaration", range)); + } + } + } + // Redeclaration of `var` in a given scope has no effect, subsequent declarations + // are equivalent to re-declarations + // ie `var a = 1; var a = 2;` is equivalent to `var a; a = 1; a = 2`. + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/var#redeclarations + if let Some(declaration) = self.scope(hoisted_scope_id).declarations.get(&name) { + let declaration = self.declaration(*declaration); + if declaration.kind == DeclarationKind::Var { + return declaration.id; + } + } + } + DeclarationKind::CatchClause + | DeclarationKind::Let + | DeclarationKind::Const + | DeclarationKind::Import + | DeclarationKind::Class + | DeclarationKind::Function => { + // When duplicate declarations occur we report an error and then resolve references to the + // first declaration. It doesn't really matter which declaration we refer to, because + // semantic results are invalid if there are errors. The main consideration is that we do + // not want to report a "cannot find declaration for `x`" reference error just because there + // were duplicate declarations of `x`. + if let Some(_declaration) = scope.declarations.get(&name) { + self.diagnostics + .push(Diagnostic::invalid_syntax("Duplicate declaration", range)); + } + } + } + + // Always create a new declaration and id... let id = DeclarationId(self.declarations.len()); self.declarations.push(Declaration { id, kind, name: name.clone(), - scope: hoisted_scope, + scope: hoisted_scope_id, }); - self.scopes[hoisted_scope.0].declarations.insert(name, id); + // ...but only save the first declaration for a given name in each scope + self.scopes[hoisted_scope_id.0] + .declarations + .entry(name) + .or_insert(id); id } @@ -294,8 +346,8 @@ impl ScopeManager { | DeclarationKind::Import | DeclarationKind::Const | DeclarationKind::CatchClause - | DeclarationKind::For => scope, - DeclarationKind::Var | DeclarationKind::FunctionDeclaration => { + | DeclarationKind::Class => scope, + DeclarationKind::Var | DeclarationKind::Function => { let mut current = scope; loop { let scope = self.scope(current); @@ -342,6 +394,18 @@ impl ScopeManager { } } +fn is_block_scoped_declaration(kind: DeclarationKind) -> bool { + match kind { + DeclarationKind::Let + | DeclarationKind::Const + | DeclarationKind::Import + | DeclarationKind::Class + | DeclarationKind::Function + | DeclarationKind::CatchClause => true, + DeclarationKind::Var => false, + } +} + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub struct ScopeId(usize); @@ -393,11 +457,11 @@ pub struct Label { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)] pub enum DeclarationKind { + Class, Const, Var, Let, - FunctionDeclaration, - For, + Function, CatchClause, Import, } diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/block-item-duplication.js b/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/block-item-duplication.js new file mode 100644 index 0000000000..146961c4b9 --- /dev/null +++ b/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/block-item-duplication.js @@ -0,0 +1,25 @@ +function Component() { + let a = 1; + let a = 2; // error + + const b = 3; + const b = 4; // error + + function foo() {} + function foo() {} // error + + try { + } catch (c) { + let c = true; // error + const c = true; // error + function c() {} // error + // class c {} // error + } +} + +function Component() { + // error +} + +const x = true; +const x = false; // error diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/var-duplication.js b/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/var-duplication.js new file mode 100644 index 0000000000..45eb815814 --- /dev/null +++ b/compiler/forget/crates/forget_semantic_analysis/tests/fixtures/var-duplication.js @@ -0,0 +1,29 @@ +function Component() { + let a; + { + var a; // error, conflicts when hoisted + } + + const b = 1; + { + var b; // error, conflicts + } + + { + let c; + var c; // error, conflicts + } + + { + const d = 2; + var d; // error, conflicts + } + + // there should be one instance of `e`: + var e = 3; + console.log(e); // 3 + var e = 4; + console.log(e); // 4 + var e; + console.log(e); // 4 +} diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@block-item-duplication.js.snap b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@block-item-duplication.js.snap new file mode 100644 index 0000000000..0bded5f03c --- /dev/null +++ b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@block-item-duplication.js.snap @@ -0,0 +1,308 @@ +--- +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/block-item-duplication.js +--- +Input: +function Component() { + let a = 1; + let a = 2; // error + + const b = 3; + const b = 4; // error + + function foo() {} + function foo() {} // error + + try { + } catch (c) { + let c = true; // error + const c = true; // error + function c() {} // error + // class c {} // error + } +} + +function Component() { + // error +} + +const x = true; +const x = false; // error + + +Analysis: +Scope { + id: ScopeId( + 0, + ), + kind: Module, + declarations: { + "Component": Declaration { + id: DeclarationId( + 0, + ), + kind: Function, + scope: ScopeId( + 0, + ), + }, + "x": Declaration { + id: DeclarationId( + 12, + ), + kind: Const, + scope: ScopeId( + 0, + ), + }, + }, + references: [], + children: [ + Scope { + id: ScopeId( + 1, + ), + kind: Function, + declarations: { + "a": Declaration { + id: DeclarationId( + 1, + ), + kind: Let, + scope: ScopeId( + 1, + ), + }, + "b": Declaration { + id: DeclarationId( + 3, + ), + kind: Const, + scope: ScopeId( + 1, + ), + }, + "foo": Declaration { + id: DeclarationId( + 5, + ), + kind: Function, + scope: ScopeId( + 1, + ), + }, + "c": Declaration { + id: DeclarationId( + 10, + ), + kind: Function, + scope: ScopeId( + 1, + ), + }, + }, + references: [], + children: [ + Scope { + id: ScopeId( + 2, + ), + kind: Function, + declarations: {}, + references: [], + children: [], + }, + Scope { + id: ScopeId( + 3, + ), + kind: Function, + declarations: {}, + references: [], + children: [], + }, + Scope { + id: ScopeId( + 4, + ), + kind: Block, + declarations: {}, + references: [], + children: [], + }, + Scope { + id: ScopeId( + 5, + ), + kind: CatchClause, + declarations: { + "c": Declaration { + id: DeclarationId( + 7, + ), + kind: CatchClause, + scope: ScopeId( + 5, + ), + }, + }, + references: [], + children: [ + Scope { + id: ScopeId( + 6, + ), + kind: Block, + declarations: { + "c": Declaration { + id: DeclarationId( + 8, + ), + kind: Let, + scope: ScopeId( + 6, + ), + }, + }, + references: [], + children: [ + Scope { + id: ScopeId( + 7, + ), + kind: Function, + declarations: {}, + references: [], + children: [], + }, + ], + }, + ], + }, + ], + }, + Scope { + id: ScopeId( + 8, + ), + kind: Function, + declarations: {}, + references: [], + children: [], + }, + ], +} +Diagnostic( + DiagnosticData { + message: "Duplicate declaration", + span: Some( + SourceSpan { + offset: SourceOffset( + 42, + ), + length: 1, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Duplicate declaration", + span: Some( + SourceSpan { + offset: SourceOffset( + 82, + ), + length: 1, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Duplicate declaration", + span: Some( + SourceSpan { + offset: SourceOffset( + 130, + ), + length: 3, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Duplicate declaration", + span: Some( + SourceSpan { + offset: SourceOffset( + 210, + ), + length: 1, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Duplicate declaration", + span: Some( + SourceSpan { + offset: SourceOffset( + 242, + ), + length: 1, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Duplicate declaration", + span: Some( + SourceSpan { + offset: SourceOffset( + 301, + ), + length: 9, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Duplicate declaration", + span: Some( + SourceSpan { + offset: SourceOffset( + 351, + ), + length: 1, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) + diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@function-hoisting.js.snap b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@function-hoisting.js.snap index abf73efd83..d12d7c7b17 100644 --- a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@function-hoisting.js.snap +++ b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@function-hoisting.js.snap @@ -39,7 +39,7 @@ Scope { id: DeclarationId( 0, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 0, ), @@ -57,7 +57,7 @@ Scope { id: DeclarationId( 1, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 1, ), @@ -66,7 +66,7 @@ Scope { id: DeclarationId( 2, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 1, ), @@ -191,7 +191,7 @@ Scope { id: DeclarationId( 4, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 9, ), 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 20ad0060d1..2e277c0db7 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 @@ -61,7 +61,7 @@ Scope { id: DeclarationId( 3, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 0, ), @@ -79,7 +79,7 @@ Scope { id: DeclarationId( 4, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 1, ), 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 c945d7994b..2beb93388a 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 @@ -30,7 +30,7 @@ Scope { id: DeclarationId( 0, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 0, ), @@ -48,7 +48,7 @@ Scope { id: DeclarationId( 1, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 1, ), diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@let-const-hoisting.js.snap b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@let-const-hoisting.js.snap index 93ca015093..a7665a2a87 100644 --- a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@let-const-hoisting.js.snap +++ b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@let-const-hoisting.js.snap @@ -25,7 +25,7 @@ Scope { id: DeclarationId( 0, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 0, ), @@ -43,7 +43,7 @@ Scope { id: DeclarationId( 1, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 1, ), 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 index c0dab8fab8..60c13dff4a 100644 --- 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 @@ -26,7 +26,7 @@ Scope { id: DeclarationId( 0, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 0, ), @@ -44,7 +44,7 @@ Scope { id: DeclarationId( 1, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 1, ), @@ -94,7 +94,7 @@ Scope { id: DeclarationId( 4, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 2, ), @@ -112,7 +112,7 @@ Scope { id: DeclarationId( 5, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 3, ), diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@tdz.js.snap b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@tdz.js.snap index cd591f3797..3668fed4ab 100644 --- a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@tdz.js.snap +++ b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@tdz.js.snap @@ -31,7 +31,7 @@ Scope { id: DeclarationId( 0, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 0, ), @@ -49,7 +49,7 @@ Scope { id: DeclarationId( 1, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 1, ), diff --git a/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@var-duplication.js.snap b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@var-duplication.js.snap new file mode 100644 index 0000000000..40e3cf90e1 --- /dev/null +++ b/compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@var-duplication.js.snap @@ -0,0 +1,323 @@ +--- +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-duplication.js +--- +Input: +function Component() { + let a; + { + var a; // error, conflicts when hoisted + } + + const b = 1; + { + var b; // error, conflicts + } + + { + let c; + var c; // error, conflicts + } + + { + const d = 2; + var d; // error, conflicts + } + + // there should be one instance of `e`: + var e = 3; + console.log(e); // 3 + var e = 4; + console.log(e); // 4 + var e; + console.log(e); // 4 +} + + +Analysis: +Scope { + id: ScopeId( + 0, + ), + kind: Module, + declarations: { + "Component": Declaration { + id: DeclarationId( + 0, + ), + kind: Function, + scope: ScopeId( + 0, + ), + }, + }, + references: [], + children: [ + Scope { + id: ScopeId( + 1, + ), + kind: Function, + declarations: { + "a": Declaration { + id: DeclarationId( + 1, + ), + kind: Let, + scope: ScopeId( + 1, + ), + }, + "b": Declaration { + id: DeclarationId( + 3, + ), + kind: Const, + scope: ScopeId( + 1, + ), + }, + "c": Declaration { + id: DeclarationId( + 6, + ), + kind: Var, + scope: ScopeId( + 1, + ), + }, + "d": Declaration { + id: DeclarationId( + 8, + ), + kind: Var, + scope: ScopeId( + 1, + ), + }, + "e": Declaration { + id: DeclarationId( + 9, + ), + kind: Var, + scope: ScopeId( + 1, + ), + }, + }, + references: [ + Reference { + id: ReferenceId( + 0, + ), + kind: Read, + declaration: DeclarationId( + 9, + ), + declaration (name): "e", + scope: ScopeId( + 1, + ), + }, + Reference { + id: ReferenceId( + 1, + ), + kind: Read, + declaration: DeclarationId( + 9, + ), + declaration (name): "e", + scope: ScopeId( + 1, + ), + }, + Reference { + id: ReferenceId( + 2, + ), + kind: Read, + declaration: DeclarationId( + 9, + ), + declaration (name): "e", + scope: ScopeId( + 1, + ), + }, + ], + children: [ + Scope { + id: ScopeId( + 2, + ), + kind: Block, + declarations: {}, + references: [], + children: [], + }, + Scope { + id: ScopeId( + 3, + ), + kind: Block, + declarations: {}, + references: [], + children: [], + }, + Scope { + id: ScopeId( + 4, + ), + kind: Block, + declarations: { + "c": Declaration { + id: DeclarationId( + 5, + ), + kind: Let, + scope: ScopeId( + 4, + ), + }, + }, + references: [], + children: [], + }, + Scope { + id: ScopeId( + 5, + ), + kind: Block, + declarations: { + "d": Declaration { + id: DeclarationId( + 7, + ), + kind: Const, + scope: ScopeId( + 5, + ), + }, + }, + references: [], + children: [], + }, + ], + }, + ], +} +Diagnostic( + DiagnosticData { + message: "Duplicate declaration", + span: Some( + SourceSpan { + offset: SourceOffset( + 44, + ), + length: 1, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Duplicate declaration", + span: Some( + SourceSpan { + offset: SourceOffset( + 112, + ), + length: 1, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Duplicate declaration", + span: Some( + SourceSpan { + offset: SourceOffset( + 163, + ), + length: 1, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Duplicate declaration", + span: Some( + SourceSpan { + offset: SourceOffset( + 220, + ), + length: 1, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Undefined variable", + span: Some( + SourceSpan { + offset: SourceOffset( + 305, + ), + length: 7, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Undefined variable", + span: Some( + SourceSpan { + offset: SourceOffset( + 341, + ), + length: 7, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) +Diagnostic( + DiagnosticData { + message: "Undefined variable", + span: Some( + SourceSpan { + offset: SourceOffset( + 373, + ), + length: 7, + }, + ), + related_information: [], + severity: InvalidSyntax, + data: [], + }, +) + 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 index 56c671d488..ccb9f084e7 100644 --- 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 @@ -36,7 +36,7 @@ Scope { id: DeclarationId( 0, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 0, ), @@ -63,7 +63,7 @@ Scope { id: DeclarationId( 1, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 1, ), @@ -72,7 +72,7 @@ Scope { id: DeclarationId( 2, ), - kind: FunctionDeclaration, + kind: Function, scope: ScopeId( 1, ),