@samitouri / QOS-React-1 / commits / cb2ba7118f

[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.

Joe Savona committed Aug 14, 2023 at 12:24 UTC cb2ba7118f6b826a4847db687e752b2815b5cb54
5 files changed +343 -43
compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs
+10 -29
@@ -167,16 +167,8 @@ impl Analyzer {
167 kind: ReferenceKind,
168 range: Option<SourceRange>,
169 ) {
170 - let declaration = self.manager.lookup_declaration(self.current, name);
171 - if let Some(declaration) = declaration {
172 - let id = self
173 - .manager
174 - .add_reference(self.current, kind, declaration.id);
175 - self.manager.node_references.insert(ast, id);
176 - } else {
177 - self.manager
178 - .add_unresolved_reference(self.current, ast, name.to_string(), kind, range);
179 - }
170 + self.manager
171 + .add_unresolved_reference(self.current, ast, name.to_string(), kind, range);
172 }
173
174 fn visit_declaration_identifier(
@@ -205,25 +197,14 @@ impl Analyzer {
197 .node_declarations
198 .insert(AstNode::from(ast), id);
199 } else {
208 - // Referencing an existing variable, it should be defined
209 - if let Some(declaration) = self.manager.lookup_declaration(self.current, &ast.name) {
210 - let reference = self.manager.add_reference(
211 - self.current,
212 - ReferenceKind::ReadWrite,
213 - declaration.id,
214 - );
215 - self.manager
216 - .node_references
217 - .insert(AstNode::from(ast), reference);
218 - } else {
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 - }
200 + // Re-assigning a variable
201 + self.manager.add_unresolved_reference(
202 + self.current,
203 + AstNode::from(ast),
204 + ast.name.to_string(),
205 + ReferenceKind::Write,
206 + ast.range,
207 + );
208 }
209 }
210
compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs
+56 -7
@@ -232,17 +232,65 @@ impl ScopeManager {
232 name: String,
233 kind: DeclarationKind,
234 ) -> DeclarationId {
235 + let hoisted_scope = self.get_scope_for_declaration(scope, kind);
236 +
237 let id = DeclarationId(self.declarations.len());
238 self.declarations.push(Declaration {
239 id,
240 kind,
241 name: name.clone(),
240 - scope,
242 + scope: hoisted_scope,
243 });
242 - self.scopes[scope.0].declarations.insert(name, id);
244 + self.scopes[hoisted_scope.0].declarations.insert(name, id);
245 id
246 }
247
248 + fn get_scope_for_declaration(&self, scope: ScopeId, kind: DeclarationKind) -> ScopeId {
249 + match kind {
250 + DeclarationKind::Let
251 + | DeclarationKind::Const
252 + | DeclarationKind::CatchClause
253 + | DeclarationKind::For => scope,
254 + DeclarationKind::Var => {
255 + let mut current = scope;
256 + loop {
257 + let scope = self.scope(current);
258 + match scope.kind {
259 + ScopeKind::Function | ScopeKind::Global | ScopeKind::StaticBlock => {
260 + return current;
261 + }
262 + _ => { /* no-op */ }
263 + }
264 + if let Some(parent) = &scope.parent {
265 + current = *parent
266 + } else {
267 + unreachable!("Expected scope without a parent to be a Global scope");
268 + }
269 + }
270 + }
271 + DeclarationKind::FunctionDeclaration => {
272 + let mut current = scope;
273 + loop {
274 + let scope = self.scope(current);
275 + match scope.kind {
276 + ScopeKind::Function
277 + | ScopeKind::Module
278 + | ScopeKind::Global
279 + | ScopeKind::StaticBlock => {
280 + return current;
281 + }
282 + _ => { /* no-op */ }
283 + }
284 + if let Some(parent) = &scope.parent {
285 + current = *parent
286 + } else {
287 + unreachable!("Expected scope without a parent to be a Global scope");
288 + }
289 + }
290 + }
291 + }
292 + }
293 +
294 pub(crate) fn add_reference(
295 &mut self,
296 scope: ScopeId,
@@ -300,14 +348,15 @@ pub struct LabelId(usize);
348
349 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
350 pub enum ScopeKind {
303 - Global,
304 - Module,
305 - Function,
306 - Class,
351 Block,
352 + CatchClause,
353 + Class,
354 For,
355 + Function,
356 + Global,
357 + Module,
358 + StaticBlock,
359 Switch,
310 - CatchClause,
360 }
361
362 #[derive(Debug, Clone)]
compiler/forget/crates/forget_semantic_analysis/tests/fixtures/var-hoisting.js new
+19
@@ -0,0 +1,19 @@
1 +function Component(props) {
2 + bar;
3 + bar = true;
4 + baz;
5 + baz = false;
6 + function foo() {
7 + bar;
8 + bar = true;
9 + if (props) {
10 + var bar;
11 + }
12 + }
13 + if (props) {
14 + // nest inside a block statement to test that the declaration hoists
15 + var bar;
16 + }
17 +}
18 +
19 +var baz;
compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@labels.js.snap
+7 -7
@@ -106,7 +106,7 @@ Scope {
106 references: [
107 Reference {
108 id: ReferenceId(
109 - 0,
109 + 4,
110 ),
111 kind: Read,
112 declaration: DeclarationId(
@@ -119,7 +119,7 @@ Scope {
119 },
120 Reference {
121 id: ReferenceId(
122 - 1,
122 + 5,
123 ),
124 kind: Read,
125 declaration: DeclarationId(
@@ -141,7 +141,7 @@ Scope {
141 references: [
142 Reference {
143 id: ReferenceId(
144 - 2,
144 + 0,
145 ),
146 kind: Read,
147 declaration: DeclarationId(
@@ -154,9 +154,9 @@ Scope {
154 },
155 Reference {
156 id: ReferenceId(
157 - 3,
157 + 1,
158 ),
159 - kind: ReadWrite,
159 + kind: Write,
160 declaration: DeclarationId(
161 2,
162 ),
@@ -167,7 +167,7 @@ Scope {
167 },
168 Reference {
169 id: ReferenceId(
170 - 4,
170 + 2,
171 ),
172 kind: Read,
173 declaration: DeclarationId(
@@ -180,7 +180,7 @@ Scope {
180 },
181 Reference {
182 id: ReferenceId(
183 - 5,
183 + 3,
184 ),
185 kind: Read,
186 declaration: DeclarationId(
compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@var-hoisting.js.snap new
+251
@@ -0,0 +1,251 @@
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/var-hoisting.js
5 +---
6 +Input:
7 +function Component(props) {
8 + bar;
9 + bar = true;
10 + baz;
11 + baz = false;
12 + function foo() {
13 + bar;
14 + bar = true;
15 + if (props) {
16 + var bar;
17 + }
18 + }
19 + if (props) {
20 + // nest inside a block statement to test that the declaration hoists
21 + var bar;
22 + }
23 +}
24 +
25 +var baz;
26 +
27 +
28 +Analysis:
29 +Scope {
30 + id: ScopeId(
31 + 0,
32 + ),
33 + kind: Global,
34 + declarations: {
35 + "baz": Declaration {
36 + id: DeclarationId(
37 + 5,
38 + ),
39 + kind: Var,
40 + scope: ScopeId(
41 + 0,
42 + ),
43 + },
44 + },
45 + references: [],
46 + children: [
47 + Scope {
48 + id: ScopeId(
49 + 1,
50 + ),
51 + kind: Module,
52 + declarations: {
53 + "Component": Declaration {
54 + id: DeclarationId(
55 + 0,
56 + ),
57 + kind: FunctionDeclaration,
58 + scope: ScopeId(
59 + 1,
60 + ),
61 + },
62 + },
63 + references: [],
64 + children: [
65 + Scope {
66 + id: ScopeId(
67 + 2,
68 + ),
69 + kind: Function,
70 + declarations: {
71 + "props": Declaration {
72 + id: DeclarationId(
73 + 1,
74 + ),
75 + kind: FunctionDeclaration,
76 + scope: ScopeId(
77 + 2,
78 + ),
79 + },
80 + "foo": Declaration {
81 + id: DeclarationId(
82 + 2,
83 + ),
84 + kind: FunctionDeclaration,
85 + scope: ScopeId(
86 + 2,
87 + ),
88 + },
89 + "bar": Declaration {
90 + id: DeclarationId(
91 + 4,
92 + ),
93 + kind: Var,
94 + scope: ScopeId(
95 + 2,
96 + ),
97 + },
98 + },
99 + references: [
100 + Reference {
101 + id: ReferenceId(
102 + 3,
103 + ),
104 + kind: Read,
105 + declaration: DeclarationId(
106 + 4,
107 + ),
108 + declaration (name): "bar",
109 + scope: ScopeId(
110 + 2,
111 + ),
112 + },
113 + Reference {
114 + id: ReferenceId(
115 + 4,
116 + ),
117 + kind: Write,
118 + declaration: DeclarationId(
119 + 4,
120 + ),
121 + declaration (name): "bar",
122 + scope: ScopeId(
123 + 2,
124 + ),
125 + },
126 + Reference {
127 + id: ReferenceId(
128 + 5,
129 + ),
130 + kind: Read,
131 + declaration: DeclarationId(
132 + 1,
133 + ),
134 + declaration (name): "props",
135 + scope: ScopeId(
136 + 2,
137 + ),
138 + },
139 + Reference {
140 + id: ReferenceId(
141 + 6,
142 + ),
143 + kind: Read,
144 + declaration: DeclarationId(
145 + 5,
146 + ),
147 + declaration (name): "baz",
148 + scope: ScopeId(
149 + 2,
150 + ),
151 + },
152 + Reference {
153 + id: ReferenceId(
154 + 7,
155 + ),
156 + kind: Write,
157 + declaration: DeclarationId(
158 + 5,
159 + ),
160 + declaration (name): "baz",
161 + scope: ScopeId(
162 + 2,
163 + ),
164 + },
165 + ],
166 + children: [
167 + Scope {
168 + id: ScopeId(
169 + 3,
170 + ),
171 + kind: Function,
172 + declarations: {
173 + "bar": Declaration {
174 + id: DeclarationId(
175 + 3,
176 + ),
177 + kind: Var,
178 + scope: ScopeId(
179 + 3,
180 + ),
181 + },
182 + },
183 + references: [
184 + Reference {
185 + id: ReferenceId(
186 + 0,
187 + ),
188 + kind: Read,
189 + declaration: DeclarationId(
190 + 3,
191 + ),
192 + declaration (name): "bar",
193 + scope: ScopeId(
194 + 3,
195 + ),
196 + },
197 + Reference {
198 + id: ReferenceId(
199 + 1,
200 + ),
201 + kind: Write,
202 + declaration: DeclarationId(
203 + 3,
204 + ),
205 + declaration (name): "bar",
206 + scope: ScopeId(
207 + 3,
208 + ),
209 + },
210 + Reference {
211 + id: ReferenceId(
212 + 2,
213 + ),
214 + kind: Read,
215 + declaration: DeclarationId(
216 + 1,
217 + ),
218 + declaration (name): "props",
219 + scope: ScopeId(
220 + 3,
221 + ),
222 + },
223 + ],
224 + children: [
225 + Scope {
226 + id: ScopeId(
227 + 4,
228 + ),
229 + kind: Block,
230 + declarations: {},
231 + references: [],
232 + children: [],
233 + },
234 + ],
235 + },
236 + Scope {
237 + id: ScopeId(
238 + 5,
239 + ),
240 + kind: Block,
241 + declarations: {},
242 + references: [],
243 + children: [],
244 + },
245 + ],
246 + },
247 + ],
248 + },
249 + ],
250 +}
251 +