@samitouri / QOS-React-1 / commits / 011570a078

[rust][sema] Bubble unresolved references (for hoisting)

This is a precursor to adding support for hoisting in semantic analysis. Previously when we encountered an unknown reference we immediately reported an error. But hoisted variables may be referenced before they're defined, so we don't know for sure when we see an unknown variable if its actually unbound or not. This PR adds the first part of hoistingn support: rather than immediately report an error when encountering an unbound variable we store it in a list of unresolved references on the current scope. As we close each scope we recheck and see if the variable can now be resolved. If yes we record that, otherwise we bubble up the unresolved reference to the parent scope (and try again there). The next PR(s) will handle hoisting of `var` and other syntax to the apropriate nearest scope boundary (function/module).

Joe Savona committed Aug 14, 2023 at 10:08 UTC 011570a0785d4d2f0fd23dd5eae37ac797ce882b
4 files changed +221 -12
compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs
+48 -11
@@ -86,10 +86,9 @@ impl Analyzer {
86 where
87 F: FnMut(&mut Self) -> (),
88 {
89 - let scope = self.manager.add_scope(self.current, kind);
90 - let previous = std::mem::replace(&mut self.current, scope);
89 + let scope = self.enter_scope(kind);
90 f(self);
92 - let scope = std::mem::replace(&mut self.current, previous);
91 + self.close_scope(scope);
92 scope
93 }
94
@@ -101,8 +100,26 @@ impl Analyzer {
100
101 fn close_scope(&mut self, id: ScopeId) {
102 assert_eq!(self.current, id, "Mismatched enter_scope/close_scope");
104 - let scope = self.manager.scope(self.current);
105 - self.current = scope.parent.unwrap();
103 + let scope = self.manager.mut_scope(self.current);
104 + let parent = scope.parent.unwrap();
105 + let unresolved = std::mem::take(&mut scope.unresolved);
106 + drop(scope);
107 + self.current = parent;
108 +
109 + // Lookup unresolved nodes from the child scope in the (now-current) parent scope
110 + for reference in unresolved {
111 + if let Some(declaration) = self
112 + .manager
113 + .lookup_declaration(reference.scope, &reference.name)
114 + {
115 + let id =
116 + self.manager
117 + .add_reference(reference.scope, reference.kind, declaration.id);
118 + self.manager.node_references.insert(reference.ast, id);
119 + } else {
120 + self.manager.push_unresolved_reference(parent, reference);
121 + }
122 + }
123 }
124
125 fn visit_function<T: IntoFunction>(&mut self, node: &T) {
@@ -157,10 +174,8 @@ impl Analyzer {
174 .add_reference(self.current, kind, declaration.id);
175 self.manager.node_references.insert(ast, id);
176 } else {
160 - // Oops, undefined variable
177 self.manager
162 - .diagnostics
163 - .push(Diagnostic::invalid_syntax("Undefined variable", range));
178 + .add_unresolved_reference(self.current, ast, name.to_string(), kind, range);
179 }
180 }
181
@@ -201,9 +216,13 @@ impl Analyzer {
216 .node_references
217 .insert(AstNode::from(ast), reference);
218 } else {
204 - self.manager
205 - .diagnostics
206 - .push(Diagnostic::invalid_syntax("Undefined variable", ast.range));
219 + self.manager.add_unresolved_reference(
220 + self.current,
221 + AstNode::from(ast),
222 + ast.name.clone(),
223 + ReferenceKind::ReadWrite,
224 + ast.range,
225 + );
226 }
227 }
228 }
@@ -616,6 +635,24 @@ impl Visitor for Analyzer {
635 self.visit_module_item(item);
636 }
637 }
638 + let scope = self.manager.mut_scope(self.current);
639 + let unresolved = std::mem::take(&mut scope.unresolved);
640 + for reference in unresolved {
641 + if let Some(declaration) = self
642 + .manager
643 + .lookup_declaration(reference.scope, &reference.name)
644 + {
645 + let id =
646 + self.manager
647 + .add_reference(reference.scope, reference.kind, declaration.id);
648 + self.manager.node_references.insert(reference.ast, id);
649 + } else {
650 + self.manager.diagnostics.push(Diagnostic::invalid_syntax(
651 + "Undefined variable",
652 + reference.range,
653 + ));
654 + }
655 + }
656 }
657
658 fn visit_property(&mut self, ast: &forget_estree::Property) {
compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs
+43 -1
@@ -1,6 +1,7 @@
1 use forget_diagnostics::Diagnostic;
2 use forget_estree::{
3 - BreakStatement, ContinueStatement, ESTreeNode, LabeledStatement, VariableDeclarationKind,
3 + BreakStatement, ContinueStatement, ESTreeNode, LabeledStatement, SourceRange,
4 + VariableDeclarationKind,
5 };
6 use forget_utils::PointerAddress;
7 use indexmap::IndexMap;
@@ -43,6 +44,7 @@ impl ScopeManager {
44 declarations: Default::default(),
45 references: Default::default(),
46 children: Default::default(),
47 + unresolved: Default::default(),
48 }],
49 labels: Default::default(),
50 declarations: Default::default(),
@@ -75,6 +77,10 @@ impl ScopeManager {
77 &self.scopes[id.0]
78 }
79
80 + pub fn mut_scope(&mut self, id: ScopeId) -> &mut Scope {
81 + &mut self.scopes[id.0]
82 + }
83 +
84 pub fn is_descendant_of(&self, maybe_descendant: ScopeId, maybe_ancestor: ScopeId) -> bool {
85 let mut current = maybe_descendant;
86 loop {
@@ -192,6 +198,7 @@ impl ScopeManager {
198 declarations: Default::default(),
199 references: Default::default(),
200 children: Default::default(),
201 + unresolved: Default::default(),
202 });
203 self.scopes[parent.0].children.push(id);
204 id
@@ -252,6 +259,31 @@ impl ScopeManager {
259 self.scopes[scope.0].references.push(id);
260 id
261 }
262 +
263 + pub(crate) fn push_unresolved_reference(
264 + &mut self,
265 + scope: ScopeId,
266 + reference: UnresolvedReference,
267 + ) {
268 + self.scopes[scope.0].unresolved.push(reference);
269 + }
270 +
271 + pub(crate) fn add_unresolved_reference(
272 + &mut self,
273 + scope: ScopeId,
274 + ast: AstNode,
275 + name: String,
276 + kind: ReferenceKind,
277 + range: Option<SourceRange>,
278 + ) {
279 + self.scopes[scope.0].unresolved.push(UnresolvedReference {
280 + ast,
281 + scope,
282 + name,
283 + kind,
284 + range,
285 + });
286 + }
287 }
288
289 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
@@ -286,6 +318,16 @@ pub struct Scope {
318 pub declarations: IndexMap<String, DeclarationId>,
319 pub references: Vec<ReferenceId>,
320 pub children: Vec<ScopeId>,
321 + pub unresolved: Vec<UnresolvedReference>,
322 +}
323 +
324 +#[derive(Debug, Clone)]
325 +pub struct UnresolvedReference {
326 + pub scope: ScopeId,
327 + pub ast: AstNode,
328 + pub name: String,
329 + pub kind: ReferenceKind,
330 + pub range: Option<SourceRange>,
331 }
332
333 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
compiler/forget/crates/forget_semantic_analysis/tests/fixtures/let-const-hoisting.js new
+8
@@ -0,0 +1,8 @@
1 +function Component() {
2 + function foo() {
3 + a;
4 + b;
5 + }
6 + let a;
7 + const b = true;
8 +}
compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@let-const-hoisting.js.snap new
+122
@@ -0,0 +1,122 @@
1 +---
2 +source: crates/forget_semantic_analysis/tests/analysis_test.rs
3 +expression: "format!(\"Input:\\n{input}\\n\\nAnalysis:\\n{output}\")"
4 +input_file: crates/forget_semantic_analysis/tests/fixtures/let-const-hoisting.js
5 +---
6 +Input:
7 +function Component() {
8 + function foo() {
9 + a;
10 + b;
11 + }
12 + let a;
13 + const b = true;
14 +}
15 +
16 +
17 +Analysis:
18 +Scope {
19 + id: ScopeId(
20 + 0,
21 + ),
22 + kind: Global,
23 + declarations: {},
24 + references: [],
25 + children: [
26 + Scope {
27 + id: ScopeId(
28 + 1,
29 + ),
30 + kind: Module,
31 + declarations: {
32 + "Component": Declaration {
33 + id: DeclarationId(
34 + 0,
35 + ),
36 + kind: FunctionDeclaration,
37 + scope: ScopeId(
38 + 1,
39 + ),
40 + },
41 + },
42 + references: [],
43 + children: [
44 + Scope {
45 + id: ScopeId(
46 + 2,
47 + ),
48 + kind: Function,
49 + declarations: {
50 + "foo": Declaration {
51 + id: DeclarationId(
52 + 1,
53 + ),
54 + kind: FunctionDeclaration,
55 + scope: ScopeId(
56 + 2,
57 + ),
58 + },
59 + "a": Declaration {
60 + id: DeclarationId(
61 + 2,
62 + ),
63 + kind: Let,
64 + scope: ScopeId(
65 + 2,
66 + ),
67 + },
68 + "b": Declaration {
69 + id: DeclarationId(
70 + 3,
71 + ),
72 + kind: Const,
73 + scope: ScopeId(
74 + 2,
75 + ),
76 + },
77 + },
78 + references: [],
79 + children: [
80 + Scope {
81 + id: ScopeId(
82 + 3,
83 + ),
84 + kind: Function,
85 + declarations: {},
86 + references: [
87 + Reference {
88 + id: ReferenceId(
89 + 0,
90 + ),
91 + kind: Read,
92 + declaration: DeclarationId(
93 + 2,
94 + ),
95 + declaration (name): "a",
96 + scope: ScopeId(
97 + 3,
98 + ),
99 + },
100 + Reference {
101 + id: ReferenceId(
102 + 1,
103 + ),
104 + kind: Read,
105 + declaration: DeclarationId(
106 + 3,
107 + ),
108 + declaration (name): "b",
109 + scope: ScopeId(
110 + 3,
111 + ),
112 + },
113 + ],
114 + children: [],
115 + },
116 + ],
117 + },
118 + ],
119 + },
120 + ],
121 +}
122 +