Changeset - 699bec97a862
[Not reviewed]
0 2 0
mh - 4 years ago 2021-12-21 15:39:08
contact@maxhenger.nl
Fix longstanding variable scoping bug, add select statement test
2 files changed with 32 insertions and 14 deletions:
0 comments (0 inline, 0 general)
src/protocol/parser/pass_validation_linking.rs
Show inline comments
 
@@ -1424,440 +1424,443 @@ impl Visitor for PassValidationLinking {
 
        var_expr.unique_id_in_definition = self.next_expr_index;
 
        self.next_expr_index += 1;
 

	
 
        Ok(())
 
    }
 
}
 

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

	
 
    /// Pushes a new scope associated with a particular statement. If that
 
    /// statement already has an associated scope (i.e. scope associated with
 
    /// sync statement or select statement's arm) then we won't do anything.
 
    /// In all cases the caller must call `pop_statement_scope` with the scope
 
    /// and relative scope position returned by this function.
 
    fn push_statement_scope(&mut self, ctx: &mut Ctx, new_scope: Scope) -> (Scope, i32) {
 
        let old_scope = self.cur_scope.clone();
 
        debug_assert!(new_scope.is_block()); // never call for Definition scope
 
        let is_new_block = if old_scope.is_block() {
 
            old_scope.to_block() != new_scope.to_block()
 
        } else {
 
            true
 
        };
 

	
 
        if !is_new_block {
 
            // No need to push, but still return old scope, we pretend like we
 
            // replaced it.
 
            debug_assert!(!ctx.heap[new_scope.to_block()].scope_node.parent.is_invalid());
 
            return (old_scope, self.relative_pos_in_block);
 
        }
 

	
 
        // This is a new block, so link it up
 
        if old_scope.is_block() {
 
            let parent_block = &mut ctx.heap[old_scope.to_block()];
 
            parent_block.scope_node.nested.push(new_scope);
 
        }
 

	
 
        self.cur_scope = new_scope;
 

	
 
        let cur_block = &mut ctx.heap[new_scope.to_block()];
 
        cur_block.scope_node.parent = old_scope;
 
        cur_block.scope_node.relative_pos_in_parent = self.relative_pos_in_block;
 

	
 
        let old_relative_pos = self.relative_pos_in_block;
 
        self.relative_pos_in_block = -1;
 

	
 
        return (old_scope, old_relative_pos)
 
    }
 

	
 
    fn pop_statement_scope(&mut self, scope_to_restore: (Scope, i32)) {
 
        self.cur_scope = scope_to_restore.0;
 
        self.relative_pos_in_block = scope_to_restore.1;
 
    }
 

	
 
    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 var_id in param_section.iter_copied() {
 
            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 {
 
                i32::MAX
 
            };
 

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

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

	
 
            // In certain cases the relative variable position is the same as
 
            // the scope position (insertion of binding variables). In that case
 
            // the variable should be treated first
 
            if relative_var_pos <= relative_scope_pos {
 
                let var = &mut ctx.heap[var_section[var_idx]];
 
                var.unique_id_in_scope = var_counter;
 
                var_counter += 1;
 
                var_idx += 1;
 
            } else {
 
                // Boy oh boy
 
                let block_id = ctx.heap[scope_section[scope_idx]].as_block().this;
 
                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;
 
    }
 

	
 
    fn resolve_pending_control_flow_targets(&mut self, ctx: &mut Ctx) -> Result<(), ParseError> {
 
        for entry in &self.control_flow_stmts {
 
            let stmt = &ctx.heap[entry.statement];
 

	
 
            match stmt {
 
                Statement::Break(stmt) => {
 
                    let stmt_id = stmt.this;
 
                    let target_while_id = Self::resolve_break_or_continue_target(ctx, entry, stmt.span, &stmt.label)?;
 
                    let target_while_stmt = &ctx.heap[target_while_id];
 
                    let target_end_while_id = target_while_stmt.end_while;
 
                    debug_assert!(!target_end_while_id.is_invalid());
 

	
 
                    let break_stmt = &mut ctx.heap[stmt_id];
 
                    break_stmt.target = target_end_while_id;
 
                },
 
                Statement::Continue(stmt) => {
 
                    let stmt_id = stmt.this;
 
                    let target_while_id = Self::resolve_break_or_continue_target(ctx, entry, stmt.span, &stmt.label)?;
 

	
 
                    let continue_stmt = &mut ctx.heap[stmt_id];
 
                    continue_stmt.target = target_while_id;
 
                },
 
                Statement::Goto(stmt) => {
 
                    let stmt_id = stmt.this;
 
                    let target_id = Self::find_label(entry.in_scope, ctx, &stmt.label)?;
 
                    let target_stmt = &ctx.heap[target_id];
 
                    if entry.in_sync != target_stmt.in_sync {
 
                        // Nested sync not allowed. And goto can only go to
 
                        // outer scopes, so we must be escaping from a sync.
 
                        debug_assert!(target_stmt.in_sync.is_invalid());    // target not in sync
 
                        debug_assert!(!entry.in_sync.is_invalid()); // but the goto is in sync
 
                        let goto_stmt = &ctx.heap[stmt_id];
 
                        let sync_stmt = &ctx.heap[entry.in_sync];
 
                        return Err(
 
                            ParseError::new_error_str_at_span(&ctx.module().source, goto_stmt.span, "goto may not escape the surrounding synchronous block")
 
                            .with_info_str_at_span(&ctx.module().source, target_stmt.label.span, "this is the target of the goto statement")
 
                            .with_info_str_at_span(&ctx.module().source, sync_stmt.span, "which will jump past this statement")
 
                        );
 
                    }
 

	
 
                    let goto_stmt = &mut ctx.heap[stmt_id];
 
                    goto_stmt.target = target_id;
 
                },
 
                _ => unreachable!("cannot resolve control flow target for {:?}", stmt),
 
            }
 
        }
 

	
 
        return Ok(())
 
    }
 

	
 
    //--------------------------------------------------------------------------
 
    // Utilities
 
    //--------------------------------------------------------------------------
 

	
 
    /// Adds a local variable to the current scope. It will also annotate the
 
    /// `Local` in the AST with its relative position in the block.
 
    fn checked_add_local(&mut self, ctx: &mut Ctx, target_scope: Scope, relative_pos: i32, id: VariableId) -> Result<(), ParseError> {
 
    fn checked_add_local(&mut self, ctx: &mut Ctx, target_scope: Scope, target_relative_pos: i32, id: VariableId) -> Result<(), ParseError> {
 
        debug_assert!(target_scope.is_block());
 
        let local = &ctx.heap[id];
 
        println!("DEBUG: Adding local '{}' at relative_pos {} in scope {:?}", local.identifier.value.as_str(), relative_pos, target_scope);
 

	
 
        let mut scope = target_scope;
 
        println!("DEBUG: Adding local '{}' at relative_pos {} in scope {:?}", local.identifier.value.as_str(), target_relative_pos, target_scope);
 

	
 
        // We immediately go to the parent scope. We check the target scope
 
        // in the call at the end. That is also where we check for collisions
 
        // with symbols.
 
        let block = &ctx.heap[target_scope.to_block()];
 
        let mut scope = block.scope_node.parent;
 
        let mut cur_relative_pos = block.scope_node.relative_pos_in_parent;
 
        loop {
 
            // We immediately go to the parent scope. We check the current scope
 
            // in the call at the end. Likewise for checking the symbol table.
 
            let block = &ctx.heap[scope.to_block()];
 

	
 
            scope = block.scope_node.parent;
 
            if let Scope::Definition(definition_id) = scope {
 
                // At outer scope, check parameters of function/component
 
                for parameter_id in ctx.heap[definition_id].parameters() {
 
                    let parameter = &ctx.heap[*parameter_id];
 
                    if local.identifier == parameter.identifier {
 
                        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()].scope_node.relative_pos_in_parent;
 
            let block = &ctx.heap[scope.to_block()];
 

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

	
 
            scope = block.scope_node.parent;
 
            cur_relative_pos = block.scope_node.relative_pos_in_parent;
 
        }
 

	
 
        // No collisions in any of the parent scope, attempt to add to scope
 
        self.checked_at_single_scope_add_local(ctx, target_scope, relative_pos, id)
 
        self.checked_at_single_scope_add_local(ctx, target_scope, target_relative_pos, id)
 
    }
 

	
 
    /// Adds a local variable to the specified scope. Will check the specified
 
    /// scope for variable conflicts and the symbol table for global conflicts.
 
    /// Will NOT check parent scopes of the specified scope.
 
    fn checked_at_single_scope_add_local(
 
        &mut self, ctx: &mut Ctx, scope: Scope, relative_pos: i32, id: VariableId
 
    ) -> Result<(), ParseError> {
 
        // Check the symbol table for conflicts
 
        {
 
            let cur_scope = SymbolScope::Definition(self.def_type.definition_id());
 
            let ident = &ctx.heap[id].identifier;
 
            if let Some(symbol) = ctx.symbols.get_symbol_by_name(cur_scope, &ident.value.as_bytes()) {
 
                return Err(ParseError::new_error_str_at_span(
 
                    &ctx.module().source, ident.span,
 
                    "local variable declaration conflicts with symbol"
 
                ).with_info_str_at_span(
 
                    &ctx.module().source, symbol.variant.span_of_introduction(&ctx.heap), "the conflicting symbol is introduced here"
 
                ));
 
            }
 
        }
 

	
 
        // 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
 
        let block = &mut ctx.heap[scope.to_block()];
 
        block.locals.push(id);
 

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

	
 
        Ok(())
 
    }
 

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

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

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

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

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

	
 
        // Make sure label is not defined within the current scope or any of the
 
        // parent scope.
 
        let label = &mut ctx.heap[id];
 
        label.relative_pos_in_block = relative_pos;
 
        label.in_sync = in_sync;
 

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

	
 
        loop {
 
            debug_assert!(scope.is_block(), "scope is not a block");
 
            let block = &ctx.heap[scope.to_block()];
 
            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.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(())
 
    }
 

	
 
    /// Finds a particular labeled statement by its identifier. Once found it
 
    /// will make sure that the target label does not skip over any variable
 
    /// declarations within the scope in which the label was found.
 
    fn find_label(mut scope: Scope, ctx: &Ctx, identifier: &Identifier) -> Result<LabeledStatementId, ParseError> {
 
        debug_assert!(scope.is_block());
 

	
 
        loop {
 
            debug_assert!(scope.is_block(), "scope is not a block");
 
            let relative_scope_pos = ctx.heap[scope.to_block()].scope_node.relative_pos_in_parent;
 

	
 
            let block = &ctx.heap[scope.to_block()];
 
            for label_id in &block.labels {
 
                let label = &ctx.heap[*label_id];
 
                if label.label == *identifier {
 
                    for local_id in &block.locals {
 
                        // TODO: Better to do this in control flow analysis, it
 
                        //  is legal to skip over a variable declaration if it
 
                        //  is not actually being used. I might be missing
 
                        //  something here when laying out the bytecode...
 
                        let local = &ctx.heap[*local_id];
 
                        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.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(mut scope: Scope, ctx: &Ctx, id: WhileStatementId) -> bool {
 
        let while_stmt = &ctx.heap[id];
 
        loop {
 
            debug_assert!(scope.is_block());
 
            let block = scope.to_block();
 
            if while_stmt.body == block {
src/protocol/tests/parser_validation.rs
Show inline comments
 
@@ -462,307 +462,322 @@ fn test_incorrect_tuple_polymorph_args() {
 
        .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(
 
        "guard variable decl",
 
        "
 
        primitive f() {
 
            channel<u32> unused -> input;
 

	
 
            u32 outer_value = 0;
 
            sync select {
 
                auto in_same_guard = get(input) -> {} // decl A1
 
                auto in_same_gaurd = get(input) -> {} // decl A2
 
                auto in_guard_and_block = get(input) -> {} // decl B1
 
                outer_value = get(input) -> { auto in_guard_and_block = outer_value; } // decl B2
 
            }
 
        }
 
        "
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "empty select",
 
        "primitive f() { sync select {} }"
 
    );
 

	
 
    Tester::new_single_source_expect_ok(
 
        "mixed uses", "
 
        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(
 
    Tester::new_single_source_expect_err(
 
        "variable in previous block",
 
        "primitive f() {
 
            channel<u32> tx -> rx;
 
            u32 a = 0; // this one will be shadowed
 
            sync select { auto a = get(rx) -> {} }
 
        }"
 
    );
 
    ).error(|e| { e
 
        .assert_num(2)
 
        .assert_occurs_at(0, "a = get").assert_msg_has(0, "variable name conflicts")
 
        .assert_occurs_at(1, "a = 0").assert_msg_has(1, "Previous variable");
 
    });
 

	
 
    Tester::new_single_source_expect_err(
 
        "put inside arm",
 
        "primitive f() {
 
            channel<u32> a -> b;
 
            sync select { put(a) -> {} }
 
        }"
 
    ).error(|e| { e
 
        .assert_occurs_at(0, "put")
 
        .assert_msg_has(0, "may not occur");
 
    });
 
}
 

	
 
#[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)