Changeset - 3bb9f89521a1
[Not reviewed]
0 9 1
MH - 4 years ago 2021-05-25 18:58:12
contact@maxhenger.nl
Prepare for binding expression inference
10 files changed with 510 insertions and 162 deletions:
0 comments (0 inline, 0 general)
src/protocol/ast.rs
Show inline comments
 
@@ -613,28 +613,47 @@ impl Scope {
 
            Scope::Synchronous(_) => true,
 
        }
 
    }
 
    pub fn to_block(&self) -> BlockStatementId {
 
        match &self {
 
            Scope::Regular(id) => *id,
 
            Scope::Synchronous((_, id)) => *id,
 
            _ => panic!("unable to get BlockStatement from Scope")
 
        }
 
    }
 
}
 

	
 
/// `ScopeNode` is a helper that links scopes in two directions. It doesn't
 
/// actually contain any information associated with the scope, this may be
 
/// found on the AST elements that `Scope` points to.
 
#[derive(Debug, Clone)]
 
pub struct ScopeNode {
 
    pub parent: Scope,
 
    pub nested: Vec<Scope>,
 
}
 

	
 
impl ScopeNode {
 
    pub(crate) fn new_invalid() -> Self {
 
        ScopeNode{
 
            parent: Scope::Definition(DefinitionId::new_invalid()),
 
            nested: Vec::new(),
 
        }
 
    }
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub enum VariableKind {
 
    Parameter,
 
    Local,
 
    Parameter,      // in parameter list of function/component
 
    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 unique_id_in_scope: i32, // Temporary fix until proper bytecode/asm is generated
 
@@ -811,29 +830,29 @@ pub enum EnumVariantValue {
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct EnumVariantDefinition {
 
    pub identifier: Identifier,
 
    pub value: EnumVariantValue,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct EnumDefinition {
 
    pub this: EnumDefinitionId,
 
    pub defined_in: RootId,
 
    // Phase 1: symbol scanning
 
    // Symbol scanning
 
    pub span: InputSpan,
 
    pub identifier: Identifier,
 
    pub poly_vars: Vec<Identifier>,
 
    // Phase 2: parsing
 
    // Parsing
 
    pub variants: Vec<EnumVariantDefinition>,
 
}
 

	
 
impl EnumDefinition {
 
    pub(crate) fn new_empty(
 
        this: EnumDefinitionId, defined_in: RootId, span: InputSpan,
 
        identifier: Identifier, poly_vars: Vec<Identifier>
 
    ) -> Self {
 
        Self{ this, defined_in, span, identifier, poly_vars, variants: Vec::new() }
 
    }
 
}
 

	
 
@@ -1163,25 +1182,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 parent_scope: Scope,
 
    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 locals: Vec<VariableId>,
 
    pub labels: Vec<LabeledStatementId>,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct EndBlockStatement {
 
    pub this: EndBlockStatementId,
 
    // Parser
 
    pub start_block: BlockStatementId,
 
@@ -1248,25 +1267,25 @@ pub struct ChannelStatement {
 
    pub relative_pos_in_block: u32,
 
    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 in_sync: Option<SynchronousStatementId>,
 
    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,
 
}
 
@@ -1277,25 +1296,25 @@ pub struct EndIfStatement {
 
    pub start_if: IfStatementId,
 
    pub next: StatementId,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct WhileStatement {
 
    pub this: WhileStatementId,
 
    // Phase 1: parser
 
    pub span: InputSpan, // of the "while" keyword
 
    pub test: ExpressionId,
 
    pub body: BlockStatementId,
 
    pub end_while: EndWhileStatementId,
 
    pub in_sync: Option<SynchronousStatementId>,
 
    pub in_sync: SynchronousStatementId, // may be invalid
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct EndWhileStatement {
 
    pub this: EndWhileStatementId,
 
    pub start_while: WhileStatementId,
 
    // Phase 2: linker
 
    pub next: StatementId,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct BreakStatement {
 
@@ -1316,25 +1335,24 @@ pub struct ContinueStatement {
 
    // Phase 2: linker
 
    pub target: Option<WhileStatementId>,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct SynchronousStatement {
 
    pub this: SynchronousStatementId,
 
    // Phase 1: parser
 
    pub span: InputSpan, // of the "sync" keyword
 
    pub body: BlockStatementId,
 
    // Phase 2: linker
 
    pub end_sync: EndSynchronousStatementId,
 
    pub parent_scope: Option<Scope>,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct EndSynchronousStatement {
 
    pub this: EndSynchronousStatementId,
 
    pub start_sync: SynchronousStatementId,
 
    // Phase 2: linker
 
    pub next: StatementId,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct ReturnStatement {
 
@@ -1577,27 +1595,27 @@ pub struct AssignmentExpression {
 
    pub left: ExpressionId,
 
    pub operation: AssignmentOperator,
 
    pub right: ExpressionId,
 
    // Validator/Linker
 
    pub parent: ExpressionParent,
 
    pub unique_id_in_definition: i32,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct BindingExpression {
 
    pub this: BindingExpressionId,
 
    // Parsing
 
    pub span: InputSpan,
 
    pub left: LiteralExpressionId,
 
    pub right: ExpressionId,
 
    pub span: InputSpan, // of the binding keyword
 
    pub bound_to: ExpressionId,
 
    pub bound_from: ExpressionId,
 
    // Validator/Linker
 
    pub parent: ExpressionParent,
 
    pub unique_id_in_definition: i32,
 
}
 

	
 
#[derive(Debug, Clone)]
 
pub struct ConditionalExpression {
 
    pub this: ConditionalExpressionId,
 
    // Parsing
 
    pub span: InputSpan, // of question mark operator
 
    pub test: ExpressionId,
 
    pub true_expression: ExpressionId,
src/protocol/ast_printer.rs
Show inline comments
 
@@ -463,25 +463,25 @@ impl ASTWriter {
 
            Statement::EndIf(stmt) => {
 
                self.kv(indent).with_id(PREFIX_ENDIF_STMT_ID, stmt.this.0.index)
 
                    .with_s_key("EndIf");
 
                self.kv(indent2).with_s_key("StartIf").with_disp_val(&stmt.start_if.0.index);
 
                self.kv(indent2).with_s_key("Next").with_disp_val(&stmt.next.index);
 
            },
 
            Statement::While(stmt) => {
 
                self.kv(indent).with_id(PREFIX_WHILE_STMT_ID, stmt.this.0.index)
 
                    .with_s_key("While");
 

	
 
                self.kv(indent2).with_s_key("EndWhile").with_disp_val(&stmt.end_while.0.index);
 
                self.kv(indent2).with_s_key("InSync")
 
                    .with_opt_disp_val(stmt.in_sync.as_ref().map(|v| &v.0.index));
 
                    .with_disp_val(&stmt.in_sync.0.index);
 
                self.kv(indent2).with_s_key("Condition");
 
                self.write_expr(heap, stmt.test, indent3);
 
                self.kv(indent2).with_s_key("Body");
 
                self.write_stmt(heap, stmt.body.upcast(), indent3);
 
            },
 
            Statement::EndWhile(stmt) => {
 
                self.kv(indent).with_id(PREFIX_ENDWHILE_STMT_ID, stmt.this.0.index)
 
                    .with_s_key("EndWhile");
 
                self.kv(indent2).with_s_key("StartWhile").with_disp_val(&stmt.start_while.0.index);
 
                self.kv(indent2).with_s_key("Next").with_disp_val(&stmt.next.index);
 
            },
 
            Statement::Break(stmt) => {
 
@@ -556,28 +556,28 @@ impl ASTWriter {
 
                    .with_s_key("AssignmentExpr");
 
                self.kv(indent2).with_s_key("Operation").with_debug_val(&expr.operation);
 
                self.kv(indent2).with_s_key("Left");
 
                self.write_expr(heap, expr.left, indent3);
 
                self.kv(indent2).with_s_key("Right");
 
                self.write_expr(heap, expr.right, indent3);
 
                self.kv(indent2).with_s_key("Parent")
 
                    .with_custom_val(|v| write_expression_parent(v, &expr.parent));
 
            },
 
            Expression::Binding(expr) => {
 
                self.kv(indent).with_id(PREFIX_BINARY_EXPR_ID, expr.this.0.index)
 
                    .with_s_key("BindingExpr");
 
                self.kv(indent2).with_s_key("LeftExpression");
 
                self.write_expr(heap, expr.left.upcast(), indent3);
 
                self.kv(indent2).with_s_key("RightExpression");
 
                self.write_expr(heap, expr.right, indent3);
 
                self.kv(indent2).with_s_key("BindToExpression");
 
                self.write_expr(heap, expr.bound_to, indent3);
 
                self.kv(indent2).with_s_key("BindFromExpression");
 
                self.write_expr(heap, expr.bound_from, indent3);
 
                self.kv(indent2).with_s_key("Parent")
 
                    .with_custom_val(|v| write_expression_parent(v, &expr.parent));
 
            },
 
            Expression::Conditional(expr) => {
 
                self.kv(indent).with_id(PREFIX_CONDITIONAL_EXPR_ID, expr.this.0.index)
 
                    .with_s_key("ConditionalExpr");
 
                self.kv(indent2).with_s_key("Condition");
 
                self.write_expr(heap, expr.test, indent3);
 
                self.kv(indent2).with_s_key("TrueExpression");
 
                self.write_expr(heap, expr.true_expression, indent3);
 
                self.kv(indent2).with_s_key("FalseExpression");
 
                self.write_expr(heap, expr.false_expression, indent3);
src/protocol/parser/depth_visitor.rs
Show inline comments
 
@@ -415,26 +415,26 @@ fn recursive_assignment_expression<T: Visitor>(
 
    h: &mut Heap,
 
    expr: AssignmentExpressionId,
 
) -> VisitorResult {
 
    this.visit_expression(h, h[expr].left)?;
 
    this.visit_expression(h, h[expr].right)
 
}
 

	
 
fn recursive_binding_expression<T: Visitor>(
 
    this: &mut T,
 
    h: &mut Heap,
 
    expr: BindingExpressionId,
 
) -> VisitorResult {
 
    this.visit_expression(h, h[expr].left.upcast())?;
 
    this.visit_expression(h, h[expr].right)
 
    this.visit_expression(h, h[expr].bound_from)?;
 
    this.visit_expression(h, h[expr].bound_to)
 
}
 

	
 
fn recursive_conditional_expression<T: Visitor>(
 
    this: &mut T,
 
    h: &mut Heap,
 
    expr: ConditionalExpressionId,
 
) -> VisitorResult {
 
    this.visit_expression(h, h[expr].test)?;
 
    this.visit_expression(h, h[expr].true_expression)?;
 
    this.visit_expression(h, h[expr].false_expression)
 
}
 

	
src/protocol/parser/pass_definitions.rs
Show inline comments
 
@@ -349,25 +349,25 @@ impl PassDefinitions {
 
            self.consume_statement(module, iter, ctx, &mut statements)?;
 
            let wrap_end_pos = iter.last_valid_pos();
 

	
 
            debug_assert_eq!(statements.len(), 1);
 
            let statements = statements.into_vec();
 

	
 
            let id = ctx.heap.alloc_block_statement(|this| BlockStatement{
 
                this,
 
                is_implicit: true,
 
                span: InputSpan::from_positions(wrap_begin_pos, wrap_end_pos), // TODO: @Span
 
                statements,
 
                end_block: EndBlockStatementId::new_invalid(),
 
                parent_scope: Scope::Definition(DefinitionId::new_invalid()),
 
                scope_node: ScopeNode::new_invalid(),
 
                first_unique_id_in_scope: -1,
 
                next_unique_id_in_scope: -1,
 
                relative_pos_in_parent: 0,
 
                locals: Vec::new(),
 
                labels: Vec::new()
 
            });
 

	
 
            let end_block = ctx.heap.alloc_end_block_statement(|this| EndBlockStatement{
 
                this, start_block: id, next: StatementId::new_invalid()
 
            });
 

	
 
            let block_stmt = &mut ctx.heap[id];
 
@@ -487,25 +487,25 @@ impl PassDefinitions {
 
        }
 

	
 
        let statements = stmt_section.into_vec();
 
        let mut block_span = consume_token(&module.source, iter, TokenKind::CloseCurly)?;
 
        block_span.begin = open_curly_pos;
 

	
 
        let id = ctx.heap.alloc_block_statement(|this| BlockStatement{
 
            this,
 
            is_implicit: false,
 
            span: block_span,
 
            statements,
 
            end_block: EndBlockStatementId::new_invalid(),
 
            parent_scope: Scope::Definition(DefinitionId::new_invalid()),
 
            scope_node: ScopeNode::new_invalid(),
 
            first_unique_id_in_scope: -1,
 
            next_unique_id_in_scope: -1,
 
            relative_pos_in_parent: 0,
 
            locals: Vec::new(),
 
            labels: Vec::new(),
 
        });
 

	
 
        let end_block = ctx.heap.alloc_end_block_statement(|this| EndBlockStatement{
 
            this, start_block: id, next: StatementId::new_invalid()
 
        });
 

	
 
        let block_stmt = &mut ctx.heap[id];
 
@@ -547,25 +547,25 @@ impl PassDefinitions {
 
        let while_span = consume_exact_ident(&module.source, iter, KW_STMT_WHILE)?;
 
        consume_token(&module.source, iter, TokenKind::OpenParen)?;
 
        let test = self.consume_expression(module, iter, ctx)?;
 
        consume_token(&module.source, iter, TokenKind::CloseParen)?;
 
        let body = self.consume_block_or_wrapped_statement(module, iter, ctx)?;
 

	
 
        Ok(ctx.heap.alloc_while_statement(|this| WhileStatement{
 
            this,
 
            span: while_span,
 
            test,
 
            body,
 
            end_while: EndWhileStatementId::new_invalid(),
 
            in_sync: None,
 
            in_sync: SynchronousStatementId::new_invalid(),
 
        }))
 
    }
 

	
 
    fn consume_break_statement(
 
        &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx
 
    ) -> Result<BreakStatementId, ParseError> {
 
        let break_span = consume_exact_ident(&module.source, iter, KW_STMT_BREAK)?;
 
        let label = if Some(TokenKind::Ident) == iter.next() {
 
            let label = consume_ident_interned(&module.source, iter, ctx)?;
 
            Some(label)
 
        } else {
 
            None
 
@@ -600,25 +600,24 @@ impl PassDefinitions {
 

	
 
    fn consume_synchronous_statement(
 
        &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx
 
    ) -> Result<SynchronousStatementId, ParseError> {
 
        let synchronous_span = consume_exact_ident(&module.source, iter, KW_STMT_SYNC)?;
 
        let body = self.consume_block_or_wrapped_statement(module, iter, ctx)?;
 

	
 
        Ok(ctx.heap.alloc_synchronous_statement(|this| SynchronousStatement{
 
            this,
 
            span: synchronous_span,
 
            body,
 
            end_sync: EndSynchronousStatementId::new_invalid(),
 
            parent_scope: None,
 
        }))
 
    }
 

	
 
    fn consume_return_statement(
 
        &mut self, module: &Module, iter: &mut TokenIter, ctx: &mut PassCtx
 
    ) -> Result<ReturnStatementId, ParseError> {
 
        let return_span = consume_exact_ident(&module.source, iter, KW_STMT_RETURN)?;
 
        let mut scoped_section = self.expressions.start_section();
 

	
 
        consume_comma_separated_until(
 
            TokenKind::SemiColon, &module.source, iter, ctx,
 
            |_source, iter, ctx| self.consume_expression(module, iter, ctx),
 
@@ -758,25 +757,25 @@ impl PassDefinitions {
 
        // The values in the section need to be in the correct order if some
 
        // kind of outer block is consumed, so we take another section, push
 
        // the expressions in that one, and then allocate the labeled statement.
 
        let mut inner_section = self.statements.start_section();
 
        self.consume_statement(module, iter, ctx, &mut inner_section)?;
 
        debug_assert!(inner_section.len() >= 1);
 

	
 
        let stmt_id = ctx.heap.alloc_labeled_statement(|this| LabeledStatement {
 
            this,
 
            label,
 
            body: inner_section[0],
 
            relative_pos_in_block: 0,
 
            in_sync: None,
 
            in_sync: SynchronousStatementId::new_invalid(),
 
        });
 

	
 
        if inner_section.len() == 1 {
 
            // Produce the labeled statement pointing to the first statement.
 
            // This is by far the most common case.
 
            inner_section.forget();
 
            section.push(stmt_id.upcast());
 
        } else {
 
            // 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());
 
@@ -1449,24 +1448,41 @@ impl PassDefinitions {
 
                        KW_LIT_TRUE => Literal::True,
 
                        KW_LIT_FALSE => Literal::False,
 
                        _ => unreachable!(),
 
                    };
 

	
 
                    ctx.heap.alloc_literal_expression(|this| LiteralExpression {
 
                        this,
 
                        span: ident_span,
 
                        value,
 
                        parent: ExpressionParent::None,
 
                        unique_id_in_definition: -1,
 
                    }).upcast()
 
                } else if ident_text == KW_LET {
 
                    // Binding expression
 
                    let keyword_span = iter.next_span();
 
                    iter.consume();
 

	
 
                    let bound_to = self.consume_expression(module, iter, ctx)?;
 
                    consume_token(&module.source, iter, TokenKind::Equal)?;
 
                    let bound_from = self.consume_expression(module, iter, ctx)?;
 

	
 
                    ctx.heap.alloc_binding_expression(|this| BindingExpression{
 
                        this,
 
                        span: keyword_span,
 
                        bound_to,
 
                        bound_from,
 
                        parent: ExpressionParent::None,
 
                        unique_id_in_definition: -1,
 
                    }).upcast()
 
                } else if ident_text == KW_CAST {
 
                    // Casting expression
 
                    iter.consume();
 
                    let to_type = if Some(TokenKind::OpenAngle) == iter.next() {
 
                        iter.consume();
 
                        let definition_id = self.cur_definition;
 
                        let poly_vars = ctx.heap[definition_id].poly_vars();
 
                        consume_parser_type(
 
                            &module.source, iter, &ctx.symbols, &ctx.heap,
 
                            poly_vars, SymbolScope::Module(module.root_id), definition_id,
 
                            true, 1
 
                        )?
src/protocol/parser/pass_typing.rs
Show inline comments
 
@@ -1161,24 +1161,38 @@ impl Visitor2 for PassTyping {
 
        self.insert_initial_expr_inference_type(ctx, upcast_id)?;
 

	
 
        let assign_expr = &ctx.heap[id];
 
        let left_expr_id = assign_expr.left;
 
        let right_expr_id = assign_expr.right;
 

	
 
        self.visit_expr(ctx, left_expr_id)?;
 
        self.visit_expr(ctx, right_expr_id)?;
 

	
 
        self.progress_assignment_expr(ctx, id)
 
    }
 

	
 
    fn visit_binding_expr(&mut self, ctx: &mut Ctx, id: BindingExpressionId) -> VisitorResult {
 
        let upcast_id = id.upcast();
 
        self.insert_initial_expr_inference_type(ctx, upcast_id)?;
 

	
 
        let binding_expr = &ctx.heap[id];
 
        let bound_to_id = binding_expr.bound_to;
 
        let bound_from_id = binding_expr.bound_from;
 

	
 
        self.visit_expr(ctx, bound_to_id)?;
 
        self.visit_expr(ctx, bound_from_id)?;
 

	
 
        self.progress_binding_expr(ctx, id)
 
    }
 

	
 
    fn visit_conditional_expr(&mut self, ctx: &mut Ctx, id: ConditionalExpressionId) -> VisitorResult {
 
        let upcast_id = id.upcast();
 
        self.insert_initial_expr_inference_type(ctx, upcast_id)?;
 

	
 
        let conditional_expr = &ctx.heap[id];
 
        let test_expr_id = conditional_expr.test;
 
        let true_expr_id = conditional_expr.true_expression;
 
        let false_expr_id = conditional_expr.false_expression;
 

	
 
        self.visit_expr(ctx, test_expr_id)?;
 
        self.visit_expr(ctx, true_expr_id)?;
 
        self.visit_expr(ctx, false_expr_id)?;
 
@@ -1509,26 +1523,27 @@ impl PassTyping {
 
        }
 

	
 
        Ok(())
 
    }
 

	
 
    fn progress_expr(&mut self, ctx: &mut Ctx, idx: i32) -> Result<(), ParseError> {
 
        let id = self.expr_types[idx as usize].expr_id; // TODO: @Temp
 
        match &ctx.heap[id] {
 
            Expression::Assignment(expr) => {
 
                let id = expr.this;
 
                self.progress_assignment_expr(ctx, id)
 
            },
 
            Expression::Binding(_expr) => {
 
                unimplemented!("progress binding expression");
 
            Expression::Binding(expr) => {
 
                let id = expr.this;
 
                self.progress_binding_expr(ctx, id)
 
            },
 
            Expression::Conditional(expr) => {
 
                let id = expr.this;
 
                self.progress_conditional_expr(ctx, id)
 
            },
 
            Expression::Binary(expr) => {
 
                let id = expr.this;
 
                self.progress_binary_expr(ctx, id)
 
            },
 
            Expression::Unary(expr) => {
 
                let id = expr.this;
 
                self.progress_unary_expr(ctx, id)
 
@@ -1602,24 +1617,28 @@ impl PassTyping {
 
        debug_log!("   - Arg1 type [{}]: {}", progress_forced || progress_arg1, self.temp_get_display_name(ctx, arg1_expr_id));
 
        debug_log!("   - Arg2 type [{}]: {}", progress_arg2, self.temp_get_display_name(ctx, arg2_expr_id));
 
        debug_log!("   - Expr type [{}]: {}", progress_expr, self.temp_get_display_name(ctx, upcast_id));
 

	
 

	
 
        if progress_expr { self.queue_expr_parent(ctx, upcast_id); }
 
        if progress_forced || progress_arg1 { self.queue_expr(ctx, arg1_expr_id); }
 
        if progress_arg2 { self.queue_expr(ctx, arg2_expr_id); }
 

	
 
        Ok(())
 
    }
 

	
 
    fn progress_binding_expr(&mut self, ctx: &mut Ctx, id: BindingExpressionId) -> Result<(), ParseError> {
 

	
 
    }
 

	
 
    fn progress_conditional_expr(&mut self, ctx: &mut Ctx, id: ConditionalExpressionId) -> Result<(), ParseError> {
 
        // Note: test expression type is already enforced
 
        let upcast_id = id.upcast();
 
        let expr = &ctx.heap[id];
 
        let arg1_expr_id = expr.true_expression;
 
        let arg2_expr_id = expr.false_expression;
 

	
 
        debug_log!("Conditional expr: {}", upcast_id.index);
 
        debug_log!(" * Before:");
 
        debug_log!("   - Arg1 type: {}", self.temp_get_display_name(ctx, arg1_expr_id));
 
        debug_log!("   - Arg2 type: {}", self.temp_get_display_name(ctx, arg2_expr_id));
 
        debug_log!("   - Expr type: {}", self.temp_get_display_name(ctx, upcast_id));
src/protocol/parser/pass_validation_linking.rs
Show inline comments
 
@@ -34,36 +34,32 @@ impl DefinitionType {
 
}
 

	
 
/// This particular visitor will go through the entire AST in a recursive manner
 
/// and check if all statements and expressions are legal (e.g. no "return"
 
/// statements in component definitions), and will link certain AST nodes to
 
/// their appropriate targets (e.g. goto statements, or function calls).
 
///
 
/// This visitor will not perform control-flow analysis (e.g. making sure that
 
/// each function actually returns) and will also not perform type checking. So
 
/// the linking of function calls and component instantiations will be checked
 
/// and linked to the appropriate definitions, but the return types and/or
 
/// arguments will not be checked for validity.
 
///
 
///
 
/// Because of this scheme expressions will not be visited in the breadth-first
 
/// pass.
 
pub(crate) struct PassValidationLinking {
 
    // `in_sync` is `Some(id)` if the visitor is visiting the children of a
 
    // synchronous statement. A single value is sufficient as nested
 
    // synchronous statements are not allowed
 
    in_sync: Option<SynchronousStatementId>,
 
    // `in_while` contains the last encountered `While` statement. This is used
 
    // to resolve unlabeled `Continue`/`Break` statements.
 
    in_while: Option<WhileStatementId>,
 
    // Traversal state, all valid IDs if inside a certain AST element. Otherwise
 
    // `id.is_invalid()` returns true.
 
    in_sync: SynchronousStatementId,
 
    in_while: WhileStatementId, // to resolve labeled continue/break
 
    in_test_expr: StatementId, // wrapping if/while stmt id
 
    in_binding_expr: BindingExpressionId, // to resolve variable expressions
 
    in_binding_expr_lhs: bool,
 
    // 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,
 
    // Parent expression (the previous stmt/expression we visited that could be
 
    // used as an expression parent)
 
    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.
 
@@ -72,45 +68,52 @@ pub(crate) struct PassValidationLinking {
 
    // 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 {
 
        Self{
 
            in_sync: None,
 
            in_while: None,
 
            in_sync: SynchronousStatementId::new_invalid(),
 
            in_while: WhileStatementId::new_invalid(),
 
            in_test_expr: StatementId::new_invalid(),
 
            in_binding_expr: BindingExpressionId::new_invalid(),
 
            in_binding_expr_lhs: false,
 
            cur_scope: Scope::Definition(DefinitionId::new_invalid()),
 
            expr_parent: ExpressionParent::None,
 
            def_type: DefinitionType::Function(FunctionDefinitionId::new_invalid()),
 
            must_be_assignable: None,
 
            relative_pos_in_block: 0,
 
            next_expr_index: 0,
 
            variable_buffer: ScopedBuffer::new_reserved(128),
 
            definition_buffer: ScopedBuffer::new_reserved(128),
 
            statement_buffer: ScopedBuffer::new_reserved(STMT_BUFFER_INIT_CAPACITY),
 
            expression_buffer: ScopedBuffer::new_reserved(EXPR_BUFFER_INIT_CAPACITY),
 
        }
 
    }
 

	
 
    fn reset_state(&mut self) {
 
        self.in_sync = None;
 
        self.in_while = None;
 
        self.in_sync = SynchronousStatementId::new_invalid();
 
        self.in_while = WhileStatementId::new_invalid();
 
        self.in_test_expr = StatementId::new_invalid();
 
        self.in_binding_expr = BindingExpressionId::new_invalid();
 
        self.in_binding_expr_lhs = false;
 
        self.cur_scope = Scope::Definition(DefinitionId::new_invalid());
 
        self.expr_parent = ExpressionParent::None;
 
        self.def_type = DefinitionType::Function(FunctionDefinitionId::new_invalid());
 
        self.expr_parent = ExpressionParent::None;
 
        self.must_be_assignable = None;
 
        self.relative_pos_in_block = 0;
 
        self.next_expr_index = 0
 
    }
 
}
 

	
 
impl Visitor2 for PassValidationLinking {
 
    fn visit_module(&mut self, ctx: &mut Ctx) -> VisitorResult {
 
        debug_assert_eq!(ctx.module.phase, ModuleCompilationPhase::TypesAddedToTable);
 

	
 
        let root = &ctx.heap[ctx.module.root_id];
 
        let section = self.definition_buffer.start_section_initialized(&root.definitions);
 
        for definition_idx in 0..section.len() {
 
@@ -141,25 +144,29 @@ impl Visitor2 for PassValidationLinking {
 
        let body_id = definition.body;
 
        let section = self.variable_buffer.start_section_initialized(&definition.parameters);
 
        for variable_idx in 0..section.len() {
 
            let variable_id = section[variable_idx];
 
            let variable = &mut ctx.heap[variable_id];
 
            variable.unique_id_in_scope = variable_idx as i32;
 
        }
 
        section.forget();
 

	
 
        // Visit statements in component body
 
        self.visit_block_stmt(ctx, body_id)?;
 

	
 
        // Assign total number of expressions and assign an in-block unique ID
 
        // to each of the locals in the procedure.
 
        ctx.heap[id].num_expressions_in_body = self.next_expr_index;
 
        self.visit_definition_and_assign_local_ids(ctx, id.upcast());
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_function_definition(&mut self, ctx: &mut Ctx, id: FunctionDefinitionId) -> VisitorResult {
 
        self.reset_state();
 

	
 
        // Set internal statement indices
 
        self.def_type = DefinitionType::Function(id);
 
        self.cur_scope = Scope::Definition(id.upcast());
 
        self.expr_parent = ExpressionParent::None;
 

	
 
        // Visit parameters and assign a unique scope ID
 
@@ -167,25 +174,29 @@ impl Visitor2 for PassValidationLinking {
 
        let body_id = definition.body;
 
        let section = self.variable_buffer.start_section_initialized(&definition.parameters);
 
        for variable_idx in 0..section.len() {
 
            let variable_id = section[variable_idx];
 
            let variable = &mut ctx.heap[variable_id];
 
            variable.unique_id_in_scope = variable_idx as i32;
 
        }
 
        section.forget();
 

	
 
        // Visit statements in function body
 
        self.visit_block_stmt(ctx, body_id)?;
 

	
 
        // Assign total number of expressions and assign an in-block unique ID
 
        // to each of the locals in the procedure.
 
        ctx.heap[id].num_expressions_in_body = self.next_expr_index;
 
        self.visit_definition_and_assign_local_ids(ctx, id.upcast());
 

	
 
        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 {
 
@@ -195,54 +206,64 @@ impl Visitor2 for PassValidationLinking {
 
    fn visit_local_channel_stmt(&mut self, _ctx: &mut Ctx, _id: ChannelStatementId) -> VisitorResult {
 
        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)?;
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_if_stmt(&mut self, ctx: &mut Ctx, id: IfStatementId) -> VisitorResult {
 
        // Traverse expression and bodies
 
        let (test_id, true_id, false_id) = {
 
            let stmt = &ctx.heap[id];
 
            (stmt.test, stmt.true_body, stmt.false_body)
 
        };
 
        let if_stmt = &ctx.heap[id];
 
        let test_expr_id = if_stmt.test;
 
        let true_stmt_id = if_stmt.true_body;
 
        let false_stmt_id = if_stmt.false_body;
 

	
 
        // Visit test expression
 
        debug_assert_eq!(self.expr_parent, ExpressionParent::None);
 
        debug_assert!(self.in_test_expr.is_invalid());
 
        self.in_test_expr = id.upcast();
 
        self.expr_parent = ExpressionParent::If(id);
 
        self.visit_expr(ctx, test_id)?;
 
        self.visit_expr(ctx, test_expr_id)?;
 
        self.in_test_expr = StatementId::new_invalid();
 

	
 
        self.expr_parent = ExpressionParent::None;
 

	
 
        self.visit_block_stmt(ctx, true_id)?;
 
        if let Some(false_id) = false_id {
 
        self.visit_block_stmt(ctx, true_stmt_id)?;
 
        if let Some(false_id) = false_stmt_id {
 
            self.visit_block_stmt(ctx, false_id)?;
 
        }
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_while_stmt(&mut self, ctx: &mut Ctx, id: WhileStatementId) -> VisitorResult {
 
        let (test_id, body_id) = {
 
            let stmt = &ctx.heap[id];
 
            (stmt.test, stmt.body)
 
        };
 
        let old_while = self.in_while.replace(id);
 
        let old_while = self.in_while;
 
        self.in_while = id;
 

	
 
        // Visit test expression
 
        debug_assert_eq!(self.expr_parent, ExpressionParent::None);
 
        debug_assert!(self.in_test_expr.is_invalid());
 
        self.in_test_expr = id.upcast();
 
        self.expr_parent = ExpressionParent::While(id);
 
        self.visit_expr(ctx, test_id)?;
 
        self.expr_parent = ExpressionParent::None;
 
        self.in_test_expr = StatementId::new_invalid();
 

	
 
        self.expr_parent = ExpressionParent::None;
 
        self.visit_block_stmt(ctx, body_id)?;
 
        self.in_while = old_while;
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_break_stmt(&mut self, ctx: &mut Ctx, id: BreakStatementId) -> VisitorResult {
 
        // Resolve break target
 
        let target_end_while = {
 
            let stmt = &ctx.heap[id];
 
            let target_while_id = self.resolve_break_or_continue_target(ctx, stmt.span, &stmt.label)?;
 
            let target_while = &ctx.heap[target_while_id];
 
@@ -264,45 +285,47 @@ impl Visitor2 for PassValidationLinking {
 
            self.resolve_break_or_continue_target(ctx, stmt.span, &stmt.label)?
 
        };
 

	
 
        let stmt = &mut ctx.heap[id];
 
        stmt.target = Some(target_while_id);
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_synchronous_stmt(&mut self, ctx: &mut Ctx, id: SynchronousStatementId) -> VisitorResult {
 
        // Check for validity of synchronous statement
 
        let cur_sync_span = ctx.heap[id].span;
 
        if self.in_sync.is_some() {
 
        if !self.in_sync.is_invalid() {
 
            // Nested synchronous statement
 
            let old_sync_span = ctx.heap[self.in_sync.unwrap()].span;
 
            let old_sync_span = ctx.heap[self.in_sync].span;
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module.source, cur_sync_span, "Illegal nested synchronous statement"
 
            ).with_info_str_at_span(
 
                &ctx.module.source, old_sync_span, "It is nested in this synchronous statement"
 
            ));
 
        }
 

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

	
 
        let sync_body = ctx.heap[id].body;
 
        let old = self.in_sync.replace(id);
 
        debug_assert!(self.in_sync.is_invalid());
 
        self.in_sync = id;
 
        self.visit_block_stmt_with_hint(ctx, sync_body, Some(id))?;
 
        self.in_sync = old;
 

	
 
        self.in_sync = SynchronousStatementId::new_invalid();
 

	
 
        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];
 
        if !self.def_type.is_function() {
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module.source, stmt.span,
 
                "return statements may only appear in function bodies"
 
            ));
 
@@ -316,29 +339,29 @@ impl Visitor2 for PassValidationLinking {
 
        self.expr_parent = ExpressionParent::None;
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_goto_stmt(&mut self, ctx: &mut Ctx, id: GotoStatementId) -> VisitorResult {
 
        let target_id = self.find_label(ctx, &ctx.heap[id].label)?;
 
        ctx.heap[id].target = Some(target_id);
 

	
 
        let target = &ctx.heap[target_id];
 
        if self.in_sync != target.in_sync {
 
            // We can only goto the current scope or outer scopes. Because
 
            // nested sync statements are not allowed so if the value does
 
            // not match, then we must be inside a sync scope
 
            debug_assert!(self.in_sync.is_some());
 
            // nested sync statements are not allowed we must be inside a sync
 
            // statement.
 
            debug_assert!(!self.in_sync.is_invalid());
 
            let goto_stmt = &ctx.heap[id];
 
            let sync_stmt = &ctx.heap[self.in_sync.unwrap()];
 
            let sync_stmt = &ctx.heap[self.in_sync];
 
            return Err(
 
                ParseError::new_error_str_at_span(&ctx.module.source, goto_stmt.span, "goto may not escape the surrounding synchronous block")
 
                .with_info_str_at_span(&ctx.module.source, target.label.span, "this is the target of the goto statement")
 
                .with_info_str_at_span(&ctx.module.source, sync_stmt.span, "which will jump past this statement")
 
            );
 
        }
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_new_stmt(&mut self, ctx: &mut Ctx, id: NewStatementId) -> VisitorResult {
 
        // Make sure the new statement occurs inside a composite component
 
@@ -390,24 +413,95 @@ impl Visitor2 for PassValidationLinking {
 
        self.next_expr_index += 1;
 

	
 
        self.expr_parent = ExpressionParent::Expression(upcast_id, 0);
 
        self.must_be_assignable = Some(assignment_expr.span);
 
        self.visit_expr(ctx, left_expr_id)?;
 
        self.expr_parent = ExpressionParent::Expression(upcast_id, 1);
 
        self.must_be_assignable = None;
 
        self.visit_expr(ctx, right_expr_id)?;
 
        self.expr_parent = old_expr_parent;
 
        Ok(())
 
    }
 

	
 
    fn visit_binding_expr(&mut self, ctx: &mut Ctx, id: BindingExpressionId) -> VisitorResult {
 
        let upcast_id = id.upcast();
 
        let binding_expr = &mut ctx.heap[id];
 

	
 
        // Check for valid context of binding expression
 
        if let Some(span) = self.must_be_assignable {
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module.source, span, "cannot assign to the result from a binding expression"
 
            ));
 
        }
 

	
 
        if self.in_test_expr.is_invalid() {
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module.source, binding_expr.span,
 
                "binding expressions can only be used inside the testing expression of 'if' and 'while' statements"
 
            ));
 
        }
 

	
 
        if !self.in_binding_expr.is_invalid() {
 
            let binding_expr = &ctx.heap[id];
 
            let previous_expr = &ctx.heap[self.in_binding_expr];
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module.source, binding_expr.span,
 
                "nested binding expressions are not allowed"
 
            ).with_info_str_at_span(
 
                &ctx.module.source, previous_expr.span,
 
                "the outer binding expression is found here"
 
            ));
 
        }
 

	
 
        let old_expr_parent = self.expr_parent;
 
        binding_expr.parent = old_expr_parent;
 
        binding_expr.unique_id_in_definition = self.next_expr_index;
 
        self.next_expr_index += 1;
 
        self.in_binding_expr = id;
 

	
 
        // Perform preliminary check on children: binding expressions only make
 
        // sense if the left hand side is just a variable expression, or if it
 
        // is a literal of some sort. The typechecker will take care of the rest
 
        let bound_to_id = binding_expr.bound_to;
 
        let bound_from_id = binding_expr.bound_from;
 

	
 
        match &ctx.heap[bound_to_id] {
 
            // Variables may not be binding variables, and literals may
 
            // actually not contain binding variables. But in that case we just
 
            // perform an equality check.
 
            Expression::Variable(_) => {}
 
            Expression::Literal(_) => {},
 
            _ => {
 
                let binding_expr = &ctx.heap[id];
 
                return Err(ParseError::new_error_str_at_span(
 
                    &ctx.module.source, binding_expr.span,
 
                    "the left hand side of a binding expression may only be a variable or a literal expression"
 
                ));
 
            },
 
        }
 

	
 
        // Visit the children themselves
 
        self.in_binding_expr_lhs = true;
 
        self.expr_parent = ExpressionParent::Expression(upcast_id, 0);
 
        self.visit_expr(ctx, bound_to_id);
 
        self.in_binding_expr_lhs = false;
 
        self.expr_parent = ExpressionParent::Expression(upcast_id, 1);
 
        self.visit_expr(ctx, bound_from_id);
 

	
 
        self.expr_parent = old_expr_parent;
 
        self.in_binding_expr = BindingExpressionId::new_invalid();
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_conditional_expr(&mut self, ctx: &mut Ctx, id: ConditionalExpressionId) -> VisitorResult {
 
        let upcast_id = id.upcast();
 
        let conditional_expr = &mut ctx.heap[id];
 

	
 
        if let Some(span) = self.must_be_assignable {
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module.source, span, "cannot assign to the result from a conditional expression"
 
            ))
 
        }
 

	
 
        let test_expr_id = conditional_expr.test;
 
        let true_expr_id = conditional_expr.true_expression;
 
@@ -762,69 +856,69 @@ impl Visitor2 for PassValidationLinking {
 

	
 
        // Check whether the method is allowed to be called within the code's
 
        // context (in sync, definition type, etc.)
 
        let mut expected_wrapping_new_stmt = false;
 
        match &mut call_expr.method {
 
            Method::Get => {
 
                if !self.def_type.is_primitive() {
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, call_expr.span,
 
                        "a call to 'get' may only occur in primitive component definitions"
 
                    ));
 
                }
 
                if self.in_sync.is_none() {
 
                if !self.in_sync.is_invalid() {
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, call_expr.span,
 
                        "a call to 'get' may only occur inside synchronous blocks"
 
                    ));
 
                }
 
            },
 
            Method::Put => {
 
                if !self.def_type.is_primitive() {
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, call_expr.span,
 
                        "a call to 'put' may only occur in primitive component definitions"
 
                    ));
 
                }
 
                if self.in_sync.is_none() {
 
                if !self.in_sync.is_invalid() {
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, call_expr.span,
 
                        "a call to 'put' may only occur inside synchronous blocks"
 
                    ));
 
                }
 
            },
 
            Method::Fires => {
 
                if !self.def_type.is_primitive() {
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, call_expr.span,
 
                        "a call to 'fires' may only occur in primitive component definitions"
 
                    ));
 
                }
 
                if self.in_sync.is_none() {
 
                if !self.in_sync.is_invalid() {
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, call_expr.span,
 
                        "a call to 'fires' may only occur inside synchronous blocks"
 
                    ));
 
                }
 
            },
 
            Method::Create => {},
 
            Method::Length => {},
 
            Method::Assert => {
 
                if self.def_type.is_function() {
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, call_expr.span,
 
                        "assert statement may only occur in components"
 
                    ));
 
                }
 
                if self.in_sync.is_none() {
 
                if !self.in_sync.is_invalid() {
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, call_expr.span,
 
                        "assert statements may only occur inside synchronous blocks"
 
                    ));
 
                }
 
            },
 
            Method::UserFunction => {},
 
            Method::UserComponent => {
 
                expected_wrapping_new_stmt = true;
 
            },
 
        }
 

	
 
@@ -877,57 +971,161 @@ impl Visitor2 for PassValidationLinking {
 
            self.expr_parent = ExpressionParent::Expression(upcast_id, arg_expr_idx as u32);
 
            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 = self.find_variable(ctx, self.relative_pos_in_block, &var_expr.identifier)?;
 
        let variable_id = match self.find_variable(ctx, self.relative_pos_in_block, &var_expr.identifier) {
 
            Ok(variable_id) => {
 
                // Regular variable
 
                variable_id
 
            },
 
            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"
 
                    ));
 
                }
 

	
 
                // 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 and arrays can have
 
                                // subexpressions, so we're always fine
 
                                if cfg!(debug_assertions) {
 
                                    match lit_expr.value {
 
                                        Literal::Struct(_) | Literal::Union(_) | Literal::Array(_) => {},
 
                                        _ => unreachable!(),
 
                                    }
 
                                }
 

	
 
                                true
 
                            },
 
                            _ => false,
 
                        }
 
                    },
 
                    _ => {
 
                        false
 
                    }
 
                };
 

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

	
 
                // 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{ full_span: bound_identifier.span, variant: ParserTypeVariant::Inferred }
 
                    ]},
 
                    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, 0, bound_variable_id)?;
 

	
 
                bound_variable_id
 
            }
 
        };
 

	
 
        let var_expr = &mut ctx.heap[id];
 
        var_expr.declaration = Some(variable_id);
 
        var_expr.parent = self.expr_parent;
 
        var_expr.unique_id_in_definition = self.next_expr_index;
 
        self.next_expr_index += 1;
 

	
 
        Ok(())
 
    }
 
}
 

	
 
impl PassValidationLinking {
 
    //--------------------------------------------------------------------------
 
    // Special traversal
 
    //--------------------------------------------------------------------------
 

	
 
    fn visit_block_stmt_with_hint(&mut self, ctx: &mut Ctx, id: BlockStatementId, hint: Option<SynchronousStatementId>) -> VisitorResult {
 
        // Set parent scope and relative position in the parent scope. Remember
 
        // these values to set them back to the old values when we're done with
 
        // the traversal of the block's statements.
 
        let scope_next_unique_id = get_scope_next_unique_id(ctx, &self.cur_scope);
 

	
 
        let body = &mut ctx.heap[id];
 
        body.parent_scope = self.cur_scope.clone();
 
        body.relative_pos_in_parent = self.relative_pos_in_block;
 
        body.first_unique_id_in_scope = scope_next_unique_id;
 
        body.next_unique_id_in_scope = scope_next_unique_id;
 

	
 
        let old_scope = self.cur_scope.clone();
 
        self.cur_scope = match hint {
 
        let new_scope = match hint {
 
            Some(sync_id) => Scope::Synchronous((sync_id, id)),
 
            None => Scope::Regular(id),
 
        };
 

	
 
        match old_scope {
 
            Scope::Definition(_def_id) => {
 
                // Don't do anything. Block is implicitly a child of a
 
                // definition scope.
 
                if cfg!(debug_assertions) {
 
                    match &ctx.heap[_def_id] {
 
                        Definition::Function(proc_def) => debug_assert_eq!(proc_def.body, id),
 
                        Definition::Component(proc_def) => debug_assert_eq!(proc_def.body, id),
 
                        _ => unreachable!(),
 
                    }
 
                }
 
            },
 
            Scope::Regular(block_id) | Scope::Synchronous((_, block_id)) => {
 
                let parent_block = &mut ctx.heap[block_id];
 
                parent_block.scope_node.nested.push(new_scope);
 
            }
 
        }
 

	
 
        self.cur_scope = new_scope;
 

	
 
        let body = &mut ctx.heap[id];
 
        body.scope_node.parent = old_scope;
 
        body.relative_pos_in_parent = self.relative_pos_in_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.visit_statement_for_locals_labels_and_in_sync(ctx, self.relative_pos_in_block, statement_section[stmt_idx])?;
 
        }
 
@@ -943,190 +1141,291 @@ impl PassValidationLinking {
 
        statement_section.forget();
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_statement_for_locals_labels_and_in_sync(&mut self, ctx: &mut Ctx, relative_pos: u32, 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_local_add(ctx, relative_pos, variable_id)?;
 
                        self.checked_add_local(ctx, relative_pos, variable_id)?;
 
                    },
 
                    LocalStatement::Channel(local) => {
 
                        let from_id = local.from;
 
                        let to_id = local.to;
 
                        self.checked_local_add(ctx, relative_pos, from_id)?;
 
                        self.checked_local_add(ctx, relative_pos, to_id)?;
 
                        self.checked_add_local(ctx, relative_pos, from_id)?;
 
                        self.checked_add_local(ctx, relative_pos, to_id)?;
 
                    }
 
                }
 
            }
 
            Statement::Labeled(stmt) => {
 
                let stmt_id = stmt.this;
 
                let body_id = stmt.body;
 
                self.checked_label_add(ctx, relative_pos, self.in_sync, stmt_id)?;
 
                self.checked_add_label(ctx, relative_pos, self.in_sync, stmt_id)?;
 
                self.visit_statement_for_locals_labels_and_in_sync(ctx, relative_pos, body_id)?;
 
            },
 
            Statement::While(stmt) => {
 
                stmt.in_sync = self.in_sync;
 
            },
 
            _ => {},
 
        }
 

	
 
        return Ok(())
 
    }
 

	
 
    fn visit_definition_and_assign_local_ids(&mut self, ctx: &mut Ctx, definition_id: DefinitionId) {
 
        let mut var_counter = 0;
 

	
 
        // Set IDs on parameters
 
        let (param_section, body_id) = match &ctx.heap[definition_id] {
 
            Definition::Function(func_def) => (
 
                self.variable_buffer.start_section_initialized(&func_def.parameters),
 
                func_def.body
 
            ),
 
            Definition::Component(comp_def) => (
 
                self.variable_buffer.start_section_initialized(&comp_def.parameters),
 
                comp_def.body
 
            ),
 
            _ => unreachable!(),
 
        } ;
 

	
 
        for idx in 0..param_section.len() {
 
            let var_id = param_section[idx];
 
            let var = &mut ctx.heap[var_id];
 
            var.unique_id_in_scope = var_counter;
 
            var_counter += 1;
 
        }
 

	
 
        param_section.forget();
 

	
 
        // Recurse into body
 
        self.visit_block_and_assign_local_ids(ctx, body_id, var_counter);
 
    }
 

	
 
    fn visit_block_and_assign_local_ids(&mut self, ctx: &mut Ctx, block_id: BlockStatementId, mut var_counter: i32) {
 
        let block_stmt = &mut ctx.heap[block_id];
 
        block_stmt.first_unique_id_in_scope = var_counter;
 

	
 
        let var_section = self.variable_buffer.start_section_initialized(&block_stmt.locals);
 
        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
 
            };
 

	
 
            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
 
            };
 

	
 
            debug_assert!(!(relative_var_pos == u32::MAX && relative_scope_pos == u32::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;
 
                self.visit_block_and_assign_local_ids(ctx, block_id, var_counter);
 
                scope_idx += 1;
 
            }
 
        }
 

	
 
        var_section.forget();
 
        scope_section.forget();
 

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

	
 
        // Make sure we do not conflict with any global symbols
 
        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"
 
                ));
 
            }
 
        }
 

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

	
 
        // Make sure we do not shadow any variables in any of the scopes. Note
 
        // that variables in parent scopes may be declared later
 
        let local = &ctx.heap[id];
 
        let mut scope = &self.cur_scope;
 
        let mut local_relative_pos = self.relative_pos_in_block;
 

	
 
        loop {
 
            debug_assert!(scope.is_block(), "scope is not a block");
 
            // 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 {
 
                        return Err(
 
                            ParseError::new_error_str_at_span(
 
                                &ctx.module.source, local.identifier.span, "Local variable name conflicts with parameter"
 
                            ).with_info_str_at_span(
 
                                &ctx.module.source, parameter.identifier.span, "Parameter definition is found here"
 
                            )
 
                        );
 
                    }
 
                }
 

	
 
                // No collisions
 
                break;
 
            }
 

	
 
            // If here then the parent scope is a block scope
 
            let local_relative_pos = ctx.heap[scope.to_block()].relative_pos_in_parent;
 

	
 
            for other_local_id in &block.locals {
 
                let other_local = &ctx.heap[*other_local_id];
 
                // Position check in case another variable with the same name
 
                // is defined in a higher-level scope, but later than the scope
 
                // in which the current variable resides.
 
                if local.this != *other_local_id &&
 
                    local_relative_pos >= other_local.relative_pos_in_block &&
 
                    local.identifier == other_local.identifier {
 
                    // Collision within this scope
 
                    return Err(
 
                        ParseError::new_error_str_at_span(
 
                            &ctx.module.source, local.identifier.span, "Local variable name conflicts with another variable"
 
                        ).with_info_str_at_span(
 
                            &ctx.module.source, other_local.identifier.span, "Previous variable is found here"
 
                        )
 
                    );
 
                }
 
            }
 
        }
 

	
 
            // Current scope is fine, move to parent scope if any
 
            scope = &block.parent_scope;
 
            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 {
 
                        return Err(
 
                            ParseError::new_error_str_at_span(
 
                                &ctx.module.source, local.identifier.span, "Local variable name conflicts with parameter"
 
                            ).with_info_str_at_span(
 
                                &ctx.module.source, parameter.identifier.span, "Parameter definition is found here"
 
                            )
 
                        );
 
                    }
 
                }
 
        // 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)
 
    }
 

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

	
 
            // If here, then we are dealing with a block-like parent block
 
            local_relative_pos = ctx.heap[scope.to_block()].relative_pos_in_parent;
 
        // Check the specified scope for conflicts
 
        let local = &ctx.heap[id];
 

	
 
        debug_assert!(scope.is_block());
 
        let block = &ctx.heap[scope.to_block()];
 
        for other_local_id in &block.locals {
 
            let other_local = &ctx.heap[*other_local_id];
 
            if local.this != other_local.this &&
 
                relative_pos >= other_local.relative_pos_in_block &&
 
                local.identifier == other_local.identifier {
 
                // Collision
 
                return Err(
 
                    ParseError::new_error_str_at_span(
 
                        &ctx.module.source, local.identifier.span, "Local variable name conflicts with another variable"
 
                    ).with_info_str_at_span(
 
                        &ctx.module.source, other_local.identifier.span, "Previous variable is found here"
 
                    )
 
                );
 
            }
 
        }
 

	
 
        // No collisions at all
 
        let block = &mut ctx.heap[self.cur_scope.to_block()];
 
        // No collisions
 
        let block = &mut ctx.heap[scope.to_block()];
 
        block.locals.push(id);
 
        let unique_id_in_scope = block.next_unique_id_in_scope;
 
        block.next_unique_id_in_scope += 1;
 

	
 
        let variable = &mut ctx.heap[id];
 
        variable.unique_id_in_scope = unique_id_in_scope;
 
        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, ParseError> {
 
    fn find_variable(&self, ctx: &Ctx, mut relative_pos: u32, identifier: &Identifier) -> Result<VariableId, ()> {
 
        debug_assert!(self.cur_scope.is_block());
 

	
 
        // TODO: May still refer to an alias of a global symbol using a single
 
        //  identifier in the namespace.
 
        // 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()];
 
            
 
            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);
 
                }
 
            }
 

	
 
            scope = &block.parent_scope;
 
            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);
 
                            }
 
                        }
 
                    },
 
                    _ => unreachable!(),
 
                }
 

	
 
                // Variable could not be found
 
                return Err(ParseError::new_error_str_at_span(
 
                    &ctx.module.source, identifier.span, "unresolved variable"
 
                ));
 
                return Err(())
 
            } 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_label_add(&mut self, ctx: &mut Ctx, relative_pos: u32, in_sync: Option<SynchronousStatementId>, id: LabeledStatementId) -> Result<(), ParseError> {
 
    fn checked_add_label(&mut self, ctx: &mut Ctx, relative_pos: u32, 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 {
 
@@ -1135,25 +1434,25 @@ impl PassValidationLinking {
 
            for other_label_id in &block.labels {
 
                let other_label = &ctx.heap[*other_label_id];
 
                if other_label.label == label.label {
 
                    // Collision
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, label.label.span, "label name is used more than once"
 
                    ).with_info_str_at_span(
 
                        &ctx.module.source, other_label.label.span, "the other label is found here"
 
                    ));
 
                }
 
            }
 

	
 
            scope = &block.parent_scope;
 
            scope = &block.scope_node.parent;
 
            if !scope.is_block() {
 
                break;
 
            }
 
        }
 

	
 
        // No collisions
 
        let block = &mut ctx.heap[self.cur_scope.to_block()];
 
        block.labels.push(id);
 

	
 
        Ok(())
 
    }
 

	
 
@@ -1181,48 +1480,48 @@ impl PassValidationLinking {
 
                        if local.relative_pos_in_block > relative_scope_pos && local.relative_pos_in_block < label.relative_pos_in_block {
 
                            return Err(
 
                                ParseError::new_error_str_at_span(&ctx.module.source, identifier.span, "this target label skips over a variable declaration")
 
                                .with_info_str_at_span(&ctx.module.source, label.label.span, "because it jumps to this label")
 
                                .with_info_str_at_span(&ctx.module.source, local.identifier.span, "which skips over this variable")
 
                            );
 
                        }
 
                    }
 
                    return Ok(*label_id);
 
                }
 
            }
 

	
 
            scope = &block.parent_scope;
 
            scope = &block.scope_node.parent;
 
            if !scope.is_block() {
 
                return Err(ParseError::new_error_str_at_span(
 
                    &ctx.module.source, identifier.span, "could not find this label"
 
                ));
 
            }
 

	
 
        }
 
    }
 

	
 
    /// This function will check if the provided while statement ID has a block
 
    /// statement that is one of our current parents.
 
    fn has_parent_while_scope(&self, ctx: &Ctx, id: WhileStatementId) -> bool {
 
        let mut scope = &self.cur_scope;
 
        let while_stmt = &ctx.heap[id];
 
        loop {
 
            debug_assert!(scope.is_block());
 
            let block = scope.to_block();
 
            if while_stmt.body == block {
 
                return true;
 
            }
 

	
 
            let block = &ctx.heap[block];
 
            scope = &block.parent_scope;
 
            scope = &block.scope_node.parent;
 
            if !scope.is_block() {
 
                return false;
 
            }
 
        }
 
    }
 

	
 
    /// This function should be called while dealing with break/continue
 
    /// statements. It will try to find the targeted while statement, using the
 
    /// target label if provided. If a valid target is found then the loop's
 
    /// ID will be returned, otherwise a parsing error is constructed.
 
    /// The provided input position should be the position of the break/continue
 
    /// statement.
 
@@ -1247,59 +1546,42 @@ impl PassValidationLinking {
 
                    target_stmt.this
 
                } else {
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, label.span, "incorrect break target label, it must target a while loop"
 
                    ).with_info_str_at_span(
 
                        &ctx.module.source, target.label.span, "The targeted label is found here"
 
                    ));
 
                }
 
            },
 
            None => {
 
                // Use the enclosing while statement, the break must be
 
                // nested within that while statement
 
                if self.in_while.is_none() {
 
                if self.in_while.is_invalid() {
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, span, "Break statement is not nested under a while loop"
 
                    ));
 
                }
 

	
 
                self.in_while.unwrap()
 
                self.in_while
 
            }
 
        };
 

	
 
        // We have a valid target for the break statement. But we need to
 
        // make sure we will not break out of a synchronous block
 
        {
 
            let target_while = &ctx.heap[target];
 
            if target_while.in_sync != self.in_sync {
 
                // Break is nested under while statement, so can only escape a
 
                // sync block if the sync is nested inside the while statement.
 
                debug_assert!(self.in_sync.is_some());
 
                let sync_stmt = &ctx.heap[self.in_sync.unwrap()];
 
                debug_assert!(!self.in_sync.is_invalid());
 
                let sync_stmt = &ctx.heap[self.in_sync];
 
                return Err(
 
                    ParseError::new_error_str_at_span(&ctx.module.source, span, "break may not escape the surrounding synchronous block")
 
                        .with_info_str_at_span(&ctx.module.source, target_while.span, "the break escapes out of this loop")
 
                        .with_info_str_at_span(&ctx.module.source, sync_stmt.span, "And would therefore escape this synchronous block")
 
                );
 
            }
 
        }
 

	
 
        Ok(target)
 
    }
 
}
 

	
 
fn get_scope_next_unique_id(ctx: &Ctx, scope: &Scope) -> i32 {
 
    match scope {
 
        Scope::Definition(definition_id) => {
 
            let definition = &ctx.heap[*definition_id];
 
            match definition {
 
                Definition::Component(definition) => definition.parameters.len() as i32,
 
                Definition::Function(definition) => definition.parameters.len() as i32,
 
                _ => unreachable!("Scope::Definition points to non-procedure type")
 
            }
 
        },
 
        Scope::Synchronous((_, block_id)) | Scope::Regular(block_id) => {
 
            let block = &ctx.heap[*block_id];
 
            block.next_unique_id_in_scope
 
        }
 
    }
 
}
 
\ No newline at end of file
src/protocol/tests/eval_casting.rs
Show inline comments
 
@@ -54,28 +54,34 @@ fn test_invalid_casting() {
 

	
 
    fn perform_test(input_type: &str, input_value: &str, output_type: &str) {
 
        Tester::new_single_source_expect_ok(
 
            format!("invalid cast {} to {}", input_type, output_type),
 
            generate_source(input_type, input_value, output_type)
 
        ).for_function("foo", |f| {
 
            f.call_err(&format!("'{}' which doesn't fit in a type '{}'", input_value, output_type));
 
        });
 
    }
 

	
 
    // Not exhaustive, good enough
 
    let tests = [
 
        // Unsigned large to small width
 
        ("u16", "256", "u8"),
 
        ("u32", "256", "u8"),
 
        ("u64", "256", "u8"),
 
        ("u32", "65536", "u16"),
 
        ("u64", "65536", "u16"),
 
        // Signed to unsigned
 
        ("s8", "-1", "u8"),
 
        ("s32", "-1", "u16"),
 
        ("s32", "65536", "u16"),
 
        // Signed to signed of smaller width
 
        ("s16", "-129", "s8"),
 
        ("s16", "128", "s8")
 
        ("s16", "128", "s8"),
 
        // Unsigned to signed
 
        ("u8", "128", "s8"),
 
        ("u16", "32768", "s16")
 
    ];
 

	
 
    for (input_type, input_value, output_type) in &tests {
 
        perform_test(input_type, input_value, output_type);
 
    }
 
}
 
\ No newline at end of file
src/protocol/tests/mod.rs
Show inline comments
 
@@ -5,19 +5,20 @@
 
 * code. These are intended to be temporary tests such that we're sure that we
 
 * don't break existing functionality.
 
 *
 
 * In the future these should be replaced by proper testing protocols.
 
 */
 

	
 
mod utils;
 
mod lexer;
 
mod parser_validation;
 
mod parser_inference;
 
mod parser_monomorphs;
 
mod parser_imports;
 
mod parser_binding;
 
mod eval_operators;
 
mod eval_calls;
 
mod eval_casting;
 
mod eval_silly;
 

	
 
pub(crate) use utils::{Tester}; // the testing harness
 
pub(crate) use crate::protocol::eval::value::*; // to test functions
 
\ No newline at end of file
src/protocol/tests/parser_binding.rs
Show inline comments
 
new file 100644
 
use super::*;
 

	
 
#[test]
 
fn test_correct_binding() {
 
    Tester::new_single_source_expect_ok("binding bare", )
 
}
 
\ No newline at end of file
src/protocol/tests/utils.rs
Show inline comments
 
@@ -583,25 +583,25 @@ impl<'a> FunctionTester<'a> {
 
        use crate::runtime::*;
 

	
 
        let (prompt, result) = self.eval_until_end();
 
        match result {
 
            Ok(_) => {
 
                assert!(
 
                    prompt.store.stack.len() > 0, // note: stack never shrinks
 
                    "[{}] No value on stack after calling function for {}",
 
                    self.ctx.test_name, self.assert_postfix()
 
                );
 
            },
 
            Err(err) => {
 
                println!("DEBUG: Formatted error:\n{}", err);
 
                println!("DEBUG: Formatted evaluation error:\n{}", err);
 
                assert!(
 
                    false,
 
                    "[{}] Expected call to succeed, but got {:?} for {}",
 
                    self.ctx.test_name, err, self.assert_postfix()
 
                )
 
            }
 
        }
 

	
 
        if let Some(expected_result) = expected_result {
 
            debug_assert!(expected_result.get_heap_pos().is_none(), "comparing against heap thingamajigs is not yet implemented");
 
            assert!(
 
                value::apply_equality_operator(&prompt.store, &prompt.store.stack[0], &expected_result),
 
@@ -619,25 +619,25 @@ impl<'a> FunctionTester<'a> {
 
        use crate::runtime::*;
 

	
 
        let (_, result) = self.eval_until_end();
 
        match result {
 
            Ok(_) => {
 
                assert!(
 
                    false,
 
                    "[{}] Expected an error, but evaluation finished successfully for {}",
 
                    self.ctx.test_name, self.assert_postfix()
 
                );
 
            },
 
            Err(err) => {
 
                println!("DEBUG: Got evaluation error:\n{}", err);
 
                println!("DEBUG: Formatted evaluation error:\n{}", err);
 
                debug_assert_eq!(err.statements.len(), 1);
 
                assert!(
 
                    err.statements[0].message.contains(&expected_result),
 
                    "[{}] Expected error message to contain '{}', but it was '{}' for {}",
 
                    self.ctx.test_name, expected_result, err.statements[0].message, self.assert_postfix()
 
                );
 
            }
 
        }
 

	
 
        self
 
    }
 

	
 
@@ -1142,26 +1142,26 @@ fn seek_stmt<F: Fn(&Statement) -> bool>(heap: &Heap, start: StatementId, f: &F)
 
fn seek_expr_in_expr<F: Fn(&Expression) -> bool>(heap: &Heap, start: ExpressionId, f: &F) -> Option<ExpressionId> {
 
    let expr = &heap[start];
 
    if f(expr) { return Some(start); }
 

	
 
    match expr {
 
        Expression::Assignment(expr) => {
 
            None
 
            .or_else(|| seek_expr_in_expr(heap, expr.left, f))
 
            .or_else(|| seek_expr_in_expr(heap, expr.right, f))
 
        },
 
        Expression::Binding(expr) => {
 
            None
 
            .or_else(|| seek_expr_in_expr(heap, expr.left.upcast(), f))
 
            .or_else(|| seek_expr_in_expr(heap, expr.right, f))
 
            .or_else(|| seek_expr_in_expr(heap, expr.bound_to, f))
 
            .or_else(|| seek_expr_in_expr(heap, expr.bound_from, f))
 
        }
 
        Expression::Conditional(expr) => {
 
            None
 
            .or_else(|| seek_expr_in_expr(heap, expr.test, f))
 
            .or_else(|| seek_expr_in_expr(heap, expr.true_expression, f))
 
            .or_else(|| seek_expr_in_expr(heap, expr.false_expression, f))
 
        },
 
        Expression::Binary(expr) => {
 
            None
 
            .or_else(|| seek_expr_in_expr(heap, expr.left, f))
 
            .or_else(|| seek_expr_in_expr(heap, expr.right, f))
 
        },
0 comments (0 inline, 0 general)