diff --git a/src/protocol/ast.rs b/src/protocol/ast.rs index 3978a7d30a793bf1c852e84768971e4221425a25..5ba51755ee94a3ca7ac27b1ef375c73608ea19f8 100644 --- a/src/protocol/ast.rs +++ b/src/protocol/ast.rs @@ -1301,7 +1301,7 @@ pub struct BreakStatement { pub span: InputSpan, // of the "break" keyword pub label: Option, // Phase 2: linker - pub target: Option, + pub target: EndWhileStatementId, // invalid if not yet set } #[derive(Debug, Clone)] @@ -1311,7 +1311,7 @@ pub struct ContinueStatement { pub span: InputSpan, // of the "continue" keyword pub label: Option, // Phase 2: linker - pub target: Option, + pub target: WhileStatementId, // invalid if not yet set } #[derive(Debug, Clone)] @@ -1388,7 +1388,7 @@ pub struct GotoStatement { pub span: InputSpan, // of the "goto" keyword pub label: Identifier, // Phase 2: linker - pub target: Option, + pub target: LabeledStatementId, // invalid if not yet set } #[derive(Debug, Clone)] diff --git a/src/protocol/ast_printer.rs b/src/protocol/ast_printer.rs index 83cbb7af3b8022fc8614ea521b8ca594bb66d4d3..b0e2d899195d33eedfd977ee874103a9eb8329c7 100644 --- a/src/protocol/ast_printer.rs +++ b/src/protocol/ast_printer.rs @@ -494,7 +494,7 @@ impl ASTWriter { self.kv(indent2).with_s_key("Label") .with_opt_identifier_val(stmt.label.as_ref()); self.kv(indent2).with_s_key("Target") - .with_opt_disp_val(stmt.target.as_ref().map(|v| &v.0.index)); + .with_disp_val(&stmt.target.0.index); }, Statement::Continue(stmt) => { self.kv(indent).with_id(PREFIX_CONTINUE_STMT_ID, stmt.this.0.index) @@ -502,7 +502,7 @@ impl ASTWriter { self.kv(indent2).with_s_key("Label") .with_opt_identifier_val(stmt.label.as_ref()); self.kv(indent2).with_s_key("Target") - .with_opt_disp_val(stmt.target.as_ref().map(|v| &v.0.index)); + .with_disp_val(&stmt.target.0.index); }, Statement::Synchronous(stmt) => { self.kv(indent).with_id(PREFIX_SYNC_STMT_ID, stmt.this.0.index) @@ -569,7 +569,7 @@ impl ASTWriter { .with_s_key("Goto"); self.kv(indent2).with_s_key("Label").with_identifier_val(&stmt.label); self.kv(indent2).with_s_key("Target") - .with_opt_disp_val(stmt.target.as_ref().map(|v| &v.0.index)); + .with_disp_val(&stmt.target.0.index); }, Statement::New(stmt) => { self.kv(indent).with_id(PREFIX_NEW_STMT_ID, stmt.this.0.index) diff --git a/src/protocol/eval/executor.rs b/src/protocol/eval/executor.rs index fe43edfd8207faf5e777a0eb40d607093c617812..feab9c135698e49f53dba5985a928d1644f48478 100644 --- a/src/protocol/eval/executor.rs +++ b/src/protocol/eval/executor.rs @@ -896,12 +896,12 @@ impl Prompt { Ok(EvalContinuation::Stepping) }, Statement::Break(stmt) => { - cur_frame.position = stmt.target.unwrap().upcast(); + cur_frame.position = stmt.target.upcast(); Ok(EvalContinuation::Stepping) }, Statement::Continue(stmt) => { - cur_frame.position = stmt.target.unwrap().upcast(); + cur_frame.position = stmt.target.upcast(); Ok(EvalContinuation::Stepping) }, @@ -992,7 +992,7 @@ impl Prompt { return Ok(EvalContinuation::Stepping); }, Statement::Goto(stmt) => { - cur_frame.position = stmt.target.unwrap().upcast(); + cur_frame.position = stmt.target.upcast(); Ok(EvalContinuation::Stepping) }, diff --git a/src/protocol/parser/pass_definitions.rs b/src/protocol/parser/pass_definitions.rs index 144ae43b5ebb17aec83f692e1f76a28f78fca8e6..8e2d188044654999bd3044fc3e9e20228e528ec5 100644 --- a/src/protocol/parser/pass_definitions.rs +++ b/src/protocol/parser/pass_definitions.rs @@ -498,7 +498,7 @@ impl PassDefinitions { } else if next == TokenKind::OpenParen { // Same as above: memory statement or normal expression if let Some(memory_stmt_id) = self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? { - consume_token(&module.source, iter, TokenKind::SemiColon); + consume_token(&module.source, iter, TokenKind::SemiColon)?; section.push(memory_stmt_id.upcast().upcast()); } else { let id = self.consume_expression_statement(module, iter, ctx)?; @@ -624,7 +624,7 @@ impl PassDefinitions { this, span: break_span, label, - target: None, + target: EndWhileStatementId::new_invalid(), })) } @@ -643,7 +643,7 @@ impl PassDefinitions { this, span: continue_span, label, - target: None + target: WhileStatementId::new_invalid(), })) } @@ -767,7 +767,7 @@ impl PassDefinitions { this, span: goto_span, label, - target: None + target: LabeledStatementId::new_invalid(), })) } diff --git a/src/protocol/parser/pass_typing.rs b/src/protocol/parser/pass_typing.rs index 3df60af0a20e6220a55019fe3345fa30ca510434..e31e13c896efe7558d80c95de258c845868faeb9 100644 --- a/src/protocol/parser/pass_typing.rs +++ b/src/protocol/parser/pass_typing.rs @@ -1191,8 +1191,8 @@ impl Visitor for PassTyping { let guard_stmt_id = section[base_index ]; let block_stmt_id = section[base_index + 1]; - self.visit_stmt(ctx, guard_stmt_id); - self.visit_stmt(ctx, block_stmt_id); + self.visit_stmt(ctx, guard_stmt_id)?; + self.visit_stmt(ctx, block_stmt_id)?; } section.forget(); diff --git a/src/protocol/parser/pass_validation_linking.rs b/src/protocol/parser/pass_validation_linking.rs index 90e035252ce53b4aaad9cc5d3aba6faed31428fb..c0dd23295de7feac26b8dcd385a504623a5b7d5c 100644 --- a/src/protocol/parser/pass_validation_linking.rs +++ b/src/protocol/parser/pass_validation_linking.rs @@ -69,6 +69,13 @@ impl DefinitionType { } } +struct ControlFlowStatement { + in_sync: SynchronousStatementId, + in_while: WhileStatementId, + in_scope: Scope, + statement: StatementId, // of 'break', 'continue' or 'goto' +} + /// This particular visitor will go through the entire AST in a recursive manner /// and check if all statements and expressions are legal (e.g. no "return" /// statements in component definitions), and will link certain AST nodes to @@ -79,6 +86,9 @@ impl DefinitionType { /// the linking of function calls and component instantiations will be checked /// and linked to the appropriate definitions, but the return types and/or /// arguments will not be checked for validity. +/// +/// The main idea is, because we're visiting nodes in a tree, to do as much as +/// we can while we have the memory in cache. pub(crate) struct PassValidationLinking { // Traversal state, all valid IDs if inside a certain AST element. Otherwise // `id.is_invalid()` returns true. @@ -103,6 +113,8 @@ pub(crate) struct PassValidationLinking { // Keeping track of relative positions and unique IDs. relative_pos_in_block: i32, // of statements: to determine when variables are visible next_expr_index: i32, // to arrive at a unique ID for all expressions within a definition + // Control flow statements that require label resolving + control_flow_stmts: Vec, // Various temporary buffers for traversal. Essentially working around // Rust's borrowing rules since it cannot understand we're modifying AST // members but not the AST container. @@ -129,6 +141,7 @@ impl PassValidationLinking { must_be_assignable: None, relative_pos_in_block: 0, next_expr_index: 0, + control_flow_stmts: Vec::with_capacity(32), variable_buffer: ScopedBuffer::with_capacity(128), definition_buffer: ScopedBuffer::with_capacity(128), statement_buffer: ScopedBuffer::with_capacity(BUFFER_INIT_CAPACITY), @@ -139,6 +152,7 @@ impl PassValidationLinking { fn reset_state(&mut self) { self.in_sync = SynchronousStatementId::new_invalid(); self.in_while = WhileStatementId::new_invalid(); + self.in_select_guard = SelectStatementId::new_invalid(); self.in_test_expr = StatementId::new_invalid(); self.in_binding_expr = BindingExpressionId::new_invalid(); self.in_binding_expr_lhs = false; @@ -148,7 +162,8 @@ impl PassValidationLinking { self.expr_parent = ExpressionParent::None; self.must_be_assignable = None; self.relative_pos_in_block = 0; - self.next_expr_index = 0 + self.next_expr_index = 0; + self.control_flow_stmts.clear(); } } @@ -216,6 +231,7 @@ impl Visitor for PassValidationLinking { // to each of the locals in the procedure. ctx.heap[id].num_expressions_in_body = self.next_expr_index; self.visit_definition_and_assign_local_ids(ctx, id.upcast()); + self.resolve_pending_control_flow_targets(ctx)?; Ok(()) } @@ -246,6 +262,7 @@ impl Visitor for PassValidationLinking { // to each of the locals in the procedure. ctx.heap[id].num_expressions_in_body = self.next_expr_index; self.visit_definition_and_assign_local_ids(ctx, id.upcast()); + self.resolve_pending_control_flow_targets(ctx)?; Ok(()) } @@ -259,11 +276,11 @@ impl Visitor for PassValidationLinking { } fn visit_local_memory_stmt(&mut self, ctx: &mut Ctx, id: MemoryStatementId) -> VisitorResult { - // Note that we're not adding the variable to its scope for lookups. - // This is done in the `visit_statement_for_locals_labels_and_in_sync` - // method. let stmt = &ctx.heap[id]; let expr_id = stmt.initial_expr; + let variable_id = stmt.variable; + + self.checked_add_local(ctx, self.cur_scope, self.relative_pos_in_block, variable_id)?; assign_and_replace_next_stmt!(self, ctx, id.upcast().upcast()); debug_assert_eq!(self.expr_parent, ExpressionParent::None); @@ -275,14 +292,24 @@ impl Visitor for PassValidationLinking { } fn visit_local_channel_stmt(&mut self, ctx: &mut Ctx, id: ChannelStatementId) -> VisitorResult { + let stmt = &ctx.heap[id]; + let from_id = stmt.from; + let to_id = stmt.to; + + self.checked_add_local(ctx, self.cur_scope, self.relative_pos_in_block, from_id)?; + self.checked_add_local(ctx, self.cur_scope, self.relative_pos_in_block, to_id)?; + assign_and_replace_next_stmt!(self, ctx, id.upcast().upcast()); Ok(()) } fn visit_labeled_stmt(&mut self, ctx: &mut Ctx, id: LabeledStatementId) -> VisitorResult { - let body_id = ctx.heap[id].body; - self.visit_stmt(ctx, body_id)?; + let stmt = &ctx.heap[id]; + let body_id = stmt.body; + + self.checked_add_label(ctx, self.relative_pos_in_block, self.in_sync, id)?; + self.visit_stmt(ctx, body_id)?; Ok(()) } @@ -354,33 +381,25 @@ impl Visitor for PassValidationLinking { } fn visit_break_stmt(&mut self, ctx: &mut Ctx, id: BreakStatementId) -> VisitorResult { - // Resolve break target - let target_end_while = { - let stmt = &ctx.heap[id]; - let target_while_id = self.resolve_break_or_continue_target(ctx, stmt.span, &stmt.label)?; - let target_while = &ctx.heap[target_while_id]; - debug_assert!(!target_while.end_while.is_invalid()); - - target_while.end_while - }; - + self.control_flow_stmts.push(ControlFlowStatement{ + in_sync: self.in_sync, + in_while: self.in_while, + in_scope: self.cur_scope, + statement: id.upcast() + }); assign_then_erase_next_stmt!(self, ctx, id.upcast()); - let stmt = &mut ctx.heap[id]; - stmt.target = Some(target_end_while); Ok(()) } fn visit_continue_stmt(&mut self, ctx: &mut Ctx, id: ContinueStatementId) -> VisitorResult { - // Resolve continue target - let target_while_id = { - let stmt = &ctx.heap[id]; - self.resolve_break_or_continue_target(ctx, stmt.span, &stmt.label)? - }; - + self.control_flow_stmts.push(ControlFlowStatement{ + in_sync: self.in_sync, + in_while: self.in_while, + in_scope: self.cur_scope, + statement: id.upcast() + }); assign_then_erase_next_stmt!(self, ctx, id.upcast()); - let stmt = &mut ctx.heap[id]; - stmt.target = Some(target_while_id); Ok(()) } @@ -502,9 +521,10 @@ impl Visitor for PassValidationLinking { match &ctx.heap[guard_id] { Statement::Local(LocalStatement::Memory(stmt)) => { debug_assert_eq!(ctx.heap[arm_block_id].as_block().this.upcast(), arm_block_id); // backwards way of saying arm_block_id is a BlockStatementId + let variable_id = stmt.variable; let block_stmt = BlockStatementId(arm_block_id); let block_scope = Scope::Regular(block_stmt); - self.checked_at_single_scope_add_local(ctx, block_scope, -1, stmt.variable)?; + self.checked_at_single_scope_add_local(ctx, block_scope, -1, variable_id)?; }, Statement::Expression(_) => {}, _ => unreachable!(), // just to be sure the parser produced the expected AST @@ -543,24 +563,30 @@ impl Visitor for PassValidationLinking { } fn visit_goto_stmt(&mut self, ctx: &mut Ctx, id: GotoStatementId) -> VisitorResult { - let target_id = self.find_label(ctx, &ctx.heap[id].label)?; - ctx.heap[id].target = Some(target_id); - - let target = &ctx.heap[target_id]; - if self.in_sync != target.in_sync { - // We can only goto the current scope or outer scopes. Because - // nested sync statements are not allowed we must be inside a sync - // statement. - debug_assert!(!self.in_sync.is_invalid()); - let goto_stmt = &ctx.heap[id]; - let sync_stmt = &ctx.heap[self.in_sync]; - return Err( - ParseError::new_error_str_at_span(&ctx.module().source, goto_stmt.span, "goto may not escape the surrounding synchronous block") - .with_info_str_at_span(&ctx.module().source, target.label.span, "this is the target of the goto statement") - .with_info_str_at_span(&ctx.module().source, sync_stmt.span, "which will jump past this statement") - ); - } - + // TODO: Remove this + // let target_id = self.find_label(ctx, &ctx.heap[id].label)?; + // ctx.heap[id].target = Some(target_id); + // + // let target = &ctx.heap[target_id]; + // if self.in_sync != target.in_sync { + // // We can only goto the current scope or outer scopes. Because + // // nested sync statements are not allowed we must be inside a sync + // // statement. + // debug_assert!(!self.in_sync.is_invalid()); + // let goto_stmt = &ctx.heap[id]; + // let sync_stmt = &ctx.heap[self.in_sync]; + // return Err( + // ParseError::new_error_str_at_span(&ctx.module().source, goto_stmt.span, "goto may not escape the surrounding synchronous block") + // .with_info_str_at_span(&ctx.module().source, target.label.span, "this is the target of the goto statement") + // .with_info_str_at_span(&ctx.module().source, sync_stmt.span, "which will jump past this statement") + // ); + // } + self.control_flow_stmts.push(ControlFlowStatement{ + in_sync: self.in_sync, + in_while: self.in_while, + in_scope: self.cur_scope, + statement: id.upcast(), + }); assign_then_erase_next_stmt!(self, ctx, id.upcast()); Ok(()) @@ -1166,13 +1192,7 @@ impl Visitor for PassValidationLinking { Method::Put => { expecting_primitive_def = true; expecting_wrapping_sync_stmt = true; - if !self.in_select_guard.is_invalid() { - let call_span = call_expr.func_span; - return Err(ParseError::new_error_str_at_span( - &ctx.module().source, call_span, - "a call to 'put' may not occur in a select statement's guard" - )); - } + expecting_no_select_stmt = true; }, Method::Fires => { expecting_primitive_def = true; @@ -1226,6 +1246,16 @@ impl Visitor for PassValidationLinking { } } + if expecting_no_select_stmt { + if !self.in_select_guard.is_invalid() { + let (call_span, func_name) = get_span_and_name(ctx, id); + return Err(ParseError::new_error_at_span( + &ctx.module().source, call_span, + format!("a call to '{}' may not occur in a select statement's guard", func_name) + )); + } + } + if expecting_wrapping_new_stmt { if !self.expr_parent.is_new() { let call_span = call_expr.func_span; @@ -1443,14 +1473,6 @@ impl PassValidationLinking { // Copy statement IDs into buffer let statement_section = self.statement_buffer.start_section_initialized(&body.statements); - // Perform the breadth-first pass. Its main purpose is to find labeled - // statements such that we can find the `goto`-targets immediately when - // performing the depth pass - for stmt_idx in 0..statement_section.len() { - self.relative_pos_in_block = stmt_idx as i32; - self.visit_statement_for_locals_labels_and_in_sync(ctx, self.relative_pos_in_block, statement_section[stmt_idx])?; - } - // Perform the depth-first traversal assign_and_replace_next_stmt!(self, ctx, id.upcast()); for stmt_idx in 0..statement_section.len() { @@ -1466,38 +1488,6 @@ impl PassValidationLinking { Ok(()) } - fn visit_statement_for_locals_labels_and_in_sync(&mut self, ctx: &mut Ctx, relative_pos: i32, id: StatementId) -> VisitorResult { - let statement = &mut ctx.heap[id]; - match statement { - Statement::Local(stmt) => { - match stmt { - LocalStatement::Memory(local) => { - let variable_id = local.variable; - self.checked_add_local(ctx, relative_pos, variable_id)?; - }, - LocalStatement::Channel(local) => { - let from_id = local.from; - let to_id = local.to; - self.checked_add_local(ctx, relative_pos, from_id)?; - self.checked_add_local(ctx, relative_pos, to_id)?; - } - } - } - Statement::Labeled(stmt) => { - let stmt_id = stmt.this; - let body_id = stmt.body; - self.checked_add_label(ctx, relative_pos, self.in_sync, stmt_id)?; - self.visit_statement_for_locals_labels_and_in_sync(ctx, relative_pos, body_id)?; - }, - Statement::While(stmt) => { - stmt.in_sync = self.in_sync; - }, - _ => {}, - } - - return Ok(()) - } - fn visit_definition_and_assign_local_ids(&mut self, ctx: &mut Ctx, definition_id: DefinitionId) { let mut var_counter = 0; @@ -1578,27 +1568,77 @@ impl PassValidationLinking { block_stmt.next_unique_id_in_scope = var_counter; } + fn resolve_pending_control_flow_targets(&mut self, ctx: &mut Ctx) -> Result<(), ParseError> { + for entry in &self.control_flow_stmts { + let stmt = &ctx.heap[entry.statement]; + + match stmt { + Statement::Break(stmt) => { + let stmt_id = stmt.this; + let target_while_id = Self::resolve_break_or_continue_target(ctx, entry, stmt.span, &stmt.label)?; + let target_while_stmt = &ctx.heap[target_while_id]; + let target_end_while_id = target_while_stmt.end_while; + debug_assert!(!target_end_while_id.is_invalid()); + + let break_stmt = &mut ctx.heap[stmt_id]; + break_stmt.target = target_end_while_id; + }, + Statement::Continue(stmt) => { + let stmt_id = stmt.this; + let target_while_id = Self::resolve_break_or_continue_target(ctx, entry, stmt.span, &stmt.label)?; + + let continue_stmt = &mut ctx.heap[stmt_id]; + continue_stmt.target = target_while_id; + }, + Statement::Goto(stmt) => { + let stmt_id = stmt.this; + let target_id = Self::find_label(entry.in_scope, ctx, &stmt.label)?; + let target_stmt = &ctx.heap[target_id]; + if entry.in_sync != target_stmt.in_sync { + // Nested sync not allowed. And goto can only go to + // outer scopes, so we must be escaping from a sync. + debug_assert!(target_stmt.in_sync.is_invalid()); // target not in sync + debug_assert!(!entry.in_sync.is_invalid()); // but the goto is in sync + let goto_stmt = &ctx.heap[stmt_id]; + let sync_stmt = &ctx.heap[entry.in_sync]; + return Err( + ParseError::new_error_str_at_span(&ctx.module().source, goto_stmt.span, "goto may not escape the surrounding synchronous block") + .with_info_str_at_span(&ctx.module().source, target_stmt.label.span, "this is the target of the goto statement") + .with_info_str_at_span(&ctx.module().source, sync_stmt.span, "which will jump past this statement") + ); + } + + let goto_stmt = &mut ctx.heap[stmt_id]; + goto_stmt.target = target_id; + }, + _ => unreachable!("cannot resolve control flow target for {:?}", stmt), + } + } + + return Ok(()) + } + //-------------------------------------------------------------------------- // Utilities //-------------------------------------------------------------------------- /// Adds a local variable to the current scope. It will also annotate the /// `Local` in the AST with its relative position in the block. - fn checked_add_local(&mut self, ctx: &mut Ctx, relative_pos: i32, id: VariableId) -> Result<(), ParseError> { - debug_assert!(self.cur_scope.is_block()); + fn checked_add_local(&mut self, ctx: &mut Ctx, target_scope: Scope, relative_pos: i32, id: VariableId) -> Result<(), ParseError> { + debug_assert!(target_scope.is_block()); let local = &ctx.heap[id]; - let mut scope = &self.cur_scope; println!("DEBUG: Adding local '{}' at relative_pos {} in scope {:?}", local.identifier.value.as_str(), relative_pos, self.cur_scope); + let mut scope = target_scope; loop { // We immediately go to the parent scope. We check the current scope // in the call at the end. Likewise for checking the symbol table. let block = &ctx.heap[scope.to_block()]; - scope = &block.scope_node.parent; + scope = block.scope_node.parent; if let Scope::Definition(definition_id) = scope { // At outer scope, check parameters of function/component - for parameter_id in ctx.heap[*definition_id].parameters() { + for parameter_id in ctx.heap[definition_id].parameters() { let parameter = &ctx.heap[*parameter_id]; if local.identifier == parameter.identifier { return Err( @@ -1639,7 +1679,7 @@ impl PassValidationLinking { } // No collisions in any of the parent scope, attempt to add to scope - self.checked_at_single_scope_add_local(ctx, self.cur_scope, relative_pos, id) + self.checked_at_single_scope_add_local(ctx, target_scope, relative_pos, id) } /// Adds a local variable to the specified scope. Will check the specified @@ -1785,10 +1825,9 @@ impl PassValidationLinking { /// Finds a particular labeled statement by its identifier. Once found it /// will make sure that the target label does not skip over any variable /// declarations within the scope in which the label was found. - fn find_label(&self, ctx: &Ctx, identifier: &Identifier) -> Result { - debug_assert!(self.cur_scope.is_block()); + fn find_label(mut scope: Scope, ctx: &Ctx, identifier: &Identifier) -> Result { + debug_assert!(scope.is_block()); - let mut scope = &self.cur_scope; loop { debug_assert!(scope.is_block(), "scope is not a block"); let relative_scope_pos = ctx.heap[scope.to_block()].relative_pos_in_parent; @@ -1815,7 +1854,7 @@ impl PassValidationLinking { } } - scope = &block.scope_node.parent; + scope = block.scope_node.parent; if !scope.is_block() { return Err(ParseError::new_error_str_at_span( &ctx.module().source, identifier.span, "could not find this label" @@ -1827,8 +1866,7 @@ impl PassValidationLinking { /// This function will check if the provided while statement ID has a block /// statement that is one of our current parents. - fn has_parent_while_scope(&self, ctx: &Ctx, id: WhileStatementId) -> bool { - let mut scope = &self.cur_scope; + fn has_parent_while_scope(mut scope: Scope, ctx: &Ctx, id: WhileStatementId) -> bool { let while_stmt = &ctx.heap[id]; loop { debug_assert!(scope.is_block()); @@ -1838,7 +1876,7 @@ impl PassValidationLinking { } let block = &ctx.heap[block]; - scope = &block.scope_node.parent; + scope = block.scope_node.parent; if !scope.is_block() { return false; } @@ -1851,17 +1889,17 @@ impl PassValidationLinking { /// ID will be returned, otherwise a parsing error is constructed. /// The provided input position should be the position of the break/continue /// statement. - fn resolve_break_or_continue_target(&self, ctx: &Ctx, span: InputSpan, label: &Option) -> Result { + fn resolve_break_or_continue_target(ctx: &Ctx, control_flow: &ControlFlowStatement, span: InputSpan, label: &Option) -> Result { let target = match label { Some(label) => { - let target_id = self.find_label(ctx, label)?; + let target_id = Self::find_label(control_flow.in_scope, ctx, label)?; // Make sure break target is a while statement let target = &ctx.heap[target_id]; if let Statement::While(target_stmt) = &ctx.heap[target.body] { // Even though we have a target while statement, the break might not be // present underneath this particular labeled while statement - if !self.has_parent_while_scope(ctx, target_stmt.this) { + if !Self::has_parent_while_scope(control_flow.in_scope, ctx, target_stmt.this) { return Err(ParseError::new_error_str_at_span( &ctx.module().source, label.span, "break statement is not nested under the target label's while statement" ).with_info_str_at_span( @@ -1881,13 +1919,13 @@ impl PassValidationLinking { None => { // Use the enclosing while statement, the break must be // nested within that while statement - if self.in_while.is_invalid() { + if control_flow.in_while.is_invalid() { return Err(ParseError::new_error_str_at_span( &ctx.module().source, span, "Break statement is not nested under a while loop" )); } - self.in_while + control_flow.in_while } }; @@ -1895,11 +1933,11 @@ impl PassValidationLinking { // make sure we will not break out of a synchronous block { let target_while = &ctx.heap[target]; - if target_while.in_sync != self.in_sync { + if target_while.in_sync != control_flow.in_sync { // Break is nested under while statement, so can only escape a // sync block if the sync is nested inside the while statement. - debug_assert!(!self.in_sync.is_invalid()); - let sync_stmt = &ctx.heap[self.in_sync]; + debug_assert!(!control_flow.in_sync.is_invalid()); + let sync_stmt = &ctx.heap[control_flow.in_sync]; return Err( ParseError::new_error_str_at_span(&ctx.module().source, span, "break may not escape the surrounding synchronous block") .with_info_str_at_span(&ctx.module().source, target_while.span, "the break escapes out of this loop")