@samitouri / QOS-React-2 / commits / c3ad8a86b1

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