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
 
@@ -743,25 +743,25 @@ pub enum VariableKind {
 
    Local,          // declared in function/component body
 
    Binding,        // may be bound to in a binding expression (determined in validator/linker)
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct Variable {
 
    pub this: VariableId,
 
    // Parsing
 
    pub kind: VariableKind,
 
    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
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub enum Definition {
 
    Struct(StructDefinition),
 
    Enum(EnumDefinition),
 
    Union(UnionDefinition),
 
    Component(ComponentDefinition),
 
    Function(FunctionDefinition),
 
}
 

	
 
@@ -1161,25 +1161,25 @@ impl Statement {
 
#[derive(Debug, Clone)]
 
pub struct BlockStatement {
 
    pub this: BlockStatementId,
 
    // Phase 1: parser
 
    pub is_implicit: bool,
 
    pub span: InputSpan, // of the complete block
 
    pub statements: Vec<StatementId>,
 
    pub end_block: EndBlockStatementId,
 
    // Phase 2: linker
 
    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,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct EndBlockStatement {
 
    pub this: EndBlockStatementId,
 
    // Parser
 
    pub start_block: BlockStatementId,
 
    // Validation/Linking
 
    pub next: StatementId,
 
@@ -1215,53 +1215,54 @@ impl LocalStatement {
 
            LocalStatement::Channel(v) => v.span,
 
            LocalStatement::Memory(v) => v.span,
 
        }
 
    }
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct MemoryStatement {
 
    pub this: MemoryStatementId,
 
    // Phase 1: parser
 
    pub span: InputSpan,
 
    pub variable: VariableId,
 
    pub initial_expr: AssignmentExpressionId,
 
    // Phase 2: linker
 
    pub next: StatementId,
 
}
 

	
 
/// ChannelStatement is the declaration of an input and output port associated
 
/// with the same channel. Note that the polarity of the ports are from the
 
/// point of view of the component. So an output port is something that a
 
/// component uses to send data over (i.e. it is the "input end" of the
 
/// channel), and vice versa.
 
#[derive(Debug, Clone)]
 
pub struct ChannelStatement {
 
    pub this: ChannelStatementId,
 
    // Phase 1: parser
 
    pub span: InputSpan, // of the "channel" keyword
 
    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,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct LabeledStatement {
 
    pub this: LabeledStatementId,
 
    // Phase 1: parser
 
    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
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct IfStatement {
 
    pub this: IfStatementId,
 
    // Phase 1: parser
 
    pub span: InputSpan, // of the "if" keyword
 
    pub test: ExpressionId,
 
    pub true_body: BlockStatementId,
 
    pub false_body: Option<BlockStatementId>,
 
    pub end_if: EndIfStatementId,
 
@@ -1348,26 +1349,27 @@ pub struct EndForkStatement {
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct SelectStatement {
 
    pub this: SelectStatementId,
 
    pub span: InputSpan, // of the "select" keyword
 
    pub cases: Vec<SelectCase>,
 
    pub end_select: EndSelectStatementId,
 
}
 

	
 
#[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,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct EndSelectStatement {
 
    pub this: EndSelectStatementId,
 
    pub start_select: SelectStatementId,
 
    pub next: StatementId,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct ReturnStatement {
 
@@ -1401,24 +1403,25 @@ pub struct NewStatement {
 
pub struct ExpressionStatement {
 
    pub this: ExpressionStatementId,
 
    // Phase 1: parser
 
    pub span: InputSpan,
 
    pub expression: ExpressionId,
 
    // Phase 2: linker
 
    pub next: StatementId,
 
}
 

	
 
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
 
pub enum ExpressionParent {
 
    None, // only set during initial parsing
 
    Memory(MemoryStatementId),
 
    If(IfStatementId),
 
    While(WhileStatementId),
 
    Return(ReturnStatementId),
 
    New(NewStatementId),
 
    ExpressionStmt(ExpressionStatementId),
 
    Expression(ExpressionId, u32) // index within expression (e.g LHS or RHS of expression)
 
}
 

	
 
impl ExpressionParent {
 
    pub fn is_new(&self) -> bool {
 
        match self {
 
            ExpressionParent::New(_) => true,
src/protocol/ast_printer.rs
Show inline comments
 
@@ -424,24 +424,26 @@ impl ASTWriter {
 
                        self.kv(indent2).with_s_key("From");
 
                        self.write_variable(heap, stmt.from, indent3);
 
                        self.kv(indent2).with_s_key("To");
 
                        self.write_variable(heap, stmt.to, indent3);
 
                        self.kv(indent2).with_s_key("Next").with_disp_val(&stmt.next.index);
 
                    },
 
                    LocalStatement::Memory(stmt) => {
 
                        self.kv(indent).with_id(PREFIX_MEM_STMT_ID, stmt.this.0.0.index)
 
                            .with_s_key("LocalMemory");
 

	
 
                        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);
 
                    }
 
                }
 
            },
 
            Statement::Labeled(stmt) => {
 
                self.kv(indent).with_id(PREFIX_LABELED_STMT_ID, stmt.this.0.index)
 
                    .with_s_key("Labeled");
 

	
 
                self.kv(indent2).with_s_key("Label").with_identifier_val(&stmt.label);
 
                self.kv(indent2).with_s_key("Statement");
 
                self.write_stmt(heap, stmt.body, indent3);
 
            },
 
@@ -532,33 +534,26 @@ impl ASTWriter {
 
                    .with_s_key("EndFork");
 
                self.kv(indent2).with_s_key("StartFork").with_disp_val(&stmt.start_fork.0.index);
 
                self.kv(indent2).with_s_key("Next").with_disp_val(&stmt.next.index);
 
            },
 
            Statement::Select(stmt) => {
 
                self.kv(indent).with_id(PREFIX_SELECT_STMT_ID, stmt.this.0.index)
 
                    .with_s_key("Select");
 
                self.kv(indent2).with_s_key("EndSelect").with_disp_val(&stmt.end_select.0.index);
 
                self.kv(indent2).with_s_key("Cases");
 
                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);
 
                }
 
            },
 
            Statement::EndSelect(stmt) => {
 
                self.kv(indent).with_id(PREFIX_END_SELECT_STMT_ID, stmt.this.0.index)
 
                    .with_s_key("EndSelect");
 
                self.kv(indent2).with_s_key("StartSelect").with_disp_val(&stmt.start_select.0.index);
 
                self.kv(indent2).with_s_key("Next").with_disp_val(&stmt.next.index);
 
            }
 
            Statement::Return(stmt) => {
 
@@ -1012,20 +1007,21 @@ fn write_concrete_type(target: &mut String, heap: &Heap, def_id: DefinitionId, t
 

	
 
        idx + 1
 
    }
 

	
 
    write_concrete_part(target, heap, def_id, t, 0);
 
}
 

	
 
fn write_expression_parent(target: &mut String, parent: &ExpressionParent) {
 
    use ExpressionParent as EP;
 

	
 
    *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),
 
        EP::New(id) => format!("NewStmt({})", id.0.index),
 
        EP::ExpressionStmt(id) => format!("ExprStmt({})", id.0.index),
 
        EP::Expression(id, idx) => format!("Expr({}, {})", id.index, idx)
 
    };
 
}
 
\ No newline at end of file
src/protocol/eval/executor.rs
Show inline comments
 
@@ -816,26 +816,31 @@ impl Prompt {
 
                Ok(EvalContinuation::Stepping)
 
            },
 
            Statement::EndBlock(stmt) => {
 
                let block = &heap[stmt.start_block];
 
                self.store.clear_stack(block.first_unique_id_in_scope as usize);
 
                cur_frame.position = stmt.next;
 

	
 
                Ok(EvalContinuation::Stepping)
 
            },
 
            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)
 
                    },
 
                    LocalStatement::Channel(stmt) => {
 
                        // Need to create a new channel by requesting it from
 
                        // the runtime.
 
                        match ctx.created_channel() {
 
                            None => {
 
                                // No channel is pending. So request one
 
                                Ok(EvalContinuation::NewChannel)
 
                            },
 
@@ -1040,24 +1045,34 @@ impl Prompt {
 
            replaced by something that clears the expression values if needed, but I'll keep this \
 
            in for now for debugging purposes."
 
        );
 

	
 
        // If the next statement requires evaluating expressions then we push
 
        // these onto the expression stack. This way we will evaluate this
 
        // stack in the next loop, then evaluate the statement using the result
 
        // from the expression evaluation.
 
        if !cur_frame.position.is_invalid() {
 
            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) => {
 
                    debug_assert_eq!(stmt.expressions.len(), 1); // TODO: @ReturnValues
 
                    cur_frame.prepare_single_expression(heap, stmt.expressions[0]);
 
                },
 
                Statement::New(stmt) => {
 
                    // Note that we will end up not evaluating the call itself.
 
                    // Rather we will evaluate its expressions and then
 
                    // instantiate the component upon reaching the "new" stmt.
 
                    let call_expr = &heap[stmt.expression];
 
                    cur_frame.prepare_multiple_expressions(heap, &call_expr.arguments);
src/protocol/parser/pass_definitions.rs
Show inline comments
 
@@ -478,39 +478,37 @@ impl PassDefinitions {
 
            } else if ident == KW_STMT_NEW {
 
                let id = self.consume_new_statement(module, iter, ctx)?;
 
                section.push(id.upcast());
 
            } else if ident == KW_STMT_CHANNEL {
 
                let id = self.consume_channel_statement(module, iter, ctx)?;
 
                section.push(id.upcast().upcast());
 
            } else if iter.peek() == Some(TokenKind::Colon) {
 
                self.consume_labeled_statement(module, iter, ctx, section)?;
 
            } else {
 
                // 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());
 
                }
 
            }
 
        } 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());
 
            }
 
        } else {
 
            let id = self.consume_expression_statement(module, iter, ctx)?;
 
            section.push(id.upcast());
 
        }
 

	
 
        return Ok(());
 
    }
 

	
 
@@ -687,44 +685,44 @@ impl PassDefinitions {
 
    }
 

	
 
    fn consume_select_statement(
 
        &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx
 
    ) -> Result<SelectStatementId, ParseError> {
 
        let select_span = consume_exact_ident(&module.source, iter, KW_STMT_SELECT)?;
 
        consume_token(&module.source, iter, TokenKind::OpenCurly)?;
 

	
 
        let mut cases = Vec::new();
 
        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();
 
        }
 

	
 
        consume_token(&module.source, iter, TokenKind::CloseCurly)?;
 

	
 
        Ok(ctx.heap.alloc_select_statement(|this| SelectStatement{
 
            this,
 
            span: select_span,
 
            cases,
 
            end_select: EndSelectStatementId::new_invalid(),
 
        }))
 
@@ -916,93 +914,95 @@ impl PassDefinitions {
 
            // Produce the labeled statement using the first statement, and push
 
            // the remaining ones at the end.
 
            let inner_statements = inner_section.into_vec();
 
            section.push(stmt_id.upcast());
 
            for idx in 1..inner_statements.len() {
 
                section.push(inner_statements[idx])
 
            }
 
        }
 

	
 
        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
 
        let iter_state = iter.save();
 
        let definition_id = self.cur_definition;
 
        let poly_vars = ctx.heap[definition_id].poly_vars();
 

	
 
        let parser_type = self.type_parser.consume_parser_type(
 
            iter, &ctx.heap, &module.source, &ctx.symbols, poly_vars,
 
            definition_id, SymbolScope::Definition(definition_id), true, None
 
        );
 

	
 
        if let Ok(parser_type) = parser_type {
 
            if Some(TokenKind::Ident) == iter.next() {
 
                // Assume this is a proper memory statement
 
                let identifier = consume_ident_interned(&module.source, iter, ctx)?;
 
                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,
 
                    identifier: identifier.clone(),
 
                    parser_type,
 
                    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,
 
                });
 
                let assignment_expr_id = ctx.heap.alloc_assignment_expression(|this| AssignmentExpression{
 
                    this,
 
                    operator_span: assign_span,
 
                    full_span: InputSpan::from_positions(memory_span.begin, initial_expr_end_pos),
 
                    left: variable_expr_id.upcast(),
 
                    operation: AssignmentOperator::Set,
 
                    right: initial_expr_id,
 
                    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));
 
            }
 
        }
 

	
 
        // If here then one of the preconditions for a memory statement was not
 
        // met. So recover the iterator and return
 
        iter.load(iter_state);
 
        Ok(None)
 
    }
 

	
 
    fn consume_expression_statement(
 
        &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx
 
    ) -> Result<ExpressionStatementId, ParseError> {
src/protocol/parser/pass_typing.rs
Show inline comments
 
@@ -1085,29 +1085,34 @@ impl Visitor for PassTyping {
 

	
 
        let section = self.stmt_buffer.start_section_initialized(block.statements.as_slice());
 
        for stmt_id in section.iter_copied() {
 
            self.visit_stmt(ctx, stmt_id)?;
 
        }
 
        section.forget();
 

	
 
        Ok(())
 
    }
 

	
 
    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(())
 
    }
 

	
 
    fn visit_local_channel_stmt(&mut self, ctx: &mut Ctx, id: ChannelStatementId) -> VisitorResult {
 
        let channel_stmt = &ctx.heap[id];
 

	
 
        let from_local = &ctx.heap[channel_stmt.from];
 
        let from_var_type = self.determine_inference_type_from_parser_type_elements(&from_local.parser_type.elements, true);
 
        self.var_types.insert(from_local.this, VarData::new_channel(from_var_type, channel_stmt.to));
 

	
 
        let to_local = &ctx.heap[channel_stmt.to];
 
        let to_var_type = self.determine_inference_type_from_parser_type_elements(&to_local.parser_type.elements, true);
 
@@ -3267,25 +3272,25 @@ impl PassTyping {
 
    /// of subexpressions before they have a chance to call this function.
 
    fn insert_initial_expr_inference_type(
 
        &mut self, ctx: &mut Ctx, expr_id: ExpressionId
 
    ) -> Result<(), ParseError> {
 
        use ExpressionParent as EP;
 
        use InferenceTypePart as ITP;
 

	
 
        let expr = &ctx.heap[expr_id];
 
        let inference_type = match expr.parent() {
 
            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) => {
 
                // If we are the test expression of a conditional expression,
 
                // then we must resolve to a boolean
 
                let is_conditional = if let Expression::Conditional(_) = &ctx.heap[*parent_id] {
 
                    true
 
                } else {
 
                    false
 
                };
 

	
 
                if is_conditional && *idx_in_parent == 0 {
src/protocol/parser/pass_validation_linking.rs
Show inline comments
 
@@ -90,25 +90,25 @@ pub(crate) struct PassValidationLinking {
 
    // Traversal state, current scope (which can be used to find the parent
 
    // scope) and the definition variant we are considering.
 
    cur_scope: Scope,
 
    def_type: DefinitionType,
 
    // "Trailing" traversal state, set be child/prev stmt/expr used by next one
 
    prev_stmt: StatementId,
 
    expr_parent: ExpressionParent,
 
    // Set by parent to indicate that child expression must be assignable. The
 
    // child will throw an error if it is not assignable. The stored span is
 
    // 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
 
    // members but not the AST container.
 
    variable_buffer: ScopedBuffer<VariableId>,
 
    definition_buffer: ScopedBuffer<DefinitionId>,
 
    statement_buffer: ScopedBuffer<StatementId>,
 
    expression_buffer: ScopedBuffer<ExpressionId>,
 
}
 

	
 
impl PassValidationLinking {
 
    pub(crate) fn new() -> Self {
 
@@ -246,25 +246,33 @@ impl Visitor for PassValidationLinking {
 
        Ok(())
 
    }
 

	
 
    //--------------------------------------------------------------------------
 
    // Statement visitors
 
    //--------------------------------------------------------------------------
 

	
 
    fn visit_block_stmt(&mut self, ctx: &mut Ctx, id: BlockStatementId) -> VisitorResult {
 
        self.visit_block_stmt_with_hint(ctx, id, None)
 
    }
 

	
 
    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(())
 
    }
 

	
 
    fn visit_local_channel_stmt(&mut self, ctx: &mut Ctx, id: ChannelStatementId) -> VisitorResult {
 
        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)?;
 

	
 
@@ -447,48 +455,43 @@ impl Visitor for PassValidationLinking {
 
                "select statements may only occur inside sync blocks"
 
            ));
 
        }
 

	
 
        if !self.def_type.is_primitive() {
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module().source, select_stmt.span,
 
                "select statements may only be used in primitive components"
 
            ));
 
        }
 

	
 
        // 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());
 
        }
 

	
 
        assign_then_erase_next_stmt!(self, ctx, id.upcast());
 

	
 
        // 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());
 
        }
 

	
 
        self.prev_stmt = end_select_id.upcast();
 
        Ok(())
 
    }
 

	
 
    fn visit_return_stmt(&mut self, ctx: &mut Ctx, id: ReturnStatementId) -> VisitorResult {
 
        // Check if "return" occurs within a function
 
        let stmt = &ctx.heap[id];
 
@@ -574,24 +577,25 @@ impl Visitor for PassValidationLinking {
 
    // Expression visitors
 
    //--------------------------------------------------------------------------
 

	
 
    fn visit_assignment_expr(&mut self, ctx: &mut Ctx, id: AssignmentExpressionId) -> VisitorResult {
 
        let upcast_id = id.upcast();
 

	
 
        let assignment_expr = &mut ctx.heap[id];
 

	
 
        // Although we call assignment an expression to simplify the compiler's
 
        // 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;
 
                return Err(ParseError::new_error_str_at_span(
 
                    &ctx.module().source, assignment_span,
 
                    "assignments are statements, and cannot be used in expressions"
 
                ))
 
            },
 
        }
 

	
 
        let left_expr_id = assignment_expr.left;
 
        let right_expr_id = assignment_expr.right;
 
@@ -1235,114 +1239,121 @@ impl Visitor for PassValidationLinking {
 
            self.visit_expr(ctx, arg_expr_id)?;
 
        }
 

	
 
        section.forget();
 
        self.expr_parent = old_expr_parent;
 

	
 
        Ok(())
 
    }
 

	
 
    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];
 
        var_expr.declaration = Some(variable_id);
 
        var_expr.used_as_binding_target = is_binding_target;
 
        var_expr.parent = self.expr_parent;
 
        var_expr.unique_id_in_definition = self.next_expr_index;
 
        self.next_expr_index += 1;
 

	
 
        Ok(())
 
    }
 
}
 
@@ -1387,44 +1398,44 @@ impl PassValidationLinking {
 
        body.relative_pos_in_parent = self.relative_pos_in_block;
 
        let end_block_id = body.end_block;
 

	
 
        let old_relative_pos = self.relative_pos_in_block;
 

	
 
        // 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 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());
 

	
 
        self.cur_scope = old_scope;
 
        self.relative_pos_in_block = old_relative_pos;
 
        statement_section.forget();
 

	
 
        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) => {
 
                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)?;
 
@@ -1483,34 +1494,34 @@ impl PassValidationLinking {
 
        let mut scope_section = self.statement_buffer.start_section();
 
        for child_scope in &block_stmt.scope_node.nested {
 
            debug_assert!(child_scope.is_block(), "found a child scope that is not a block statement");
 
            scope_section.push(child_scope.to_block().upcast());
 
        }
 

	
 
        let mut var_idx = 0;
 
        let mut scope_idx = 0;
 
        while var_idx < var_section.len() || scope_idx < scope_section.len() {
 
            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
 
            // the variable should be treated first
 
            if relative_var_pos <= relative_scope_pos {
 
                let var = &mut ctx.heap[var_section[var_idx]];
 
                var.unique_id_in_scope = var_counter;
 
                var_counter += 1;
 
                var_idx += 1;
 
            } else {
 
                // Boy oh boy
 
                let block_id = ctx.heap[scope_section[scope_idx]].as_block().this;
 
@@ -1524,28 +1535,29 @@ impl PassValidationLinking {
 

	
 
        // Done assigning all IDs, assign the last ID to the block statement scope
 
        let block_stmt = &mut ctx.heap[block_id];
 
        block_stmt.next_unique_id_in_scope = var_counter;
 
    }
 

	
 
    //--------------------------------------------------------------------------
 
    // 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: 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
 
            // in the call at the end. Likewise for checking the symbol table.
 
            let block = &ctx.heap[scope.to_block()];
 

	
 
            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() {
 
                    let parameter = &ctx.heap[*parameter_id];
 
                    if local.identifier == parameter.identifier {
 
@@ -1585,25 +1597,25 @@ 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)
 
    }
 

	
 
    /// Adds a local variable to the specified scope. Will check the specified
 
    /// 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
 
        {
 
            let cur_scope = SymbolScope::Definition(self.def_type.definition_id());
 
            let ident = &ctx.heap[id].identifier;
 
            if let Some(symbol) = ctx.symbols.get_symbol_by_name(cur_scope, &ident.value.as_bytes()) {
 
                return Err(ParseError::new_error_str_at_span(
 
                    &ctx.module().source, ident.span,
 
                    "local variable declaration conflicts with symbol"
 
                ).with_info_str_at_span(
 
                    &ctx.module().source, symbol.variant.span_of_introduction(&ctx.heap), "the conflicting symbol is introduced here"
 
                ));
 
@@ -1634,69 +1646,72 @@ impl PassValidationLinking {
 
        // No collisions
 
        let block = &mut ctx.heap[scope.to_block()];
 
        block.locals.push(id);
 

	
 
        let local = &mut ctx.heap[id];
 
        local.relative_pos_in_block = relative_pos;
 

	
 
        Ok(())
 
    }
 

	
 
    /// 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
 
        let mut scope = &self.cur_scope;
 
        
 
        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);
 
                }
 
            }
 

	
 
            scope = &block.scope_node.parent;
 
            if !scope.is_block() {
 
                // Definition scope, need to check arguments to definition
 
                match scope {
 
                    Scope::Definition(definition_id) => {
 
                        let definition = &ctx.heap[*definition_id];
 
                        for parameter_id in definition.parameters() {
 
                            let parameter = &ctx.heap[*parameter_id];
 
                            if identifier == &parameter.identifier {
 
                                return Ok(*parameter_id);
 
                                return Some(*parameter_id);
 
                            }
 
                        }
 
                    },
 
                    _ => unreachable!(),
 
                }
 

	
 
                // Variable could not be found
 
                return Err(())
 
                return None
 
            } else {
 
                relative_pos = block.relative_pos_in_parent;
 
            }
 
        }
 
    }
 

	
 
    /// 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
 
        // parent scope.
 
        let label = &mut ctx.heap[id];
 
        label.relative_pos_in_block = relative_pos;
 
        label.in_sync = in_sync;
 

	
 
        let label = &ctx.heap[id];
 
        let mut scope = &self.cur_scope;
 

	
 
        loop {
src/protocol/tests/parser_validation.rs
Show inline comments
 
@@ -556,35 +556,57 @@ fn test_incorrect_modifying_operators() {
 

	
 
    Tester::new_single_source_expect_err(
 
        "inside function",
 
        "func f(u32 a) -> u32 { auto b = 0; auto c = f(a += 2); }"
 
    ).error(|e| { e.assert_msg_has(0, "assignments are statements"); });
 

	
 
    Tester::new_single_source_expect_err(
 
        "inside tuple",
 
        "func f(u32 a) -> u32 { auto b = (a += 2, a /= 2); return 0; }"
 
    ).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(
 
        "correct single-use", "
 
        primitive f() {
 
            channel unused_output -> input;
 
            u32 outer_value = 0;
 
            sync select {
 
                outer_value = get(input) -> outer_value = 0;
 
                auto new_value = get(input) -> {
 
                    f();
 
                    outer_value = new_value;
 
                }
 
                get(input) + get(input) ->
 
                    outer_value = 8;
 
                get(input) ->
 
                    {}
 
                outer_value %= get(input) -> {
 
                    outer_value *= outer_value;
 
                    auto new_value = get(input);
 
                    outer_value += new_value;
 
                }
 
            }
src/protocol/tests/utils.rs
Show inline comments
 
@@ -526,24 +526,25 @@ impl<'a> FunctionTester<'a> {
 
                        if var.identifier.value.as_str() == name {
 
                            return true;
 
                        }
 
                    }
 
                }
 

	
 
                false
 
            }
 
        );
 

	
 
        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];
 
                if var.identifier.value.as_str() == name {
 
                    found_local_id = Some(*local_id);
 
                }
 
            }
 
        }
 

	
 
        assert!(
 
            found_local_id.is_some(), "[{}] Failed to find variable '{}' in {}",
 
            self.ctx.test_name, name, self.assert_postfix()
 
@@ -1185,24 +1186,30 @@ fn seek_expr_in_expr<F: Fn(&Expression) -> bool>(heap: &Heap, start: ExpressionI
 
            None
 
        },
 
        Expression::Variable(_expr) => {
 
            None
 
        }
 
    }
 
}
 

	
 
fn seek_expr_in_stmt<F: Fn(&Expression) -> bool>(heap: &Heap, start: StatementId, f: &F) -> Option<ExpressionId> {
 
    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) {
 
                    return Some(id)
 
                }
 
            }
 
            None
 
        },
 
        Statement::Labeled(stmt) => {
 
            seek_expr_in_stmt(heap, stmt.body, f)
 
        },
 
        Statement::If(stmt) => {
0 comments (0 inline, 0 general)