Changeset - a88feb123006
[Not reviewed]
0 8 0
MH - 4 years ago 2021-12-20 13:17:12
contact@maxhenger.nl
Fixed introduced bug of incorrect variable resolving
8 files changed with 216 insertions and 153 deletions:
0 comments (0 inline, 0 general)
src/protocol/ast.rs
Show inline comments
 
@@ -752,7 +752,7 @@ pub struct Variable {
 
    pub parser_type: ParserType,
 
    pub identifier: Identifier,
 
    // Validator/linker
 
    pub relative_pos_in_block: u32,
 
    pub relative_pos_in_block: i32,
 
    pub unique_id_in_scope: i32, // Temporary fix until proper bytecode/asm is generated
 
}
 

	
 
@@ -1170,7 +1170,7 @@ pub struct BlockStatement {
 
    pub scope_node: ScopeNode,
 
    pub first_unique_id_in_scope: i32, // Temporary fix until proper bytecode/asm is generated
 
    pub next_unique_id_in_scope: i32, // Temporary fix until proper bytecode/asm is generated
 
    pub relative_pos_in_parent: u32,
 
    pub relative_pos_in_parent: i32,
 
    pub locals: Vec<VariableId>,
 
    pub labels: Vec<LabeledStatementId>,
 
    pub next: StatementId,
 
@@ -1224,6 +1224,7 @@ pub struct MemoryStatement {
 
    // Phase 1: parser
 
    pub span: InputSpan,
 
    pub variable: VariableId,
 
    pub initial_expr: AssignmentExpressionId,
 
    // Phase 2: linker
 
    pub next: StatementId,
 
}
 
@@ -1241,7 +1242,7 @@ pub struct ChannelStatement {
 
    pub from: VariableId, // output
 
    pub to: VariableId,   // input
 
    // Phase 2: linker
 
    pub relative_pos_in_block: u32,
 
    pub relative_pos_in_block: i32,
 
    pub next: StatementId,
 
}
 

	
 
@@ -1252,7 +1253,7 @@ pub struct LabeledStatement {
 
    pub label: Identifier,
 
    pub body: StatementId,
 
    // Phase 2: linker
 
    pub relative_pos_in_block: u32,
 
    pub relative_pos_in_block: i32,
 
    pub in_sync: SynchronousStatementId, // may be invalid
 
}
 

	
 
@@ -1357,8 +1358,9 @@ pub struct SelectStatement {
 

	
 
#[derive(Debug, Clone)]
 
pub struct SelectCase {
 
    pub guard_var: MemoryStatementId, // invalid ID if there is no declaration of a variable
 
    pub guard_expr: ExpressionStatementId, // if `guard_var.is_some()`, then always assignment expression
 
    // The guard statement of a `select` is either a MemoryStatement or an
 
    // ExpressionStatement. Nothing else is allowed by the initial parsing
 
    pub guard: StatementId,
 
    pub block: BlockStatementId,
 
}
 

	
 
@@ -1410,6 +1412,7 @@ pub struct ExpressionStatement {
 
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
 
pub enum ExpressionParent {
 
    None, // only set during initial parsing
 
    Memory(MemoryStatementId),
 
    If(IfStatementId),
 
    While(WhileStatementId),
 
    Return(ReturnStatementId),
src/protocol/ast_printer.rs
Show inline comments
 
@@ -433,6 +433,8 @@ impl ASTWriter {
 

	
 
                        self.kv(indent2).with_s_key("Variable");
 
                        self.write_variable(heap, stmt.variable, indent3);
 
                        self.kv(indent2).with_s_key("InitialValue");
 
                        self.write_expr(heap, stmt.initial_expr.upcast(), indent3);
 
                        self.kv(indent2).with_s_key("Next").with_disp_val(&stmt.next.index);
 
                    }
 
                }
 
@@ -541,15 +543,8 @@ impl ASTWriter {
 
                let indent3 = indent2 + 1;
 
                let indent4 = indent3 + 1;
 
                for case in &stmt.cases {
 
                    if !case.guard_var.is_invalid() {
 
                        self.kv(indent3).with_s_key("GuardStatement");
 
                        self.write_stmt(heap, case.guard_var.upcast().upcast(), indent4);
 
                    } else {
 
                        self.kv(indent3).with_s_key("GuardStatement").with_s_val("None");
 
                    }
 

	
 
                    self.kv(indent3).with_s_key("GuardExpression");
 
                    self.write_stmt(heap, case.guard_expr.upcast(), indent4);
 
                    self.kv(indent3).with_s_key("Guard");
 
                    self.write_stmt(heap, case.guard, indent4);
 

	
 
                    self.kv(indent3).with_s_key("Block");
 
                    self.write_stmt(heap, case.block.upcast(), indent4);
 
@@ -1021,6 +1016,7 @@ fn write_expression_parent(target: &mut String, parent: &ExpressionParent) {
 

	
 
    *target = match parent {
 
        EP::None => String::from("None"),
 
        EP::Memory(id) => format!("MemStmt({})", id.0.0.index),
 
        EP::If(id) => format!("IfStmt({})", id.0.index),
 
        EP::While(id) => format!("WhileStmt({})", id.0.index),
 
        EP::Return(id) => format!("ReturnStmt({})", id.0.index),
src/protocol/eval/executor.rs
Show inline comments
 
@@ -825,8 +825,13 @@ impl Prompt {
 
            Statement::Local(stmt) => {
 
                match stmt {
 
                    LocalStatement::Memory(stmt) => {
 
                        let variable = &heap[stmt.variable];
 
                        self.store.write(ValueId::Stack(variable.unique_id_in_scope as u32), Value::Unassigned);
 
                        if cfg!(debug_assertions) {
 
                            let variable = &heap[stmt.variable];
 
                            debug_assert!(match self.store.read_ref(ValueId::Stack(variable.unique_id_in_scope as u32)) {
 
                                Value::Unassigned => false,
 
                                _ => true,
 
                            });
 
                        }
 

	
 
                        cur_frame.position = stmt.next;
 
                        Ok(EvalContinuation::Stepping)
 
@@ -1049,6 +1054,16 @@ impl Prompt {
 
            let stmt = &heap[cur_frame.position];
 

	
 
            match stmt {
 
                Statement::Local(stmt) => {
 
                    if let LocalStatement::Memory(stmt) = stmt {
 
                        // Setup as unassigned, when we execute the memory
 
                        // statement (after evaluating expression), it should no
 
                        // longer be `Unassigned`.
 
                        let variable = &heap[stmt.variable];
 
                        self.store.write(ValueId::Stack(variable.unique_id_in_scope as u32), Value::Unassigned);
 
                        cur_frame.prepare_single_expression(heap, stmt.initial_expr.upcast());
 
                    }
 
                },
 
                Statement::If(stmt) => cur_frame.prepare_single_expression(heap, stmt.test),
 
                Statement::While(stmt) => cur_frame.prepare_single_expression(heap, stmt.test),
 
                Statement::Return(stmt) => {
src/protocol/parser/pass_definitions.rs
Show inline comments
 
@@ -487,10 +487,9 @@ impl PassDefinitions {
 
                // Two fallback possibilities: the first one is a memory
 
                // declaration, the other one is to parse it as a normal
 
                // expression. This is a bit ugly.
 
                if let Some((memory_stmt_id, assignment_stmt_id)) = self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? {
 
                if let Some(memory_stmt_id) = self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? {
 
                    consume_token(&module.source, iter, TokenKind::SemiColon)?;
 
                    section.push(memory_stmt_id.upcast().upcast());
 
                    section.push(assignment_stmt_id.upcast());
 
                } else {
 
                    let id = self.consume_expression_statement(module, iter, ctx)?;
 
                    section.push(id.upcast());
 
@@ -498,10 +497,9 @@ impl PassDefinitions {
 
            }
 
        } else if next == TokenKind::OpenParen {
 
            // Same as above: memory statement or normal expression
 
            if let Some((memory_stmt_id, assignment_stmt_id)) = self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? {
 
            if let Some(memory_stmt_id) = self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? {
 
                consume_token(&module.source, iter, TokenKind::SemiColon);
 
                section.push(memory_stmt_id.upcast().upcast());
 
                section.push(assignment_stmt_id.upcast());
 
            } else {
 
                let id = self.consume_expression_statement(module, iter, ctx)?;
 
                section.push(id.upcast());
 
@@ -696,26 +694,26 @@ impl PassDefinitions {
 
        let mut next = iter.next();
 

	
 
        while Some(TokenKind::CloseCurly) != next {
 
            let (guard_var, guard_expr) = match self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? {
 
                Some(guard_var_and_expr) => guard_var_and_expr,
 
            let guard = match self.maybe_consume_memory_statement_without_semicolon(module, iter, ctx)? {
 
                Some(guard_mem_stmt) => guard_mem_stmt.upcast().upcast(),
 
                None => {
 
                    let start_pos = iter.last_valid_pos();
 
                    let expr = self.consume_expression(module, iter, ctx)?;
 
                    let end_pos = iter.last_valid_pos();
 

	
 
                    let guard_expr = ctx.heap.alloc_expression_statement(|this| ExpressionStatement{
 
                    let guard_expr_stmt = ctx.heap.alloc_expression_statement(|this| ExpressionStatement{
 
                        this,
 
                        span: InputSpan::from_positions(start_pos, end_pos),
 
                        expression: expr,
 
                        next: StatementId::new_invalid(),
 
                    });
 

	
 
                    (MemoryStatementId::new_invalid(), guard_expr)
 
                    guard_expr_stmt.upcast()
 
                },
 
            };
 
            consume_token(&module.source, iter, TokenKind::ArrowRight)?;
 
            let block = self.consume_block_or_wrapped_statement(module, iter, ctx)?;
 
            cases.push(SelectCase{ guard_var, guard_expr, block });
 
            cases.push(SelectCase{ guard, block });
 

	
 
            next = iter.next();
 
        }
 
@@ -925,9 +923,14 @@ impl PassDefinitions {
 
        Ok(())
 
    }
 

	
 
    /// Attempts to consume a memory statement (a statement along the lines of
 
    /// `type var_name = initial_expr`). Will return `Ok(None)` if it didn't
 
    /// seem like there was a memory statement, `Ok(Some(...))` if there was
 
    /// one, and `Err(...)` if its reasonable to assume that there was a memory
 
    /// statement, but we failed to parse it.
 
    fn maybe_consume_memory_statement_without_semicolon(
 
        &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx
 
    ) -> Result<Option<(MemoryStatementId, ExpressionStatementId)>, ParseError> {
 
    ) -> Result<Option<MemoryStatementId>, ParseError> {
 
        // This is a bit ugly. It would be nicer if we could somehow
 
        // consume the expression with a type hint if we do get a valid
 
        // type, but we don't get an identifier following it
 
@@ -947,11 +950,10 @@ impl PassDefinitions {
 
                let memory_span = InputSpan::from_positions(parser_type.full_span.begin, identifier.span.end);
 
                let assign_span = consume_token(&module.source, iter, TokenKind::Equal)?;
 

	
 
                let initial_expr_begin_pos = iter.last_valid_pos();
 
                let initial_expr_id = self.consume_expression(module, iter, ctx)?;
 
                let initial_expr_end_pos = iter.last_valid_pos();
 

	
 
                // Allocate the memory statement with the variable
 
                // Create the AST variable
 
                let local_id = ctx.heap.alloc_variable(|this| Variable{
 
                    this,
 
                    kind: VariableKind::Local,
 
@@ -960,18 +962,13 @@ impl PassDefinitions {
 
                    relative_pos_in_block: 0,
 
                    unique_id_in_scope: -1,
 
                });
 
                let memory_stmt_id = ctx.heap.alloc_memory_statement(|this| MemoryStatement{
 
                    this,
 
                    span: memory_span,
 
                    variable: local_id,
 
                    next: StatementId::new_invalid()
 
                });
 

	
 
                // Allocate the initial assignment
 
                // Create the initial assignment expression
 
                // Note: we set the initial variable declaration here
 
                let variable_expr_id = ctx.heap.alloc_variable_expression(|this| VariableExpression{
 
                    this,
 
                    identifier,
 
                    declaration: None,
 
                    declaration: Some(local_id),
 
                    used_as_binding_target: false,
 
                    parent: ExpressionParent::None,
 
                    unique_id_in_definition: -1,
 
@@ -986,14 +983,17 @@ impl PassDefinitions {
 
                    parent: ExpressionParent::None,
 
                    unique_id_in_definition: -1,
 
                });
 
                let assignment_stmt_id = ctx.heap.alloc_expression_statement(|this| ExpressionStatement{
 

	
 
                // Put both together in the memory statement
 
                let memory_stmt_id = ctx.heap.alloc_memory_statement(|this| MemoryStatement{
 
                    this,
 
                    span: InputSpan::from_positions(initial_expr_begin_pos, initial_expr_end_pos),
 
                    expression: assignment_expr_id.upcast(),
 
                    next: StatementId::new_invalid(),
 
                    span: memory_span,
 
                    variable: local_id,
 
                    initial_expr: assignment_expr_id,
 
                    next: StatementId::new_invalid()
 
                });
 

	
 
                return Ok(Some((memory_stmt_id, assignment_stmt_id)))
 
                return Ok(Some(memory_stmt_id));
 
            }
 
        }
 

	
src/protocol/parser/pass_typing.rs
Show inline comments
 
@@ -1094,11 +1094,16 @@ impl Visitor for PassTyping {
 

	
 
    fn visit_local_memory_stmt(&mut self, ctx: &mut Ctx, id: MemoryStatementId) -> VisitorResult {
 
        let memory_stmt = &ctx.heap[id];
 
        let initial_expr_id = memory_stmt.initial_expr;
 

	
 
        // Setup memory statement inference
 
        let local = &ctx.heap[memory_stmt.variable];
 
        let var_type = self.determine_inference_type_from_parser_type_elements(&local.parser_type.elements, true);
 
        self.var_types.insert(memory_stmt.variable, VarData::new_local(var_type));
 

	
 
        // Process the initial value
 
        self.visit_assignment_expr(ctx, initial_expr_id)?;
 

	
 
        Ok(())
 
    }
 

	
 
@@ -3276,7 +3281,7 @@ impl PassTyping {
 
            EP::None =>
 
                // Should have been set by linker
 
                unreachable!(),
 
            EP::ExpressionStmt(_) =>
 
            EP::Memory(_) | EP::ExpressionStmt(_) =>
 
                // Determined during type inference
 
                InferenceType::new(false, false, vec![ITP::Unknown]),
 
            EP::Expression(parent_id, idx_in_parent) => {
src/protocol/parser/pass_validation_linking.rs
Show inline comments
 
@@ -99,7 +99,7 @@ pub(crate) struct PassValidationLinking {
 
    // used for the error's position
 
    must_be_assignable: Option<InputSpan>,
 
    // Keeping track of relative positions and unique IDs.
 
    relative_pos_in_block: u32, // of statements: to determine when variables are visible
 
    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
 
    // Various temporary buffers for traversal. Essentially working around
 
    // Rust's borrowing rules since it cannot understand we're modifying AST
 
@@ -255,7 +255,15 @@ impl Visitor for PassValidationLinking {
 
    }
 

	
 
    fn visit_local_memory_stmt(&mut self, ctx: &mut Ctx, id: MemoryStatementId) -> VisitorResult {
 
        let stmt = &ctx.heap[id];
 
        let expr_id = stmt.initial_expr;
 

	
 
        assign_and_replace_next_stmt!(self, ctx, id.upcast().upcast());
 
        debug_assert_eq!(self.expr_parent, ExpressionParent::None);
 
        self.expr_parent = ExpressionParent::Memory(id);
 
        self.visit_assignment_expr(ctx, expr_id)?;
 
        self.expr_parent = ExpressionParent::None;
 

	
 
        Ok(())
 
    }
 

	
 
@@ -456,12 +464,12 @@ impl Visitor for PassValidationLinking {
 
        }
 

	
 
        // Visit the various arms in the select block
 
        // note: three statements per case, so we lookup as `3 * index + offset`
 
        let mut case_stmt_ids = self.statement_buffer.start_section();
 
        let num_cases = select_stmt.cases.len();
 
        for case in &select_stmt.cases {
 
            case_stmt_ids.push(case.guard_var.upcast().upcast());
 
            case_stmt_ids.push(case.guard_expr.upcast());
 
            // Note: we add both to the buffer, retrieve them later in indexed
 
            // fashion
 
            case_stmt_ids.push(case.guard);
 
            case_stmt_ids.push(case.block.upcast());
 
        }
 

	
 
@@ -470,16 +478,11 @@ impl Visitor for PassValidationLinking {
 
        // Link up the "Select" with the "EndSelect". If there are no cases then
 
        // runtime will pick the "EndSelect" immediately.
 
        for idx in 0..num_cases {
 
            let base_idx = 3 * idx;
 
            let guard_var_id  = case_stmt_ids[base_idx    ];
 
            let guard_expr_id = case_stmt_ids[base_idx + 1];
 
            let arm_block_id  = case_stmt_ids[base_idx + 2];
 
            let base_idx = 2 * idx;
 
            let guard_id     = case_stmt_ids[base_idx    ];
 
            let arm_block_id = case_stmt_ids[base_idx + 1];
 

	
 
            if !guard_var_id.is_invalid() {
 
                self.visit_stmt(ctx, guard_var_id)?;
 
            }
 

	
 
            self.visit_stmt(ctx, guard_expr_id)?;
 
            self.visit_stmt(ctx, guard_id)?;
 
            self.visit_stmt(ctx, arm_block_id)?;
 

	
 
            assign_then_erase_next_stmt!(self, ctx, end_select_id.upcast());
 
@@ -583,6 +586,7 @@ impl Visitor for PassValidationLinking {
 
        // code (mainly typechecking), we disallow nested use in expressions
 
        match self.expr_parent {
 
            // Look at us: lying through our teeth while providing error messages.
 
            ExpressionParent::Memory(_) => {},
 
            ExpressionParent::ExpressionStmt(_) => {},
 
            _ => {
 
                let assignment_span = assignment_expr.full_span;
 
@@ -1244,96 +1248,103 @@ impl Visitor for PassValidationLinking {
 
    fn visit_variable_expr(&mut self, ctx: &mut Ctx, id: VariableExpressionId) -> VisitorResult {
 
        let var_expr = &ctx.heap[id];
 

	
 
        let (variable_id, is_binding_target) = match self.find_variable(ctx, self.relative_pos_in_block, &var_expr.identifier) {
 
            Ok(variable_id) => {
 
                // Regular variable
 
                (variable_id, false)
 
            },
 
            Err(()) => {
 
                // Couldn't find variable, but if we're in a binding expression,
 
                // then this may be the thing we're binding to.
 
                if self.in_binding_expr.is_invalid() || !self.in_binding_expr_lhs {
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module().source, var_expr.identifier.span, "unresolved variable"
 
                    ));
 
                }
 
        // Check if declaration was already resolved (this occurs for the
 
        // variable expr that is on the LHS of the assignment expr that is
 
        // associated with a variable declaration)
 
        let mut variable_id = var_expr.declaration;
 
        let mut is_binding_target = false;
 

	
 
                // This is a binding variable, but it may only appear in very
 
                // specific locations.
 
                let is_valid_binding = match self.expr_parent {
 
                    ExpressionParent::Expression(expr_id, idx) => {
 
                        match &ctx.heap[expr_id] {
 
                            Expression::Binding(_binding_expr) => {
 
                                // Nested binding is disallowed, and because of
 
                                // the check above we know we're directly at the
 
                                // LHS of the binding expression
 
                                debug_assert_eq!(_binding_expr.this, self.in_binding_expr);
 
                                debug_assert_eq!(idx, 0);
 
                                true
 
                            }
 
                            Expression::Literal(lit_expr) => {
 
                                // Only struct, unions, tuples and arrays can
 
                                // have subexpressions, so we're always fine
 
                                if cfg!(debug_assertions) {
 
                                    match lit_expr.value {
 
                                        Literal::Struct(_) | Literal::Union(_) | Literal::Array(_) | Literal::Tuple(_) => {},
 
                                        _ => unreachable!(),
 
                                    }
 
                                }
 
        // Otherwise try to find it
 
        if variable_id.is_none() {
 
            variable_id = self.find_variable(ctx, self.relative_pos_in_block, &var_expr.identifier);
 
        }
 

	
 
                                true
 
                            },
 
                            _ => false,
 
        // Otherwise try to see if is a variable introduced by a binding expr
 
        let variable_id = if let Some(variable_id) = variable_id {
 
            variable_id
 
        } else {
 
            if self.in_binding_expr.is_invalid() || !self.in_binding_expr_lhs {
 
                return Err(ParseError::new_error_str_at_span(
 
                    &ctx.module().source, var_expr.identifier.span, "unresolved variable"
 
                ));
 
            }
 

	
 
            // This is a binding variable, but it may only appear in very
 
            // specific locations.
 
            let is_valid_binding = match self.expr_parent {
 
                ExpressionParent::Expression(expr_id, idx) => {
 
                    match &ctx.heap[expr_id] {
 
                        Expression::Binding(_binding_expr) => {
 
                            // Nested binding is disallowed, and because of
 
                            // the check above we know we're directly at the
 
                            // LHS of the binding expression
 
                            debug_assert_eq!(_binding_expr.this, self.in_binding_expr);
 
                            debug_assert_eq!(idx, 0);
 
                            true
 
                        }
 
                    },
 
                    _ => {
 
                        false
 
                    }
 
                };
 
                        Expression::Literal(lit_expr) => {
 
                            // Only struct, unions, tuples and arrays can
 
                            // have subexpressions, so we're always fine
 
                            if cfg!(debug_assertions) {
 
                                match lit_expr.value {
 
                                    Literal::Struct(_) | Literal::Union(_) | Literal::Array(_) | Literal::Tuple(_) => {},
 
                                    _ => unreachable!(),
 
                                }
 
                            }
 

	
 
                if !is_valid_binding {
 
                    let binding_expr = &ctx.heap[self.in_binding_expr];
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module().source, var_expr.identifier.span,
 
                        "illegal location for binding variable: binding variables may only be nested under a binding expression, or a struct, union or array literal"
 
                    ).with_info_at_span(
 
                        &ctx.module().source, binding_expr.operator_span, format!(
 
                            "'{}' was interpreted as a binding variable because the variable is not declared and it is nested under this binding expression",
 
                            var_expr.identifier.value.as_str()
 
                        )
 
                    ));
 
                            true
 
                        },
 
                        _ => false,
 
                    }
 
                },
 
                _ => {
 
                    false
 
                }
 
            };
 

	
 
                // By now we know that this is a valid binding expression. Given
 
                // that a binding expression must be nested under an if/while
 
                // statement, we now add the variable to the (implicit) block
 
                // statement following the if/while statement.
 
                let bound_identifier = var_expr.identifier.clone();
 
                let bound_variable_id = ctx.heap.alloc_variable(|this| Variable{
 
                    this,
 
                    kind: VariableKind::Binding,
 
                    parser_type: ParserType{
 
                        elements: vec![ParserTypeElement{
 
                            element_span: bound_identifier.span,
 
                            variant: ParserTypeVariant::Inferred
 
                        }],
 
                        full_span: bound_identifier.span
 
                    },
 
                    identifier: bound_identifier,
 
                    relative_pos_in_block: 0,
 
                    unique_id_in_scope: -1,
 
                });
 
            if !is_valid_binding {
 
                let binding_expr = &ctx.heap[self.in_binding_expr];
 
                return Err(ParseError::new_error_str_at_span(
 
                    &ctx.module().source, var_expr.identifier.span,
 
                    "illegal location for binding variable: binding variables may only be nested under a binding expression, or a struct, union or array literal"
 
                ).with_info_at_span(
 
                    &ctx.module().source, binding_expr.operator_span, format!(
 
                        "'{}' was interpreted as a binding variable because the variable is not declared and it is nested under this binding expression",
 
                        var_expr.identifier.value.as_str()
 
                    )
 
                ));
 
            }
 

	
 
                let body_stmt_id = match &ctx.heap[self.in_test_expr] {
 
                    Statement::If(stmt) => stmt.true_body,
 
                    Statement::While(stmt) => stmt.body,
 
                    _ => unreachable!(),
 
                };
 
                let body_scope = Scope::Regular(body_stmt_id);
 
                self.checked_at_single_scope_add_local(ctx, body_scope, 0, bound_variable_id)?;
 
            // By now we know that this is a valid binding expression. Given
 
            // that a binding expression must be nested under an if/while
 
            // statement, we now add the variable to the (implicit) block
 
            // statement following the if/while statement.
 
            let bound_identifier = var_expr.identifier.clone();
 
            let bound_variable_id = ctx.heap.alloc_variable(|this| Variable {
 
                this,
 
                kind: VariableKind::Binding,
 
                parser_type: ParserType {
 
                    elements: vec![ParserTypeElement {
 
                        element_span: bound_identifier.span,
 
                        variant: ParserTypeVariant::Inferred
 
                    }],
 
                    full_span: bound_identifier.span
 
                },
 
                identifier: bound_identifier,
 
                relative_pos_in_block: 0,
 
                unique_id_in_scope: -1,
 
            });
 

	
 
            let body_stmt_id = match &ctx.heap[self.in_test_expr] {
 
                Statement::If(stmt) => stmt.true_body,
 
                Statement::While(stmt) => stmt.body,
 
                _ => unreachable!(),
 
            };
 
            let body_scope = Scope::Regular(body_stmt_id);
 
            self.checked_at_single_scope_add_local(ctx, body_scope, -1, bound_variable_id)?; // add at -1 such that first statement can access
 

	
 
                (bound_variable_id, true)
 
            }
 
            is_binding_target = true;
 
            bound_variable_id
 
        };
 

	
 
        let var_expr = &mut ctx.heap[id];
 
@@ -1396,14 +1407,14 @@ impl PassValidationLinking {
 
        // 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 u32;
 
            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() {
 
            self.relative_pos_in_block = stmt_idx as u32;
 
            self.relative_pos_in_block = stmt_idx as i32;
 
            self.visit_stmt(ctx, statement_section[stmt_idx])?;
 
        }
 
        assign_and_replace_next_stmt!(self, ctx, end_block_id.upcast());
 
@@ -1415,7 +1426,7 @@ impl PassValidationLinking {
 
        Ok(())
 
    }
 

	
 
    fn visit_statement_for_locals_labels_and_in_sync(&mut self, ctx: &mut Ctx, relative_pos: u32, id: StatementId) -> VisitorResult {
 
    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) => {
 
@@ -1492,16 +1503,16 @@ impl PassValidationLinking {
 
            let relative_var_pos = if var_idx < var_section.len() {
 
                ctx.heap[var_section[var_idx]].relative_pos_in_block
 
            } else {
 
                u32::MAX
 
                i32::MAX
 
            };
 

	
 
            let relative_scope_pos = if scope_idx < scope_section.len() {
 
                ctx.heap[scope_section[scope_idx]].as_block().relative_pos_in_parent
 
            } else {
 
                u32::MAX
 
                i32::MAX
 
            };
 

	
 
            debug_assert!(!(relative_var_pos == u32::MAX && relative_scope_pos == u32::MAX));
 
            debug_assert!(!(relative_var_pos == i32::MAX && relative_scope_pos == i32::MAX));
 

	
 
            // In certain cases the relative variable position is the same as
 
            // the scope position (insertion of binding variables). In that case
 
@@ -1533,10 +1544,11 @@ impl PassValidationLinking {
 

	
 
    /// 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: u32, id: VariableId) -> Result<(), ParseError> {
 
    fn checked_add_local(&mut self, ctx: &mut Ctx, relative_pos: i32, id: VariableId) -> Result<(), ParseError> {
 
        debug_assert!(self.cur_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);
 

	
 
        loop {
 
            // We immediately go to the parent scope. We check the current scope
 
@@ -1594,7 +1606,7 @@ impl PassValidationLinking {
 
    /// scope for variable conflicts and the symbol table for global conflicts.
 
    /// Will NOT check parent scopes of the specified scope.
 
    fn checked_at_single_scope_add_local(
 
        &mut self, ctx: &mut Ctx, scope: Scope, relative_pos: u32, id: VariableId
 
        &mut self, ctx: &mut Ctx, scope: Scope, relative_pos: i32, id: VariableId
 
    ) -> Result<(), ParseError> {
 
        // Check the symbol table for conflicts
 
        {
 
@@ -1643,7 +1655,8 @@ impl PassValidationLinking {
 

	
 
    /// Finds a variable in the visitor's scope that must appear before the
 
    /// specified relative position within that block.
 
    fn find_variable(&self, ctx: &Ctx, mut relative_pos: u32, identifier: &Identifier) -> Result<VariableId, ()> {
 
    fn find_variable(&self, ctx: &Ctx, mut relative_pos: i32, identifier: &Identifier) -> Option<VariableId> {
 
        println!("DEBUG: Calling find_variable for '{}' at relative_pos {}", identifier.value.as_str(), relative_pos);
 
        debug_assert!(self.cur_scope.is_block());
 

	
 
        // No need to use iterator over namespaces if here
 
@@ -1652,12 +1665,14 @@ impl PassValidationLinking {
 
        loop {
 
            debug_assert!(scope.is_block());
 
            let block = &ctx.heap[scope.to_block()];
 
            println!("DEBUG: > Looking in block {:?} at relative_pos {}", scope.to_block().0, relative_pos);
 
            
 
            for local_id in &block.locals {
 
                let local = &ctx.heap[*local_id];
 
                
 
                if local.relative_pos_in_block <= relative_pos && identifier == &local.identifier {
 
                    return Ok(*local_id);
 
                if local.relative_pos_in_block < relative_pos && identifier == &local.identifier {
 
                    println!("DEBUG: > Matched at local with relative_pos {}", local.relative_pos_in_block);
 
                    return Some(*local_id);
 
                }
 
            }
 

	
 
@@ -1670,7 +1685,7 @@ impl PassValidationLinking {
 
                        for parameter_id in definition.parameters() {
 
                            let parameter = &ctx.heap[*parameter_id];
 
                            if identifier == &parameter.identifier {
 
                                return Ok(*parameter_id);
 
                                return Some(*parameter_id);
 
                            }
 
                        }
 
                    },
 
@@ -1678,7 +1693,7 @@ impl PassValidationLinking {
 
                }
 

	
 
                // Variable could not be found
 
                return Err(())
 
                return None
 
            } else {
 
                relative_pos = block.relative_pos_in_parent;
 
            }
 
@@ -1687,7 +1702,7 @@ impl PassValidationLinking {
 

	
 
    /// Adds a particular label to the current scope. Will return an error if
 
    /// there is another label with the same name visible in the current scope.
 
    fn checked_add_label(&mut self, ctx: &mut Ctx, relative_pos: u32, in_sync: SynchronousStatementId, id: LabeledStatementId) -> Result<(), ParseError> {
 
    fn checked_add_label(&mut self, ctx: &mut Ctx, relative_pos: i32, in_sync: SynchronousStatementId, id: LabeledStatementId) -> Result<(), ParseError> {
 
        debug_assert!(self.cur_scope.is_block());
 

	
 
        // Make sure label is not defined within the current scope or any of the
src/protocol/tests/parser_validation.rs
Show inline comments
 
@@ -565,6 +565,29 @@ fn test_incorrect_modifying_operators() {
 
    ).error(|e| { e.assert_msg_has(0, "assignments are statements"); });
 
}
 

	
 
#[test]
 
fn test_variable_introduction_in_scope() {
 
    Tester::new_single_source_expect_err(
 
        "variable use before declaration",
 
        "func f() -> u8 { return thing; auto thing = 5; }"
 
    ).error(|e| { e.assert_msg_has(0, "unresolved variable"); });
 

	
 
    Tester::new_single_source_expect_err(
 
        "variable use in declaration",
 
        "func f() -> u8 { auto thing = 5 + thing; return thing; }"
 
    ).error(|e| { e.assert_msg_has(0, "unresolved variable"); });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "variable use after declaration",
 
        "func f() -> u8 { auto thing = 5; return thing; }"
 
    );
 

	
 
    Tester::new_single_source_expect_err(
 
        "variable use of closed scope",
 
        "func f() -> u8 { { auto thing = 5; } return thing; }"
 
    ).error(|e| { e.assert_msg_has(0, "unresolved variable"); });
 
}
 

	
 
#[test]
 
fn test_correct_select_statement() {
 
    Tester::new_single_source_expect_ok(
 
@@ -575,7 +598,6 @@ fn test_correct_select_statement() {
 
            sync select {
 
                outer_value = get(input) -> outer_value = 0;
 
                auto new_value = get(input) -> {
 
                    f();
 
                    outer_value = new_value;
 
                }
 
                get(input) + get(input) ->
src/protocol/tests/utils.rs
Show inline comments
 
@@ -535,6 +535,7 @@ impl<'a> FunctionTester<'a> {
 

	
 
        let mut found_local_id = None;
 
        if let Some(block_id) = wrapping_block_id {
 
            // Found the right block, find the variable inside the block again
 
            let block_stmt = self.ctx.heap[block_id].as_block();
 
            for local_id in &block_stmt.locals {
 
                let var = &self.ctx.heap[*local_id];
 
@@ -1194,6 +1195,12 @@ fn seek_expr_in_stmt<F: Fn(&Expression) -> bool>(heap: &Heap, start: StatementId
 
    let stmt = &heap[start];
 

	
 
    match stmt {
 
        Statement::Local(stmt) => {
 
            match stmt {
 
                LocalStatement::Memory(stmt) => seek_expr_in_expr(heap, stmt.initial_expr.upcast(), f),
 
                LocalStatement::Channel(_) => None
 
            }
 
        }
 
        Statement::Block(stmt) => {
 
            for stmt_id in &stmt.statements {
 
                if let Some(id) = seek_expr_in_stmt(heap, *stmt_id, f) {
0 comments (0 inline, 0 general)