mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[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:
@@ -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)]
|
||||
|
||||
+19
@@ -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;
|
||||
+7
-7
@@ -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(
|
||||
|
||||
+251
@@ -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: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user