Changeset - fe0efc81bd4e
[Not reviewed]
src/collections/scoped_buffer.rs
Show inline comments
 
@@ -74,12 +74,13 @@ impl<T: Sized> ScopedSection<T> {
 
        let vec = unsafe{&mut *self.inner};
 
        #[cfg(debug_assertions)] debug_assert_eq!(vec.len(), self.cur_size as usize, "trying to get section length, but size is larger than expected");
 
        return vec.len() - self.start_size as usize;
 
    }
 

	
 
    #[inline]
 
    #[allow(unused_mut)] // used in debug mode
 
    pub(crate) fn forget(mut self) {
 
        let vec = unsafe{&mut *self.inner};
 
        #[cfg(debug_assertions)] {
 
            debug_assert_eq!(
 
                vec.len(), self.cur_size as usize,
 
                "trying to forget section, but size is larger than expected"
 
@@ -87,12 +88,13 @@ impl<T: Sized> ScopedSection<T> {
 
            self.cur_size = self.start_size;
 
        }
 
        vec.truncate(self.start_size as usize);
 
    }
 

	
 
    #[inline]
 
    #[allow(unused_mut)] // used in debug mode
 
    pub(crate) fn into_vec(mut self) -> Vec<T> {
 
        let vec = unsafe{&mut *self.inner};
 
        #[cfg(debug_assertions)]  {
 
            debug_assert_eq!(
 
                vec.len(), self.cur_size as usize,
 
                "trying to turn section into vec, but size is larger than expected"
src/common.rs
Show inline comments
 
@@ -12,13 +12,12 @@ pub(crate) use maplit::hashmap;
 
pub(crate) use mio::{
 
    net::{TcpListener, TcpStream},
 
    Events, Interest, Poll, Token,
 
};
 
pub(crate) use std::{
 
    collections::{BTreeMap, HashMap, HashSet},
 
    convert::TryInto,
 
    io::{Read, Write},
 
    net::SocketAddr,
 
    sync::Arc,
 
    time::Instant,
 
};
 
pub(crate) use Polarity::*;
src/protocol/arena.rs
Show inline comments
 
@@ -54,12 +54,21 @@ impl<T> Arena<T> {
 
        assert!(self.store.len() < i32::max_value() as usize, "Arena out of capacity");
 
        let id = Id::new(self.store.len() as i32);
 
        self.store.push(f(id));
 
        id
 
    }
 

	
 
    // Compiler-internal direct retrieval
 
    pub(crate) fn get(&self, idx: usize) -> &T {
 
        return &self.store[idx]
 
    }
 
    pub(crate) fn get_id(&self, idx: usize) -> Id<T> {
 
        debug_assert!(idx < self.store.len());
 
        return Id::new(idx as i32);
 
    }
 

	
 
    pub fn iter(&self) -> impl Iterator<Item = &T> {
 
        self.store.iter()
 
    }
 

	
 
    pub fn len(&self) -> usize {
 
        self.store.len()
src/protocol/ast.rs
Show inline comments
 
@@ -1874,9 +1874,10 @@ pub struct LiteralUnion {
 
pub struct VariableExpression {
 
    pub this: VariableExpressionId,
 
    // Parsing
 
    pub identifier: Identifier,
 
    // Validator/Linker
 
    pub declaration: Option<VariableId>,
 
    pub used_as_binding_target: bool,
 
    pub parent: ExpressionParent,
 
    pub unique_id_in_definition: i32,
 
}
 
\ No newline at end of file
src/protocol/ast_printer.rs
Show inline comments
 
@@ -48,12 +48,13 @@ const PREFIX_CONDITIONAL_EXPR_ID: &'static str = "ECnd";
 
const PREFIX_BINARY_EXPR_ID: &'static str = "EBin";
 
const PREFIX_UNARY_EXPR_ID: &'static str = "EUna";
 
const PREFIX_INDEXING_EXPR_ID: &'static str = "EIdx";
 
const PREFIX_SLICING_EXPR_ID: &'static str = "ESli";
 
const PREFIX_SELECT_EXPR_ID: &'static str = "ESel";
 
const PREFIX_LITERAL_EXPR_ID: &'static str = "ELit";
 
const PREFIX_CAST_EXPR_ID: &'static str = "ECas";
 
const PREFIX_CALL_EXPR_ID: &'static str = "ECll";
 
const PREFIX_VARIABLE_EXPR_ID: &'static str = "EVar";
 

	
 
struct KV<'a> {
 
    buffer: &'a mut String,
 
    prefix: Option<(&'static str, i32)>,
 
@@ -702,13 +703,20 @@ impl ASTWriter {
 
                }
 

	
 
                self.kv(indent2).with_s_key("Parent")
 
                    .with_custom_val(|v| write_expression_parent(v, &expr.parent));
 
            },
 
            Expression::Cast(expr) => {
 
                todo!("print casting expression")
 
                self.kv(indent).with_id(PREFIX_CAST_EXPR_ID, expr.this.0.index)
 
                    .with_s_key("CallExpr");
 
                self.kv(indent2).with_s_key("ToType")
 
                    .with_custom_val(|t| write_parser_type(t, heap, &expr.to_type));
 
                self.kv(indent2).with_s_key("Subject");
 
                self.write_expr(heap, expr.subject, indent3);
 
                self.kv(indent2).with_s_key("Parent")
 
                    .with_custom_val(|v| write_expression_parent(v, &expr.parent));
 
            }
 
            Expression::Call(expr) => {
 
                self.kv(indent).with_id(PREFIX_CALL_EXPR_ID, expr.this.0.index)
 
                    .with_s_key("CallExpr");
 

	
 
                let definition = &heap[expr.definition];
src/protocol/eval/executor.rs
Show inline comments
 
@@ -28,30 +28,51 @@ pub(crate) enum ExprInstruction {
 
pub(crate) struct Frame {
 
    pub(crate) definition: DefinitionId,
 
    pub(crate) monomorph_idx: i32,
 
    pub(crate) position: StatementId,
 
    pub(crate) expr_stack: VecDeque<ExprInstruction>, // hack for expression evaluation, evaluated by popping from back
 
    pub(crate) expr_values: VecDeque<Value>, // hack for expression results, evaluated by popping from front/back
 
    pub(crate) max_stack_size: u32,
 
}
 

	
 
impl Frame {
 
    /// Creates a new execution frame. Does not modify the stack in any way.
 
    pub fn new(heap: &Heap, definition_id: DefinitionId, monomorph_idx: i32) -> Self {
 
        let definition = &heap[definition_id];
 
        let first_statement = match definition {
 
            Definition::Component(definition) => definition.body,
 
            Definition::Function(definition) => definition.body,
 
            _ => unreachable!("initializing frame with {:?} instead of a function/component", definition),
 
        };
 

	
 
        // Another not-so-pretty thing that has to be replaced somewhere in the
 
        // future...
 
        fn determine_max_stack_size(heap: &Heap, block_id: BlockStatementId, max_size: &mut u32) {
 
            let block_stmt = &heap[block_id];
 
            debug_assert!(block_stmt.next_unique_id_in_scope >= 0);
 

	
 
            // Check current block
 
            let cur_size = block_stmt.next_unique_id_in_scope as u32;
 
            if cur_size > *max_size { *max_size = cur_size; }
 

	
 
            // And child blocks
 
            for child_scope in &block_stmt.scope_node.nested {
 
                determine_max_stack_size(heap, child_scope.to_block(), max_size);
 
            }
 
        }
 

	
 
        let mut max_stack_size = 0;
 
        determine_max_stack_size(heap, first_statement, &mut max_stack_size);
 

	
 
        Frame{
 
            definition: definition_id,
 
            monomorph_idx,
 
            position: first_statement.upcast(),
 
            expr_stack: VecDeque::with_capacity(128),
 
            expr_values: VecDeque::with_capacity(128),
 
            max_stack_size,
 
        }
 
    }
 

	
 
    /// Prepares a single expression for execution. This involves walking the
 
    /// expression tree and putting them in the `expr_stack` such that
 
    /// continuously popping from its back will evaluate the expression. The
 
@@ -86,13 +107,14 @@ impl Frame {
 
        match &heap[id] {
 
            Expression::Assignment(expr) => {
 
                self.serialize_expression(heap, expr.left);
 
                self.serialize_expression(heap, expr.right);
 
            },
 
            Expression::Binding(expr) => {
 
                todo!("implement binding expression");
 
                self.serialize_expression(heap, expr.bound_to);
 
                self.serialize_expression(heap, expr.bound_from);
 
            },
 
            Expression::Conditional(expr) => {
 
                self.serialize_expression(heap, expr.test);
 
            },
 
            Expression::Binary(expr) => {
 
                self.serialize_expression(heap, expr.left);
 
@@ -196,14 +218,17 @@ impl Prompt {
 
            frames: Vec::new(),
 
            store: Store::new(),
 
        };
 

	
 
        // Maybe do typechecking in the future?
 
        debug_assert!((monomorph_idx as usize) < _types.get_base_definition(&def).unwrap().definition.procedure_monomorphs().len());
 
        prompt.frames.push(Frame::new(heap, def, monomorph_idx));
 
        let new_frame = Frame::new(heap, def, monomorph_idx);
 
        let max_stack_size = new_frame.max_stack_size;
 
        prompt.frames.push(new_frame);
 
        args.into_store(&mut prompt.store);
 
        prompt.store.reserve_stack(max_stack_size);
 

	
 
        prompt
 
    }
 

	
 
    pub(crate) fn step(&mut self, types: &TypeTable, heap: &Heap, modules: &[Module], ctx: &mut EvalContext) -> EvalResult {
 
        // Helper function to transfer multiple values from the expression value
 
@@ -276,13 +301,21 @@ impl Prompt {
 
                            // of the right-hand side value when possible. So we do not drop the
 
                            // rhs's optionally owned heap data.
 
                            let rhs = self.store.read_take_ownership(rhs);
 
                            apply_assignment_operator(&mut self.store, to, expr.operation, rhs);
 
                        },
 
                        Expression::Binding(_expr) => {
 
                            todo!("Binding expression");
 
                            let bind_to = cur_frame.expr_values.pop_back().unwrap();
 
                            let bind_from = cur_frame.expr_values.pop_back().unwrap();
 
                            let bind_to_heap_pos = bind_to.get_heap_pos();
 
                            let bind_from_heap_pos = bind_from.get_heap_pos();
 

	
 
                            let result = apply_binding_operator(&mut self.store, bind_to, bind_from);
 
                            self.store.drop_value(bind_to_heap_pos);
 
                            self.store.drop_value(bind_from_heap_pos);
 
                            cur_frame.expr_values.push_back(Value::Bool(result));
 
                        },
 
                        Expression::Conditional(expr) => {
 
                            // Evaluate testing expression, then extend the
 
                            // expression stack with the appropriate expression
 
                            let test_result = cur_frame.expr_values.pop_back().unwrap().as_bool();
 
                            if test_result {
 
@@ -506,12 +539,34 @@ impl Prompt {
 
                                }
 
                            }
 

	
 
                            self.store.drop_value(subject.get_heap_pos());
 
                        }
 
                        Expression::Call(expr) => {
 
                            // If we're dealing with a builtin we don't do any
 
                            // fancy shenanigans at all, just push the result.
 
                            match expr.method {
 
                                Method::Length => {
 
                                    let value = cur_frame.expr_values.pop_front().unwrap();
 
                                    let value_heap_pos = value.get_heap_pos();
 
                                    let value = self.store.maybe_read_ref(&value);
 

	
 
                                    let heap_pos = match value {
 
                                        Value::Array(pos) => *pos,
 
                                        Value::String(pos) => *pos,
 
                                        _ => unreachable!("length(...) on {:?}", value),
 
                                    };
 

	
 
                                    let len = self.store.heap_regions[heap_pos as usize].values.len();
 

	
 
                                    // TODO: @PtrInt
 
                                    cur_frame.expr_values.push_back(Value::UInt32(len as u32));
 
                                    self.store.drop_value(value_heap_pos);
 
                                },
 
                                Method::UserComponent => todo!("component creation"),
 
                                Method::UserFunction => {
 
                                    // Push a new frame. Note that all expressions have
 
                                    // been pushed to the front, so they're in the order
 
                                    // of the definition.
 
                                    let num_args = expr.arguments.len();
 

	
 
                                    // Determine stack boundaries
 
@@ -526,32 +581,48 @@ impl Prompt {
 
                                    }
 

	
 
                                    // Determine the monomorph index of the function we're calling
 
                                    let mono_data = types.get_procedure_expression_data(&cur_frame.definition, cur_frame.monomorph_idx);
 
                                    let call_data = &mono_data.expr_data[expr.unique_id_in_definition as usize];
 

	
 
                            // Push the new frame
 
                            self.frames.push(Frame::new(heap, expr.definition, call_data.field_or_monomorph_idx));
 
                                    // Push the new frame and reserve its stack size
 
                                    let new_frame = Frame::new(heap, expr.definition, call_data.field_or_monomorph_idx);
 
                                    let new_stack_size = new_frame.max_stack_size;
 
                                    self.frames.push(new_frame);
 
                                    self.store.cur_stack_boundary = new_stack_boundary;
 
                                    self.store.reserve_stack(new_stack_size);
 

	
 
                                    // To simplify the logic a little bit we will now
 
                                    // return and ask our caller to call us again
 
                                    return Ok(EvalContinuation::Stepping);
 
                                },
 
                                _ => todo!("other builtins"),
 
                            }
 
                        },
 
                        Expression::Variable(expr) => {
 
                            let variable = &heap[expr.declaration.unwrap()];
 
                            cur_frame.expr_values.push_back(Value::Ref(ValueId::Stack(variable.unique_id_in_scope as StackPos)));
 
                            let ref_value = if expr.used_as_binding_target {
 
                                Value::Binding(variable.unique_id_in_scope as StackPos)
 
                            } else {
 
                                Value::Ref(ValueId::Stack(variable.unique_id_in_scope as StackPos))
 
                            };
 
                            cur_frame.expr_values.push_back(ref_value);
 
                        }
 
                    }
 
                }
 
            }
 
        }
 

	
 
        debug_log!("Frame [{:?}] at {:?}, stack size = {}", cur_frame.definition, cur_frame.position, self.store.stack.len());
 
        debug_log!("Frame [{:?}] at {:?}", cur_frame.definition, cur_frame.position);
 
        if debug_enabled!() {
 
            debug_log!("Stack:");
 
            debug_log!("Expression value stack (size = {}):", cur_frame.expr_values.len());
 
            for (stack_idx, stack_val) in cur_frame.expr_values.iter().enumerate() {
 
                debug_log!("  [{:03}] {:?}", stack_idx, stack_val);
 
            }
 

	
 
            debug_log!("Stack (size = {}):", self.store.stack.len());
 
            for (stack_idx, stack_val) in self.store.stack.iter().enumerate() {
 
                debug_log!("  [{:03}] {:?}", stack_idx, stack_val);
 
            }
 

	
 
            debug_log!("Heap:");
 
            for (heap_idx, heap_region) in self.store.heap_regions.iter().enumerate() {
 
@@ -561,18 +632,13 @@ impl Prompt {
 
        }
 
        // No (more) expressions to evaluate. So evaluate statement (that may
 
        // depend on the result on the last evaluated expression(s))
 
        let stmt = &heap[cur_frame.position];
 
        let return_value = match stmt {
 
            Statement::Block(stmt) => {
 
                // Reserve space on stack, but also make sure excess stack space
 
                // is cleared
 
                self.store.clear_stack(stmt.first_unique_id_in_scope as usize);
 
                self.store.reserve_stack(stmt.next_unique_id_in_scope as usize);
 
                cur_frame.position = stmt.statements[0];
 

	
 
                Ok(EvalContinuation::Stepping)
 
            },
 
            Statement::EndBlock(stmt) => {
 
                let block = &heap[stmt.start_block];
 
                self.store.clear_stack(block.first_unique_id_in_scope as usize);
 
                cur_frame.position = stmt.next;
 
@@ -673,12 +739,13 @@ impl Prompt {
 

	
 
                // Pre-emptively pop our stack frame
 
                self.frames.pop();
 

	
 
                // Clean up our section of the stack
 
                self.store.clear_stack(0);
 
                self.store.stack.truncate(self.store.cur_stack_boundary + 1);
 
                let prev_stack_idx = self.store.stack.pop().unwrap().as_stack_boundary();
 

	
 
                // TODO: Temporary hack for testing, remove at some point
 
                if self.frames.is_empty() {
 
                    debug_assert!(prev_stack_idx == -1);
 
                    debug_assert!(self.store.stack.len() == 0);
src/protocol/eval/store.rs
Show inline comments
 
@@ -36,28 +36,28 @@ impl Store {
 
        store
 
    }
 

	
 
    /// Resizes(!) the stack to fit the required number of values. Any
 
    /// unallocated slots are initialized to `Unassigned`. The specified stack
 
    /// index is exclusive.
 
    pub(crate) fn reserve_stack(&mut self, unique_stack_idx: usize) {
 
        let new_size = self.cur_stack_boundary + unique_stack_idx + 1;
 
    pub(crate) fn reserve_stack(&mut self, unique_stack_idx: u32) {
 
        let new_size = self.cur_stack_boundary + unique_stack_idx as usize + 1;
 
        if new_size > self.stack.len() {
 
            self.stack.resize(new_size, Value::Unassigned);
 
        }
 
    }
 

	
 
    /// Clears values on the stack and removes their heap allocations when
 
    /// applicable. The specified index itself will also be cleared (so if you
 
    /// specify 0 all values in the frame will be destroyed)
 
    pub(crate) fn clear_stack(&mut self, unique_stack_idx: usize) {
 
        let new_size = self.cur_stack_boundary + unique_stack_idx + 1;
 
        for idx in new_size..self.stack.len() {
 
            self.drop_value(self.stack[idx].get_heap_pos());
 
            self.stack[idx] = Value::Unassigned;
 
        }
 
        self.stack.truncate(new_size);
 
    }
 

	
 
    /// Reads a value and takes ownership. This is different from a move because
 
    /// the value might indirectly reference stack/heap values. For these kinds
 
    /// values we will actually return a cloned value.
 
    pub(crate) fn read_take_ownership(&mut self, value: Value) -> Value {
src/protocol/eval/value.rs
Show inline comments
 
@@ -26,12 +26,13 @@ pub enum ValueId {
 
#[derive(Debug, Clone)]
 
pub enum Value {
 
    // Special types, never encountered during evaluation if the compiler works correctly
 
    Unassigned,                 // Marker when variables are first declared, immediately followed by assignment
 
    PrevStackBoundary(isize),   // Marker for stack frame beginning, so we can pop stack values
 
    Ref(ValueId),               // Reference to a value, used by expressions producing references
 
    Binding(StackPos),          // Reference to a binding variable (reserved on the stack)
 
    // Builtin types
 
    Input(PortId),
 
    Output(PortId),
 
    Message(HeapPos),
 
    Null,
 
    Bool(bool),
 
@@ -528,13 +529,13 @@ pub(crate) fn apply_casting(store: &mut Store, output_type: &ConcreteType, subje
 
                CTP::SInt16 => Value::SInt16($input as i16),
 
                CTP::SInt32 => Value::SInt32($input as i32),
 
                CTP::SInt64 => Value::SInt64($input as i64),
 
                _ => unreachable!()
 
            })
 
        }
 
    };
 
    }
 

	
 
    macro_rules! from_unsigned_cast {
 
        ($input:expr, $input_type:ty, $output_part:expr) => {
 
            {
 
                let target_type_name = match $output_part {
 
                    CTP::Bool => return Ok(Value::Bool($input != 0)),
 
@@ -703,12 +704,13 @@ pub(crate) fn apply_casting(store: &mut Store, output_type: &ConcreteType, subje
 
        Value::SInt32(val) => from_signed_cast!(*val, i32, part),
 
        Value::SInt64(val) => from_signed_cast!(*val, i64, part),
 
        _ => unreachable!("mismatch between 'cast' type checking and 'cast' evaluation"),
 
    }
 
}
 

	
 
/// Recursively checks for equality.
 
pub(crate) fn apply_equality_operator(store: &Store, lhs: &Value, rhs: &Value) -> bool {
 
    let lhs = store.maybe_read_ref(lhs);
 
    let rhs = store.maybe_read_ref(rhs);
 

	
 
    fn eval_equality_heap(store: &Store, lhs_pos: HeapPos, rhs_pos: HeapPos) -> bool {
 
        let lhs_vals = &store.heap_regions[lhs_pos as usize].values;
 
@@ -756,12 +758,13 @@ pub(crate) fn apply_equality_operator(store: &Store, lhs: &Value, rhs: &Value) -
 
        },
 
        Value::Struct(lhs_pos) => eval_equality_heap(store, *lhs_pos, rhs.as_struct()),
 
        _ => unreachable!("apply_equality_operator to lhs {:?}", lhs),
 
    }
 
}
 

	
 
/// Recursively checks for inequality
 
pub(crate) fn apply_inequality_operator(store: &Store, lhs: &Value, rhs: &Value) -> bool {
 
    let lhs = store.maybe_read_ref(lhs);
 
    let rhs = store.maybe_read_ref(rhs);
 

	
 
    fn eval_inequality_heap(store: &Store, lhs_pos: HeapPos, rhs_pos: HeapPos) -> bool {
 
        let lhs_vals = &store.heap_regions[lhs_pos as usize].values;
 
@@ -807,6 +810,71 @@ pub(crate) fn apply_inequality_operator(store: &Store, lhs: &Value, rhs: &Value)
 
            eval_inequality_heap(store, *lhs_pos, rhs_pos)
 
        },
 
        Value::String(lhs_pos) => eval_inequality_heap(store, *lhs_pos, rhs.as_struct()),
 
        _ => unreachable!("apply_inequality_operator to lhs {:?}", lhs)
 
    }
 
}
 

	
 
/// Recursively applies binding operator. Essentially an equality operator with
 
/// special handling if the LHS contains a binding reference to a stack
 
/// stack variable.
 
// Note: that there is a lot of `Value.clone()` going on here. As always: this
 
// is potentially cloning the references to heap values, not actually cloning
 
// those heap regions into a new heap region.
 
pub(crate) fn apply_binding_operator(store: &mut Store, lhs: Value, rhs: Value) -> bool {
 
    let lhs = store.maybe_read_ref(&lhs).clone();
 
    let rhs = store.maybe_read_ref(&rhs).clone();
 

	
 
    fn eval_binding_heap(store: &mut Store, lhs_pos: HeapPos, rhs_pos: HeapPos) -> bool {
 
        let lhs_len = store.heap_regions[lhs_pos as usize].values.len();
 
        let rhs_len = store.heap_regions[rhs_pos as usize].values.len();
 
        if lhs_len != rhs_len {
 
            return false;
 
        }
 

	
 
        for idx in 0..lhs_len {
 
            // More rust shenanigans... I'm going to calm myself by saying that
 
            // this is just a temporary evaluator implementation.
 
            let lhs_val = store.heap_regions[lhs_pos as usize].values[idx].clone();
 
            let rhs_val = store.heap_regions[rhs_pos as usize].values[idx].clone();
 
            if !apply_binding_operator(store, lhs_val, rhs_val) {
 
                return false;
 
            }
 
        }
 

	
 
        return true;
 
    }
 

	
 
    match lhs {
 
        Value::Binding(var_pos) => {
 
            let to_write = store.clone_value(rhs.clone());
 
            store.write(ValueId::Stack(var_pos), to_write);
 
            return true;
 
        },
 
        Value::Input(v) => v == rhs.as_input(),
 
        Value::Output(v) => v == rhs.as_output(),
 
        Value::Message(lhs_pos) => eval_binding_heap(store, lhs_pos, rhs.as_message()),
 
        Value::Null => todo!("remove null"),
 
        Value::Bool(v) => v == rhs.as_bool(),
 
        Value::Char(v) => v == rhs.as_char(),
 
        Value::String(lhs_pos) => eval_binding_heap(store, lhs_pos, rhs.as_string()),
 
        Value::UInt8(v) => v == rhs.as_uint8(),
 
        Value::UInt16(v) => v == rhs.as_uint16(),
 
        Value::UInt32(v) => v == rhs.as_uint32(),
 
        Value::UInt64(v) => v == rhs.as_uint64(),
 
        Value::SInt8(v) => v == rhs.as_sint8(),
 
        Value::SInt16(v) => v == rhs.as_sint16(),
 
        Value::SInt32(v) => v == rhs.as_sint32(),
 
        Value::SInt64(v) => v == rhs.as_sint64(),
 
        Value::Array(lhs_pos) => eval_binding_heap(store, lhs_pos, rhs.as_array()),
 
        Value::Enum(v) => v == rhs.as_enum(),
 
        Value::Union(lhs_tag, lhs_pos) => {
 
            let (rhs_tag, rhs_pos) = rhs.as_union();
 
            if lhs_tag != rhs_tag {
 
                return false;
 
            }
 
            eval_binding_heap(store, lhs_pos, rhs_pos)
 
        },
 
        Value::Struct(lhs_pos) => eval_binding_heap(store, lhs_pos, rhs.as_struct()),
 
        _ => unreachable!("apply_binding_operator to lhs {:?}", lhs),
 
    }
 
}
 
\ No newline at end of file
src/protocol/parser/mod.rs
Show inline comments
 
@@ -126,13 +126,13 @@ impl Parser {
 
            quick_type(&[PTV::ArrayLike, PTV::PolymorphicArgument(id.upcast(), 0)])
 
        ));
 
        insert_builtin_function(&mut parser, "length", &["T"], |id| (
 
            vec![
 
                ("array", quick_type(&[PTV::ArrayLike, PTV::PolymorphicArgument(id.upcast(), 0)]))
 
            ],
 
            quick_type(&[PTV::IntegerLike])
 
            quick_type(&[PTV::UInt32]) // TODO: @PtrInt
 
        ));
 
        insert_builtin_function(&mut parser, "assert", &[], |id| (
 
            vec![
 
                ("condition", quick_type(&[PTV::Bool])),
 
            ],
 
            quick_type(&[PTV::Void])
src/protocol/parser/pass_definitions.rs
Show inline comments
 
@@ -346,13 +346,12 @@ impl PassDefinitions {
 
            // Not a block statement, so wrap it in one
 
            let mut statements = self.statements.start_section();
 
            let wrap_begin_pos = iter.last_valid_pos();
 
            self.consume_statement(module, iter, ctx, &mut statements)?;
 
            let wrap_end_pos = iter.last_valid_pos();
 

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

	
 
            let id = ctx.heap.alloc_block_statement(|this| BlockStatement{
 
                this,
 
                is_implicit: true,
 
                span: InputSpan::from_positions(wrap_begin_pos, wrap_end_pos), // TODO: @Span
 
@@ -832,12 +831,13 @@ impl PassDefinitions {
 

	
 
                // Allocate the initial assignment
 
                let variable_expr_id = ctx.heap.alloc_variable_expression(|this| VariableExpression{
 
                    this,
 
                    identifier,
 
                    declaration: None,
 
                    used_as_binding_target: false,
 
                    parent: ExpressionParent::None,
 
                    unique_id_in_definition: -1,
 
                });
 
                let assignment_expr_id = ctx.heap.alloc_assignment_expression(|this| AssignmentExpression{
 
                    this,
 
                    span: assign_span,
 
@@ -1533,12 +1533,13 @@ impl PassDefinitions {
 
                    let identifier = Identifier { span: ident_span, value: ident_text };
 

	
 
                    ctx.heap.alloc_variable_expression(|this| VariableExpression {
 
                        this,
 
                        identifier,
 
                        declaration: None,
 
                        used_as_binding_target: false,
 
                        parent: ExpressionParent::None,
 
                        unique_id_in_definition: -1,
 
                    }).upcast()
 
                }
 
            }
 
        } else {
src/protocol/parser/pass_typing.rs
Show inline comments
 
@@ -482,15 +482,24 @@ impl InferenceType {
 
        }
 
    }
 

	
 
    /// Attempts to infer the first subtree based on the template. Like
 
    /// `infer_subtrees_for_both_types`, but now only applying inference to
 
    /// `to_infer` based on the type information in `template`.
 
    ///
 
    /// The `forced_template` flag controls whether `to_infer` is considered
 
    /// valid if it is more specific then the template. When `forced_template`
 
    /// is false, then as long as the `to_infer` and `template` types are
 
    /// compatible the inference will succeed. If `forced_template` is true,
 
    /// then `to_infer` MUST be less specific than `template` (e.g.
 
    /// `IntegerLike` is less specific than `UInt32`. Likewise `Bool` is less
 
    /// specific than `BindingBool`)
 
    fn infer_subtree_for_single_type(
 
        to_infer: &mut InferenceType, mut to_infer_idx: usize,
 
        template: &[InferenceTypePart], mut template_idx: usize,
 
        forced_template: bool,
 
    ) -> SingleInferenceResult {
 
        let mut modified = false;
 
        let mut depth = 1;
 

	
 
        while depth > 0 {
 
            let to_infer_part = &to_infer.parts[to_infer_idx];
 
@@ -514,20 +523,22 @@ impl InferenceType {
 
            ) {
 
                depth += depth_change;
 
                modified = true;
 
                continue;
 
            }
 

	
 
            if !forced_template {
 
                // We cannot infer anything, but the template may still be
 
                // compatible with the type we're inferring
 
                if let Some(depth_change) = Self::check_part_for_single_type(
 
                    template, &mut template_idx, &to_infer.parts, &mut to_infer_idx
 
                ) {
 
                    depth += depth_change;
 
                    continue;
 
                }
 
            }
 

	
 
            return SingleInferenceResult::Incompatible
 
        }
 

	
 
        if modified {
 
            to_infer.recompute_is_done();
 
@@ -1627,16 +1638,16 @@ impl PassTyping {
 

	
 
        // Apply forced constraint to LHS value
 
        let progress_forced = match expr.operation {
 
            AO::Set =>
 
                false,
 
            AO::Multiplied | AO::Divided | AO::Added | AO::Subtracted =>
 
                self.apply_forced_constraint(ctx, arg1_expr_id, &NUMBERLIKE_TEMPLATE)?,
 
                self.apply_template_constraint(ctx, arg1_expr_id, &NUMBERLIKE_TEMPLATE)?,
 
            AO::Remained | AO::ShiftedLeft | AO::ShiftedRight |
 
            AO::BitwiseAnded | AO::BitwiseXored | AO::BitwiseOred =>
 
                self.apply_forced_constraint(ctx, arg1_expr_id, &INTEGERLIKE_TEMPLATE)?,
 
                self.apply_template_constraint(ctx, arg1_expr_id, &INTEGERLIKE_TEMPLATE)?,
 
        };
 

	
 
        let (progress_arg1, progress_arg2) = self.apply_equal2_constraint(
 
            ctx, upcast_id, arg1_expr_id, 0, arg2_expr_id, 0
 
        )?;
 
        debug_assert!(if progress_forced { progress_arg2 } else { true });
 
@@ -1717,29 +1728,29 @@ impl PassTyping {
 
        debug_log!("   - Arg2 type: {}", self.temp_get_display_name(ctx, arg2_id));
 
        debug_log!("   - Expr type: {}", self.temp_get_display_name(ctx, upcast_id));
 

	
 
        let (progress_expr, progress_arg1, progress_arg2) = match expr.operation {
 
            BO::Concatenate => {
 
                // Arguments may be arrays/slices, output is always an array
 
                let progress_expr = self.apply_forced_constraint(ctx, upcast_id, &ARRAY_TEMPLATE)?;
 
                let progress_arg1 = self.apply_forced_constraint(ctx, arg1_id, &ARRAYLIKE_TEMPLATE)?;
 
                let progress_arg2 = self.apply_forced_constraint(ctx, arg2_id, &ARRAYLIKE_TEMPLATE)?;
 
                let progress_expr = self.apply_template_constraint(ctx, upcast_id, &ARRAY_TEMPLATE)?;
 
                let progress_arg1 = self.apply_template_constraint(ctx, arg1_id, &ARRAYLIKE_TEMPLATE)?;
 
                let progress_arg2 = self.apply_template_constraint(ctx, arg2_id, &ARRAYLIKE_TEMPLATE)?;
 

	
 
                // If they're all arraylike, then we want the subtype to match
 
                let (subtype_expr, subtype_arg1, subtype_arg2) =
 
                    self.apply_equal3_constraint(ctx, upcast_id, arg1_id, arg2_id, 1)?;
 

	
 
                (progress_expr || subtype_expr, progress_arg1 || subtype_arg1, progress_arg2 || subtype_arg2)
 
            },
 
            BO::LogicalAnd => {
 
                // Logical AND may operate both on normal booleans and on
 
                // booleans that are the result of a binding expression. So we
 
                // force the expression to bool-like, then apply an equal-3
 
                // force the expression to bool-like, then apply an equal_3
 
                // constraint. Any BindingBool will promote all the other Bool
 
                // types.
 
                let base_expr = self.apply_forced_constraint(ctx, upcast_id, &BOOLLIKE_TEMPLATE)?;
 
                let base_expr = self.apply_template_constraint(ctx, upcast_id, &BOOLLIKE_TEMPLATE)?;
 
                let (progress_expr, progress_arg1, progress_arg2) =
 
                    self.apply_equal3_constraint(ctx, upcast_id, arg1_id, arg2_id, 0)?;
 

	
 
                (base_expr || progress_expr, progress_arg1, progress_arg2)
 
            },
 
            BO::LogicalOr => {
 
@@ -1749,38 +1760,38 @@ impl PassTyping {
 
                let progress_arg2 = self.apply_forced_constraint(ctx, arg2_id, &BOOL_TEMPLATE)?;
 

	
 
                (progress_expr, progress_arg1, progress_arg2)
 
            },
 
            BO::BitwiseOr | BO::BitwiseXor | BO::BitwiseAnd | BO::Remainder | BO::ShiftLeft | BO::ShiftRight => {
 
                // All equal of integer type
 
                let progress_base = self.apply_forced_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)?;
 
                let progress_base = self.apply_template_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)?;
 
                let (progress_expr, progress_arg1, progress_arg2) =
 
                    self.apply_equal3_constraint(ctx, upcast_id, arg1_id, arg2_id, 0)?;
 

	
 
                (progress_base || progress_expr, progress_base || progress_arg1, progress_base || progress_arg2)
 
            },
 
            BO::Equality | BO::Inequality => {
 
                // Equal2 on args, forced boolean output
 
                let progress_expr = self.apply_forced_constraint(ctx, upcast_id, &BOOL_TEMPLATE)?;
 
                let progress_expr = self.apply_template_constraint(ctx, upcast_id, &BOOLLIKE_TEMPLATE)?;
 
                let (progress_arg1, progress_arg2) =
 
                    self.apply_equal2_constraint(ctx, upcast_id, arg1_id, 0, arg2_id, 0)?;
 

	
 
                (progress_expr, progress_arg1, progress_arg2)
 
            },
 
            BO::LessThan | BO::GreaterThan | BO::LessThanEqual | BO::GreaterThanEqual => {
 
                // Equal2 on args with numberlike type, forced boolean output
 
                let progress_expr = self.apply_forced_constraint(ctx, upcast_id, &BOOL_TEMPLATE)?;
 
                let progress_arg_base = self.apply_forced_constraint(ctx, arg1_id, &NUMBERLIKE_TEMPLATE)?;
 
                let progress_expr = self.apply_template_constraint(ctx, upcast_id, &BOOLLIKE_TEMPLATE)?;
 
                let progress_arg_base = self.apply_template_constraint(ctx, arg1_id, &NUMBERLIKE_TEMPLATE)?;
 
                let (progress_arg1, progress_arg2) =
 
                    self.apply_equal2_constraint(ctx, upcast_id, arg1_id, 0, arg2_id, 0)?;
 

	
 
                (progress_expr, progress_arg_base || progress_arg1, progress_arg_base || progress_arg2)
 
            },
 
            BO::Add | BO::Subtract | BO::Multiply | BO::Divide => {
 
                // All equal of number type
 
                let progress_base = self.apply_forced_constraint(ctx, upcast_id, &NUMBERLIKE_TEMPLATE)?;
 
                let progress_base = self.apply_template_constraint(ctx, upcast_id, &NUMBERLIKE_TEMPLATE)?;
 
                let (progress_expr, progress_arg1, progress_arg2) =
 
                    self.apply_equal3_constraint(ctx, upcast_id, arg1_id, arg2_id, 0)?;
 

	
 
                (progress_base || progress_expr, progress_base || progress_arg1, progress_base || progress_arg2)
 
            },
 
        };
 
@@ -1809,21 +1820,21 @@ impl PassTyping {
 
        debug_log!("   - Arg  type: {}", self.temp_get_display_name(ctx, arg_id));
 
        debug_log!("   - Expr type: {}", self.temp_get_display_name(ctx, upcast_id));
 

	
 
        let (progress_expr, progress_arg) = match expr.operation {
 
            UO::Positive | UO::Negative => {
 
                // Equal types of numeric class
 
                let progress_base = self.apply_forced_constraint(ctx, upcast_id, &NUMBERLIKE_TEMPLATE)?;
 
                let progress_base = self.apply_template_constraint(ctx, upcast_id, &NUMBERLIKE_TEMPLATE)?;
 
                let (progress_expr, progress_arg) =
 
                    self.apply_equal2_constraint(ctx, upcast_id, upcast_id, 0, arg_id, 0)?;
 

	
 
                (progress_base || progress_expr, progress_base || progress_arg)
 
            },
 
            UO::BitwiseNot | UO::PreIncrement | UO::PreDecrement | UO::PostIncrement | UO::PostDecrement => {
 
                // Equal types of integer class
 
                let progress_base = self.apply_forced_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)?;
 
                let progress_base = self.apply_template_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)?;
 
                let (progress_expr, progress_arg) =
 
                    self.apply_equal2_constraint(ctx, upcast_id, upcast_id, 0, arg_id, 0)?;
 

	
 
                (progress_base || progress_expr, progress_base || progress_arg)
 
            },
 
            UO::LogicalNot => {
 
@@ -1854,14 +1865,14 @@ impl PassTyping {
 
        debug_log!(" * Before:");
 
        debug_log!("   - Subject type: {}", self.temp_get_display_name(ctx, subject_id));
 
        debug_log!("   - Index   type: {}", self.temp_get_display_name(ctx, index_id));
 
        debug_log!("   - Expr    type: {}", self.temp_get_display_name(ctx, upcast_id));
 

	
 
        // Make sure subject is arraylike and index is integerlike
 
        let progress_subject_base = self.apply_forced_constraint(ctx, subject_id, &ARRAYLIKE_TEMPLATE)?;
 
        let progress_index = self.apply_forced_constraint(ctx, index_id, &INTEGERLIKE_TEMPLATE)?;
 
        let progress_subject_base = self.apply_template_constraint(ctx, subject_id, &ARRAYLIKE_TEMPLATE)?;
 
        let progress_index = self.apply_template_constraint(ctx, index_id, &INTEGERLIKE_TEMPLATE)?;
 

	
 
        // Make sure if output is of T then subject is Array<T>
 
        let (progress_expr, progress_subject) =
 
            self.apply_equal2_constraint(ctx, upcast_id, upcast_id, 0, subject_id, 1)?;
 

	
 
        debug_log!(" * After:");
 
@@ -1888,18 +1899,18 @@ impl PassTyping {
 
        debug_log!("   - Subject type: {}", self.temp_get_display_name(ctx, subject_id));
 
        debug_log!("   - FromIdx type: {}", self.temp_get_display_name(ctx, from_id));
 
        debug_log!("   - ToIdx   type: {}", self.temp_get_display_name(ctx, to_id));
 
        debug_log!("   - Expr    type: {}", self.temp_get_display_name(ctx, upcast_id));
 

	
 
        // Make sure subject is arraylike and indices are of equal integerlike
 
        let progress_subject_base = self.apply_forced_constraint(ctx, subject_id, &ARRAYLIKE_TEMPLATE)?;
 
        let progress_idx_base = self.apply_forced_constraint(ctx, from_id, &INTEGERLIKE_TEMPLATE)?;
 
        let progress_subject_base = self.apply_template_constraint(ctx, subject_id, &ARRAYLIKE_TEMPLATE)?;
 
        let progress_idx_base = self.apply_template_constraint(ctx, from_id, &INTEGERLIKE_TEMPLATE)?;
 
        let (progress_from, progress_to) = self.apply_equal2_constraint(ctx, upcast_id, from_id, 0, to_id, 0)?;
 

	
 
        // Make sure if output is of Slice<T> then subject is Array<T>
 
        let progress_expr_base = self.apply_forced_constraint(ctx, upcast_id, &SLICE_TEMPLATE)?;
 
        let progress_expr_base = self.apply_template_constraint(ctx, upcast_id, &SLICE_TEMPLATE)?;
 
        let (progress_expr, progress_subject) =
 
            self.apply_equal2_constraint(ctx, upcast_id, upcast_id, 1, subject_id, 1)?;
 

	
 

	
 
        debug_log!(" * After:");
 
        debug_log!("   - Subject type [{}]: {}", progress_subject_base || progress_subject, self.temp_get_display_name(ctx, subject_id));
 
@@ -2088,16 +2099,16 @@ impl PassTyping {
 
        debug_log!("Literal expr: {}", upcast_id.index);
 
        debug_log!(" * Before:");
 
        debug_log!("   - Expr type: {}", self.temp_get_display_name(ctx, upcast_id));
 

	
 
        let progress_expr = match &expr.value {
 
            Literal::Null => {
 
                self.apply_forced_constraint(ctx, upcast_id, &MESSAGE_TEMPLATE)?
 
                self.apply_template_constraint(ctx, upcast_id, &MESSAGE_TEMPLATE)?
 
            },
 
            Literal::Integer(_) => {
 
                self.apply_forced_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)?
 
                self.apply_template_constraint(ctx, upcast_id, &INTEGERLIKE_TEMPLATE)?
 
            },
 
            Literal::True | Literal::False => {
 
                self.apply_forced_constraint(ctx, upcast_id, &BOOL_TEMPLATE)?
 
            },
 
            Literal::Character(_) => {
 
                self.apply_forced_constraint(ctx, upcast_id, &CHARACTER_TEMPLATE)?
 
@@ -2341,13 +2352,13 @@ impl PassTyping {
 
                    if *progress_arg {
 
                        self.queue_expr(ctx, *arg_id);
 
                    }
 
                }
 

	
 
                // And the output should be an array of the element types
 
                let mut progress_expr = self.apply_forced_constraint(ctx, upcast_id, &ARRAY_TEMPLATE)?;
 
                let mut progress_expr = self.apply_template_constraint(ctx, upcast_id, &ARRAY_TEMPLATE)?;
 
                if !expr_elements.is_empty() {
 
                    let first_arg_id = expr_elements[0];
 
                    let (inner_expr_progress, arg_progress) = self.apply_equal2_constraint(
 
                        ctx, upcast_id, upcast_id, 1, first_arg_id, 0
 
                    )?;
 

	
 
@@ -2366,43 +2377,52 @@ impl PassTyping {
 
            },
 
        };
 

	
 
        debug_log!(" * After:");
 
        debug_log!("   - Expr type: {}", self.temp_get_display_name(ctx, upcast_id));
 

	
 
        // TODO: FIX!!!!
 
        if progress_expr { self.queue_expr_parent(ctx, upcast_id); }
 

	
 

	
 
        Ok(())
 
    }
 

	
 
    fn progress_cast_expr(&mut self, ctx: &mut Ctx, id: CastExpressionId) -> Result<(), ParseError> {
 
        let upcast_id = id.upcast();
 
        let expr = &ctx.heap[id];
 
        let expr_idx = expr.unique_id_in_definition;
 

	
 
        debug_log!("Casting expr: {}", upcast_id.index);
 
        debug_log!(" * Before:");
 
        debug_log!("   - Expr type:    {}", self.temp_get_display_name(ctx, upcast_id));
 
        debug_log!("   - Subject type: {}", self.temp_get_display_name(ctx, expr.subject));
 

	
 
        // The cast expression might have its output type fixed by the
 
        // programmer, so apply that type to the output. Apart from that casting
 
        // acts like a blocker for two-way inference. So we'll just have to wait
 
        // until we know if the cast is valid.
 
        // TODO: Another thing that has to be updated the moment the type
 
        //  inferencer is fully index/job-based
 
        let infer_type = self.determine_inference_type_from_parser_type_elements(&expr.to_type.elements, true);
 
        let expr_progress = self.apply_forced_constraint(ctx, upcast_id, &infer_type.parts)?;
 
        let expr_progress = self.apply_template_constraint(ctx, upcast_id, &infer_type.parts)?;
 

	
 
        if expr_progress {
 
            self.queue_expr_parent(ctx, upcast_id);
 
        }
 

	
 
        // Check if the two types are compatible
 
        debug_log!(" * After:");
 
        debug_log!("   - Expr type [{}]: {}", expr_progress, self.temp_get_display_name(ctx, upcast_id));
 
        debug_log!("   - Note that the subject type can never be inferred");
 
        debug_log!(" * Decision:");
 

	
 
        let subject_idx = ctx.heap[expr.subject].get_unique_id_in_definition();
 
        let expr_type = &self.expr_types[expr_idx as usize].expr_type;
 
        let subject_type = &self.expr_types[subject_idx as usize].expr_type;
 
        if !expr_type.is_done || !subject_type.is_done {
 
            // Not yet done
 
            debug_log!("   - Casting is valid: unknown as the types are not yet complete");
 
            return Ok(())
 
        }
 

	
 
        // Valid casts: (bool, integer, character) can always be cast to one
 
        // another. A cast from a type to itself is also valid.
 
        fn is_bool_int_or_char(parts: &[InferenceTypePart]) -> bool {
 
@@ -2418,12 +2438,14 @@ impl PassTyping {
 
        } else if expr_type.parts == subject_type.parts {
 
            true
 
        } else {
 
            false
 
        };
 

	
 
        debug_log!("   - Casting is valid: {}", is_valid);
 

	
 
        if !is_valid {
 
            let cast_expr = &ctx.heap[id];
 
            let subject_expr = &ctx.heap[cast_expr.subject];
 
            return Err(ParseError::new_error_str_at_span(
 
                &ctx.module.source, cast_expr.span, "invalid casting operation"
 
            ).with_info_at_span(
 
@@ -2612,13 +2634,13 @@ impl PassTyping {
 
                    unsafe{&*var_type}.parts[0] == InferenceTypePart::Output
 
                );
 
                debug_assert!(
 
                    link_data.var_type.parts[0] == InferenceTypePart::Input ||
 
                    link_data.var_type.parts[0] == InferenceTypePart::Output
 
                );
 
                match InferenceType::infer_subtree_for_single_type(&mut link_data.var_type, 1, &unsafe{&*var_type}.parts, 1) {
 
                match InferenceType::infer_subtree_for_single_type(&mut link_data.var_type, 1, &unsafe{&*var_type}.parts, 1, false) {
 
                    SingleInferenceResult::Modified => {
 
                        for other_expr in &link_data.used_at {
 
                            let other_expr_idx = ctx.heap[*other_expr].get_unique_id_in_definition();
 
                            self.expr_queued.push_back(other_expr_idx);
 
                        }
 
                    },
 
@@ -2663,44 +2685,61 @@ impl PassTyping {
 

	
 
    fn queue_expr(&mut self, ctx: &Ctx, expr_id: ExpressionId) {
 
        let expr_idx = ctx.heap[expr_id].get_unique_id_in_definition();
 
        self.expr_queued.push_back(expr_idx);
 
    }
 

	
 
    /// Applies a forced type constraint: the type associated with the supplied
 
    /// expression will be molded into the provided "template". The template may
 
    /// be fully specified (e.g. a bool) or contain "inference" variables (e.g.
 
    /// an array of T)
 
    fn apply_forced_constraint(
 
    /// Applies a template type constraint: the type associated with the
 
    /// supplied expression will be molded into the provided `template`. But
 
    /// will be considered valid if the template could've been molded into the
 
    /// expression type as well. Hence the template may be fully specified (e.g.
 
    /// a bool) or contain "inference" variables (e.g. an array of T)
 
    fn apply_template_constraint(
 
        &mut self, ctx: &Ctx, expr_id: ExpressionId, template: &[InferenceTypePart]
 
    ) -> Result<bool, ParseError> {
 
        let expr_idx = ctx.heap[expr_id].get_unique_id_in_definition(); // TODO: @Temp
 
        let expr_type = &mut self.expr_types[expr_idx as usize].expr_type;
 
        match InferenceType::infer_subtree_for_single_type(expr_type, 0, template, 0) {
 
        match InferenceType::infer_subtree_for_single_type(expr_type, 0, template, 0, false) {
 
            SingleInferenceResult::Modified => Ok(true),
 
            SingleInferenceResult::Unmodified => Ok(false),
 
            SingleInferenceResult::Incompatible => Err(
 
                self.construct_template_type_error(ctx, expr_id, template)
 
            )
 
        }
 
    }
 

	
 
    fn apply_forced_constraint_types(
 
    fn apply_template_constraint_to_types(
 
        to_infer: *mut InferenceType, to_infer_start_idx: usize,
 
        template: &[InferenceTypePart], template_start_idx: usize
 
    ) -> Result<bool, ()> {
 
        match InferenceType::infer_subtree_for_single_type(
 
            unsafe{ &mut *to_infer }, to_infer_start_idx,
 
            template, template_start_idx
 
            template, template_start_idx, false
 
        ) {
 
            SingleInferenceResult::Modified => Ok(true),
 
            SingleInferenceResult::Unmodified => Ok(false),
 
            SingleInferenceResult::Incompatible => Err(()),
 
        }
 
    }
 

	
 
    /// Applies a forced constraint: the supplied expression's type MUST be
 
    /// inferred from the template, the other way around is considered invalid.
 
    fn apply_forced_constraint(
 
        &mut self, ctx: &Ctx, expr_id: ExpressionId, template: &[InferenceTypePart]
 
    ) -> Result<bool, ParseError> {
 
        let expr_idx = ctx.heap[expr_id].get_unique_id_in_definition();
 
        let expr_type = &mut self.expr_types[expr_idx as usize].expr_type;
 
        match InferenceType::infer_subtree_for_single_type(expr_type, 0, template, 0, true) {
 
            SingleInferenceResult::Modified => Ok(true),
 
            SingleInferenceResult::Unmodified => Ok(false),
 
            SingleInferenceResult::Incompatible => Err(
 
                self.construct_template_type_error(ctx, expr_id, template)
 
            )
 
        }
 
    }
 

	
 
    /// Applies a type constraint that expects the two provided types to be
 
    /// equal. We attempt to make progress in inferring the types. If the call
 
    /// is successful then the composition of all types are made equal.
 
    /// The "parent" `expr_id` is provided to construct errors.
 
    fn apply_equal2_constraint(
 
        &mut self, ctx: &Ctx, expr_id: ExpressionId,
 
@@ -2780,13 +2819,13 @@ impl PassTyping {
 
            debug_assert!(
 
                signature_type.has_marker,
 
                "made progress on signature type, but it doesn't have a marker"
 
            );
 
            for (poly_idx, poly_section) in signature_type.marker_iter() {
 
                let polymorph_type = &mut polymorph_data.poly_vars[poly_idx as usize];
 
                match Self::apply_forced_constraint_types(
 
                match Self::apply_template_constraint_to_types(
 
                    polymorph_type, 0, poly_section, 0
 
                ) {
 
                    Ok(true) => { polymorph_progress.insert(poly_idx); },
 
                    Ok(false) => {},
 
                    Err(()) => { return Err(Self::construct_poly_arg_error(ctx, polymorph_data, outer_expr_id))}
 
                }
 
@@ -2821,13 +2860,13 @@ impl PassTyping {
 
        
 
        while let Some((poly_idx, start_idx)) = signature_type.find_marker(seek_idx) {
 
            let end_idx = InferenceType::find_subtree_end_idx(&signature_type.parts, start_idx);
 
            // if polymorph_progress.contains(&poly_idx) {
 
                // Need to match subtrees
 
                let polymorph_type = &polymorph_data.poly_vars[poly_idx as usize];
 
                let modified_at_marker = Self::apply_forced_constraint_types(
 
                let modified_at_marker = Self::apply_template_constraint_to_types(
 
                    signature_type, start_idx, 
 
                    &polymorph_type.parts, 0
 
                ).expect("no failure when applying polyvar constraints");
 

	
 
                modified_sig = modified_sig || modified_at_marker;
 
            // }
 
@@ -2836,13 +2875,13 @@ impl PassTyping {
 
        }
 

	
 
        // If we made any progress on the signature's type, then we also need to
 
        // apply it to the expression that is supposed to match the signature.
 
        if modified_sig {
 
            match InferenceType::infer_subtree_for_single_type(
 
                expr_type, 0, &signature_type.parts, 0
 
                expr_type, 0, &signature_type.parts, 0, true
 
            ) {
 
                SingleInferenceResult::Modified => true,
 
                SingleInferenceResult::Unmodified => false,
 
                SingleInferenceResult::Incompatible =>
 
                    unreachable!("encountered failure while reapplying modified signature to expression after polyvar inference")
 
            }
 
@@ -2975,16 +3014,15 @@ impl PassTyping {
 
    ) -> Result<(), ParseError> {
 
        use ExpressionParent as EP;
 
        use InferenceTypePart as ITP;
 

	
 
        let expr = &ctx.heap[expr_id];
 
        let inference_type = match expr.parent() {
 
            EP::None => {
 
            EP::None =>
 
                // Should have been set by linker
 
                println!("DEBUG: CRAP!\n{:?}", expr);
 
                unreachable!() },
 
                unreachable!(),
 
            EP::ExpressionStmt(_) =>
 
                // Determined during type inference
 
                InferenceType::new(false, false, vec![ITP::Unknown]),
 
            EP::Expression(parent_id, idx_in_parent) => {
 
                // If we are the test expression of a conditional expression,
 
                // then we must resolve to a boolean
 
@@ -3041,13 +3079,13 @@ impl PassTyping {
 
                infer_expr.extra_data_idx = extra_idx;
 
            }
 
        } else {
 
            // We already have an entry
 
            debug_assert!(false, "does this ever happen?");
 
            if let SingleInferenceResult::Incompatible = InferenceType::infer_subtree_for_single_type(
 
                &mut infer_expr.expr_type, 0, &inference_type.parts, 0
 
                &mut infer_expr.expr_type, 0, &inference_type.parts, 0, false
 
            ) {
 
                return Err(self.construct_expr_type_error(ctx, expr_id, expr_id));
 
            }
 

	
 
            debug_assert!((infer_expr.extra_data_idx != -1) == needs_extra_data);
 
        }
 
@@ -3360,15 +3398,15 @@ impl PassTyping {
 
        let mut has_markers = false;
 

	
 
        for element in elements {
 
            match &element.variant {
 
                // Compiler-only types
 
                PTV::Void => { infer_type.push(ITP::Void); },
 
                PTV::InputOrOutput => { infer_type.push(ITP::PortLike); },
 
                PTV::ArrayLike => { infer_type.push(ITP::ArrayLike); },
 
                PTV::IntegerLike => { infer_type.push(ITP::IntegerLike); },
 
                PTV::InputOrOutput => { infer_type.push(ITP::PortLike); has_inferred = true },
 
                PTV::ArrayLike => { infer_type.push(ITP::ArrayLike); has_inferred = true },
 
                PTV::IntegerLike => { infer_type.push(ITP::IntegerLike); has_inferred = true },
 
                // Builtins
 
                PTV::Message => {
 
                    // TODO: @types Remove the Message -> Byte hack at some point...
 
                    infer_type.push(ITP::Message);
 
                    infer_type.push(ITP::UInt8);
 
                },
 
@@ -3708,13 +3746,13 @@ mod tests {
 
            assert_eq!(lhs_type.parts, rhs_type.parts);
 

	
 
            // Using infer-single
 
            let mut lhs_type = IT::new(false, false, vec![lhs.clone()]);
 
            let rhs_type = IT::new(false, true, vec![rhs.clone()]);
 
            let result = IT::infer_subtree_for_single_type(
 
                &mut lhs_type, 0, &rhs_type.parts, 0
 
                &mut lhs_type, 0, &rhs_type.parts, 0, false
 
            );
 
            assert_eq!(SingleInferenceResult::Modified, result);
 
            assert_eq!(lhs_type.parts, rhs_type.parts);
 
        }
 
    }
 

	
 
@@ -3740,13 +3778,13 @@ mod tests {
 
            assert_eq!(DualInferenceResult::First, result);
 
            assert_eq!(lhs_type.parts, rhs_type.parts);
 

	
 
            let mut lhs_type = IT::new(false, false, lhs.clone());
 
            let rhs_type = IT::new(false, true, rhs.clone());
 
            let result = IT::infer_subtree_for_single_type(
 
                &mut lhs_type, 0, &rhs_type.parts, 0
 
                &mut lhs_type, 0, &rhs_type.parts, 0, false
 
            );
 
            assert_eq!(SingleInferenceResult::Modified, result);
 
            assert_eq!(lhs_type.parts, rhs_type.parts)
 
        }
 
    }
 
}
 
\ No newline at end of file
src/protocol/parser/pass_validation_linking.rs
Show inline comments
 
@@ -977,24 +977,22 @@ impl Visitor2 for PassValidationLinking {
 

	
 
        Ok(())
 
    }
 

	
 
    fn visit_variable_expr(&mut self, ctx: &mut Ctx, id: VariableExpressionId) -> VisitorResult {
 
        let var_expr = &ctx.heap[id];
 
        println!("DEBUG: Visiting:\nname: {}\nat:  {:?}", var_expr.identifier.value.as_str(), var_expr.identifier.span);
 

	
 
        let variable_id = match self.find_variable(ctx, self.relative_pos_in_block, &var_expr.identifier) {
 
        let (variable_id, is_binding_target) = match self.find_variable(ctx, self.relative_pos_in_block, &var_expr.identifier) {
 
            Ok(variable_id) => {
 
                // Regular variable
 
                variable_id
 
                (variable_id, false)
 
            },
 
            Err(()) => {
 
                // Couldn't find variable, but if we're in a binding expression,
 
                // then this may be the thing we're binding to.
 
                if self.in_binding_expr.is_invalid() || !self.in_binding_expr_lhs {
 
                    println!("DEBUG: INVAALLIIIIIIID ({})", var_expr.identifier.value.as_str());
 
                    return Err(ParseError::new_error_str_at_span(
 
                        &ctx.module.source, var_expr.identifier.span, "unresolved variable"
 
                    ));
 
                }
 

	
 
                // This is a binding variable, but it may only appear in very
 
@@ -1064,18 +1062,19 @@ impl Visitor2 for PassValidationLinking {
 
                    Statement::While(stmt) => stmt.body,
 
                    _ => unreachable!(),
 
                };
 
                let body_scope = Scope::Regular(body_stmt_id);
 
                self.checked_at_single_scope_add_local(ctx, body_scope, 0, bound_variable_id)?;
 

	
 
                bound_variable_id
 
                (bound_variable_id, true)
 
            }
 
        };
 

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

	
 
        Ok(())
 
    }
src/protocol/parser/type_table.rs
Show inline comments
 
@@ -316,21 +316,18 @@ impl TypeTable {
 
        }
 

	
 
        // Use context to guess hashmap size
 
        let reserve_size = ctx.heap.definitions.len();
 
        self.lookup.reserve(reserve_size);
 

	
 
        for root_idx in 0..modules.len() {
 
            let last_definition_idx = ctx.heap[modules[root_idx].root_id].definitions.len();
 
            for definition_idx in 0..last_definition_idx {
 
                let definition_id = ctx.heap[modules[root_idx].root_id].definitions[definition_idx];
 
        for definition_idx in 0..ctx.heap.definitions.len() {
 
            let definition_id = ctx.heap.definitions.get_id(definition_idx);
 
            self.resolve_base_definition(modules, ctx, definition_id)?;
 
        }
 
        }
 

	
 
        debug_assert_eq!(self.lookup.len() + 6, reserve_size, "mismatch in reserved size of type table"); // NOTE: Temp fix for builtin functions
 
        debug_assert_eq!(self.lookup.len(), reserve_size, "mismatch in reserved size of type table"); // NOTE: Temp fix for builtin functions
 
        for module in modules {
 
            module.phase = ModuleCompilationPhase::TypesAddedToTable;
 
        }
 

	
 
        Ok(())
 
    }
 
@@ -540,13 +537,13 @@ impl TypeTable {
 
        // Make sure all embedded types are resolved
 
        for variant in &definition.variants {
 
            match &variant.value {
 
                UnionVariantValue::None => {},
 
                UnionVariantValue::Embedded(embedded) => {
 
                    for parser_type in embedded {
 
                        let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, parser_type)?;
 
                        let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, parser_type, false)?;
 
                        if !self.ingest_resolve_result(modules, ctx, resolve_result)? {
 
                            return Ok(false)
 
                        }
 
                    }
 
                }
 
            }
 
@@ -613,13 +610,13 @@ impl TypeTable {
 
        debug_assert!(!self.lookup.contains_key(&definition_id), "base struct already resolved");
 

	
 
        let definition = ctx.heap[definition_id].as_struct();
 

	
 
        // Make sure all fields point to resolvable types
 
        for field_definition in &definition.fields {
 
            let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &field_definition.parser_type)?;
 
            let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &field_definition.parser_type, false)?;
 
            if !self.ingest_resolve_result(modules, ctx, resolve_result)? {
 
                return Ok(false)
 
            }
 
        }
 

	
 
        // All fields types are resolved, construct base type
 
@@ -667,21 +664,21 @@ impl TypeTable {
 
        debug_assert!(!self.lookup.contains_key(&definition_id), "base function already resolved");
 

	
 
        let definition = ctx.heap[definition_id].as_function();
 

	
 
        // Check the return type
 
        debug_assert_eq!(definition.return_types.len(), 1, "not one return type"); // TODO: @ReturnValues
 
        let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &definition.return_types[0])?;
 
        let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &definition.return_types[0], definition.builtin)?;
 
        if !self.ingest_resolve_result(modules, ctx, resolve_result)? {
 
            return Ok(false)
 
        }
 

	
 
        // Check the argument types
 
        for param_id in &definition.parameters {
 
            let param = &ctx.heap[*param_id];
 
            let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &param.parser_type)?;
 
            let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &param.parser_type, definition.builtin)?;
 
            if !self.ingest_resolve_result(modules, ctx, resolve_result)? {
 
                return Ok(false)
 
            }
 
        }
 

	
 
        // Construct arguments to function
 
@@ -734,13 +731,13 @@ impl TypeTable {
 
        let definition = ctx.heap[definition_id].as_component();
 
        let component_variant = definition.variant;
 

	
 
        // Check argument types
 
        for param_id in &definition.parameters {
 
            let param = &ctx.heap[*param_id];
 
            let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &param.parser_type)?;
 
            let resolve_result = self.resolve_base_parser_type(modules, ctx, root_id, &param.parser_type, false)?;
 
            if !self.ingest_resolve_result(modules, ctx, resolve_result)? {
 
                return Ok(false)
 
            }
 
        }
 

	
 
        // Construct argument types
 
@@ -830,13 +827,16 @@ impl TypeTable {
 
    /// a port identifier, so it has a fixed implementation) then this function
 
    /// will traverse the embedded types to ensure all of them are resolved.
 
    ///
 
    /// Hence if one checks a particular parser type for being resolved, one may
 
    /// get back a result value indicating an embedded type (with a different
 
    /// DefinitionId) is unresolved.
 
    fn resolve_base_parser_type(&mut self, modules: &[Module], ctx: &PassCtx, root_id: RootId, parser_type: &ParserType) -> Result<ResolveResult, ParseError> {
 
    fn resolve_base_parser_type(
 
        &mut self, modules: &[Module], ctx: &PassCtx, root_id: RootId,
 
        parser_type: &ParserType, is_builtin: bool
 
    ) -> Result<ResolveResult, ParseError> {
 
        // Note that as we iterate over the elements of a
 
        use ParserTypeVariant as PTV;
 

	
 
        // Result for the very first time we resolve a type (i.e the outer type
 
        // that we're actually looking up)
 
        let mut resolve_result = None;
 
@@ -844,13 +844,17 @@ impl TypeTable {
 
            if resolve_result.is_none() { resolve_result = Some(v); }
 
        };
 

	
 
        for element in parser_type.elements.iter() {
 
            match element.variant {
 
                PTV::Void | PTV::InputOrOutput | PTV::ArrayLike | PTV::IntegerLike => {
 
                    if is_builtin {
 
                        set_resolve_result(ResolveResult::Builtin);
 
                    } else {
 
                        unreachable!("compiler-only ParserTypeVariant within type definition");
 
                    }
 
                },
 
                PTV::Message | PTV::Bool |
 
                PTV::UInt8 | PTV::UInt16 | PTV::UInt32 | PTV::UInt64 |
 
                PTV::SInt8 | PTV::SInt16 | PTV::SInt32 | PTV::SInt64 |
 
                PTV::Character | PTV::String |
 
                PTV::Array | PTV::Input | PTV::Output => {
src/protocol/tests/eval_binding.rs
Show inline comments
 
new file 100644
 
use super::*;
 

	
 
#[test]
 
fn test_binding_from_struct() {
 
    Tester::new_single_source_expect_ok("top level", "
 
        struct Foo{ u32 field }
 
        func foo() -> u32 {
 
            if (let Foo{ field: thing } = Foo{ field: 1337 }) { return thing; }
 
            return 0;
 
        }
 
    ").for_function("foo", |f| {
 
        f.call_ok(Some(Value::UInt32(1337)));
 
    });
 

	
 
    Tester::new_single_source_expect_ok("nested", "
 
        struct Nested<T>{ T inner }
 
        struct Foo{ u32 field }
 
        func make<T>(T val) -> Nested<T> {
 
            return Nested{ inner: val };
 
        }
 
        func foo() -> u32 {
 
            if (let Nested{ inner: Nested { inner: to_get } } = make(make(Foo{ field: 42 }))) {
 
                return to_get.field;
 
            }
 
            return 0;
 
        }
 
    ").for_function("foo", |f| {
 
        f.call_ok(Some(Value::UInt32(42)));
 
    });
 
}
 

	
 
#[test]
 
fn test_binding_from_array() {
 
    Tester::new_single_source_expect_ok("top level", "
 
        func foo() -> bool {
 
            // Mismatches in length
 
            bool failure1 = false;
 
            u32[] vals = { 1, 2, 3 };
 
            if (let {1, a} = vals) { failure1 = true; }
 

	
 
            bool failure2 = false;
 
            if (let {} = vals) { failure2 = true; }
 

	
 
            bool failure3 = false;
 
            if (let {1, 2, 3, 4} = vals) { failure3 = true; }
 

	
 
            // Mismatches in known values
 
            bool failure4 = false;
 
            if (let {1, 3, a} = vals) { failure4 = true; }
 

	
 
            // Matching with known variables
 
            auto constant_one = 1;
 
            auto constant_two = 2;
 
            auto constant_three = 3;
 
            bool success1 = false;
 
            if (
 
                let {binding_one, constant_two, constant_three} = vals &&
 
                let {constant_one, binding_two, constant_three} = vals &&
 
                let {constant_one, constant_two, binding_three} = vals
 
            ) {
 
                success1 = binding_one == 1 && binding_two == 2 && binding_three == 3;
 
            }
 

	
 
            // Matching with literals
 
            bool success2 = false;
 
            if (let {binding_one, 2, 3} = vals && let {1, binding_two, 3} = vals && let {1, 2, binding_three} = vals) {
 
                success2 = binding_one == 1 && binding_two == 2 && binding_three == 3;
 
            }
 

	
 
            // Matching all
 
            bool success3 = false;
 
            if (let {binding_one, binding_two, binding_three} = vals && let {1, 2, 3} = vals) {
 
                success3 = true;
 
            }
 

	
 
            return
 
                !failure1 && !failure2 && !failure3 && !failure4 &&
 
                success1 && success2 && success3;
 
        }
 
    ").for_function("foo", |f| { f
 
        .call_ok(Some(Value::Bool(true)));
 
    });
 
}
 

	
 
#[test]
 
fn test_binding_fizz_buzz() {
 
    Tester::new_single_source_expect_ok("am I employable?", "
 
        union Fizzable { Number(u32), FizzBuzz, Fizz, Buzz }
 

	
 
        func construct_fizz_buzz(u32 num) -> Fizzable[] {
 
            u32 counter = 1;
 
            auto result = {};
 
            while (counter <= num) {
 
                auto value = Fizzable::Number(counter);
 
                if (counter % 5 == 0) {
 
                    if (counter % 3 == 0) {
 
                        value = Fizzable::FizzBuzz;
 
                    } else {
 
                        value = Fizzable::Buzz;
 
                    }
 
                } else if (counter % 3 == 0) {
 
                    value = Fizzable::Fizz;
 
                }
 

	
 
                result = result @ { value }; // woohoo, no array builtins!
 
                counter += 1;
 
            }
 

	
 
            return result;
 
        }
 

	
 
        func test_fizz_buzz(Fizzable[] fizzoid) -> bool {
 
            u32 idx = 0;
 
            bool valid = true;
 
            while (idx < length(fizzoid)) {
 
                auto number = idx + 1;
 
                auto is_div3 = number % 3 == 0;
 
                auto is_div5 = number % 5 == 0;
 

	
 
                if (let Fizzable::Number(got) = fizzoid[idx]) {
 
                    valid = valid && got == number && !is_div3 && !is_div5;
 
                } else if (let Fizzable::FizzBuzz = fizzoid[idx]) {
 
                    valid = valid && is_div3 && is_div5;
 
                } else if (let Fizzable::Fizz = fizzoid[idx]) {
 
                    valid = valid && is_div3 && !is_div5;
 
                } else if (let Fizzable::Buzz = fizzoid[idx]) {
 
                    valid = valid && !is_div3 && is_div5;
 
                } else {
 
                    // Impossibruuu!
 
                    valid = false;
 
                }
 

	
 
                idx += 1;
 
            }
 

	
 
            return valid;
 
        }
 

	
 
        func fizz_buzz() -> bool {
 
            auto fizz = construct_fizz_buzz(100);
 
            return test_fizz_buzz(fizz);
 
        }
 
    ").for_function("fizz_buzz", |f| { f
 
        .call_ok(Some(Value::Bool(true)));
 
    });
 
}
 
\ No newline at end of file
src/protocol/tests/eval_calls.rs
Show inline comments
 
@@ -10,14 +10,12 @@ fn test_function_call() {
 
        return add_two(5);
 
    }
 
    ").for_function("foo", |f| {
 
        f.call_ok(Some(Value::UInt32(7)));
 
    });
 

	
 
    println!("\n\n\n\n\n\n\n");
 

	
 
    Tester::new_single_source_expect_ok("with variable arg", "
 
    func add_two(u32 value) -> u32 {
 
        value += 1;
 
        return value + 1;
 
    }
 
    func foo() -> bool {
src/protocol/tests/mod.rs
Show inline comments
 
@@ -3,22 +3,26 @@
 
 *
 
 * 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 lexer;
 
mod parser_validation;
 
mod parser_inference;
 
mod parser_monomorphs;
 
mod parser_imports;
 
mod parser_binding;
 
mod eval_operators;
 
mod eval_calls;
 
mod eval_casting;
 
mod eval_binding;
 
mod eval_silly;
 

	
 
pub(crate) use utils::{Tester}; // the testing harness
 
pub(crate) use crate::protocol::eval::value::*; // to test functions
 
\ No newline at end of file
src/protocol/tests/parser_binding.rs
Show inline comments
 
@@ -28,56 +28,116 @@ fn test_correct_binding() {
 
        .for_variable("test_union_a", |v| { v.assert_concrete_type("TestUnion"); })
 
        .for_variable("test_union_b", |v| { v.assert_concrete_type("TestUnion"); })
 
        .for_variable("test_struct", |v| { v.assert_concrete_type("TestStruct"); });
 
    });
 
}
 

	
 
#[test]
 
fn test_incorrect_binding() {
 
    Tester::new_single_source_expect_err("binding at statement level", "
 
        func foo() -> bool {
 
            return let a = 5;
 
        }
 
    ").error(|e| { e
 
        .assert_num(1)
 
        .assert_occurs_at(0, "let")
 
        .assert_msg_has(0, "only be used inside the testing expression");
 
    });
 

	
 
    Tester::new_single_source_expect_err("nested bindings", "
 
        struct Struct{ bool field }
 
        func foo() -> bool {
 
            if (let Struct{ field: let a = Struct{ field: false } } = Struct{ field: true }) {
 
                return test;
 
            }
 
            return false;
 
        }
 
    ").error(|e| { e
 
        .assert_num(2)
 
        .assert_occurs_at(0, "let a = ")
 
        .assert_msg_has(0, "nested binding")
 
        .assert_occurs_at(1, "let Struct")
 
        .assert_msg_has(1, "outer binding");
 
    });
 
}
 

	
 
#[test]
 
fn test_boolean_ops_on_binding() {
 
    // Tester::new_single_source_expect_ok("apply && to binding result", "
 
    //     union TestUnion{ Two(u16), Four(u32), Eight(u64) }
 
    //     func foo() -> u32 {
 
    //         auto lit_2 = TestUnion::Two(2);
 
    //         auto lit_4 = TestUnion::Four(4);
 
    //         auto lit_8 = TestUnion::Eight(8);
 
    //
 
    //         // Testing combined forms of bindings
 
    //         if (
 
    //             let TestUnion::Two(test_2) = lit_2 &&
 
    //             let TestUnion::Four(test_4) = lit_4 &&
 
    //             let TestUnion::Eight(test_8) = lit_8
 
    //         ) {
 
    //             auto valid_2 = test_2;
 
    //             auto valid_4 = test_4;
 
    //             auto valid_8 = test_8;
 
    //         }
 
    //
 
    //         // Testing in combination with regular expressions, and to the correct
 
    //         // literals
 
    //         if (let TestUnion::Two(inter_a) = lit_2 && 5 + 2 == 7)               { inter_a = 0; }
 
    //         if (5 + 2 == 7 && let TestUnion::Two(inter_b) = lit_2)               { inter_b = 0; }
 
    //         if (2 + 2 == 4 && let TestUnion::Two(inter_c) = lit_2 && 3 + 3 == 8) { inter_c = 0; }
 
    //
 
    //         // Testing with the 'incorrect' target union
 
    //         if (let TestUnion::Four(nope) = lit_2 && let TestUnion::Two(zilch) = lit_8) { }
 
    //
 
    //         return 0;
 
    //     }
 
    // ").for_function("foo", |f| { f
 
    //     .for_variable("valid_2", |v| { v.assert_concrete_type("u16"); })
 
    //     .for_variable("valid_4", |v| { v.assert_concrete_type("u32"); })
 
    //     .for_variable("valid_8", |v| { v.assert_concrete_type("u64"); })
 
    //     .for_variable("inter_a", |v| { v.assert_concrete_type("u16"); })
 
    //     .for_variable("inter_b", |v| { v.assert_concrete_type("u16"); })
 
    //     .for_variable("inter_c", |v| { v.assert_concrete_type("u16"); });
 
    // });
 
    Tester::new_single_source_expect_ok("apply && to binding result", "
 
        union TestUnion{ Two(u16), Four(u32), Eight(u64) }
 
        func foo() -> u32 {
 
            auto lit_2 = TestUnion::Two(2);
 
            auto lit_4 = TestUnion::Four(4);
 
            auto lit_8 = TestUnion::Eight(8);
 

	
 
            // Testing combined forms of bindings
 
            if (
 
                let TestUnion::Two(test_2) = lit_2 &&
 
                let TestUnion::Four(test_4) = lit_4 &&
 
                let TestUnion::Eight(test_8) = lit_8
 
            ) {
 
                auto valid_2 = test_2;
 
                auto valid_4 = test_4;
 
                auto valid_8 = test_8;
 
            }
 

	
 
            // Testing in combination with regular expressions, and to the correct
 
            // literals
 
            if (let TestUnion::Two(inter_a) = lit_2 && 5 + 2 == 7)               { inter_a = 0; }
 
            if (5 + 2 == 7 && let TestUnion::Two(inter_b) = lit_2)               { inter_b = 0; }
 
            if (2 + 2 == 4 && let TestUnion::Two(inter_c) = lit_2 && 3 + 3 == 8) { inter_c = 0; }
 

	
 
            // Testing with the 'incorrect' target union
 
            if (let TestUnion::Four(nope) = lit_2 && let TestUnion::Two(zilch) = lit_8) { }
 

	
 
            return 0;
 
        }
 
    ").for_function("foo", |f| { f
 
        .for_variable("valid_2", |v| { v.assert_concrete_type("u16"); })
 
        .for_variable("valid_4", |v| { v.assert_concrete_type("u32"); })
 
        .for_variable("valid_8", |v| { v.assert_concrete_type("u64"); })
 
        .for_variable("inter_a", |v| { v.assert_concrete_type("u16"); })
 
        .for_variable("inter_b", |v| { v.assert_concrete_type("u16"); })
 
        .for_variable("inter_c", |v| { v.assert_concrete_type("u16"); });
 
    });
 

	
 
    Tester::new_single_source_expect_ok("apply || before binding", "
 
    Tester::new_single_source_expect_err("apply || before binding", "
 
        enum Test{ A, B }
 
        func foo() -> u32 {
 
            if (let a = Test::A || 5 + 2 == 7) {
 
                auto mission_impossible = 5;
 
            }
 
            return 0;
 
        }
 
    ");
 
    ").error(|e| { e
 
        .assert_num(1)
 
        .assert_occurs_at(0, "let")
 
        .assert_msg_has(0, "expected a 'bool'");
 
    });
 

	
 
    Tester::new_single_source_expect_err("apply || after binding", "
 
        enum Test{ A, B }
 
        func foo() -> u32 {
 
            if (5 + 2 == 7 || let b = Test::B) {
 
                auto magic_number = 7;
 
            }
 
            return 0;
 
        }
 
    ").error(|e| { e
 
        .assert_num(1)
 
        .assert_occurs_at(0, "let")
 
        .assert_msg_has(0, "expected a 'bool'");
 
    });
 

	
 
    Tester::new_single_source_expect_err("apply || before and after binding", "
 
        enum Test{ A, B }
 
        func foo() -> u32 {
 
            if (1 + 2 == 3 || (let a = Test::A && let b = Test::B) || (2 + 2 == 4)) {
 
                auto darth_vader_says = \"Noooooooooo\";
 
            }
 
            return 0;
 
        }
 
    ").error(|e| { e
 
        .assert_num(1)
 
        .assert_msg_has(0, "expected a 'bool'");
 
    });
 
}
 
\ No newline at end of file
src/protocol/tests/utils.rs
Show inline comments
 
@@ -621,15 +621,12 @@ impl<'a> FunctionTester<'a> {
 

	
 
        self
 
    }
 

	
 
    // Keeping this simple for now, will likely change
 
    pub(crate) fn call_err(self, expected_result: &str) -> Self {
 
        use crate::protocol::*;
 
        use crate::runtime::*;
 

	
 
        let (_, result) = self.eval_until_end();
 
        match result {
 
            Ok(_) => {
 
                assert!(
 
                    false,
 
                    "[{}] Expected an error, but evaluation finished successfully for {}",
 
@@ -649,13 +646,12 @@ impl<'a> FunctionTester<'a> {
 

	
 
        self
 
    }
 

	
 
    fn eval_until_end(&self) -> (Prompt, Result<EvalContinuation, EvalError>) {
 
        use crate::protocol::*;
 
        use crate::runtime::*;
 

	
 
        let mut prompt = Prompt::new(&self.ctx.types, &self.ctx.heap, self.def.this.upcast(), 0, ValueGroup::new_stack(Vec::new()));
 
        let mut call_context = EvalContext::None;
 
        loop {
 
            let result = prompt.step(&self.ctx.types, &self.ctx.heap, &self.ctx.modules, &mut call_context);
 
            match result {
0 comments (0 inline, 0 general)