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