Changeset - 8ae142310cc7
[Not reviewed]
2 4 2
MH - 4 years ago 2021-12-21 11:58:35
contact@maxhenger.nl
Add some tests before refactoring variable resolving
7 files changed with 216 insertions and 111 deletions:
0 comments (0 inline, 0 general)
src/protocol/parser/pass_validation_linking.rs
Show inline comments
 
/*
 
 * pass_validation_linking.rs
 
 *
 
 * The pass that will validate properties of the AST statements (one is not
 
 * allowed to nest synchronous statements, instantiating components occurs in
 
 * the right places, etc.) and expressions (assignments may not occur in
 
 * arbitrary expressions).
 
 *
 
 * Furthermore, this pass will also perform "linking", in the sense of: some AST
 
 * nodes have something to do with one another, so we link them up in this pass
 
 * (e.g. setting the parents of expressions, linking the control flow statements
 
 * like `continue` and `break` up to the respective loop statement, etc.).
 
 *
 
 * There are several "confusing" parts about this pass:
 
 *
 
 * Setting expression parents: this is the simplest one. The pass struct acts
 
 * like a little state machine. When visiting an expression it will set the
 
 * "parent expression" field of the pass to itself, then visit its child. The
 
 * child will look at this "parent expression" field to determine its parent.
 
 *
 
 * Setting the `next` statement: the AST is a tree, but during execution we walk
 
 * a linear path through all statements. So where appropriate a statement may
 
 * set the "previous statement" field of the pass to itself. When visiting the
 
 * subsequent statement it will check this "previous statement", and if set, it
 
 * will link this previous statement up to itself. Not every statement has a
 
 * previous statement. Hence there are two patterns that occur: assigning the
 
 * `next` value, then clearing the "previous statement" field. And assigning the
 
 * `next` value, and then putting the current statement's ID in the "previous
 
 * statement" field. Because it is so common, this file contain two macros that
 
 * perform that operation.
 
 *
 
 * To make storing types for polymorphic procedures simpler and more efficient,
 
 * we assign to each expression in the procedure a unique ID. This is what the
 
 * "next expression index" field achieves. Each expression simply takes the
 
 * current value, and then increments this counter.
 
 */
 

	
 
use crate::collections::{ScopedBuffer};
 
use crate::protocol::ast::*;
 
use crate::protocol::input_source::*;
 
use crate::protocol::parser::symbol_table::*;
 
use crate::protocol::parser::type_table::*;
 

	
 
use super::visitor::{
 
    BUFFER_INIT_CAPACITY,
 
    Ctx,
 
    Visitor,
 
    VisitorResult
 
};
 
use crate::protocol::parser::ModuleCompilationPhase;
 

	
 
#[derive(PartialEq, Eq)]
 
enum DefinitionType {
 
    Primitive(ComponentDefinitionId),
 
    Composite(ComponentDefinitionId),
 
    Function(FunctionDefinitionId)
 
}
 

	
 
impl DefinitionType {
 
    fn is_primitive(&self) -> bool { if let Self::Primitive(_) = self { true } else { false } }
 
    fn is_composite(&self) -> bool { if let Self::Composite(_) = self { true } else { false } }
 
    fn is_function(&self) -> bool { if let Self::Function(_) = self { true } else { false } }
 
    fn definition_id(&self) -> DefinitionId {
 
        match self {
 
            DefinitionType::Primitive(v) => v.upcast(),
 
            DefinitionType::Composite(v) => v.upcast(),
 
            DefinitionType::Function(v) => v.upcast(),
 
        }
 
    }
 
}
 

	
 
/// 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.
 
pub(crate) struct PassValidationLinking {
 
    // 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_select_guard: SelectStatementId, // for detection/rejection of builtin calls
 
    in_select_arm: u32,
 
    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,
 
    // "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: 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 {
 
        Self{
 
            in_sync: SynchronousStatementId::new_invalid(),
 
            in_while: WhileStatementId::new_invalid(),
 
            in_select_guard: SelectStatementId::new_invalid(),
 
            in_select_arm: 0,
 
            in_test_expr: StatementId::new_invalid(),
 
            in_binding_expr: BindingExpressionId::new_invalid(),
 
            in_binding_expr_lhs: false,
 
            cur_scope: Scope::Definition(DefinitionId::new_invalid()),
 
            prev_stmt: StatementId::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::with_capacity(128),
 
            definition_buffer: ScopedBuffer::with_capacity(128),
 
            statement_buffer: ScopedBuffer::with_capacity(BUFFER_INIT_CAPACITY),
 
            expression_buffer: ScopedBuffer::with_capacity(BUFFER_INIT_CAPACITY),
 
        }
 
    }
 

	
 
    fn reset_state(&mut self) {
 
        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.def_type = DefinitionType::Function(FunctionDefinitionId::new_invalid());
 
        self.prev_stmt = StatementId::new_invalid();
 
        self.expr_parent = ExpressionParent::None;
 
        self.must_be_assignable = None;
 
        self.relative_pos_in_block = 0;
 
        self.next_expr_index = 0
 
    }
 
}
 

	
 
macro_rules! assign_then_erase_next_stmt {
 
    ($self:ident, $ctx:ident, $stmt_id:expr) => {
 
        if !$self.prev_stmt.is_invalid() {
 
            $ctx.heap[$self.prev_stmt].link_next($stmt_id);
 
            $self.prev_stmt = StatementId::new_invalid();
 
        }
 
    }
 
}
 

	
 
macro_rules! assign_and_replace_next_stmt {
 
    ($self:ident, $ctx:ident, $stmt_id:expr) => {
 
        if !$self.prev_stmt.is_invalid() {
 
            $ctx.heap[$self.prev_stmt].link_next($stmt_id);
 
        }
 
        $self.prev_stmt = $stmt_id;
 
    }
 
}
 

	
 
impl Visitor 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_id in section.iter_copied() {
 
            self.visit_definition(ctx, definition_id)?;
 
        }
 
        section.forget();
 

	
 
        ctx.module_mut().phase = ModuleCompilationPhase::ValidatedAndLinked;
 
        Ok(())
 
    }
 
    //--------------------------------------------------------------------------
 
    // Definition visitors
 
    //--------------------------------------------------------------------------
 

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

	
 
        self.def_type = match &ctx.heap[id].variant {
 
            ComponentVariant::Primitive => DefinitionType::Primitive(id),
 
            ComponentVariant::Composite => DefinitionType::Composite(id),
 
        };
 
        self.cur_scope = Scope::Definition(id.upcast());
 
        self.expr_parent = ExpressionParent::None;
 

	
 
        // Visit parameters and assign a unique scope ID
 
        let definition = &ctx.heap[id];
 
        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
 
        let definition = &ctx.heap[id];
 
        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 {
 
        // Note that we're not adding the variable to its scope for lookups.
 
        // This is done in the `visit_statement_for_locals_labels_and_in_sync`
 
        // method.
 
        let stmt = &ctx.heap[id];
 
        let expr_id = stmt.initial_expr;
 

	
 
        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)?;
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_if_stmt(&mut self, ctx: &mut Ctx, id: IfStatementId) -> VisitorResult {
 
        let if_stmt = &ctx.heap[id];
 
        let end_if_id = if_stmt.end_if;
 
        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_expr_id)?;
 
        self.in_test_expr = StatementId::new_invalid();
 

	
 
        self.expr_parent = ExpressionParent::None;
 

	
 
        // Visit true and false branch. Executor chooses next statement based on
 
        // test expression, not on if-statement itself. Hence the if statement
 
        // does not have a static subsequent statement.
 
        assign_then_erase_next_stmt!(self, ctx, id.upcast());
 
        self.visit_block_stmt(ctx, true_stmt_id)?;
 
        assign_then_erase_next_stmt!(self, ctx, end_if_id.upcast());
 

	
 
        if let Some(false_id) = false_stmt_id {
 
            self.visit_block_stmt(ctx, false_id)?;
 
            assign_then_erase_next_stmt!(self, ctx, end_if_id.upcast());
 
        }
 

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

	
 
    fn visit_while_stmt(&mut self, ctx: &mut Ctx, id: WhileStatementId) -> VisitorResult {
 
        let stmt = &ctx.heap[id];
 
        let end_while_id = stmt.end_while;
 
        let test_expr_id = stmt.test;
 
        let body_stmt_id = stmt.body;
 

	
 
        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_expr_id)?;
 
        self.in_test_expr = StatementId::new_invalid();
 

	
 
        // Link up to body statement
 
        assign_then_erase_next_stmt!(self, ctx, id.upcast());
 

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

	
 
        // Link final entry in while's block statement back to the while. The
 
        // executor will go to the end-while statement if the test expression
 
        // is false, so put that in as the new previous stmt
 
        assign_then_erase_next_stmt!(self, ctx, id.upcast());
 
        self.prev_stmt = end_while_id.upcast();
 

	
 
        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];
 
            debug_assert!(!target_while.end_while.is_invalid());
 

	
 
            target_while.end_while
 
        };
 

	
 
        assign_then_erase_next_stmt!(self, ctx, id.upcast());
 
        let stmt = &mut ctx.heap[id];
 
        stmt.target = Some(target_end_while);
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_continue_stmt(&mut self, ctx: &mut Ctx, id: ContinueStatementId) -> VisitorResult {
 
        // Resolve continue target
 
        let target_while_id = {
 
            let stmt = &ctx.heap[id];
 
            self.resolve_break_or_continue_target(ctx, stmt.span, &stmt.label)?
 
        };
 

	
 
        assign_then_erase_next_stmt!(self, ctx, id.upcast());
 
        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 sync_stmt = &ctx.heap[id];
 
        let end_sync_id = sync_stmt.end_sync;
 
        let cur_sync_span = sync_stmt.span;
 
        if !self.in_sync.is_invalid() {
 
            // Nested synchronous statement
 
            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"
 
            ));
 
        }
 

	
 
        // Synchronous statement implicitly moves to its block
 
        assign_then_erase_next_stmt!(self, ctx, id.upcast());
 

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

	
 
        self.in_sync = SynchronousStatementId::new_invalid();
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_fork_stmt(&mut self, ctx: &mut Ctx, id: ForkStatementId) -> VisitorResult {
 
        let fork_stmt = &ctx.heap[id];
 
        let end_fork_id = fork_stmt.end_fork;
 
        let left_body_id = fork_stmt.left_body;
 
        let right_body_id = fork_stmt.right_body;
 

	
 
        // Fork statements may only occur inside sync blocks
 
        if self.in_sync.is_invalid() {
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module().source, fork_stmt.span,
 
                "Forking may only occur inside sync blocks"
 
            ));
 
        }
 

	
 
        // Visit the respective bodies. Like the if statement, a fork statement
 
        // does not have a single static subsequent statement. It forks and then
 
        // each fork has a different next statement.
 
        assign_then_erase_next_stmt!(self, ctx, id.upcast());
 
        self.visit_block_stmt(ctx, left_body_id)?;
 
        assign_then_erase_next_stmt!(self, ctx, end_fork_id.upcast());
 

	
 
        if let Some(right_body_id) = right_body_id {
 
            self.visit_block_stmt(ctx, right_body_id)?;
 
            assign_then_erase_next_stmt!(self, ctx, end_fork_id.upcast());
 
        }
 

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

	
 
    fn visit_select_stmt(&mut self, ctx: &mut Ctx, id: SelectStatementId) -> VisitorResult {
 
        let select_stmt = &ctx.heap[id];
 
        let end_select_id = select_stmt.end_select;
 

	
 
        // Select statements may only occur inside sync blocks
 
        if self.in_sync.is_invalid() {
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module().source, select_stmt.span,
 
                "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
 
        let mut case_stmt_ids = self.statement_buffer.start_section();
 
        let num_cases = select_stmt.cases.len();
 
        for case in &select_stmt.cases {
 
            // 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 should pick the "EndSelect" immediately.
 

	
 
        for idx in 0..num_cases {
 
            let base_idx = 2 * idx;
 
            let guard_id     = case_stmt_ids[base_idx    ];
 
            let arm_block_id = case_stmt_ids[base_idx + 1];
 

	
 
            // Visit the guard of this arm
 
            debug_assert!(self.in_select_guard.is_invalid());
 
            self.in_select_guard = id;
 
            self.in_select_arm = idx as u32;
 
            self.visit_stmt(ctx, guard_id)?;
 
            self.in_select_guard = SelectStatementId::new_invalid();
 

	
 
            // If the arm declares a variable, then add it to the variables of
 
            // this arm's code block
 
            match &ctx.heap[guard_id] {
 
                Statement::Local(LocalStatement::Memory(stmt)) => {
 
                    debug_assert_eq!(ctx.heap[arm_block_id].as_block().this.upcast(), arm_block_id); // backwards way of saying arm_block_id is a BlockStatementId
 
                    let block_stmt = BlockStatementId(arm_block_id);
 
                    let block_scope =  Scope::Regular(block_stmt);
 
                    self.checked_at_single_scope_add_local(ctx, block_scope, -1, stmt.variable)?;
 
                },
 
                Statement::Expression(_) => {},
 
                _ => unreachable!(), // just to be sure the parser produced the expected AST
 
            }
 

	
 
            // Visit the code associated with the guard
 
            self.visit_stmt(ctx, arm_block_id)?;
 

	
 
            assign_then_erase_next_stmt!(self, ctx, end_select_id.upcast());
 
        }
 

	
 
        self.in_select_guard = SelectStatementId::new_invalid();
 
        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];
 
        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"
 
            ));
 
        }
 

	
 
        // If here then we are within a function
 
        assign_then_erase_next_stmt!(self, ctx, id.upcast());
 
        debug_assert_eq!(self.expr_parent, ExpressionParent::None);
 
        debug_assert_eq!(ctx.heap[id].expressions.len(), 1);
 
        self.expr_parent = ExpressionParent::Return(id);
 
        self.visit_expr(ctx, ctx.heap[id].expressions[0])?;
 
        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 we must be inside a sync
 
            // statement.
 
            debug_assert!(!self.in_sync.is_invalid());
 
            let goto_stmt = &ctx.heap[id];
 
            let sync_stmt = &ctx.heap[self.in_sync];
 
            return Err(
 
                ParseError::new_error_str_at_span(&ctx.module().source, goto_stmt.span, "goto may not escape the surrounding synchronous block")
 
                .with_info_str_at_span(&ctx.module().source, target.label.span, "this is the target of the goto statement")
 
                .with_info_str_at_span(&ctx.module().source, sync_stmt.span, "which will jump past this statement")
 
            );
 
        }
 

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

	
 
        Ok(())
 
    }
 

	
 
    fn visit_new_stmt(&mut self, ctx: &mut Ctx, id: NewStatementId) -> VisitorResult {
 
        // Make sure the new statement occurs inside a composite component
 
        if !self.def_type.is_composite() {
 
            let new_stmt = &ctx.heap[id];
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module().source, new_stmt.span,
 
                "instantiating components may only be done in composite components"
 
            ));
 
        }
 

	
 
        // Recurse into call expression (which will check the expression parent
 
        // to ensure that the "new" statment instantiates a component)
 
        let call_expr_id = ctx.heap[id].expression;
 

	
 
        assign_and_replace_next_stmt!(self, ctx, id.upcast());
 
        debug_assert_eq!(self.expr_parent, ExpressionParent::None);
 
        self.expr_parent = ExpressionParent::New(id);
 
        self.visit_call_expr(ctx, call_expr_id)?;
 
        self.expr_parent = ExpressionParent::None;
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_expr_stmt(&mut self, ctx: &mut Ctx, id: ExpressionStatementId) -> VisitorResult {
 
        let expr_id = ctx.heap[id].expression;
 

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

	
 
        Ok(())
 
    }
 

	
 

	
 
    //--------------------------------------------------------------------------
 
    // 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;
 
        let old_expr_parent = self.expr_parent;
 
        assignment_expr.parent = old_expr_parent;
 
        assignment_expr.unique_id_in_definition = self.next_expr_index;
 
        self.next_expr_index += 1;
 

	
 
        self.expr_parent = ExpressionParent::Expression(upcast_id, 0);
 
        self.must_be_assignable = Some(assignment_expr.operator_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();
 

	
src/protocol/tests/mod.rs
Show inline comments
 
/**
 
 * protocol/tests.rs
 
 *
 
 * Contains tests for various parts of the lexer/parser and the evaluator of the
 
 * 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.
 
 *
 
 * If any of these tests fail, and you think they're not needed anymore, feel
 
 * free to cast them out into oblivion, where dead code goes to die.
 
 */
 

	
 
mod utils;
 
mod parser_after_tokenizing;
 
mod parser_binding;
 
mod parser_imports;
 
mod parser_inference;
 
mod parser_literals;
 
mod parser_monomorphs;
 
mod parser_types;
 
mod parser_type_declaration;
 
mod parser_type_layout;
 
mod parser_validation;
 
mod eval_binding;
 
mod eval_calls;
 
mod eval_casting;
 
mod eval_operators;
 
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_after_tokenizing.rs
Show inline comments
 
deleted file
src/protocol/tests/parser_inference.rs
Show inline comments
 
@@ -87,443 +87,493 @@ fn test_binary_expr_inference() {
 
            return i;
 
        }"
 
    ).for_function("call", |f| { f
 
        .for_expression_by_source(
 
            "b0 + b1", "+", 
 
            |e| { e.assert_concrete_type("s8"); }
 
        )
 
        .for_expression_by_source(
 
            "s0 + s1", "+", 
 
            |e| { e.assert_concrete_type("s16"); }
 
        )
 
        .for_expression_by_source(
 
            "i0 + i1", "+", 
 
            |e| { e.assert_concrete_type("s32"); }
 
        )
 
        .for_expression_by_source(
 
            "l0 + l1", "+", 
 
            |e| { e.assert_concrete_type("s64"); }
 
        );
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "incompatible types", 
 
        "func call() -> s32 {
 
            s8 b = 0;
 
            s64 l = 1;
 
            auto r = b + l;
 
            return 0;
 
        }"
 
    ).error(|e| { e
 
        .assert_ctx_has(0, "b + l")
 
        .assert_msg_has(0, "cannot apply")
 
        .assert_occurs_at(0, "+")
 
        .assert_msg_has(1, "has type 's8'")
 
        .assert_msg_has(2, "has type 's64'");
 
    });
 
}
 

	
 
#[test]
 
fn test_tuple_inference() {
 
    Tester::new_single_source_expect_ok(
 
        "from tuple to variable",
 
        "
 
        func test() -> u32 {
 
            (u8,u16,u32,u64) tuple = (1, 2, 3, 4);
 
            auto a = tuple.0;
 
            auto b = tuple.1;
 
            auto c = tuple.2;
 
            auto d = tuple.3;
 
            return cast(a) + cast(b) + c + cast(d);
 
        }
 
        "
 
    ).for_function("test", |f| { f
 
        .for_variable("a", |v| { v.assert_concrete_type("u8"); })
 
        .for_variable("b", |v| { v.assert_concrete_type("u16"); })
 
        .for_variable("c", |v| { v.assert_concrete_type("u32"); })
 
        .for_variable("d", |v| { v.assert_concrete_type("u64"); })
 
        .call_ok(Some(Value::UInt32(10)));
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "from variable to tuple",
 
        "
 
        func test() -> u32 {
 
            auto tuple = (1, 2, 3, 4);
 
            u8  a = tuple.0;
 
            u16 b = tuple.1;
 
            u32 c = tuple.2;
 
            u64 d = tuple.3;
 
            return cast(a) + cast(b) + c + cast(d);
 
        }
 
        "
 
    ).for_function("test", |f| { f
 
        .for_variable("tuple", |v| { v.assert_concrete_type("(u8,u16,u32,u64)"); })
 
        .call_ok(Some(Value::UInt32(10)));
 
    });
 
}
 

	
 
#[test]
 
fn test_struct_inference() {
 
    Tester::new_single_source_expect_ok(
 
        "by function calls",
 
        "
 
        struct Pair<T1, T2>{ T1 first, T2 second }
 
        func construct<T1, T2>(T1 first, T2 second) -> Pair<T1, T2> {
 
            return Pair{ first: first, second: second };
 
        }
 
        func fix_t1<T2>(Pair<s8, T2> arg) -> s32 { return 0; }
 
        func fix_t2<T1>(Pair<T1, s32> arg) -> s32 { return 0; }
 
        func test() -> s32 {
 
            auto first = 0;
 
            auto second = 1;
 
            auto pair = construct(first, second);
 
            fix_t1(pair);
 
            fix_t2(pair);
 
            return 0;
 
        }
 
        "
 
    ).for_function("test", |f| { f
 
        .for_variable("first", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("s8");
 
        })
 
        .for_variable("second", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("s32");
 
        })
 
        .for_variable("pair", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("Pair<s8,s32>");
 
        });
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "by field access",
 
        "
 
        struct Pair<T1, T2>{ T1 first, T2 second }
 
        func construct<T1, T2>(T1 first, T2 second) -> Pair<T1, T2> {
 
            return Pair{ first: first, second: second };
 
        }
 
        func test() -> s32 {
 
            auto first = 0;
 
            auto second = 1;
 
            auto pair = construct(first, second);
 
            s8 assign_first = 0;
 
            s64 assign_second = 1;
 
            pair.first = assign_first;
 
            pair.second = assign_second;
 
            return 0;
 
        }
 
        "
 
    ).for_function("test", |f| { f
 
        .for_variable("first", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("s8");
 
        })
 
        .for_variable("second", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("s64");
 
        })
 
        .for_variable("pair", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("Pair<s8,s64>");
 
        });
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "by nested field access",
 
        "
 
        struct Node<T1, T2>{ T1 l, T2 r }
 
        func construct<T1, T2>(T1 l, T2 r) -> Node<T1, T2> {
 
            return Node{ l: l, r: r };
 
        }
 
        func fix_poly<T>(Node<T, T> a) -> s32 { return 0; }
 
        func test() -> s32 {
 
            s8 assigned = 0;
 
            auto thing = construct(assigned, construct(0, 1));
 
            fix_poly(thing.r);
 
            thing.r.r = assigned;
 
            return 0;
 
        }
 
        ",
 
    ).for_function("test", |f| { f
 
        .for_variable("thing", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("Node<s8,Node<s8,s8>>");
 
        });
 
    });
 
}
 

	
 
#[test]
 
fn test_enum_inference() {
 
    Tester::new_single_source_expect_ok(
 
        "no polymorphic vars",
 
        "
 
        enum Choice { A, B }
 
        func test_instances() -> s32 {
 
            auto foo = Choice::A;
 
            auto bar = Choice::B;
 
            return 0;
 
        }
 
        "
 
    ).for_function("test_instances", |f| { f
 
        .for_variable("foo", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("Choice");
 
        })
 
        .for_variable("bar", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("Choice");
 
        });
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "one polymorphic var",
 
        "
 
        enum Choice<T>{
 
            A,
 
            B,
 
        }
 
        func fix_as_s8(Choice<s8> arg) -> s32 { return 0; }
 
        func fix_as_s32(Choice<s32> arg) -> s32 { return 0; }
 
        func test_instances() -> s32 {
 
            auto choice_s8 = Choice::A;
 
            auto choice_s32_1 = Choice::B;
 
            Choice<auto> choice_s32_2 = Choice::B;
 
            fix_as_s8(choice_s8);
 
            fix_as_s32(choice_s32_1);
 
            return fix_as_s32(choice_s32_2);
 
        }
 
        "
 
    ).for_function("test_instances", |f| { f
 
        .for_variable("choice_s8", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("Choice<s8>");
 
        })
 
        .for_variable("choice_s32_1", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("Choice<s32>");
 
        })
 
        .for_variable("choice_s32_2", |v| { v
 
            .assert_parser_type("Choice<auto>")
 
            .assert_concrete_type("Choice<s32>");
 
        });
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "two polymorphic vars",
 
        "
 
        enum Choice<T1, T2>{ A, B, }
 
        func fix_t1<T>(Choice<s8, T> arg) -> s32 { return 0; }
 
        func fix_t2<T>(Choice<T, s32> arg) -> s32 { return 0; }
 
        func test_instances() -> s32 {
 
            Choice<s8, auto> choice1 = Choice::A;
 
            Choice<auto, s32> choice2 = Choice::A;
 
            Choice<auto, auto> choice3 = Choice::B;
 
            auto choice4 = Choice::B;
 
            fix_t1(choice1); fix_t1(choice2); fix_t1(choice3); fix_t1(choice4);
 
            fix_t2(choice1); fix_t2(choice2); fix_t2(choice3); fix_t2(choice4);
 
            return 0;
 
        }
 
        "
 
    ).for_function("test_instances", |f| { f
 
        .for_variable("choice1", |v| { v
 
            .assert_parser_type("Choice<s8,auto>")
 
            .assert_concrete_type("Choice<s8,s32>");
 
        })
 
        .for_variable("choice2", |v| { v
 
            .assert_parser_type("Choice<auto,s32>")
 
            .assert_concrete_type("Choice<s8,s32>");
 
        })
 
        .for_variable("choice3", |v| { v
 
            .assert_parser_type("Choice<auto,auto>")
 
            .assert_concrete_type("Choice<s8,s32>");
 
        })
 
        .for_variable("choice4", |v| { v
 
            .assert_parser_type("auto")
 
            .assert_concrete_type("Choice<s8,s32>");
 
        });
 
    });
 
}
 

	
 
#[test]
 
fn test_failed_variable_inference() {
 
    Tester::new_single_source_expect_err(
 
        "variable assignment mismatch",
 
        "
 
        func call() -> u32 {
 
            u16 val16 = 0;
 
            u32 val32 = 0;
 
            auto a = val16;
 
            a = val32;
 
            return 0;
 
        }
 
        "
 
    ).error(|e| { e
 
        .assert_num(2)
 
        .assert_msg_has(0, "type 'u16'")
 
        .assert_msg_has(1, "type 'u32'");
 
    });
 
}
 

	
 
#[test]
 
fn test_failed_polymorph_inference() {
 
    Tester::new_single_source_expect_err(
 
        "function call inference mismatch",
 
        "
 
        func poly<T>(T a, T b) -> s32 { return 0; }
 
        func call() -> s32 {
 
            s8 first_arg = 5;
 
            s64 second_arg = 2;
 
            return poly(first_arg, second_arg);
 
        }
 
        "
 
    ).error(|e| { e
 
        .assert_num(3)
 
        .assert_ctx_has(0, "poly(first_arg, second_arg)")
 
        .assert_occurs_at(0, "poly")
 
        .assert_msg_has(0, "Conflicting type for polymorphic variable 'T'")
 
        .assert_occurs_at(1, "second_arg")
 
        .assert_msg_has(1, "inferred it to 's64'")
 
        .assert_occurs_at(2, "first_arg")
 
        .assert_msg_has(2, "inferred it to 's8'");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "struct literal inference mismatch",
 
        "
 
        struct Pair<T>{ T first, T second }
 
        func call() -> s32 {
 
            s8 first_arg = 5;
 
            s64 second_arg = 2;
 
            auto pair = Pair{ first: first_arg, second: second_arg };
 
            return 3;
 
        }
 
        "
 
    ).error(|e| { e
 
        .assert_num(3)
 
        .assert_ctx_has(0, "Pair{ first: first_arg, second: second_arg }")
 
        .assert_occurs_at(0, "Pair{")
 
        .assert_msg_has(0, "Conflicting type for polymorphic variable 'T'")
 
        .assert_occurs_at(1, "second_arg")
 
        .assert_msg_has(1, "inferred it to 's64'")
 
        .assert_occurs_at(2, "first_arg")
 
        .assert_msg_has(2, "inferred it to 's8'");
 
    });
 

	
 
    // Cannot really test literal inference error, but this comes close
 
    Tester::new_single_source_expect_err(
 
        "enum literal inference mismatch",
 
        "
 
        enum Uninteresting<T>{ Variant }
 
        func fix_t<T>(Uninteresting<T> arg) -> s32 { return 0; }
 
        func call() -> s32 {
 
            auto a = Uninteresting::Variant;
 
            fix_t<s8>(a);
 
            fix_t<s32>(a);
 
            return 4;
 
        }
 
        "
 
    ).error(|e| { e
 
        .assert_num(2)
 
        .assert_msg_has(0, "type 'Uninteresting<s8>'")
 
        .assert_msg_has(1, "type 'Uninteresting<s32>'");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "field access inference mismatch",
 
        "
 
        struct Holder<Shazam>{ Shazam a }
 
        func call() -> s32 {
 
            s8 to_hold = 0;
 
            auto holder = Holder{ a: to_hold };
 
            return holder.a;
 
        }
 
        "
 
    ).error(|e| { e
 
        .assert_num(3)
 
        .assert_ctx_has(0, "holder.a")
 
        .assert_occurs_at(0, "holder.a")
 
        .assert_msg_has(0, "Conflicting type for polymorphic variable 'Shazam'")
 
        .assert_msg_has(1, "inferred it to 's8'")
 
        .assert_msg_has(2, "inferred it to 's32'");
 
    });
 

	
 
    // Silly regression test
 
    Tester::new_single_source_expect_err(
 
        "nested field access inference mismatch",
 
        "
 
        struct Node<T1, T2>{ T1 l, T2 r }
 
        func construct<T1, T2>(T1 l, T2 r) -> Node<T1, T2> { return Node{ l: l, r: r }; }
 
        func fix_poly<T>(Node<T, T> a) -> s32 { return 0; }
 
        func test() -> s32 {
 
            s8 assigned = 0;
 
            s64 another = 1;
 
            auto thing = construct(assigned, construct(another, 1));
 
            fix_poly(thing.r);
 
            thing.r.r = assigned;
 
            return 0;
 
        }
 
        ",
 
    );
 
}
 

	
 
#[test]
 
fn test_disallowed_inference() {
 
    Tester::new_single_source_expect_err(
 
        "argument auto inference",
 
        "func thing(auto arg) -> s32 { return 0; }"
 
    ).error(|e| { e
 
        .assert_msg_has(0, "inference is not allowed")
 
        .assert_occurs_at(0, "auto arg");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "return type auto inference",
 
        "func thing(s32 arg) -> auto { return 0; }"
 
    ).error(|e| { e
 
        .assert_msg_has(0, "inference is not allowed")
 
        .assert_occurs_at(0, "auto {");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "implicit polymorph argument auto inference",
 
        "func thing(in port) -> s32 { return port; }"
 
    ).error(|e| { e
 
        .assert_msg_has(0, "inference is not allowed")
 
        .assert_occurs_at(0, "in port");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "explicit polymorph argument auto inference",
 
        "func thing(in<auto> port) -> s32 { return port; }"
 
    ).error(|e| { e
 
        .assert_msg_has(0, "inference is not allowed")
 
        .assert_occurs_at(0, "auto> port");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "implicit polymorph return type auto inference",
 
        "func thing(in<msg> a, in<msg> b) -> in { return a; }"
 
    ).error(|e| { e
 
        .assert_msg_has(0, "inference is not allowed")
 
        .assert_occurs_at(0, "in {");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "explicit polymorph return type auto inference",
 
        "func thing(in<msg> a) -> in<auto> { return a; }"
 
    ).error(|e| { e
 
        .assert_msg_has(0, "inference is not allowed")
 
        .assert_occurs_at(0, "auto> {");
 
    });
 
}
 

	
 
#[test]
 
fn test_explicit_polymorph_argument() {
 
    // Failed because array was put at same type depth as u32. So interpreted
 
    // as a function with two polymorphic arguments
 
    Tester::new_single_source_expect_ok("explicit with array", "
 
    func foo<T>(T a, T b) -> T {
 
        return a @ b;
 
    }
 
    func test() -> u32 {
 
        return foo<u32[]>({1}, {2})[1];
 
    }").for_function("test", |f| { f
 
        .call_ok(Some(Value::UInt32(2)));
 
    });
 

	
 
    Tester::new_single_source_expect_err("multi-explicit with array", "
 
    func foo<A, B, C>(A a, B b) -> C {
 
        return (a @ b)[1];
 
    }
 
    func test() -> u32 {
 
        return foo<u32[], u32[], u32, u32>({1}, {2});
 
    }").error(|e| { e
 
        .assert_num(1)
 
        .assert_occurs_at(0, "foo<u32")
 
        .assert_msg_has(0, "expected 3")
 
        .assert_msg_has(0, "4 were provided");
 
    });
 

	
 
    // Failed because type inferencer did not construct polymorph errors by
 
    // considering that argument/return types failed against explicitly
 
    // specified polymorphic arguments
 
    Tester::new_single_source_expect_err("explicit polymorph argument mismatch", "
 
    func foo<T>(T a, T b) -> T { return a + b; }
 
    struct Bar<A, B>{A a, B b}
 
    func test() -> u32 {
 
        return foo<Bar<u32, u64>[]>(5, 6);
 
    }").error(|e| { e
 
        .assert_num(2)
 
        .assert_occurs_at(0, "foo<Bar")
 
        .assert_msg_has(0, "'T' of 'foo'")
 
        .assert_occurs_at(1, "5, ")
 
        .assert_msg_has(1, "has type 'Bar<u32, u64>[]")
 
        .assert_msg_has(1, "inferred it to 'integerlike'");
 
    });
 

	
 
    // Similar to above, now for return type
 
    Tester::new_single_source_expect_err("explicit polymorph return mismatch", "
 
    func foo<T>(T a, T b) -> T { return a + b; }
 
    func test() -> u32 {
 
        return foo<u64>(5, 6);
 
    }").error(|e| { e
 
        .assert_num(2)
 
        .assert_occurs_at(0, "foo<u64")
 
        .assert_msg_has(0, "'T' of 'foo'")
 
        .assert_occurs_at(1, "foo<u64")
 
        .assert_msg_has(1, "has type 'u64'")
 
        .assert_msg_has(1, "return type inferred it to 'u32'");
 
    });
 
}
 
\ No newline at end of file
src/protocol/tests/parser_type_declaration.rs
Show inline comments
 
new file 100644
 
use super::*;
 

	
 
#[test]
 
fn test_simple_struct_definition() {
 
    Tester::new_single_source_expect_ok(
 
        "empty struct",
 
        "struct Foo{}"
 
    ).for_struct("Foo", |t| { t.assert_num_fields(0); });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "single field, no comma",
 
        "struct Foo{ s32 field }"
 
    ).for_struct("Foo", |t| { t
 
        .assert_num_fields(1)
 
        .for_field("field", |f| {
 
            f.assert_parser_type("s32");
 
        });
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "single field, with comma",
 
        "struct Foo{ s32 field, }"
 
    ).for_struct("Foo", |t| { t
 
        .assert_num_fields(1)
 
        .for_field("field", |f| { f
 
            .assert_parser_type("s32");
 
        });
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple fields, no comma",
 
        "struct Foo{ u8 a, s16 b, s32 c }"
 
    ).for_struct("Foo", |t| { t
 
        .assert_num_fields(3)
 
        .for_field("a", |f| { f.assert_parser_type("u8"); })
 
        .for_field("b", |f| { f.assert_parser_type("s16"); })
 
        .for_field("c", |f| { f.assert_parser_type("s32"); });
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple fields, with comma",
 
        "struct Foo{
 
            u8 a,
 
            s16 b,
 
            s32 c,
 
        }"
 
    ).for_struct("Foo", |t| { t
 
        .assert_num_fields(3)
 
        .for_field("a", |f| { f.assert_parser_type("u8"); })
 
        .for_field("b", |f| { f.assert_parser_type("s16"); })
 
        .for_field("c", |f| { f.assert_parser_type("s32"); });
 
    });
 
}
 
\ No newline at end of file
src/protocol/tests/parser_type_layout.rs
Show inline comments
 
file renamed from src/protocol/tests/parser_types.rs to src/protocol/tests/parser_type_layout.rs
src/protocol/tests/parser_validation.rs
Show inline comments
 
/// parser_validation.rs
 
///
 
/// Simple tests for the validation phase
 

	
 
use super::*;
 

	
 

	
 

	
 
#[test]
 
fn test_correct_struct_instance() {
 
    Tester::new_single_source_expect_ok(
 
        "single field",
 
        "
 
        struct Foo { s32 a }
 
        func bar(s32 arg) -> Foo { return Foo{ a: arg }; }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple fields",
 
        "
 
        struct Foo { s32 a, s32 b }
 
        func bar(s32 arg) -> Foo { return Foo{ a: arg, b: arg }; }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "single field, explicit polymorph",
 
        "
 
        struct Foo<T>{ T field }
 
        func bar(s32 arg) -> Foo<s32> { return Foo<s32>{ field: arg }; }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "single field, implicit polymorph",
 
        "
 
        struct Foo<T>{ T field }
 
        func bar(s32 arg) -> s32 {
 
            auto thingo = Foo{ field: arg };
 
            return arg;
 
        }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple fields, same explicit polymorph",
 
        "
 
        struct Pair<T1, T2>{ T1 first, T2 second }
 
        func bar(s32 arg) -> s32 {
 
            auto qux = Pair<s32, s32>{ first: arg, second: arg };
 
            return arg;
 
        }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple fields, same implicit polymorph", 
 
        "
 
        struct Pair<T1, T2>{ T1 first, T2 second }
 
        func bar(s32 arg) -> s32 {
 
            auto wup = Pair{ first: arg, second: arg };
 
            return arg;
 
        }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple fields, different explicit polymorph",
 
        "
 
        struct Pair<T1, T2>{ T1 first, T2 second }
 
        func bar(s32 arg1, s8 arg2) -> s32 {
 
            auto shoo = Pair<s32, s8>{ first: arg1, second: arg2 };
 
            return arg1;
 
        }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple fields, different implicit polymorph",
 
        "
 
        struct Pair<T1, T2>{ T1 first, T2 second }
 
        func bar(s32 arg1, s8 arg2) -> s32 {
 
            auto shrubbery = Pair{ first: arg1, second: arg2 };
 
            return arg1;
 
        }
 
        "
 
    );
 
}
 

	
 
#[test]
 
fn test_incorrect_struct_instance() {
 
    Tester::new_single_source_expect_err(
 
        "reused field in definition",
 
        "struct Foo{ s32 a, s8 a }"
 
    ).error(|e| { e
 
        .assert_num(2)
 
        .assert_occurs_at(0, "a }")
 
        .assert_msg_has(0, "defined more than once")
 
        .assert_occurs_at(1, "a, ")
 
        .assert_msg_has(1, "other struct field");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "reused field in instance",
 
        "
 
        struct Foo{ s32 a, s32 b }
 
        func bar() -> s32 {
 
            auto foo = Foo{ a: 5, a: 3 };
 
            return 0;
 
        }
 
        "
 
    ).error(|e| { e
 
        .assert_occurs_at(0, "a: 3")
 
        .assert_msg_has(0, "field is specified more than once");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "missing field",
 
        "
 
        struct Foo { s32 a, s32 b }
 
        func bar() -> s32 {
 
            auto foo = Foo{ a: 2 };
 
            return 0;
 
        }
 
        "
 
    ).error(|e| { e
 
        .assert_occurs_at(0, "Foo{")
 
        .assert_msg_has(0, "'b' is missing");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "missing fields",
 
        "
 
        struct Foo { s32 a, s32 b, s32 c }
 
        func bar() -> s32 {
 
            auto foo = Foo{ a: 2 };
 
            return 0;
 
        }
 
        "
 
    ).error(|e| { e
 
        .assert_occurs_at(0, "Foo{")
 
        .assert_msg_has(0, "[b, c] are missing");
 
    });
 
}
 

	
 
#[test]
 
fn test_correct_enum_instance() {
 
    Tester::new_single_source_expect_ok(
 
        "single variant",
 
        "
 
        enum Foo { A }
 
        func bar() -> Foo { return Foo::A; }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple variants",
 
        "
 
        enum Foo { A=15, B = 0xF }
 
        func bar() -> Foo { auto a = Foo::A; return Foo::B; }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "explicit single polymorph",
 
        "
 
        enum Foo<T>{ A }
 
        func bar() -> Foo<s32> { return Foo::A; }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "explicit multi-polymorph",
 
        "
 
        enum Foo<A, B>{ A, B }
 
        func bar() -> Foo<s8, s32> { return Foo::B; }
 
        "
 
    );
 
}
 

	
 
#[test]
 
fn test_incorrect_enum_instance() {
 
    Tester::new_single_source_expect_err(
 
        "variant name reuse",
 
        "
 
        enum Foo { A, A }
 
        func bar() -> Foo { return Foo::A; }
 
        "
 
    ).error(|e| { e
 
        .assert_num(2)
 
        .assert_occurs_at(0, "A }")
 
        .assert_msg_has(0, "defined more than once")
 
        .assert_occurs_at(1, "A, ")
 
        .assert_msg_has(1, "other enum variant is defined here");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "undefined variant",
 
        "
 
        enum Foo { A }
 
        func bar() -> Foo { return Foo::B; }
 
        "
 
    ).error(|e| { e
 
        .assert_num(1)
 
        .assert_msg_has(0, "variant 'B' does not exist on the enum 'Foo'");
 
    });
 
}
 

	
 
#[test]
 
fn test_correct_union_instance() {
 
    Tester::new_single_source_expect_ok(
 
        "single tag",
 
        "
 
        union Foo { A }
 
        func bar() -> Foo { return Foo::A; }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple tags",
 
        "
 
        union Foo { A, B }
 
        func bar() -> Foo { return Foo::B; }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "single embedded",
 
        "
 
        union Foo { A(s32) }
 
        func bar() -> Foo { return Foo::A(5); }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple embedded",
 
        "
 
        union Foo { A(s32), B(s8) }
 
        func bar() -> Foo { return Foo::B(2); }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple values in embedded",
 
        "
 
        union Foo { A(s32, s8) }
 
        func bar() -> Foo { return Foo::A(0, 2); }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "mixed tag/embedded",
 
        "
 
        union OptionInt { None, Some(s32) }
 
        func bar() -> OptionInt { return OptionInt::Some(3); }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "single polymorphic var",
 
        "
 
        union Option<T> { None, Some(T) }
 
        func bar() -> Option<s32> { return Option::Some(3); }"
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple polymorphic vars",
 
        "
 
        union Result<T, E> { Ok(T), Err(E), }
 
        func bar() -> Result<s32, s8> { return Result::Ok(3); }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "multiple polymorphic in one variant",
 
        "
 
        union MaybePair<T1, T2>{ None, Some(T1, T2) }
 
        func bar() -> MaybePair<s8, s32> { return MaybePair::Some(1, 2); }
 
        "
 
    );
 
}
 

	
 
#[test]
 
fn test_incorrect_union_instance() {
 
    Tester::new_single_source_expect_err(
 
        "tag-variant name reuse",
 
        "
 
        union Foo{ A, A }
 
        "
 
    ).error(|e| { e
 
        .assert_num(2)
 
        .assert_occurs_at(0, "A }")
 
        .assert_msg_has(0, "union variant is defined more than once")
 
        .assert_occurs_at(1, "A, ")
 
        .assert_msg_has(1, "other union variant");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "embedded-variant name reuse",
 
        "
 
        union Foo{ A(s32), A(s8) }
 
        "
 
    ).error(|e| { e 
 
        .assert_num(2)
 
        .assert_occurs_at(0, "A(s8)")
 
        .assert_msg_has(0, "union variant is defined more than once")
 
        .assert_occurs_at(1, "A(s32)")
 
        .assert_msg_has(1, "other union variant");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "undefined variant",
 
        "
 
        union Silly{ Thing(s8) }
 
        func bar() -> Silly { return Silly::Undefined(5); }
 
        "
 
    ).error(|e| { e
 
        .assert_msg_has(0, "variant 'Undefined' does not exist on the union 'Silly'");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "using tag instead of embedded",
 
        "
 
        union Foo{ A(s32) }
 
        func bar() -> Foo { return Foo::A; }
 
        "
 
    ).error(|e| { e
 
        .assert_msg_has(0, "variant 'A' of union 'Foo' expects 1 embedded values, but 0 were");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "using embedded instead of tag",
 
        "
 
        union Foo{ A }
 
        func bar() -> Foo { return Foo::A(3); }
 
        "
 
    ).error(|e| { e 
 
        .assert_msg_has(0, "The variant 'A' of union 'Foo' expects 0");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "wrong embedded value",
 
        "
 
        union Foo{ A(s32) }
 
        func bar() -> Foo { return Foo::A(false); }
 
        "
 
    ).error(|e| { e
 
        .assert_occurs_at(0, "Foo::A")
 
        .assert_msg_has(0, "failed to fully resolve")
 
        .assert_occurs_at(1, "false")
 
        .assert_msg_has(1, "has been resolved to 's32'")
 
        .assert_msg_has(1, "has been resolved to 'bool'");
 
    });
 
}
 

	
 
#[test]
 
fn test_correct_tuple_members() {
 
    // Tuples with zero members
 
    Tester::new_single_source_expect_ok(
 
        "single zero-tuple",
 
        "struct Foo{ () bar }"
 
    ).for_struct("Foo", |s| { s
 
        .for_field("bar", |f| { f.assert_parser_type("()"); })
 
        .assert_size_alignment("Foo", 0, 1);
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "triple zero-tuple",
 
        "struct Foo{ () bar, () baz, () qux }"
 
    ).for_struct("Foo", |s| { s
 
        .assert_size_alignment("Foo", 0, 1);
 
    });
 

	
 
    // Tuples with one member (which are elided, because due to ambiguity
 
    // between a one-tuple literal and a parenthesized expression, we're not
 
    // going to be able to construct one-tuples).
 
    Tester::new_single_source_expect_ok(
 
        "single elided one-tuple",
 
        "struct Foo{ (u32) bar }"
 
    ).for_struct("Foo", |s| { s
 
        .for_field("bar", |f| { f.assert_parser_type("u32"); })
 
        .assert_size_alignment("Foo", 4, 4);
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "triple elided one-tuple",
 
        "struct Foo{ (u8) bar, (u16) baz, (u32) qux }"
 
    ).for_struct("Foo", |s| { s
 
        .assert_size_alignment("Foo", 8, 4);
 
    });
 

	
 
    // Tuples with three members
 
    Tester::new_single_source_expect_ok(
 
        "single three-tuple",
 
        "struct Foo{ (u8, u16, u32) bar }"
 
    ).for_struct("Foo", |s| { s
 
        .for_field("bar", |f| { f.assert_parser_type("(u8,u16,u32)"); })
 
        .assert_size_alignment("Foo", 8, 4);
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "double three-tuple",
 
        "struct Foo{ (u8,u16,u32,) bar, (s8,s16,s32,) baz }"
 
    ).for_struct("Foo", |s| { s
 
        .for_field("bar", |f| { f.assert_parser_type("(u8,u16,u32)"); })
 
        .for_field("baz", |f| { f.assert_parser_type("(s8,s16,s32)"); })
 
        .assert_size_alignment("Foo", 16, 4);
 
    });
 
}
 

	
 
#[test]
 
fn test_incorrect_tuple_member() {
 
    // Test not really necessary, but hey, what's a test between friends
 
    Tester::new_single_source_expect_err(
 
        "unknown tuple member",
 
        "struct Foo{ (u32, u32, u32, YouThirstySchmoo) field }"
 
    ).error(|e| { e
 
        .assert_num(1)
 
        .assert_msg_has(0, "unknown type")
 
        .assert_occurs_at(0, "YouThirstySchmoo");
 
    });
 
}
 

	
 
#[test]
 
fn test_correct_tuple_polymorph_args() {
 
    Tester::new_single_source_expect_ok(
 
        "single tuple arg",
 
        "
 
        union Option<T>{ Some(T), None }
 
        func thing() -> u32 {
 
            auto a = Option<()>::None;
 
            auto b = Option<(u32, u64)>::None;
 
            auto c = Option<(Option<(u8, s8)>, Option<(s8, u8)>)>::None;
 
            return 0;
 
        }
 
        "
 
    ).for_union("Option", |u| { u
 
        .assert_has_monomorph("Option<()>")
 
        .assert_has_monomorph("Option<(u32,u64)>")
 
        .assert_has_monomorph("Option<(Option<(u8,s8)>,Option<(s8,u8)>)>")
 
        .assert_size_alignment("Option<()>", 1, 1, 0, 0)
 
        .assert_size_alignment("Option<(u32,u64)>", 24, 8, 0, 0) // (u32, u64) becomes size 16, alignment 8. Hence union tag is aligned to 8
 
        .assert_size_alignment("Option<(Option<(u8,s8)>,Option<(s8,u8)>)>", 7, 1, 0, 0); // inner unions are size 3, alignment 1. Two of those with a tag is size 7
 
    });
 
}
 

	
 
#[test]
 
fn test_incorrect_tuple_polymorph_args() {
 
    // Do some mismatching brackets. I don't know what else to test
 
    Tester::new_single_source_expect_err(
 
        "mismatch angle bracket",
 
        "
 
        union Option<T>{ Some(T), None }
 
        func f() -> u32 {
 
            auto a = Option<(u32>)::None;
 
            return 0;
 
        }"
 
    ).error(|e| { e
 
        .assert_num(2)
 
        .assert_msg_has(0, "closing '>'").assert_occurs_at(0, ">)::None")
 
        .assert_msg_has(1, "match this '('").assert_occurs_at(1, "(u32>");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "wrongly placed angle",
 
        "
 
        union O<T>{ S(T), N }
 
        func f() -> u32 {
 
            auto a = O<(<u32>)>::None;
 
            return 0;
 
        }
 
        "
 
    ).error(|e| { e
 
        .assert_num(1)
 
        .assert_msg_has(0, "expected typename")
 
        .assert_occurs_at(0, "<u32");
 
    });
 
}
 

	
 
#[test]
 
fn test_incorrect_tuple_member_access() {
 
    Tester::new_single_source_expect_err(
 
        "zero-tuple",
 
        "func foo() -> () { () a = (); auto b = a.0; return a; }"
 
    ).error(|e| { e
 
        .assert_num(1)
 
        .assert_msg_has(0, "out of bounds")
 
        .assert_occurs_at(0, "a.0");
 
    });
 

	
 
    // Make the type checker do some shenanigans before we can decide the tuple
 
    // type.
 
    Tester::new_single_source_expect_err(
 
        "sized tuple",
 
        "
 
        func determinator<A,B>((A,B,A) v) -> B { return v.1; }
 
        func tester() -> u64 {
 
            auto v = (0,1,2);
 
            u32 a_u32 = 5;
 
            v.2 = a_u32;
 
            v.8 = 5;
 
            return determinator(v);
 
        }
 
        "
 
    ).error(|e| { e
 
        .assert_num(1)
 
        .assert_msg_has(0, "out of bounds")
 
        .assert_occurs_at(0, "v.8");
 
    });
 
}
 

	
 
#[test]
 
fn test_polymorph_array_types() {
 
    Tester::new_single_source_expect_ok(
 
        "array of polymorph in struct",
 
        "
 
        struct Foo<T> { T[] hello }
 
        struct Bar { Foo<u32>[] world }
 
        "
 
    ).for_struct("Bar", |s| { s
 
        .for_field("world", |f| { f.assert_parser_type("Foo<u32>[]"); });
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "array of port in struct",
 
        "
 
        struct Bar { in<u32>[] inputs }
 
        "
 
    ).for_struct("Bar", |s| { s
 
        .for_field("inputs", |f| { f.assert_parser_type("in<u32>[]"); });
 
    });
 
}
 

	
 
#[test]
 
fn test_correct_modifying_operators() {
 
    // Not testing the types, just that it parses
 
    Tester::new_single_source_expect_ok(
 
        "valid uses",
 
        "
 
        func f() -> u32 {
 
            auto a = 5;
 
            a += 2; a -= 2; a *= 2; a /= 2; a %= 2;
 
            a <<= 2; a >>= 2;
 
            a |= 2; a &= 2; a ^= 2;
 
            return a;
 
        }
 
        "
 
    );
 
}
 

	
 
#[test]
 
fn test_incorrect_modifying_operators() {
 
    Tester::new_single_source_expect_err(
 
        "wrong declaration",
 
        "func f() -> u8 { auto a += 2; return a; }"
 
    ).error(|e| { e.assert_msg_has(0, "expected '='"); });
 

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

	
 
#[test]
 
fn test_incorrect_select_statement() {
 
    Tester::new_single_source_expect_err(
 
        "outside sync",
 
        "primitive f() { select {} }"
 
    ).error(|e| { e
 
        .assert_num(1)
 
        .assert_occurs_at(0, "select")
 
        .assert_msg_has(0, "inside sync blocks");
 
    });
 

	
 
    Tester::new_single_source_expect_ok(
 
        "variable in previous block",
 
        "primitive f() {
 
            channel<u32> tx -> rx;
 
            u32 a = 0; // this one will be shadowed
 
            sync select { auto a = get(rx) -> {} }
 
        }"
 
    );
 
}
 

	
 
#[test]
 
fn test_incorrect_goto_statement() {
 
    Tester::new_single_source_expect_err(
 
        "goto missing var in same scope",
 
        "func f() -> u32 {
 
            goto exit;
 
            auto v = 5;
 
            exit: return 0;
 
        }"
 
    ).error(|e| { e
 
        .assert_num(3)
 
        .assert_occurs_at(0, "exit;").assert_msg_has(0, "skips over a variable")
 
        .assert_occurs_at(1, "exit:").assert_msg_has(1, "jumps to this label")
 
        .assert_occurs_at(2, "v = 5").assert_msg_has(2, "skips over this variable");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "goto missing var in outer scope",
 
        "func f() -> u32 {
 
            if (true) {
 
                goto exit;
 
            }
 
            auto v = 0;
 
            exit: return 1;
 
        }"
 
    ).error(|e| { e
 
        .assert_num(3)
 
        .assert_occurs_at(0, "exit;").assert_msg_has(0, "skips over a variable")
 
        .assert_occurs_at(1, "exit:").assert_msg_has(1, "jumps to this label")
 
        .assert_occurs_at(2, "v = 0").assert_msg_has(2, "skips over this variable");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "goto jumping into scope",
 
        "func f() -> u32 {
 
            goto nested;
 
            {
 
                nested: return 0;
 
            }
 
            return 1;
 
        }"
 
    ).error(|e| { e
 
        .assert_num(1)
 
        .assert_occurs_at(0, "nested;")
 
        .assert_msg_has(0, "could not find this label");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "goto jumping outside sync",
 
        "primitive f() {
 
            sync { goto exit; }
 
            exit: u32 v = 0;
 
        }"
 
    ).error(|e| { e
 
        .assert_num(3)
 
        .assert_occurs_at(0, "goto exit;").assert_msg_has(0, "not escape the surrounding sync")
 
        .assert_occurs_at(1, "exit: u32 v").assert_msg_has(1, "target of the goto")
 
        .assert_occurs_at(2, "sync {").assert_msg_has(2, "jump past this");
 
    })
 
}
 

	
 
#[test]
 
fn test_incorrect_while_statement() {
 
    // Just testing the error cases caught at compile-time. Other ones need
 
    // evaluation testing
 
    Tester::new_single_source_expect_err(
 
        "break wrong earlier loop",
 
        "func f() -> u32 {
 
            target: while (true) {}
 
            while (true) { break target; }
 
            return 0;
 
        }"
 
    ).error(|e| { e
 
        .assert_num(2)
 
        .assert_occurs_at(0, "target; }").assert_msg_has(0, "not nested under the target")
 
        .assert_occurs_at(1, "target: while").assert_msg_has(1, "is found here");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "break wrong later loop",
 
        "func f() -> u32 {
 
            while (true) { break target; }
 
            target: while (true) {}
 
            return 0;
 
        }"
 
    ).error(|e| { e
 
        .assert_num(2)
 
        .assert_occurs_at(0, "target; }").assert_msg_has(0, "not nested under the target")
 
        .assert_occurs_at(1, "target: while").assert_msg_has(1, "is found here");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "break outside of sync",
 
        "primitive f() {
 
            outer: while (true) { //mark
 
                sync while(true) { break outer; }
 
            }
 
        }"
 
    ).error(|e| { e
 
        .assert_num(3)
 
        .assert_occurs_at(0, "break outer;").assert_msg_has(0, "may not escape the surrounding")
 
        .assert_occurs_at(1, "while (true) { //mark").assert_msg_has(1, "escapes out of this loop")
 
        .assert_occurs_at(2, "sync while").assert_msg_has(2, "escape this synchronous block");
 
    });
 
}
 
\ No newline at end of file
0 comments (0 inline, 0 general)