[rust] Semantic analysis resolves break/continue
Joe Savona committed
Aug 10, 2023 at 10:59 UTC
c3ad8a86b180772dfee9cf2f2b32b0a6848893b4
6 files changed
+127
-145
compiler/forget/crates/forget_semantic_analysis/src/analyzer.rs
+114
-64
@@ -5,7 +5,9 @@ use forget_estree::{
5
Pattern, Program, SourceRange, SourceType, Statement, VariableDeclarationKind, Visitor,
6
};
7
8
-use crate::{AstNode, DeclarationKind, LabelKind, ReferenceKind, ScopeId, ScopeKind, ScopeManager};
8
+use crate::{
9
+ AstNode, DeclarationKind, LabelId, LabelKind, ReferenceKind, ScopeId, ScopeKind, ScopeManager,
10
+};
11
12
pub fn analyze(ast: &Program) -> ScopeManager {
13
let mut analyzer = Analyzer::new();
@@ -15,6 +17,7 @@ pub fn analyze(ast: &Program) -> ScopeManager {
17
18
struct Analyzer {
19
manager: ScopeManager,
20
+ labels: Vec<LabelId>,
21
current: ScopeId,
22
}
23
@@ -22,7 +25,64 @@ impl Analyzer {
25
fn new() -> Self {
26
let manager = ScopeManager::new();
27
let current = manager.root_id();
25
- Self { manager, current }
28
+ let labels = Default::default();
29
+ Self {
30
+ manager,
31
+ labels,
32
+ current,
33
+ }
34
+ }
35
+
36
+ fn enter_label<F>(&mut self, id: LabelId, mut f: F)
37
+ where
38
+ F: FnMut(&mut Self) -> (),
39
+ {
40
+ self.labels.push(id);
41
+ f(self);
42
+ let last = self.labels.pop().unwrap();
43
+ assert_eq!(last, id);
44
+ }
45
+
46
+ fn lookup_break(&self, name: Option<&str>) -> Option<LabelId> {
47
+ for id in self.labels.iter().rev() {
48
+ let label = self.manager.label(*id);
49
+ match (name, &label.name) {
50
+ // If this is a labeled break, only return if an exact match
51
+ // is in scope
52
+ (Some(name), Some(label_name)) if name == label_name => {
53
+ return Some(label.id);
54
+ }
55
+ // If this is an unlabeld break, return the innermost label id
56
+ (None, _) => {
57
+ return Some(label.id);
58
+ }
59
+ _ => { /* no-op */ }
60
+ }
61
+ }
62
+ None
63
+ }
64
+
65
+ fn lookup_continue(&self, name: Option<&str>) -> Option<LabelId> {
66
+ for id in self.labels.iter().rev() {
67
+ let label = self.manager.label(*id);
68
+ // Skip labels that are not for loops, can only continue to a loop
69
+ if label.kind != LabelKind::Loop {
70
+ continue;
71
+ }
72
+ match (name, &label.name) {
73
+ // If this is a labeled break, only return if an exact match
74
+ // is in scope
75
+ (Some(name), Some(label_name)) if &name == label_name => {
76
+ return Some(label.id);
77
+ }
78
+ // If this is an unlabeld break, return the innermost label id
79
+ (None, _) => {
80
+ return Some(label.id);
81
+ }
82
+ _ => { /* no-op */ }
83
+ }
84
+ }
85
+ None
86
}
87
88
fn enter<F>(&mut self, kind: ScopeKind, mut f: F) -> ScopeId
@@ -196,12 +256,6 @@ impl Analyzer {
256
body: &Statement,
257
_range: Option<SourceRange>,
258
) {
199
- // Record an anonymous label for the statement to resolve unlabeled break/continue
200
- let label = self
201
- .manager
202
- .add_anonymous_label(self.current, LabelKind::Loop);
203
- self.manager.node_labels.insert(ast, label);
204
-
259
let mut for_scope: Option<ScopeId> = None;
260
match left {
261
ForInInit::VariableDeclaration(left) => {
@@ -215,7 +269,13 @@ impl Analyzer {
269
}
270
}
271
self.visit_expression(right);
218
- self.visit_statement(body);
272
+ let id = self
273
+ .manager
274
+ .add_anonymous_label(self.current, LabelKind::Loop);
275
+ self.manager.node_labels.insert(ast, id);
276
+ self.enter_label(id, |visitor| {
277
+ visitor.visit_statement(body);
278
+ });
279
if let Some(for_scope) = for_scope {
280
self.close_scope(for_scope);
281
}
@@ -365,34 +425,22 @@ impl Visitor for Analyzer {
425
}
426
427
fn visit_break_statement(&mut self, ast: &forget_estree::BreakStatement) {
368
- if let Some(label_node) = &ast.label {
369
- if let Some(label) = self
370
- .manager
371
- .lookup_label(self.current, &label_node.name)
372
- .cloned()
373
- {
374
- self.manager
375
- .node_labels
376
- .insert(AstNode::from(ast), label.id);
428
+ if let Some(label_id) =
429
+ self.lookup_break(ast.label.as_ref().map(|ident| ident.name.as_str()))
430
+ {
431
+ self.manager
432
+ .node_labels
433
+ .insert(AstNode::from(ast), label_id);
434
+ if let Some(label_node) = &ast.label {
435
self.manager
436
.node_labels
379
- .insert(AstNode::from(label_node), label.id);
380
- } else {
381
- self.manager.diagnostics.push(Diagnostic::invalid_syntax(
382
- "Unknown break label",
383
- label_node.range,
384
- ));
437
+ .insert(AstNode::from(label_node), label_id);
438
}
439
} else {
387
- if let Some(label) = self.manager.lookup_break(self.current).cloned() {
388
- self.manager
389
- .node_labels
390
- .insert(AstNode::from(ast), label.id);
391
- } else {
392
- self.manager
393
- .diagnostics
394
- .push(Diagnostic::invalid_syntax("Invalid break", ast.range));
395
- }
440
+ self.manager.diagnostics.push(Diagnostic::invalid_syntax(
441
+ "Non-syntactic break, could not resolve break target",
442
+ ast.range,
443
+ ));
444
}
445
}
446
@@ -414,34 +462,22 @@ impl Visitor for Analyzer {
462
}
463
464
fn visit_continue_statement(&mut self, ast: &forget_estree::ContinueStatement) {
417
- if let Some(label_node) = &ast.label {
418
- if let Some(label) = self
419
- .manager
420
- .lookup_label(self.current, &label_node.name)
421
- .cloned()
422
- {
423
- self.manager
424
- .node_labels
425
- .insert(AstNode::from(ast), label.id);
465
+ if let Some(label_id) =
466
+ self.lookup_continue(ast.label.as_ref().map(|ident| ident.name.as_str()))
467
+ {
468
+ self.manager
469
+ .node_labels
470
+ .insert(AstNode::from(ast), label_id);
471
+ if let Some(label_node) = &ast.label {
472
self.manager
473
.node_labels
428
- .insert(AstNode::from(label_node), label.id);
429
- } else {
430
- self.manager.diagnostics.push(Diagnostic::invalid_syntax(
431
- "Unknown continue label",
432
- label_node.range,
433
- ));
474
+ .insert(AstNode::from(label_node), label_id);
475
}
476
} else {
436
- if let Some(label) = self.manager.lookup_continue(self.current).cloned() {
437
- self.manager
438
- .node_labels
439
- .insert(AstNode::from(ast), label.id);
440
- } else {
441
- self.manager
442
- .diagnostics
443
- .push(Diagnostic::invalid_syntax("Invalid continue", ast.range));
444
- }
477
+ self.manager.diagnostics.push(Diagnostic::invalid_syntax(
478
+ "Non-syntactic continue, could not resolve continue target",
479
+ ast.range,
480
+ ));
481
}
482
}
483
@@ -485,7 +521,13 @@ impl Visitor for Analyzer {
521
if let Some(update) = &ast.update {
522
self.visit_expression(update);
523
}
488
- self.visit_statement(&ast.body);
524
+ let id = self
525
+ .manager
526
+ .add_anonymous_label(self.current, LabelKind::Loop);
527
+ self.manager.node_labels.insert(AstNode::from(ast), id);
528
+ self.enter_label(id, |visitor| {
529
+ visitor.visit_statement(&ast.body);
530
+ });
531
if let Some(for_scope) = for_scope {
532
self.close_scope(for_scope);
533
}
@@ -522,7 +564,9 @@ impl Visitor for Analyzer {
564
.manager
565
.add_label(self.current, kind, ast.label.name.clone());
566
self.manager.node_labels.insert(AstNode::from(ast), id);
525
- self.visit_statement(body);
567
+ self.enter_label(id, |visitor| {
568
+ visitor.visit_statement(body);
569
+ })
570
}
571
572
fn visit_member_expression(&mut self, ast: &forget_estree::MemberExpression) {
@@ -578,10 +622,16 @@ impl Visitor for Analyzer {
622
623
fn visit_switch_statement(&mut self, ast: &forget_estree::SwitchStatement) {
624
self.visit_expression(&ast.discriminant);
581
- self.enter(ScopeKind::Switch, |visitor| {
582
- for case_ in &ast.cases {
583
- visitor.visit_switch_case(case_);
584
- }
625
+ let id = self
626
+ .manager
627
+ .add_anonymous_label(self.current, LabelKind::Other);
628
+ self.manager.node_labels.insert(AstNode::from(ast), id);
629
+ self.enter_label(id, |visitor| {
630
+ visitor.enter(ScopeKind::Switch, |visitor| {
631
+ for case_ in &ast.cases {
632
+ visitor.visit_switch_case(case_);
633
+ }
634
+ });
635
});
636
}
637
@@ -651,7 +701,7 @@ impl Visitor for Analyzer {
701
// should never result in an empty JSXIdentifier node. but just in
702
// case we report this rather than silently fail
703
self.manager.diagnostics.push(Diagnostic::invalid_syntax(
654
- "Expected JSXOpenintElement.name to be non-empty",
704
+ "Expected JSXOpeningElement.name to be non-empty",
705
name.range,
706
));
707
}
compiler/forget/crates/forget_semantic_analysis/src/scope_manager.rs
+13
-30
@@ -40,7 +40,6 @@ impl ScopeManager {
40
id: root_id,
41
kind: ScopeKind::Global,
42
parent: None,
43
- labels: Default::default(),
43
declarations: Default::default(),
44
references: Default::default(),
45
children: Default::default(),
@@ -166,28 +165,6 @@ impl ScopeManager {
165
})
166
}
167
169
- pub fn lookup_label(&self, scope: ScopeId, name: &str) -> Option<&Label> {
170
- let mut current = &self.scopes[scope.0];
171
- loop {
172
- if let Some(id) = current.labels.get(name) {
173
- return Some(&self.labels[id.0]);
174
- }
175
- if let Some(parent) = current.parent {
176
- current = &self.scopes[parent.0];
177
- } else {
178
- return None;
179
- }
180
- }
181
- }
182
-
183
- pub fn lookup_break(&self, _scope: ScopeId) -> Option<&Label> {
184
- todo!()
185
- }
186
-
187
- pub fn lookup_continue(&self, _scope: ScopeId) -> Option<&Label> {
188
- todo!()
189
- }
190
-
168
pub fn lookup_declaration(&self, scope: ScopeId, name: &str) -> Option<&Declaration> {
169
let mut current = &self.scopes[scope.0];
170
loop {
@@ -212,7 +189,6 @@ impl ScopeManager {
189
id,
190
kind,
191
parent: Some(parent),
215
- labels: Default::default(),
192
declarations: Default::default(),
193
references: Default::default(),
194
children: Default::default(),
@@ -223,16 +199,23 @@ impl ScopeManager {
199
200
pub(crate) fn add_label(&mut self, scope: ScopeId, kind: LabelKind, name: String) -> LabelId {
201
let id = LabelId(self.labels.len());
226
- self.labels.push(Label { id, kind, scope });
227
- self.scopes[scope.0].labels.insert(name, id);
202
+ self.labels.push(Label {
203
+ id,
204
+ kind,
205
+ scope,
206
+ name: Some(name),
207
+ });
208
id
209
}
210
211
pub(crate) fn add_anonymous_label(&mut self, scope: ScopeId, kind: LabelKind) -> LabelId {
212
let id = LabelId(self.labels.len());
233
- let name = format!("#{}", id.0);
234
- self.labels.push(Label { id, kind, scope });
235
- self.scopes[scope.0].labels.insert(name, id);
213
+ self.labels.push(Label {
214
+ id,
215
+ kind,
216
+ scope,
217
+ name: None,
218
+ });
219
id
220
}
221
@@ -300,7 +283,6 @@ pub struct Scope {
283
pub id: ScopeId,
284
pub kind: ScopeKind,
285
pub parent: Option<ScopeId>,
303
- pub labels: IndexMap<String, LabelId>,
286
pub declarations: IndexMap<String, DeclarationId>,
287
pub references: Vec<ReferenceId>,
288
pub children: Vec<ScopeId>,
@@ -317,6 +299,7 @@ pub struct Label {
299
pub id: LabelId,
300
pub kind: LabelKind,
301
pub scope: ScopeId,
302
+ pub name: Option<String>,
303
}
304
305
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy, Clone)]
compiler/forget/crates/forget_semantic_analysis/src/scope_view.rs
-15
@@ -83,20 +83,6 @@ impl<'m> ScopeView<'m> {
83
84
impl<'m> std::fmt::Debug for ScopeView<'m> {
85
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86
- let labels: IndexMap<_, _> = self
87
- .scope
88
- .labels
89
- .iter()
90
- .map(|(name, label)| {
91
- (
92
- name.clone(),
93
- LabelView {
94
- manager: &self.manager,
95
- label: self.manager.label(*label),
96
- },
97
- )
98
- })
99
- .collect();
86
let declarations: IndexMap<_, _> = self
87
.scope
88
.declarations
@@ -132,7 +118,6 @@ impl<'m> std::fmt::Debug for ScopeView<'m> {
118
f.debug_struct("Scope")
119
.field("id", &self.scope.id)
120
.field("kind", &self.scope.kind)
135
- .field("labels", &labels)
121
.field("declarations", &declarations)
122
.field("references", &references)
123
.field("children", &children)
compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@globals-and-imports.js.snap
-5
@@ -29,7 +29,6 @@ Scope {
29
0,
30
),
31
kind: Global,
32
- labels: {},
32
declarations: {},
33
references: [],
34
children: [
@@ -38,7 +37,6 @@ Scope {
37
1,
38
),
39
kind: Module,
41
- labels: {},
40
declarations: {
41
"Component": Declaration {
42
id: DeclarationId(
@@ -57,7 +55,6 @@ Scope {
55
2,
56
),
57
kind: Function,
60
- labels: {},
58
declarations: {
59
"props": Declaration {
60
id: DeclarationId(
@@ -170,7 +167,6 @@ Scope {
167
3,
168
),
169
kind: Function,
173
- labels: {},
170
declarations: {},
171
references: [],
172
children: [],
@@ -180,7 +176,6 @@ Scope {
176
4,
177
),
178
kind: Function,
183
- labels: {},
179
declarations: {},
180
references: [],
181
children: [],
compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@labels.js.snap
-26
@@ -25,7 +25,6 @@ Scope {
25
0,
26
),
27
kind: Global,
28
- labels: {},
28
declarations: {},
29
references: [],
30
children: [
@@ -34,7 +33,6 @@ Scope {
33
1,
34
),
35
kind: Module,
37
- labels: {},
36
declarations: {
37
"Component": Declaration {
38
id: DeclarationId(
@@ -53,26 +51,6 @@ Scope {
51
2,
52
),
53
kind: Function,
56
- labels: {
57
- "foo": Label {
58
- id: LabelId(
59
- 0,
60
- ),
61
- kind: Loop,
62
- scope: ScopeId(
63
- 2,
64
- ),
65
- },
66
- "bar": Label {
67
- id: LabelId(
68
- 1,
69
- ),
70
- kind: Other,
71
- scope: ScopeId(
72
- 2,
73
- ),
74
- },
75
- },
54
declarations: {
55
"props": Declaration {
56
id: DeclarationId(
@@ -114,7 +92,6 @@ Scope {
92
3,
93
),
94
kind: For,
117
- labels: {},
95
declarations: {
96
"x": Declaration {
97
id: DeclarationId(
@@ -160,7 +137,6 @@ Scope {
137
4,
138
),
139
kind: Block,
163
- labels: {},
140
declarations: {},
141
references: [
142
Reference {
@@ -222,7 +198,6 @@ Scope {
198
5,
199
),
200
kind: Block,
225
- labels: {},
201
declarations: {},
202
references: [],
203
children: [],
@@ -236,7 +211,6 @@ Scope {
211
6,
212
),
213
kind: Block,
239
- labels: {},
214
declarations: {},
215
references: [],
216
children: [],
compiler/forget/crates/forget_semantic_analysis/tests/snapshots/analysis_test__fixtures@simple-function.js.snap
-5
@@ -21,7 +21,6 @@ Scope {
21
0,
22
),
23
kind: Global,
24
- labels: {},
24
declarations: {},
25
references: [],
26
children: [
@@ -30,7 +29,6 @@ Scope {
29
1,
30
),
31
kind: Module,
33
- labels: {},
32
declarations: {
33
"Component": Declaration {
34
id: DeclarationId(
@@ -49,7 +47,6 @@ Scope {
47
2,
48
),
49
kind: Function,
52
- labels: {},
50
declarations: {
51
"a": Declaration {
52
id: DeclarationId(
@@ -100,7 +97,6 @@ Scope {
97
3,
98
),
99
kind: Function,
103
- labels: {},
100
declarations: {
101
"foo_": Declaration {
102
id: DeclarationId(
@@ -119,7 +115,6 @@ Scope {
115
4,
116
),
117
kind: Function,
122
- labels: {},
118
declarations: {
119
"c": Declaration {
120
id: DeclarationId(