From e6e2d9437e26bbb39c07a94fa9cbd6611d83bcc4 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Fri, 4 Aug 2023 09:45:56 -0700 Subject: [PATCH] [rust] Improved name resolution This is the start of an improved semantic analysis pass, reusing the ScopeManager added earlier in the stack but with new analysis built using the new visitor trait. The logic is a rough port of https://github.com/facebook/hermes/blob/main/tools/hermes-parser/js/hermes-eslint/src/scope-manager/referencer/Referencer.js Lots of bits are still missing, i'm starting with the parts that Forget needs. --- .../forget_diagnostics/src/diagnostic.rs | 2 +- .../crates/forget_estree/src/generated.rs | 163 ++---- .../forget_estree/src/generated_extensions.rs | 2 +- ...t_estree__tests__fixtures@simple.json.snap | 2 +- .../forget_estree_codegen/src/ecmascript.json | 21 +- .../forget_hermes_parser/src/generated.rs | 50 +- .../src/generated_extension.rs | 15 +- .../forget_hermes_parser/tests/parser_test.rs | 5 +- ...bug.useMemo-deps-array-not-cleared.js.snap | 4 + ..._test__fixtures@bug_object-pattern.js.snap | 2 + ...apturing-function-member-expr-call.js.snap | 2 + ...es@destructure-direct-reassignment.js.snap | 4 + ..._fixtures@destructuring-assignment.js.snap | 10 + ...e-and-local-variables-with-default.js.snap | 6 + ...ixed-scope-declarations-and-locals.js.snap | 6 + ...tures@destructuring-object-default.js.snap | 4 + ...destructuring-object-param-default.js.snap | 2 + ...s@destructuring-property-inference.js.snap | 2 + ...arser_test__fixtures@destructuring.js.snap | 10 + ..._todo.computed-lval-in-destructure.js.snap | 2 + ...ref-added-to-dep-without-type-info.js.snap | 2 + ...t__fixtures@error.todo-kitchensink.js.snap | 12 + ...analysis-destructured-rest-element.js.snap | 2 + ..._test__fixtures@for-of-destructure.js.snap | 2 + ...st__fixtures@object-pattern-params.js.snap | 4 + ...current-aliased-not-added-to-dep-2.js.snap | 2 + ...res@ref-current-not-added-to-dep-2.js.snap | 2 + ...es@remove-memoization-kitchen-sink.js.snap | 4 + ...-ternary-destruction-with-mutation.js.snap | 2 + ...s@ssa-renaming-ternary-destruction.js.snap | 2 + ...ng-via-destructuring-with-mutation.js.snap | 6 + ...res@ssa-renaming-via-destructuring.js.snap | 6 + ...es@unused-object-element-with-rest.js.snap | 2 + ...st__fixtures@unused-object-element.js.snap | 4 + .../forget_semantic_analysis/src/analyzer.rs | 539 ++++++++++++++++++ .../forget_semantic_analysis/src/lib.rs | 7 +- .../src/{analyze.rs => scope_manager.rs} | 108 ++-- .../src/scope_view.rs | 115 ++++ .../snapshots/analysis_test__fixtures.snap | 460 +++++++-------- 39 files changed, 1147 insertions(+), 448 deletions(-) create mode 100644 compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs rename compiler/forget/crates/forget_semantic_analysis/src/{analyze.rs => scope_manager.rs} (85%) create mode 100644 compiler/forget/crates/forget_semantic_analysis/src/scope_view.rs diff --git a/compiler/forget/crates/forget_diagnostics/src/diagnostic.rs b/compiler/forget/crates/forget_diagnostics/src/diagnostic.rs index 68c8ee679b..b0b3e19b54 100644 --- a/compiler/forget/crates/forget_diagnostics/src/diagnostic.rs +++ b/compiler/forget/crates/forget_diagnostics/src/diagnostic.rs @@ -272,7 +272,7 @@ impl From for Diagnostics { fn source_span_from_range(range: SourceRange) -> SourceSpan { SourceSpan::new( - ByteOffset::from(range.start as usize - 1).into(), + ByteOffset::from(range.start as usize).into(), ByteOffset::from((u32::from(range.end) - range.start) as usize).into(), ) } diff --git a/compiler/forget/crates/forget_estree/src/generated.rs b/compiler/forget/crates/forget_estree/src/generated.rs index a5dc8e632d..fed2590590 100644 --- a/compiler/forget/crates/forget_estree/src/generated.rs +++ b/compiler/forget/crates/forget_estree/src/generated.rs @@ -1993,10 +1993,15 @@ impl Serialize for ObjectPattern { } #[derive(Deserialize, Clone, Debug)] pub struct AssignmentProperty { - pub key: PropertyKey, + pub key: Expression, pub value: Pattern, pub kind: PropertyKind, - pub method: bool, + #[serde(rename = "computed")] + pub is_computed: bool, + #[serde(rename = "shorthand")] + pub is_shorthand: bool, + #[serde(rename = "method")] + pub is_method: bool, #[serde(default)] pub loc: Option, #[serde(default)] @@ -2013,7 +2018,9 @@ impl Serialize for AssignmentProperty { state.serialize_entry("key", &self.key)?; state.serialize_entry("value", &self.value)?; state.serialize_entry("kind", &self.kind)?; - state.serialize_entry("method", &self.method)?; + state.serialize_entry("computed", &self.is_computed)?; + state.serialize_entry("shorthand", &self.is_shorthand)?; + state.serialize_entry("method", &self.is_method)?; state.serialize_entry("loc", &self.loc)?; state.serialize_entry("range", &self.range)?; state.end() @@ -5287,54 +5294,17 @@ impl<'de> serde::Deserialize<'de> for AssignmentPropertyOrRestElement { } #[derive(Serialize, Clone, Debug)] #[serde(untagged)] -pub enum PropertyKey { - Identifier(Box), - Literal(Box), -} -#[derive(Deserialize, Debug)] -enum __PropertyKeyTag { - Identifier, - Literal, -} -impl<'de> serde::Deserialize<'de> for PropertyKey { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let tagged = serde::Deserializer::deserialize_any( - deserializer, - serde::__private::de::TaggedContentVisitor::< - __PropertyKeyTag, - >::new("type", "PropertyKey"), - )?; - match tagged.0 { - __PropertyKeyTag::Identifier => { - let node: Box = as Deserialize>::deserialize( - serde::__private::de::ContentDeserializer::::new(tagged.1), - )?; - Ok(PropertyKey::Identifier(node)) - } - __PropertyKeyTag::Literal => { - let node: Box = as Deserialize>::deserialize( - serde::__private::de::ContentDeserializer::::new(tagged.1), - )?; - Ok(PropertyKey::Literal(node)) - } - } - } -} -#[derive(Serialize, Clone, Debug)] -#[serde(untagged)] pub enum AssignmentTarget { Expression(Expression), Pattern(Pattern), } #[derive(Deserialize, Debug)] enum __AssignmentTargetTag { + Identifier, + ArrayPattern, + ObjectPattern, + RestElement, + AssignmentPattern, ArrayExpression, ArrowFunctionExpression, AssignmentExpression, @@ -5347,7 +5317,6 @@ enum __AssignmentTargetTag { ConditionalExpression, CoverTypedIdentifier, FunctionExpression, - Identifier, ImportExpression, JSXElement, JSXFragment, @@ -5370,10 +5339,6 @@ enum __AssignmentTargetTag { UnaryExpression, UpdateExpression, YieldExpression, - ArrayPattern, - ObjectPattern, - RestElement, - AssignmentPattern, } impl<'de> serde::Deserialize<'de> for AssignmentTarget { fn deserialize(deserializer: D) -> Result @@ -5387,6 +5352,46 @@ impl<'de> serde::Deserialize<'de> for AssignmentTarget { >::new("type", "AssignmentTarget"), )?; match tagged.0 { + __AssignmentTargetTag::Identifier => { + let node: Box = as Deserialize>::deserialize( + serde::__private::de::ContentDeserializer::::new(tagged.1), + )?; + Ok(AssignmentTarget::Pattern(Pattern::Identifier(node))) + } + __AssignmentTargetTag::ArrayPattern => { + let node: Box = as Deserialize>::deserialize( + serde::__private::de::ContentDeserializer::::new(tagged.1), + )?; + Ok(AssignmentTarget::Pattern(Pattern::ArrayPattern(node))) + } + __AssignmentTargetTag::ObjectPattern => { + let node: Box = as Deserialize>::deserialize( + serde::__private::de::ContentDeserializer::::new(tagged.1), + )?; + Ok(AssignmentTarget::Pattern(Pattern::ObjectPattern(node))) + } + __AssignmentTargetTag::RestElement => { + let node: Box = as Deserialize>::deserialize( + serde::__private::de::ContentDeserializer::::new(tagged.1), + )?; + Ok(AssignmentTarget::Pattern(Pattern::RestElement(node))) + } + __AssignmentTargetTag::AssignmentPattern => { + let node: Box = as Deserialize>::deserialize( + serde::__private::de::ContentDeserializer::::new(tagged.1), + )?; + Ok(AssignmentTarget::Pattern(Pattern::AssignmentPattern(node))) + } __AssignmentTargetTag::ArrayExpression => { let node: Box = serde::Deserialize<'de> for AssignmentTarget { )?; Ok(AssignmentTarget::Expression(Expression::FunctionExpression(node))) } - __AssignmentTargetTag::Identifier => { - let node: Box = as Deserialize>::deserialize( - serde::__private::de::ContentDeserializer::::new(tagged.1), - )?; - Ok(AssignmentTarget::Expression(Expression::Identifier(node))) - } __AssignmentTargetTag::ImportExpression => { let node: Box = serde::Deserialize<'de> for AssignmentTarget { )?; Ok(AssignmentTarget::Expression(Expression::YieldExpression(node))) } - __AssignmentTargetTag::ArrayPattern => { - let node: Box = as Deserialize>::deserialize( - serde::__private::de::ContentDeserializer::::new(tagged.1), - )?; - Ok(AssignmentTarget::Pattern(Pattern::ArrayPattern(node))) - } - __AssignmentTargetTag::ObjectPattern => { - let node: Box = as Deserialize>::deserialize( - serde::__private::de::ContentDeserializer::::new(tagged.1), - )?; - Ok(AssignmentTarget::Pattern(Pattern::ObjectPattern(node))) - } - __AssignmentTargetTag::RestElement => { - let node: Box = as Deserialize>::deserialize( - serde::__private::de::ContentDeserializer::::new(tagged.1), - )?; - Ok(AssignmentTarget::Pattern(Pattern::RestElement(node))) - } - __AssignmentTargetTag::AssignmentPattern => { - let node: Box = as Deserialize>::deserialize( - serde::__private::de::ContentDeserializer::::new(tagged.1), - )?; - Ok(AssignmentTarget::Pattern(Pattern::AssignmentPattern(node))) - } } } } @@ -8464,7 +8429,7 @@ pub trait Visitor2 { } } fn visit_assignment_property(&mut self, ast: &AssignmentProperty) { - self.visit_property_key(&ast.key); + self.visit_expression(&ast.key); self.visit_pattern(&ast.value); } fn visit_rest_element(&mut self, ast: &RestElement) { @@ -8884,24 +8849,14 @@ pub trait Visitor2 { } } } - fn visit_property_key(&mut self, ast: &PropertyKey) { - match ast { - PropertyKey::Identifier(ast) => { - self.visit_identifier(ast); - } - PropertyKey::Literal(ast) => { - self.visit_literal(ast); - } - } - } fn visit_assignment_target(&mut self, ast: &AssignmentTarget) { match ast { - AssignmentTarget::Expression(ast) => { - self.visit_expression(ast); - } AssignmentTarget::Pattern(ast) => { self.visit_pattern(ast); } + AssignmentTarget::Expression(ast) => { + self.visit_expression(ast); + } } } fn visit_chain_element(&mut self, ast: &ChainElement) { diff --git a/compiler/forget/crates/forget_estree/src/generated_extensions.rs b/compiler/forget/crates/forget_estree/src/generated_extensions.rs index c593db989b..b864037b34 100644 --- a/compiler/forget/crates/forget_estree/src/generated_extensions.rs +++ b/compiler/forget/crates/forget_estree/src/generated_extensions.rs @@ -6,7 +6,7 @@ pub trait ESTreeNode {} impl Default for SourceType { fn default() -> Self { - Self::Script + Self::Module } } diff --git a/compiler/forget/crates/forget_estree/src/snapshots/forget_estree__tests__fixtures@simple.json.snap b/compiler/forget/crates/forget_estree/src/snapshots/forget_estree__tests__fixtures@simple.json.snap index b1d3ef551f..096e3a558b 100644 --- a/compiler/forget/crates/forget_estree/src/snapshots/forget_estree__tests__fixtures@simple.json.snap +++ b/compiler/forget/crates/forget_estree/src/snapshots/forget_estree__tests__fixtures@simple.json.snap @@ -360,7 +360,7 @@ Output: } } ], - "sourceType": "script", + "sourceType": "module", "loc": { "source": null, "start": { diff --git a/compiler/forget/crates/forget_estree_codegen/src/ecmascript.json b/compiler/forget/crates/forget_estree_codegen/src/ecmascript.json index 62c8327da8..f88f3e2e60 100644 --- a/compiler/forget/crates/forget_estree_codegen/src/ecmascript.json +++ b/compiler/forget/crates/forget_estree_codegen/src/ecmascript.json @@ -879,7 +879,7 @@ "type": "Property", "fields": { "key": { - "type": "PropertyKey" + "type": "Expression" }, "value": { "type": "Pattern" @@ -888,8 +888,17 @@ "type": "PropertyKind", "TODO": "fixed value `init`" }, - "method": { + "is_computed": { "type": "bool", + "rename": "computed" + }, + "is_shorthand": { + "type": "bool", + "rename": "shorthand" + }, + "is_method": { + "type": "bool", + "rename": "method", "TODO": "fixed value `false`" } } @@ -1210,13 +1219,9 @@ "AssignmentProperty", "RestElement" ], - "PropertyKey": [ - "Identifier", - "Literal" - ], "AssignmentTarget": [ - "Expression", - "Pattern" + "Pattern", + "Expression" ], "ChainElement": [ "CallExpression", diff --git a/compiler/forget/crates/forget_hermes_parser/src/generated.rs b/compiler/forget/crates/forget_hermes_parser/src/generated.rs index 5b97360787..85e23e4c2c 100644 --- a/compiler/forget/crates/forget_hermes_parser/src/generated.rs +++ b/compiler/forget/crates/forget_hermes_parser/src/generated.rs @@ -3034,26 +3034,30 @@ impl FromHermes for AssignmentPropertyOrRestElement { } } } -impl FromHermes for PropertyKey { +impl FromHermes for AssignmentTarget { fn convert(cx: &mut Context, node: NodePtr) -> Self { let node_ref = node.as_ref(); match node_ref.kind { NodeKind::Identifier => { let node = Identifier::convert(cx, node); - PropertyKey::Identifier(Box::new(node)) + AssignmentTarget::Pattern(Pattern::Identifier(Box::new(node))) } - _ => { - panic!( - "Unexpected node kind `{:?}` for `{}`", node_ref.kind, "PropertyKey" - ) + NodeKind::ArrayPattern => { + let node = ArrayPattern::convert(cx, node); + AssignmentTarget::Pattern(Pattern::ArrayPattern(Box::new(node))) + } + NodeKind::ObjectPattern => { + let node = ObjectPattern::convert(cx, node); + AssignmentTarget::Pattern(Pattern::ObjectPattern(Box::new(node))) + } + NodeKind::RestElement => { + let node = RestElement::convert(cx, node); + AssignmentTarget::Pattern(Pattern::RestElement(Box::new(node))) + } + NodeKind::AssignmentPattern => { + let node = AssignmentPattern::convert(cx, node); + AssignmentTarget::Pattern(Pattern::AssignmentPattern(Box::new(node))) } - } - } -} -impl FromHermes for AssignmentTarget { - fn convert(cx: &mut Context, node: NodePtr) -> Self { - let node_ref = node.as_ref(); - match node_ref.kind { NodeKind::ArrayExpression => { let node = ArrayExpression::convert(cx, node); AssignmentTarget::Expression(Expression::ArrayExpression(Box::new(node))) @@ -3110,10 +3114,6 @@ impl FromHermes for AssignmentTarget { Expression::FunctionExpression(Box::new(node)), ) } - NodeKind::Identifier => { - let node = Identifier::convert(cx, node); - AssignmentTarget::Expression(Expression::Identifier(Box::new(node))) - } NodeKind::ImportExpression => { let node = ImportExpression::convert(cx, node); AssignmentTarget::Expression( @@ -3216,22 +3216,6 @@ impl FromHermes for AssignmentTarget { let node = YieldExpression::convert(cx, node); AssignmentTarget::Expression(Expression::YieldExpression(Box::new(node))) } - NodeKind::ArrayPattern => { - let node = ArrayPattern::convert(cx, node); - AssignmentTarget::Pattern(Pattern::ArrayPattern(Box::new(node))) - } - NodeKind::ObjectPattern => { - let node = ObjectPattern::convert(cx, node); - AssignmentTarget::Pattern(Pattern::ObjectPattern(Box::new(node))) - } - NodeKind::RestElement => { - let node = RestElement::convert(cx, node); - AssignmentTarget::Pattern(Pattern::RestElement(Box::new(node))) - } - NodeKind::AssignmentPattern => { - let node = AssignmentPattern::convert(cx, node); - AssignmentTarget::Pattern(Pattern::AssignmentPattern(Box::new(node))) - } _ => { panic!( "Unexpected node kind `{:?}` for `{}`", node_ref.kind, diff --git a/compiler/forget/crates/forget_hermes_parser/src/generated_extension.rs b/compiler/forget/crates/forget_hermes_parser/src/generated_extension.rs index 3dfbb95508..54af5d991f 100644 --- a/compiler/forget/crates/forget_hermes_parser/src/generated_extension.rs +++ b/compiler/forget/crates/forget_hermes_parser/src/generated_extension.rs @@ -17,9 +17,10 @@ use hermes::parser::{ hermes_get_FunctionDeclaration_id, hermes_get_FunctionDeclaration_params, hermes_get_FunctionExpression_async, hermes_get_FunctionExpression_body, hermes_get_FunctionExpression_generator, hermes_get_FunctionExpression_id, - hermes_get_FunctionExpression_params, hermes_get_Property_key, hermes_get_Property_kind, - hermes_get_Property_method, hermes_get_Property_value, NodeKind, NodeLabel, NodeLabelOpt, - NodeListRef, NodePtr, NodePtrOpt, NodeString, NodeStringOpt, SMRange, + hermes_get_FunctionExpression_params, hermes_get_Property_computed, hermes_get_Property_key, + hermes_get_Property_kind, hermes_get_Property_method, hermes_get_Property_shorthand, + hermes_get_Property_value, NodeKind, NodeLabel, NodeLabelOpt, NodeListRef, NodePtr, NodePtrOpt, + NodeString, NodeStringOpt, SMRange, }; use hermes::utf::utf8_with_surrogates_to_string; @@ -137,14 +138,18 @@ impl FromHermes for AssignmentProperty { let key = FromHermes::convert(cx, unsafe { hermes_get_Property_key(node) }); let value = FromHermes::convert(cx, unsafe { hermes_get_Property_value(node) }); let kind = FromHermesLabel::convert(cx, unsafe { hermes_get_Property_kind(node) }); - let method = unsafe { hermes_get_Property_method(node) }; + let is_method = unsafe { hermes_get_Property_method(node) }; + let is_computed = unsafe { hermes_get_Property_computed(node) }; + let is_shorthand = unsafe { hermes_get_Property_shorthand(node) }; let loc = None; let range = convert_range(node); AssignmentProperty { key, value, kind, - method, + is_method, + is_computed, + is_shorthand, loc, range: Some(range), } diff --git a/compiler/forget/crates/forget_hermes_parser/tests/parser_test.rs b/compiler/forget/crates/forget_hermes_parser/tests/parser_test.rs index 345c8e8891..c76b0c5de0 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/parser_test.rs +++ b/compiler/forget/crates/forget_hermes_parser/tests/parser_test.rs @@ -1,5 +1,6 @@ use std::env; +use forget_estree::SourceType; use forget_hermes_parser::parse; use insta::{assert_snapshot, glob}; use serde_json; @@ -9,7 +10,9 @@ fn fixtures() { glob!("fixtures/**.js", |path| { println!("fixture {}", path.to_str().unwrap()); let input = std::fs::read_to_string(path).unwrap(); - let ast = parse(&input, path.to_str().unwrap()).unwrap(); + let mut ast = parse(&input, path.to_str().unwrap()).unwrap(); + // TODO: hack to prevent changing lots of fixtures all at once + ast.source_type = SourceType::Script; let output = serde_json::to_string_pretty(&ast).unwrap(); let output = output.trim(); assert_snapshot!(format!("Input:\n{input}\n\nOutput:\n{output}")); diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@bug.useMemo-deps-array-not-cleared.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@bug.useMemo-deps-array-not-cleared.js.snap index c0387e11ff..f79804102c 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@bug.useMemo-deps-array-not-cleared.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@bug.useMemo-deps-array-not-cleared.js.snap @@ -58,6 +58,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -88,6 +90,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@bug_object-pattern.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@bug_object-pattern.js.snap index 661e3bb5da..06c24450ba 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@bug_object-pattern.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@bug_object-pattern.js.snap @@ -74,6 +74,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@capturing-function-member-expr-call.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@capturing-function-member-expr-call.js.snap index 758ec979bf..38bda4c499 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@capturing-function-member-expr-call.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@capturing-function-member-expr-call.js.snap @@ -60,6 +60,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructure-direct-reassignment.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructure-direct-reassignment.js.snap index ca0fd2b8a3..e9bdd31ffe 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructure-direct-reassignment.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructure-direct-reassignment.js.snap @@ -124,6 +124,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -154,6 +156,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-assignment.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-assignment.js.snap index f32bbe6671..0337c0bd6a 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-assignment.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-assignment.js.snap @@ -224,6 +224,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": false, "method": false, "loc": null, "range": { @@ -239,6 +241,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": false, "method": false, "loc": null, "range": { @@ -356,6 +360,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": false, "method": false, "loc": null, "range": { @@ -371,6 +377,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": false, "method": false, "loc": null, "range": { @@ -401,6 +409,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-mixed-scope-and-local-variables-with-default.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-mixed-scope-and-local-variables-with-default.js.snap index 083895bab9..4ff8cc6172 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-mixed-scope-and-local-variables-with-default.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-mixed-scope-and-local-variables-with-default.js.snap @@ -263,6 +263,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -310,6 +312,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -357,6 +361,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-mixed-scope-declarations-and-locals.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-mixed-scope-declarations-and-locals.js.snap index 50fcb70941..5c8bab3846 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-mixed-scope-declarations-and-locals.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-mixed-scope-declarations-and-locals.js.snap @@ -247,6 +247,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -277,6 +279,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -307,6 +311,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-object-default.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-object-default.js.snap index a6626f4cde..9cb947a92a 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-object-default.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-object-default.js.snap @@ -90,6 +90,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -152,6 +154,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": false, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-object-param-default.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-object-param-default.js.snap index bbd2a96507..b85a1bc751 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-object-param-default.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-object-param-default.js.snap @@ -69,6 +69,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-property-inference.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-property-inference.js.snap index cb946891c1..f645cb8b29 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-property-inference.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring-property-inference.js.snap @@ -191,6 +191,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": false, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring.js.snap index 47a93ecbc7..1d35614976 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@destructuring.js.snap @@ -139,6 +139,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -154,6 +156,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": false, "method": false, "loc": null, "range": { @@ -326,6 +330,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": false, "method": false, "loc": null, "range": { @@ -341,6 +347,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": false, "method": false, "loc": null, "range": { @@ -371,6 +379,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error._todo.computed-lval-in-destructure.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error._todo.computed-lval-in-destructure.js.snap index b4d0f0c22e..951139fa12 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error._todo.computed-lval-in-destructure.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error._todo.computed-lval-in-destructure.js.snap @@ -133,6 +133,8 @@ Output: } }, "kind": "init", + "computed": true, + "shorthand": false, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error.invalid-use-ref-added-to-dep-without-type-info.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error.invalid-use-ref-added-to-dep-without-type-info.js.snap index 8c5581ee80..f688842047 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error.invalid-use-ref-added-to-dep-without-type-info.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error.invalid-use-ref-added-to-dep-without-type-info.js.snap @@ -61,6 +61,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error.todo-kitchensink.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error.todo-kitchensink.js.snap index e9d4ae0956..0da2b0676e 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error.todo-kitchensink.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@error.todo-kitchensink.js.snap @@ -149,6 +149,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -179,6 +181,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -226,6 +230,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -721,6 +727,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -768,6 +776,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -1604,6 +1614,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@escape-analysis-destructured-rest-element.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@escape-analysis-destructured-rest-element.js.snap index 4d615c7c8e..8c48711622 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@escape-analysis-destructured-rest-element.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@escape-analysis-destructured-rest-element.js.snap @@ -76,6 +76,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@for-of-destructure.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@for-of-destructure.js.snap index a99dcd502c..7508308225 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@for-of-destructure.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@for-of-destructure.js.snap @@ -269,6 +269,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@object-pattern-params.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@object-pattern-params.js.snap index f121e9f058..d62de0077c 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@object-pattern-params.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@object-pattern-params.js.snap @@ -54,6 +54,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -84,6 +86,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ref-current-aliased-not-added-to-dep-2.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ref-current-aliased-not-added-to-dep-2.js.snap index f4255a8f0e..775f3ca9fb 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ref-current-aliased-not-added-to-dep-2.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ref-current-aliased-not-added-to-dep-2.js.snap @@ -57,6 +57,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ref-current-not-added-to-dep-2.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ref-current-not-added-to-dep-2.js.snap index 61a05685e1..0a2d4dfec7 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ref-current-not-added-to-dep-2.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ref-current-not-added-to-dep-2.js.snap @@ -56,6 +56,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@remove-memoization-kitchen-sink.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@remove-memoization-kitchen-sink.js.snap index 32a6fcefc8..7231865df5 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@remove-memoization-kitchen-sink.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@remove-memoization-kitchen-sink.js.snap @@ -488,6 +488,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -518,6 +520,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-ternary-destruction-with-mutation.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-ternary-destruction-with-mutation.js.snap index 797e3d699f..f8fd81a8db 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-ternary-destruction-with-mutation.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-ternary-destruction-with-mutation.js.snap @@ -232,6 +232,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-ternary-destruction.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-ternary-destruction.js.snap index 1542d0d847..5d193655cb 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-ternary-destruction.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-ternary-destruction.js.snap @@ -231,6 +231,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-via-destructuring-with-mutation.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-via-destructuring-with-mutation.js.snap index e8e05f63b8..8f2a8aa0d1 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-via-destructuring-with-mutation.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-via-destructuring-with-mutation.js.snap @@ -80,6 +80,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -290,6 +292,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -389,6 +393,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-via-destructuring.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-via-destructuring.js.snap index 208b9a81b3..a2409051fb 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-via-destructuring.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@ssa-renaming-via-destructuring.js.snap @@ -79,6 +79,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -289,6 +291,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -388,6 +392,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@unused-object-element-with-rest.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@unused-object-element-with-rest.js.snap index 4fd4695964..4202a9e418 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@unused-object-element-with-rest.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@unused-object-element-with-rest.js.snap @@ -74,6 +74,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@unused-object-element.js.snap b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@unused-object-element.js.snap index e751a13db9..87a4768cb4 100644 --- a/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@unused-object-element.js.snap +++ b/compiler/forget/crates/forget_hermes_parser/tests/snapshots/parser_test__fixtures@unused-object-element.js.snap @@ -73,6 +73,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { @@ -103,6 +105,8 @@ Output: } }, "kind": "init", + "computed": false, + "shorthand": true, "method": false, "loc": null, "range": { diff --git a/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs b/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs new file mode 100644 index 0000000000..201c7a9fbb --- /dev/null +++ b/compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs @@ -0,0 +1,539 @@ +use forget_diagnostics::Diagnostic; +use forget_estree::{ + AssignmentOperator, AssignmentPropertyOrRestElement, AssignmentTarget, Expression, + ExpressionOrSuper, ForInInit, Function, FunctionBody, Identifier, Pattern, Program, + SourceRange, SourceType, Statement, VariableDeclarationKind, Visitor2, +}; + +use crate::{AstNode, DeclarationKind, LabelKind, ReferenceKind, ScopeId, ScopeKind, ScopeManager}; + +pub fn analyze(ast: &Program) -> ScopeManager { + let mut analyzer = Analyzer::new(); + analyzer.visit_program(ast); + analyzer.manager +} + +struct Analyzer { + manager: ScopeManager, + current: ScopeId, +} + +impl Analyzer { + fn new() -> Self { + let manager = ScopeManager::new(); + let current = manager.root_id(); + Self { manager, current } + } + + fn enter(&mut self, kind: ScopeKind, mut f: F) -> ScopeId + where + F: FnMut(&mut Self) -> (), + { + let scope = self.manager.add_scope(self.current, kind); + let previous = std::mem::replace(&mut self.current, scope); + f(self); + let scope = std::mem::replace(&mut self.current, previous); + scope + } + + fn enter_scope(&mut self, kind: ScopeKind) -> ScopeId { + let scope = self.manager.add_scope(self.current, kind); + self.current = scope; + scope + } + + fn close_scope(&mut self, id: ScopeId) { + assert_eq!(self.current, id, "Mismatched enter_scope/close_scope"); + let scope = self.manager.scope(self.current); + self.current = scope.parent.unwrap(); + } + + fn visit_function(&mut self, function: &Function) { + self.enter(ScopeKind::Function, |visitor| { + for param in &function.params { + // `this` parameters don't declare variables, nor can they have + // default values + if let Pattern::Identifier(param) = param { + if ¶m.name == "this" { + continue; + } + } + Analyzer::visit_declaration_pattern( + visitor, + param, + Some(DeclarationKind::FunctionDeclaration), + ); + } + + if let Some(body) = &function.body { + match body { + FunctionBody::BlockStatement(body) => { + // Skip calling visit_block_statement to avoid creating an extra + // block scope + for item in &body.body { + visitor.visit_statement(item); + } + } + FunctionBody::Expression(body) => { + visitor.visit_expression(body); + } + } + } + }); + } + + fn visit_reference_identifier( + &mut self, + name: &str, + ast: AstNode, + 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 { + // Oops, undefined variable + self.manager + .diagnostics + .push(Diagnostic::invalid_syntax("Undefined variable", range)); + } + } + + fn visit_declaration_identifier( + &mut self, + ast: &Identifier, + 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); + self.manager + .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 + .diagnostics + .push(Diagnostic::invalid_syntax("Undefined variable", ast.range)); + } + } + } + + fn visit_declaration_pattern(&mut self, ast: &Pattern, decl_kind: Option) { + match ast { + Pattern::Identifier(ast) => { + self.visit_declaration_identifier(ast, decl_kind); + } + Pattern::ArrayPattern(ast) => { + for pat in &ast.elements { + if let Some(pat) = pat { + self.visit_declaration_pattern(pat, decl_kind); + } + } + } + Pattern::ObjectPattern(ast) => { + for property in &ast.properties { + match property { + AssignmentPropertyOrRestElement::AssignmentProperty(property) => { + if property.is_computed { + self.visit_expression(&property.key); + } + self.visit_declaration_pattern(&property.value, decl_kind); + } + AssignmentPropertyOrRestElement::RestElement(property) => { + self.visit_declaration_pattern(&property.argument, decl_kind); + } + } + } + } + Pattern::RestElement(ast) => { + self.visit_declaration_pattern(&ast.argument, decl_kind); + } + Pattern::AssignmentPattern(ast) => { + self.visit_expression(&ast.right); + self.visit_declaration_pattern(&ast.left, decl_kind); + } + } + } +} + +impl Visitor2 for Analyzer { + fn visit_function_declaration(&mut self, ast: &forget_estree::FunctionDeclaration) { + if let Some(id) = &ast.function.id { + let declaration = self.manager.add_declaration( + self.current, + id.name.clone(), + DeclarationKind::FunctionDeclaration, + ); + self.manager + .node_declarations + .insert(AstNode::from(id), declaration); + } + Analyzer::visit_function(self, &ast.function); + } + + fn visit_function_expression(&mut self, ast: &forget_estree::FunctionExpression) { + let mut function_scope: Option = None; + if let Some(id) = &ast.function.id { + function_scope = Some(self.enter_scope(ScopeKind::Function)); + let declaration = self.manager.add_declaration( + self.current, + id.name.clone(), + DeclarationKind::FunctionDeclaration, + ); + self.manager + .node_declarations + .insert(AstNode::from(id), declaration); + } + + Analyzer::visit_function(self, &ast.function); + 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); + } + + fn visit_assignment_expression(&mut self, ast: &forget_estree::AssignmentExpression) { + if ast.operator == AssignmentOperator::Equals { + match &ast.left { + AssignmentTarget::Pattern(left) => { + Analyzer::visit_declaration_pattern(self, left, None); + } + AssignmentTarget::Expression(left) => match left { + Expression::MemberExpression(left) => { + let mut current = left; + // If this is a chain of member expressions, find the innermost .object + // If that's an identifier, record it as a Read. + // Technically we could probably just visit .object normally, + // but in case we want to change the Read to something else we do this + // expansion. + // TODO: revisit and maybe revert this to just visit ast.left normally + loop { + if current.is_computed { + self.visit_expression_or_private_identifier(¤t.property); + } + match ¤t.object { + ExpressionOrSuper::Expression(object) => match object { + Expression::MemberExpression(object) => { + current = object; + } + Expression::Identifier(object) => { + Analyzer::visit_reference_identifier( + self, + &object.name, + AstNode::from(object.as_ref()), + ReferenceKind::Read, + object.range, + ); + break; + } + _ => { + self.visit_expression(object); + break; + } + }, + ExpressionOrSuper::Super(object) => { + self.visit_super(object); + break; + } + } + } + } + _ => { + self.manager.diagnostics.push(Diagnostic::invalid_syntax( + "Invalid AssignmentExpression, expected left-hand side to be a Pattern or MemberExpression", + ast.range + )); + } + }, + } + self.visit_expression(&ast.right); + } else { + let left: &Identifier; + if let AssignmentTarget::Pattern(pat) = &ast.left { + if let Pattern::Identifier(pat) = pat { + left = pat; + } else { + self.manager.diagnostics.push(Diagnostic::invalid_syntax( + "Expected AssignmentExpression.left to be an Identifier when using operator {}", + pat.range() + )); + // Visit the right-hand side anyway to find any errors there + self.visit_expression(&ast.right); + return; + } + } else { + self.manager.diagnostics.push(Diagnostic::invalid_syntax( + "Expected AssignmentExpression.left to be an Identifier when using operator {}", + ast.range, + )); + // Visit the right-hand side anyway to find any errors there + self.visit_expression(&ast.right); + return; + } + Analyzer::visit_reference_identifier( + self, + &left.name, + AstNode::from(left), + ReferenceKind::ReadWrite, + left.range, + ); + self.visit_expression(&ast.right); + } + } + + fn visit_block_statement(&mut self, ast: &forget_estree::BlockStatement) { + self.enter(ScopeKind::Block, |visitor| { + for stmt in &ast.body { + visitor.visit_statement(stmt); + } + }); + } + + fn visit_break_statement(&mut self, ast: &forget_estree::BreakStatement) { + if let Some(label_node) = &ast.label { + if let Some(label) = self + .manager + .lookup_label(self.current, &label_node.name) + .cloned() + { + self.manager + .node_labels + .insert(AstNode::from(ast), label.id); + self.manager + .node_labels + .insert(AstNode::from(label_node), label.id); + } else { + self.manager.diagnostics.push(Diagnostic::invalid_syntax( + "Unknown break label", + label_node.range, + )); + } + } else { + if let Some(label) = self.manager.lookup_break(self.current).cloned() { + self.manager + .node_labels + .insert(AstNode::from(ast), label.id); + } else { + self.manager + .diagnostics + .push(Diagnostic::invalid_syntax("Invalid break", ast.range)); + } + } + } + + fn visit_catch_clause(&mut self, ast: &forget_estree::CatchClause) { + if let Some(param) = &ast.param { + self.enter(ScopeKind::CatchClause, |visitor| { + Analyzer::visit_declaration_pattern( + visitor, + param, + Some(DeclarationKind::CatchClause), + ); + visitor.visit_block_statement(&ast.body); + }); + } else { + self.visit_block_statement(&ast.body); + } + } + + fn visit_continue_statement(&mut self, ast: &forget_estree::ContinueStatement) { + if let Some(label_node) = &ast.label { + if let Some(label) = self + .manager + .lookup_label(self.current, &label_node.name) + .cloned() + { + self.manager + .node_labels + .insert(AstNode::from(ast), label.id); + self.manager + .node_labels + .insert(AstNode::from(label_node), label.id); + } else { + self.manager.diagnostics.push(Diagnostic::invalid_syntax( + "Unknown continue label", + label_node.range, + )); + } + } else { + if let Some(label) = self.manager.lookup_continue(self.current).cloned() { + self.manager + .node_labels + .insert(AstNode::from(ast), label.id); + } else { + self.manager + .diagnostics + .push(Diagnostic::invalid_syntax("Invalid continue", ast.range)); + } + } + } + + fn visit_for_in_statement(&mut self, ast: &forget_estree::ForInStatement) { + // Record an anonymous label for the statement to resolve unlabeled break/continue + let label = self + .manager + .add_anonymous_label(self.current, LabelKind::Loop); + self.manager.node_labels.insert(AstNode::from(ast), label); + + let mut for_scope: Option = None; + match &ast.left { + ForInInit::VariableDeclaration(left) => { + if left.kind != VariableDeclarationKind::Var { + for_scope = Some(self.enter_scope(ScopeKind::For)); + } + self.visit_variable_declaration(left); + } + ForInInit::Pattern(left) => { + Analyzer::visit_declaration_pattern(self, left, None); + } + } + self.visit_expression(&ast.right); + self.visit_statement(&ast.body); + if let Some(for_scope) = for_scope { + self.close_scope(for_scope); + } + } + + fn visit_for_of_statement(&mut self, ast: &forget_estree::ForOfStatement) { + todo!("ForOfStatement") + } + + fn visit_identifier(&mut self, ast: &forget_estree::Identifier) { + Analyzer::visit_reference_identifier( + self, + &ast.name, + AstNode::from(ast), + ReferenceKind::Read, + ast.range, + ); + } + + fn visit_jsxidentifier(&mut self, ast: &forget_estree::JSXIdentifier) { + Analyzer::visit_reference_identifier( + self, + &ast.name, + AstNode::from(ast), + ReferenceKind::Read, + ast.range, + ); + } + + fn visit_labeled_statement(&mut self, ast: &forget_estree::LabeledStatement) { + let body = &ast.body; + let kind = match body { + Statement::ForStatement(_) + | Statement::ForInStatement(_) + | Statement::ForOfStatement(_) + | Statement::WhileStatement(_) + | Statement::DoWhileStatement(_) => LabelKind::Loop, + _ => LabelKind::Other, + }; + let id = self + .manager + .add_label(self.current, kind, ast.label.name.clone()); + self.manager.node_labels.insert(AstNode::from(ast), id); + self.visit_statement(body); + } + + fn visit_member_expression(&mut self, ast: &forget_estree::MemberExpression) { + self.visit_expression_or_super(&ast.object); + if ast.is_computed { + self.visit_expression_or_private_identifier(&ast.property); + } + } + + fn visit_meta_property(&mut self, _ast: &forget_estree::MetaProperty) { + // no-op, these are all builtins + } + + fn visit_private_identifier(&mut self, _ast: &forget_estree::PrivateIdentifier) { + // no-op, these refere to class properties + } + + fn visit_private_name(&mut self, _ast: &forget_estree::PrivateName) { + // no-op, these refere to class properties + } + + fn visit_pattern(&mut self, _ast: &Pattern) { + // This is an internal compiler error: all paths to a `Pattern` node should have been + // covered such that this is unreachable: + // - VariableDeclaration + // - AssignmentExpression + // - CatchClause + unreachable!( + "visit_pattern should not be called directly, call Analyzer::visit_declaration_pattern() instead" + ) + } + + fn visit_program(&mut self, ast: &forget_estree::Program) { + if ast.source_type == SourceType::Module { + self.enter(ScopeKind::Module, |visitor| { + for item in &ast.body { + visitor.visit_module_item(item); + } + }); + } else { + for item in &ast.body { + self.visit_module_item(item); + } + } + } + + fn visit_property(&mut self, ast: &forget_estree::Property) { + if ast.is_computed { + self.visit_expression(&ast.key); + } + self.visit_expression(&ast.value); + } + + fn visit_switch_statement(&mut self, ast: &forget_estree::SwitchStatement) { + self.visit_expression(&ast.discriminant); + self.enter(ScopeKind::Switch, |visitor| { + for case_ in &ast.cases { + visitor.visit_switch_case(case_); + } + }); + } + + fn visit_variable_declaration(&mut self, ast: &forget_estree::VariableDeclaration) { + let kind = ast.kind; + for declaration in &ast.declarations { + Analyzer::visit_declaration_pattern(self, &declaration.id, Some(kind.into())); + if let Some(init) = &declaration.init { + self.visit_expression(init); + } + } + } +} diff --git a/compiler/forget/crates/forget_semantic_analysis/src/lib.rs b/compiler/forget/crates/forget_semantic_analysis/src/lib.rs index 67b61b0aa8..f2849d4890 100644 --- a/compiler/forget/crates/forget_semantic_analysis/src/lib.rs +++ b/compiler/forget/crates/forget_semantic_analysis/src/lib.rs @@ -1,3 +1,6 @@ -mod analyze; +mod analyzer; +mod scope_manager; +mod scope_view; -pub use analyze::analyze; +pub use analyzer::analyze; +pub use scope_manager::*; diff --git a/compiler/forget/crates/forget_semantic_analysis/src/analyze.rs b/compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs similarity index 85% rename from compiler/forget/crates/forget_semantic_analysis/src/analyze.rs rename to compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs index 80305d1e5e..025ae362b5 100644 --- a/compiler/forget/crates/forget_semantic_analysis/src/analyze.rs +++ b/compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs @@ -1,18 +1,20 @@ use forget_diagnostics::Diagnostic; use forget_estree::{ BreakStatement, ContinueStatement, ESTreeNode, Identifier, LabeledStatement, Program, - Statement, Visitor, + Statement, VariableDeclarationKind, Visitor, }; use forget_utils::PointerAddress; use indexmap::IndexMap; -pub fn analyze(ast: &Program) -> SemanticAnalysis { +use crate::scope_view::ScopeView; + +pub fn analyze(ast: &Program) -> ScopeManager { let mut analyzer = Analyzer::new(); analyzer.visit_program(ast); analyzer.results } -pub struct SemanticAnalysis { +pub struct ScopeManager { root: ScopeId, // Storage of the semantic information @@ -23,27 +25,15 @@ pub struct SemanticAnalysis { // Mapping of AST nodes (by pointer address) to semantic information // Not all nodes will have all types of information available - node_scopes: IndexMap, - node_labels: IndexMap, - node_declarations: IndexMap, - node_references: IndexMap, - diagnostics: Vec, + pub(crate) node_scopes: IndexMap, + pub(crate) node_labels: IndexMap, + pub(crate) node_declarations: IndexMap, + pub(crate) node_references: IndexMap, + pub(crate) diagnostics: Vec, } -#[derive(Debug)] -#[allow(dead_code)] -pub struct SemanticAnalysisDebug<'a> { - root: ScopeId, - - // Storage of the semantic information - scopes: &'a Vec, - labels: &'a Vec