[rust][sema] Support hoisting semantics

Adds basic support for hoisting semantics: * Resolution of variable references 
is _always_ deferred in case the correct binding   hasn't been seet yet due to 
hoisting. * Var and function declarations bubble to the appropriate scope 

There are lots of subtleties that aren't implemented yet but these rules cover a 
lot.
This commit is contained in:
Joe Savona
2023-08-14 12:24:05 -07:00
parent 011570a078
commit cb2ba7118f
5 changed files with 343 additions and 43 deletions
@@ -167,16 +167,8 @@ impl Analyzer {
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 {
self.manager
.add_unresolved_reference(self.current, ast, name.to_string(), kind, range);
}
self.manager
.add_unresolved_reference(self.current, ast, name.to_string(), kind, range);
}
fn visit_declaration_identifier(
@@ -205,25 +197,14 @@ impl Analyzer {
.node_declarations
.insert(AstNode::from(ast), id);
} else {
// Referencing an existing variable, it should be defined
if let Some(declaration) = self.manager.lookup_declaration(self.current, &ast.name) {
let reference = self.manager.add_reference(
self.current,
ReferenceKind::ReadWrite,
declaration.id,
);
self.manager
.node_references
.insert(AstNode::from(ast), reference);
} else {
self.manager.add_unresolved_reference(
self.current,
AstNode::from(ast),
ast.name.clone(),
ReferenceKind::ReadWrite,
ast.range,
);
}
// Re-assigning a variable
self.manager.add_unresolved_reference(
self.current,
AstNode::from(ast),
ast.name.to_string(),
ReferenceKind::Write,
ast.range,
);
}
}
@@ -232,17 +232,65 @@ impl ScopeManager {
name: String,
kind: DeclarationKind,
) -> DeclarationId {
let hoisted_scope = self.get_scope_for_declaration(scope, kind);
let id = DeclarationId(self.declarations.len());
self.declarations.push(Declaration {
id,
kind,
name: name.clone(),
scope,
scope: hoisted_scope,
});
self.scopes[scope.0].declarations.insert(name, id);
self.scopes[hoisted_scope.0].declarations.insert(name, id);
id
}
fn get_scope_for_declaration(&self, scope: ScopeId, kind: DeclarationKind) -> ScopeId {
match kind {
DeclarationKind::Let
| DeclarationKind::Const
| DeclarationKind::CatchClause
| DeclarationKind::For => scope,
DeclarationKind::Var => {
let mut current = scope;
loop {
let scope = self.scope(current);
match scope.kind {
ScopeKind::Function | ScopeKind::Global | ScopeKind::StaticBlock => {
return current;
}
_ => { /* no-op */ }
}
if let Some(parent) = &scope.parent {
current = *parent
} else {
unreachable!("Expected scope without a parent to be a Global scope");
}
}
}
DeclarationKind::FunctionDeclaration => {
let mut current = scope;
loop {
let scope = self.scope(current);
match scope.kind {
ScopeKind::Function
| ScopeKind::Module
| ScopeKind::Global
| ScopeKind::StaticBlock => {
return current;
}
_ => { /* no-op */ }
}
if let Some(parent) = &scope.parent {
current = *parent
} else {
unreachable!("Expected scope without a parent to be a Global scope");
}
}
}
}
}
pub(crate) fn add_reference(
&mut self,
scope: ScopeId,
@@ -300,14 +348,15 @@ pub struct LabelId(usize);
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
pub enum ScopeKind {
Block,
CatchClause,
Class,
For,
Function,
Global,
Module,
Function,
Class,
Block,
For,
StaticBlock,
Switch,
CatchClause,
}
#[derive(Debug, Clone)]
@@ -0,0 +1,19 @@
function Component(props) {
bar;
bar = true;
baz;
baz = false;
function foo() {
bar;
bar = true;
if (props) {
var bar;
}
}
if (props) {
// nest inside a block statement to test that the declaration hoists
var bar;
}
}
var baz;
@@ -106,7 +106,7 @@ Scope {
references: [
Reference {
id: ReferenceId(
0,
4,
),
kind: Read,
declaration: DeclarationId(
@@ -119,7 +119,7 @@ Scope {
},
Reference {
id: ReferenceId(
1,
5,
),
kind: Read,
declaration: DeclarationId(
@@ -141,7 +141,7 @@ Scope {
references: [
Reference {
id: ReferenceId(
2,
0,
),
kind: Read,
declaration: DeclarationId(
@@ -154,9 +154,9 @@ Scope {
},
Reference {
id: ReferenceId(
3,
1,
),
kind: ReadWrite,
kind: Write,
declaration: DeclarationId(
2,
),
@@ -167,7 +167,7 @@ Scope {
},
Reference {
id: ReferenceId(
4,
2,
),
kind: Read,
declaration: DeclarationId(
@@ -180,7 +180,7 @@ Scope {
},
Reference {
id: ReferenceId(
5,
3,
),
kind: Read,
declaration: DeclarationId(
@@ -0,0 +1,251 @@
---
source: crates/forget_semantic_analysis/tests/analysis_test.rs
expression: "format!(\"Input:\\n{input}\\n\\nAnalysis:\\n{output}\")"
input_file: crates/forget_semantic_analysis/tests/fixtures/var-hoisting.js
---
Input:
function Component(props) {
bar;
bar = true;
baz;
baz = false;
function foo() {
bar;
bar = true;
if (props) {
var bar;
}
}
if (props) {
// nest inside a block statement to test that the declaration hoists
var bar;
}
}
var baz;
Analysis:
Scope {
id: ScopeId(
0,
),
kind: Global,
declarations: {
"baz": Declaration {
id: DeclarationId(
5,
),
kind: Var,
scope: ScopeId(
0,
),
},
},
references: [],
children: [
Scope {
id: ScopeId(
1,
),
kind: Module,
declarations: {
"Component": Declaration {
id: DeclarationId(
0,
),
kind: FunctionDeclaration,
scope: ScopeId(
1,
),
},
},
references: [],
children: [
Scope {
id: ScopeId(
2,
),
kind: Function,
declarations: {
"props": Declaration {
id: DeclarationId(
1,
),
kind: FunctionDeclaration,
scope: ScopeId(
2,
),
},
"foo": Declaration {
id: DeclarationId(
2,
),
kind: FunctionDeclaration,
scope: ScopeId(
2,
),
},
"bar": Declaration {
id: DeclarationId(
4,
),
kind: Var,
scope: ScopeId(
2,
),
},
},
references: [
Reference {
id: ReferenceId(
3,
),
kind: Read,
declaration: DeclarationId(
4,
),
declaration (name): "bar",
scope: ScopeId(
2,
),
},
Reference {
id: ReferenceId(
4,
),
kind: Write,
declaration: DeclarationId(
4,
),
declaration (name): "bar",
scope: ScopeId(
2,
),
},
Reference {
id: ReferenceId(
5,
),
kind: Read,
declaration: DeclarationId(
1,
),
declaration (name): "props",
scope: ScopeId(
2,
),
},
Reference {
id: ReferenceId(
6,
),
kind: Read,
declaration: DeclarationId(
5,
),
declaration (name): "baz",
scope: ScopeId(
2,
),
},
Reference {
id: ReferenceId(
7,
),
kind: Write,
declaration: DeclarationId(
5,
),
declaration (name): "baz",
scope: ScopeId(
2,
),
},
],
children: [
Scope {
id: ScopeId(
3,
),
kind: Function,
declarations: {
"bar": Declaration {
id: DeclarationId(
3,
),
kind: Var,
scope: ScopeId(
3,
),
},
},
references: [
Reference {
id: ReferenceId(
0,
),
kind: Read,
declaration: DeclarationId(
3,
),
declaration (name): "bar",
scope: ScopeId(
3,
),
},
Reference {
id: ReferenceId(
1,
),
kind: Write,
declaration: DeclarationId(
3,
),
declaration (name): "bar",
scope: ScopeId(
3,
),
},
Reference {
id: ReferenceId(
2,
),
kind: Read,
declaration: DeclarationId(
1,
),
declaration (name): "props",
scope: ScopeId(
3,
),
},
],
children: [
Scope {
id: ScopeId(
4,
),
kind: Block,
declarations: {},
references: [],
children: [],
},
],
},
Scope {
id: ScopeId(
5,
),
kind: Block,
declarations: {},
references: [],
children: [],
},
],
},
],
},
],
}