[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.
This commit is contained in:
Joe Savona
2023-08-04 09:45:56 -07:00
parent c6ae535e94
commit e6e2d9437e
39 changed files with 1147 additions and 448 deletions
@@ -272,7 +272,7 @@ impl From<Diagnostic> 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(),
)
}
@@ -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<SourceLocation>,
#[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<Identifier>),
Literal(Box<Literal>),
}
#[derive(Deserialize, Debug)]
enum __PropertyKeyTag {
Identifier,
Literal,
}
impl<'de> serde::Deserialize<'de> for PropertyKey {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
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<Identifier> = <Box<
Identifier,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(PropertyKey::Identifier(node))
}
__PropertyKeyTag::Literal => {
let node: Box<Literal> = <Box<
Literal,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::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<D>(deserializer: D) -> Result<Self, D::Error>
@@ -5387,6 +5352,46 @@ impl<'de> serde::Deserialize<'de> for AssignmentTarget {
>::new("type", "AssignmentTarget"),
)?;
match tagged.0 {
__AssignmentTargetTag::Identifier => {
let node: Box<Identifier> = <Box<
Identifier,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(AssignmentTarget::Pattern(Pattern::Identifier(node)))
}
__AssignmentTargetTag::ArrayPattern => {
let node: Box<ArrayPattern> = <Box<
ArrayPattern,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(AssignmentTarget::Pattern(Pattern::ArrayPattern(node)))
}
__AssignmentTargetTag::ObjectPattern => {
let node: Box<ObjectPattern> = <Box<
ObjectPattern,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(AssignmentTarget::Pattern(Pattern::ObjectPattern(node)))
}
__AssignmentTargetTag::RestElement => {
let node: Box<RestElement> = <Box<
RestElement,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(AssignmentTarget::Pattern(Pattern::RestElement(node)))
}
__AssignmentTargetTag::AssignmentPattern => {
let node: Box<AssignmentPattern> = <Box<
AssignmentPattern,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(AssignmentTarget::Pattern(Pattern::AssignmentPattern(node)))
}
__AssignmentTargetTag::ArrayExpression => {
let node: Box<ArrayExpression> = <Box<
ArrayExpression,
@@ -5487,14 +5492,6 @@ impl<'de> serde::Deserialize<'de> for AssignmentTarget {
)?;
Ok(AssignmentTarget::Expression(Expression::FunctionExpression(node)))
}
__AssignmentTargetTag::Identifier => {
let node: Box<Identifier> = <Box<
Identifier,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(AssignmentTarget::Expression(Expression::Identifier(node)))
}
__AssignmentTargetTag::ImportExpression => {
let node: Box<ImportExpression> = <Box<
ImportExpression,
@@ -5683,38 +5680,6 @@ impl<'de> serde::Deserialize<'de> for AssignmentTarget {
)?;
Ok(AssignmentTarget::Expression(Expression::YieldExpression(node)))
}
__AssignmentTargetTag::ArrayPattern => {
let node: Box<ArrayPattern> = <Box<
ArrayPattern,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(AssignmentTarget::Pattern(Pattern::ArrayPattern(node)))
}
__AssignmentTargetTag::ObjectPattern => {
let node: Box<ObjectPattern> = <Box<
ObjectPattern,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(AssignmentTarget::Pattern(Pattern::ObjectPattern(node)))
}
__AssignmentTargetTag::RestElement => {
let node: Box<RestElement> = <Box<
RestElement,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::new(tagged.1),
)?;
Ok(AssignmentTarget::Pattern(Pattern::RestElement(node)))
}
__AssignmentTargetTag::AssignmentPattern => {
let node: Box<AssignmentPattern> = <Box<
AssignmentPattern,
> as Deserialize>::deserialize(
serde::__private::de::ContentDeserializer::<D::Error>::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) {
@@ -6,7 +6,7 @@ pub trait ESTreeNode {}
impl Default for SourceType {
fn default() -> Self {
Self::Script
Self::Module
}
}
@@ -360,7 +360,7 @@ Output:
}
}
],
"sourceType": "script",
"sourceType": "module",
"loc": {
"source": null,
"start": {
@@ -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",
@@ -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,
@@ -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),
}
@@ -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}"));
@@ -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": {
@@ -74,6 +74,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": true,
"method": false,
"loc": null,
"range": {
@@ -60,6 +60,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": true,
"method": false,
"loc": null,
"range": {
@@ -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": {
@@ -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": {
@@ -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": {
@@ -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": {
@@ -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": {
@@ -69,6 +69,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": true,
"method": false,
"loc": null,
"range": {
@@ -191,6 +191,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": false,
"method": false,
"loc": null,
"range": {
@@ -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": {
@@ -133,6 +133,8 @@ Output:
}
},
"kind": "init",
"computed": true,
"shorthand": false,
"method": false,
"loc": null,
"range": {
@@ -61,6 +61,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": true,
"method": false,
"loc": null,
"range": {
@@ -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": {
@@ -76,6 +76,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": true,
"method": false,
"loc": null,
"range": {
@@ -269,6 +269,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": true,
"method": false,
"loc": null,
"range": {
@@ -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": {
@@ -57,6 +57,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": true,
"method": false,
"loc": null,
"range": {
@@ -56,6 +56,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": true,
"method": false,
"loc": null,
"range": {
@@ -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": {
@@ -232,6 +232,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": true,
"method": false,
"loc": null,
"range": {
@@ -231,6 +231,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": true,
"method": false,
"loc": null,
"range": {
@@ -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": {
@@ -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": {
@@ -74,6 +74,8 @@ Output:
}
},
"kind": "init",
"computed": false,
"shorthand": true,
"method": false,
"loc": null,
"range": {
@@ -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": {
@@ -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<F>(&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 &param.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<SourceRange>,
) {
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<DeclarationKind>,
) {
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<DeclarationKind>) {
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<ScopeId> = 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(&current.property);
}
match &current.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<ScopeId> = 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);
}
}
}
}
@@ -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::*;
@@ -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<AstNode, ScopeId>,
node_labels: IndexMap<AstNode, LabelId>,
node_declarations: IndexMap<AstNode, DeclarationId>,
node_references: IndexMap<AstNode, ReferenceId>,
diagnostics: Vec<Diagnostic>,
pub(crate) node_scopes: IndexMap<AstNode, ScopeId>,
pub(crate) node_labels: IndexMap<AstNode, LabelId>,
pub(crate) node_declarations: IndexMap<AstNode, DeclarationId>,
pub(crate) node_references: IndexMap<AstNode, ReferenceId>,
pub(crate) diagnostics: Vec<Diagnostic>,
}
#[derive(Debug)]
#[allow(dead_code)]
pub struct SemanticAnalysisDebug<'a> {
root: ScopeId,
// Storage of the semantic information
scopes: &'a Vec<Scope>,
labels: &'a Vec<Label>,
declarations: &'a Vec<Declaration>,
references: &'a Vec<Reference>,
}
impl SemanticAnalysis {
fn new() -> Self {
impl ScopeManager {
pub(crate) fn new() -> Self {
let root_id = ScopeId(0);
Self {
root: root_id,
@@ -67,13 +57,11 @@ impl SemanticAnalysis {
}
}
pub fn debug(&self) -> SemanticAnalysisDebug<'_> {
SemanticAnalysisDebug {
root: self.root,
scopes: &self.scopes,
labels: &self.labels,
declarations: &self.declarations,
references: &self.references,
pub fn debug(&self) -> ScopeView<'_> {
let root = self.root();
ScopeView {
manager: self,
scope: root,
}
}
@@ -93,7 +81,7 @@ impl SemanticAnalysis {
&self.declarations[id.0]
}
pub fn reference(&self, id: ScopeId) -> &Reference {
pub fn reference(&self, id: ReferenceId) -> &Reference {
&self.references[id.0]
}
@@ -147,6 +135,14 @@ impl SemanticAnalysis {
}
}
pub fn lookup_break(&self, scope: ScopeId) -> Option<&Label> {
todo!()
}
pub fn lookup_continue(&self, scope: ScopeId) -> Option<&Label> {
todo!()
}
pub fn lookup_declaration(&self, scope: ScopeId, name: &str) -> Option<&Declaration> {
let mut current = &self.scopes[scope.0];
loop {
@@ -187,6 +183,14 @@ impl SemanticAnalysis {
id
}
pub(crate) fn add_anonymous_label(&mut self, scope: ScopeId, kind: LabelKind) -> LabelId {
let id = LabelId(self.labels.len());
let name = format!("#{}", id.0);
self.labels.push(Label { id, kind, scope });
self.scopes[scope.0].labels.insert(name, id);
id
}
pub(crate) fn add_declaration(
&mut self,
scope: ScopeId,
@@ -194,7 +198,12 @@ impl SemanticAnalysis {
kind: DeclarationKind,
) -> DeclarationId {
let id = DeclarationId(self.declarations.len());
self.declarations.push(Declaration { id, kind, scope });
self.declarations.push(Declaration {
id,
kind,
name: name.clone(),
scope,
});
self.scopes[scope.0].declarations.insert(name, id);
id
}
@@ -232,9 +241,13 @@ pub struct LabelId(usize);
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
pub enum ScopeKind {
Global,
Module,
Function,
Class,
Block,
For,
Switch,
CatchClause,
}
#[derive(Debug, Clone)]
@@ -266,12 +279,26 @@ pub enum DeclarationKind {
Const,
Var,
Let,
FunctionDeclaration,
For,
CatchClause,
}
impl From<VariableDeclarationKind> for DeclarationKind {
fn from(value: VariableDeclarationKind) -> Self {
match value {
VariableDeclarationKind::Const => Self::Const,
VariableDeclarationKind::Let => Self::Let,
VariableDeclarationKind::Var => Self::Var,
}
}
}
#[derive(Debug, Clone)]
pub struct Declaration {
pub id: DeclarationId,
pub kind: DeclarationKind,
pub name: String,
pub scope: ScopeId,
}
@@ -291,7 +318,7 @@ pub struct Reference {
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, Copy)]
struct AstNode(PointerAddress);
pub(crate) struct AstNode(PointerAddress);
impl AstNode {
fn new<T: ESTreeNode>(node: &T) -> Self {
@@ -318,14 +345,14 @@ where
}
struct Analyzer {
results: SemanticAnalysis,
results: ScopeManager,
current: ScopeId,
is_lvalue: bool,
}
impl Analyzer {
fn new() -> Self {
let results = SemanticAnalysis::new();
let results = ScopeManager::new();
let current = results.root_id();
Self {
results,
@@ -334,7 +361,7 @@ impl Analyzer {
}
}
fn enter<F>(&mut self, kind: ScopeKind, mut f: F) -> ScopeId
pub(crate) fn enter<F>(&mut self, kind: ScopeKind, mut f: F) -> ScopeId
where
F: FnMut(&mut Self) -> (),
{
@@ -344,6 +371,17 @@ impl Analyzer {
let scope = std::mem::replace(&mut self.current, previous);
scope
}
pub(crate) fn enter_scope(&mut self, kind: ScopeKind) -> ScopeId {
let scope = self.results.add_scope(self.current, kind);
self.current = scope;
scope
}
pub(crate) fn close_scope(&mut self) {
let scope = self.results.scope(self.current);
self.current = scope.parent.unwrap();
}
}
impl<'ast> Visitor<'ast> for Analyzer {
@@ -0,0 +1,115 @@
use indexmap::IndexMap;
use crate::{Declaration, Label, Reference, Scope, ScopeManager};
pub struct ScopeView<'m> {
pub(crate) manager: &'m ScopeManager,
pub(crate) scope: &'m Scope,
}
impl<'m> std::fmt::Debug for ScopeView<'m> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let labels: IndexMap<_, _> = self
.scope
.labels
.iter()
.map(|(name, label)| {
(
name.clone(),
LabelView {
manager: &self.manager,
label: self.manager.label(*label),
},
)
})
.collect();
let declarations: IndexMap<_, _> = self
.scope
.declarations
.iter()
.map(|(name, declaration)| {
(
name.clone(),
DeclarationView {
manager: &self.manager,
declaration: self.manager.declaration(*declaration),
},
)
})
.collect();
let references: Vec<_> = self
.scope
.references
.iter()
.map(|reference| ReferenceView {
manager: &self.manager,
reference: self.manager.reference(*reference),
})
.collect();
let children: Vec<_> = self
.scope
.children
.iter()
.map(|child| ScopeView {
manager: &self.manager,
scope: self.manager.scope(*child),
})
.collect();
f.debug_struct("Scope")
.field("id", &self.scope.id)
.field("kind", &self.scope.kind)
.field("labels", &labels)
.field("declarations", &declarations)
.field("references", &references)
.field("children", &children)
.finish()
}
}
pub struct LabelView<'m> {
manager: &'m ScopeManager,
label: &'m Label,
}
impl<'m> std::fmt::Debug for LabelView<'m> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Label")
.field("id", &self.label.id)
.field("kind", &self.label.kind)
.field("scope", &self.label.scope)
.finish()
}
}
pub struct DeclarationView<'m> {
manager: &'m ScopeManager,
declaration: &'m Declaration,
}
impl<'m> std::fmt::Debug for DeclarationView<'m> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Declaration")
.field("id", &self.declaration.id)
.field("kind", &self.declaration.kind)
.field("scope", &self.declaration.scope)
.finish()
}
}
pub struct ReferenceView<'m> {
manager: &'m ScopeManager,
reference: &'m Reference,
}
impl<'m> std::fmt::Debug for ReferenceView<'m> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let declaration = self.manager.declaration(self.reference.declaration);
f.debug_struct("Reference")
.field("id", &self.reference.id)
.field("kind", &self.reference.kind)
.field("declaration", &self.reference.declaration)
.field("declaration (name)", &declaration.name)
.field("scope", &self.reference.scope)
.finish()
}
}
@@ -436,7 +436,7 @@ AST:
}
}
],
"sourceType": "script",
"sourceType": "module",
"loc": null,
"range": {
"start": 0,
@@ -445,271 +445,217 @@ AST:
}
Analysis:
SemanticAnalysisDebug {
root: ScopeId(
Scope {
id: ScopeId(
0,
),
scopes: [
Scope {
id: ScopeId(
0,
),
kind: Global,
parent: None,
labels: {},
declarations: {},
references: [],
children: [
ScopeId(
1,
),
],
},
kind: Global,
labels: {},
declarations: {},
references: [],
children: [
Scope {
id: ScopeId(
1,
),
kind: Function,
parent: Some(
ScopeId(
0,
),
),
labels: {
"foo": LabelId(
0,
),
"bar": LabelId(
1,
),
},
kind: Module,
labels: {},
declarations: {
"props": DeclarationId(
0,
),
"y": DeclarationId(
1,
),
"x": DeclarationId(
2,
),
"Component": Declaration {
id: DeclarationId(
0,
),
kind: FunctionDeclaration,
scope: ScopeId(
1,
),
},
},
references: [
ReferenceId(
0,
),
ReferenceId(
1,
),
ReferenceId(
6,
),
],
children: [
ScopeId(
2,
),
ScopeId(
4,
),
],
},
Scope {
id: ScopeId(
2,
),
kind: Block,
parent: Some(
ScopeId(
1,
),
),
labels: {},
declarations: {},
references: [
ReferenceId(
2,
),
ReferenceId(
3,
),
ReferenceId(
4,
),
ReferenceId(
5,
),
],
children: [
ScopeId(
3,
),
],
},
Scope {
id: ScopeId(
3,
),
kind: Block,
parent: Some(
ScopeId(
2,
),
),
labels: {},
declarations: {},
references: [],
children: [],
},
Scope {
id: ScopeId(
4,
),
kind: Block,
parent: Some(
ScopeId(
1,
),
),
labels: {},
declarations: {},
references: [],
children: [],
},
],
labels: [
Label {
id: LabelId(
0,
),
kind: Loop,
scope: ScopeId(
1,
),
},
Label {
id: LabelId(
1,
),
kind: Other,
scope: ScopeId(
1,
),
},
],
declarations: [
Declaration {
id: DeclarationId(
0,
),
kind: Let,
scope: ScopeId(
1,
),
},
Declaration {
id: DeclarationId(
1,
),
kind: Let,
scope: ScopeId(
1,
),
},
Declaration {
id: DeclarationId(
2,
),
kind: Let,
scope: ScopeId(
1,
),
},
],
references: [
Reference {
id: ReferenceId(
0,
),
kind: Read,
declaration: DeclarationId(
2,
),
scope: ScopeId(
1,
),
},
Reference {
id: ReferenceId(
1,
),
kind: Read,
declaration: DeclarationId(
2,
),
scope: ScopeId(
1,
),
},
Reference {
id: ReferenceId(
2,
),
kind: Read,
declaration: DeclarationId(
2,
),
scope: ScopeId(
2,
),
},
Reference {
id: ReferenceId(
3,
),
kind: ReadWrite,
declaration: DeclarationId(
1,
),
scope: ScopeId(
2,
),
},
Reference {
id: ReferenceId(
4,
),
kind: Read,
declaration: DeclarationId(
2,
),
scope: ScopeId(
2,
),
},
Reference {
id: ReferenceId(
5,
),
kind: Read,
declaration: DeclarationId(
1,
),
scope: ScopeId(
2,
),
},
Reference {
id: ReferenceId(
6,
),
kind: Read,
declaration: DeclarationId(
0,
),
scope: ScopeId(
1,
),
children: [
Scope {
id: ScopeId(
2,
),
kind: Function,
labels: {
"foo": Label {
id: LabelId(
0,
),
kind: Loop,
scope: ScopeId(
2,
),
},
"bar": Label {
id: LabelId(
1,
),
kind: Other,
scope: ScopeId(
2,
),
},
},
declarations: {
"props": Declaration {
id: DeclarationId(
1,
),
kind: FunctionDeclaration,
scope: ScopeId(
2,
),
},
"y": Declaration {
id: DeclarationId(
2,
),
kind: Let,
scope: ScopeId(
2,
),
},
"x": Declaration {
id: DeclarationId(
3,
),
kind: Let,
scope: ScopeId(
2,
),
},
},
references: [
Reference {
id: ReferenceId(
0,
),
kind: Read,
declaration: DeclarationId(
3,
),
declaration (name): "x",
scope: ScopeId(
2,
),
},
Reference {
id: ReferenceId(
1,
),
kind: Read,
declaration: DeclarationId(
3,
),
declaration (name): "x",
scope: ScopeId(
2,
),
},
Reference {
id: ReferenceId(
6,
),
kind: Read,
declaration: DeclarationId(
1,
),
declaration (name): "props",
scope: ScopeId(
2,
),
},
],
children: [
Scope {
id: ScopeId(
3,
),
kind: Block,
labels: {},
declarations: {},
references: [
Reference {
id: ReferenceId(
2,
),
kind: Read,
declaration: DeclarationId(
3,
),
declaration (name): "x",
scope: ScopeId(
3,
),
},
Reference {
id: ReferenceId(
3,
),
kind: ReadWrite,
declaration: DeclarationId(
2,
),
declaration (name): "y",
scope: ScopeId(
3,
),
},
Reference {
id: ReferenceId(
4,
),
kind: Read,
declaration: DeclarationId(
3,
),
declaration (name): "x",
scope: ScopeId(
3,
),
},
Reference {
id: ReferenceId(
5,
),
kind: Read,
declaration: DeclarationId(
2,
),
declaration (name): "y",
scope: ScopeId(
3,
),
},
],
children: [
Scope {
id: ScopeId(
4,
),
kind: Block,
labels: {},
declarations: {},
references: [],
children: [],
},
],
},
Scope {
id: ScopeId(
5,
),
kind: Block,
labels: {},
declarations: {},
references: [],
children: [],
},
],
},
],
},
],
}