Changeset - 54cd3e2d6639
[Not reviewed]
0 6 0
mh - 4 years ago 2021-05-26 12:23:06
contact@maxhenger.nl
WIP on typing of binding expr
6 files changed with 184 insertions and 42 deletions:
0 comments (0 inline, 0 general)
src/protocol/ast.rs
Show inline comments
 
@@ -640,7 +640,7 @@ impl ScopeNode {
 
    }
 
}
 

	
 
#[derive(Debug, Clone)]
 
#[derive(Debug, Clone, PartialEq, Eq)]
 
pub enum VariableKind {
 
    Parameter,      // in parameter list of function/component
 
    Local,          // declared in function/component body
src/protocol/parser/pass_definitions.rs
Show inline comments
 
@@ -1430,7 +1430,6 @@ impl PassDefinitions {
 
                        }
 
                    },
 
                    _ => {
 
                        // TODO: Casting expressions
 
                        return Err(ParseError::new_error_str_at_span(
 
                            &module.source, parser_type.elements[0].full_span,
 
                            "unexpected type in expression, note that casting expressions are not yet implemented"
 
@@ -1462,9 +1461,9 @@ impl PassDefinitions {
 
                    let keyword_span = iter.next_span();
 
                    iter.consume();
 

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

	
 
                    ctx.heap.alloc_binding_expression(|this| BindingExpression{
 
                        this,
 
@@ -1652,7 +1651,7 @@ fn consume_parser_type(
 
        }
 

	
 
        for _ in 0..first_angle_depth {
 
            consume_token(source, iter, TokenKind::CloseAngle);
 
            consume_token(source, iter, TokenKind::CloseAngle)?;
 
        }
 

	
 
        return Ok(ParserType{ elements });
src/protocol/parser/pass_typing.rs
Show inline comments
 
@@ -66,6 +66,8 @@ use super::visitor::{
 
const VOID_TEMPLATE: [InferenceTypePart; 1] = [ InferenceTypePart::Void ];
 
const MESSAGE_TEMPLATE: [InferenceTypePart; 2] = [ InferenceTypePart::Message, InferenceTypePart::UInt8 ];
 
const BOOL_TEMPLATE: [InferenceTypePart; 1] = [ InferenceTypePart::Bool ];
 
const BOOLLIKE_TEMPLATE: [InferenceTypePart; 1] = [ InferenceTypePart::BoolLike ];
 
const BINDING_BOOL_TEMPLATE: [InferenceTypePart; 1] = [ InferenceTypePart::BindingBool ];
 
const CHARACTER_TEMPLATE: [InferenceTypePart; 1] = [ InferenceTypePart::Character ];
 
const STRING_TEMPLATE: [InferenceTypePart; 1] = [ InferenceTypePart::String ];
 
const NUMBERLIKE_TEMPLATE: [InferenceTypePart; 1] = [ InferenceTypePart::NumberLike ];
 
@@ -90,6 +92,7 @@ pub(crate) enum InferenceTypePart {
 
    // Partially known type, may be inferred to to be the appropriate related 
 
    // type.
 
    // IndexLike,      // index into array/slice
 
    BoolLike,       // boolean or binding boolean
 
    NumberLike,     // any kind of integer/float
 
    IntegerLike,    // any kind of integer
 
    ArrayLike,      // array or slice. Note that this must have a subtype
 
@@ -97,6 +100,7 @@ pub(crate) enum InferenceTypePart {
 
    // Special types that cannot be instantiated by the user
 
    Void, // For builtin functions that do not return anything
 
    // Concrete types without subtypes
 
    BindingBool,    // boolean result from a binding expression
 
    Bool,
 
    UInt8,
 
    UInt16,
 
@@ -131,8 +135,8 @@ impl InferenceTypePart {
 
    fn is_concrete(&self) -> bool {
 
        use InferenceTypePart as ITP;
 
        match self {
 
            ITP::Unknown | ITP::NumberLike | ITP::IntegerLike | 
 
            ITP::ArrayLike | ITP::PortLike => false,
 
            ITP::Unknown | ITP::BoolLike | ITP::NumberLike |
 
            ITP::IntegerLike | ITP::ArrayLike | ITP::PortLike => false,
 
            _ => true
 
        }
 
    }
 
@@ -180,9 +184,13 @@ impl InferenceTypePart {
 
        (*self == ITP::IntegerLike && arg.is_concrete_integer()) ||
 
        (*self == ITP::NumberLike && (arg.is_concrete_number() || *arg == ITP::IntegerLike)) ||
 
        (*self == ITP::ArrayLike && arg.is_concrete_msg_array_or_slice()) ||
 
        (*self == ITP::PortLike && arg.is_concrete_port())
 
        (*self == ITP::PortLike && arg.is_concrete_port()) ||
 
        (*self == ITP::BoolLike && (*arg == ITP::Bool || *arg == ITP::BindingBool)) ||
 
        (*self == ITP::Bool && *arg == ITP::BindingBool)
 
    }
 

	
 
    /// Checks if a part is more specific
 

	
 
    /// Returns the change in "iteration depth" when traversing this particular
 
    /// part. The iteration depth is used to traverse the tree in a linear 
 
    /// fashion. It is basically `number_of_subtypes - 1`
 
@@ -190,7 +198,7 @@ impl InferenceTypePart {
 
        use InferenceTypePart as ITP;
 
        match &self {
 
            ITP::Unknown | ITP::NumberLike | ITP::IntegerLike |
 
            ITP::Void | ITP::Bool |
 
            ITP::Void | ITP::BoolLike | ITP::Bool | ITP::BindingBool |
 
            ITP::UInt8 | ITP::UInt16 | ITP::UInt32 | ITP::UInt64 |
 
            ITP::SInt8 | ITP::SInt16 | ITP::SInt32 | ITP::SInt64 |
 
            ITP::Character | ITP::String => {
 
@@ -594,7 +602,8 @@ impl InferenceType {
 
                    idx += 1;
 
                    continue;
 
                },
 
                ITP::Unknown | ITP::NumberLike | ITP::IntegerLike | ITP::ArrayLike | ITP::PortLike => {
 
                ITP::Unknown | ITP::BoolLike | ITP::NumberLike |
 
                ITP::IntegerLike | ITP::ArrayLike | ITP::PortLike => {
 
                    // Should not happen if type inferencing works correctly: we
 
                    // should have returned a programmer-readable error or have
 
                    // inferred all types.
 
@@ -602,6 +611,7 @@ impl InferenceType {
 
                },
 
                ITP::Void => CTP::Void,
 
                ITP::Message => CTP::Message,
 
                ITP::BindingBool => CTP::Bool,
 
                ITP::Bool => CTP::Bool,
 
                ITP::UInt8 => CTP::UInt8,
 
                ITP::UInt16 => CTP::UInt16,
 
@@ -640,6 +650,7 @@ impl InferenceType {
 
                idx = Self::write_display_name(buffer, heap, parts, idx + 1);
 
            },
 
            ITP::Unknown => buffer.push_str("?"),
 
            ITP::BoolLike => buffer.push_str("boollike"),
 
            ITP::NumberLike => buffer.push_str("numberlike"),
 
            ITP::IntegerLike => buffer.push_str("integerlike"),
 
            ITP::ArrayLike => {
 
@@ -652,6 +663,7 @@ impl InferenceType {
 
                buffer.push('>');
 
            }
 
            ITP::Void => buffer.push_str("void"),
 
            ITP::BindingBool => buffer.push_str("binding result"),
 
            ITP::Bool => buffer.push_str(KW_TYPE_BOOL_STR),
 
            ITP::UInt8 => buffer.push_str(KW_TYPE_UINT8_STR),
 
            ITP::UInt16 => buffer.push_str(KW_TYPE_UINT16_STR),
 
@@ -1357,8 +1369,24 @@ impl Visitor2 for PassTyping {
 

	
 
        let var_expr = &ctx.heap[id];
 
        debug_assert!(var_expr.declaration.is_some());
 
        let var_data = self.var_types.get_mut(var_expr.declaration.as_ref().unwrap()).unwrap();
 

	
 
        // Not pretty: if a binding expression, then this is the first time we
 
        // encounter the variable, so we still need to insert the variable data.
 
        let declaration = &ctx.heap[var_expr.declaration.unwrap()];
 
        if !self.var_types.contains_key(&declaration.this)  {
 
            debug_assert!(declaration.kind == VariableKind::Binding);
 
            let var_type = self.determine_inference_type_from_parser_type_elements(
 
                &declaration.parser_type.elements, true
 
            );
 
            self.var_types.insert(declaration.this, VarData{
 
                var_type,
 
                used_at: vec![upcast_id],
 
                linked_var: None
 
            });
 
        } else {
 
            let var_data = self.var_types.get_mut(&declaration.this).unwrap();
 
            var_data.used_at.push(upcast_id);
 
        }
 

	
 
        self.progress_variable_expr(ctx, id)
 
    }
 
@@ -1627,7 +1655,21 @@ impl PassTyping {
 
    }
 

	
 
    fn progress_binding_expr(&mut self, ctx: &mut Ctx, id: BindingExpressionId) -> Result<(), ParseError> {
 
        let upcast_id = id.upcast();
 
        let binding_expr = &ctx.heap[id];
 
        let bound_from_id = binding_expr.bound_from;
 
        let bound_to_id = binding_expr.bound_to;
 

	
 
        // Output of a binding expression is a special kind of boolean that can
 
        // only be used in binary-and expressions
 
        let progress_expr = self.apply_forced_constraint(ctx, upcast_id, &BINDING_BOOL_TEMPLATE)?;
 
        let (progress_from, progress_to) = self.apply_equal2_constraint(ctx, upcast_id, bound_from_id, 0, bound_to_id, 0)?;
 

	
 
        if progress_expr { self.queue_expr_parent(ctx, upcast_id); }
 
        if progress_from { self.queue_expr(ctx, bound_from_id); }
 
        if progress_to { self.queue_expr(ctx, bound_to_id); }
 

	
 
        Ok(())
 
    }
 

	
 
    fn progress_conditional_expr(&mut self, ctx: &mut Ctx, id: ConditionalExpressionId) -> Result<(), ParseError> {
 
@@ -1688,7 +1730,19 @@ impl PassTyping {
 

	
 
                (progress_expr || subtype_expr, progress_arg1 || subtype_arg1, progress_arg2 || subtype_arg2)
 
            },
 
            BO::LogicalOr | BO::LogicalAnd => {
 
            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
 
                // constraint. Any BindingBool will promote all the other Bool
 
                // types.
 
                let base_expr = self.apply_forced_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 => {
 
                // Forced boolean on all
 
                let progress_expr = self.apply_forced_constraint(ctx, upcast_id, &BOOL_TEMPLATE)?;
 
                let progress_arg1 = self.apply_forced_constraint(ctx, arg1_id, &BOOL_TEMPLATE)?;
 
@@ -2924,9 +2978,10 @@ impl PassTyping {
 

	
 
        let expr = &ctx.heap[expr_id];
 
        let inference_type = match expr.parent() {
 
            EP::None =>
 
            EP::None => {
 
                // Should have been set by linker
 
                unreachable!(),
 
                println!("DEBUG: CRAP!\n{:?}", expr);
 
                unreachable!() },
 
            EP::ExpressionStmt(_) =>
 
                // Determined during type inference
 
                InferenceType::new(false, false, vec![ITP::Unknown]),
src/protocol/parser/pass_validation_linking.rs
Show inline comments
 
@@ -482,10 +482,10 @@ impl Visitor2 for PassValidationLinking {
 
        // Visit the children themselves
 
        self.in_binding_expr_lhs = true;
 
        self.expr_parent = ExpressionParent::Expression(upcast_id, 0);
 
        self.visit_expr(ctx, bound_to_id);
 
        self.visit_expr(ctx, bound_to_id)?;
 
        self.in_binding_expr_lhs = false;
 
        self.expr_parent = ExpressionParent::Expression(upcast_id, 1);
 
        self.visit_expr(ctx, bound_from_id);
 
        self.visit_expr(ctx, bound_from_id)?;
 

	
 
        self.expr_parent = old_expr_parent;
 
        self.in_binding_expr = BindingExpressionId::new_invalid();
 
@@ -980,6 +980,8 @@ impl Visitor2 for PassValidationLinking {
 

	
 
    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) {
 
            Ok(variable_id) => {
 
                // Regular variable
 
@@ -989,6 +991,7 @@ impl Visitor2 for PassValidationLinking {
 
                // 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"
 
                    ));
 
@@ -1385,7 +1388,7 @@ impl PassValidationLinking {
 
            for local_id in &block.locals {
 
                let local = &ctx.heap[*local_id];
 
                
 
                if local.relative_pos_in_block < relative_pos && identifier == &local.identifier {
 
                if local.relative_pos_in_block <= relative_pos && identifier == &local.identifier {
 
                    return Ok(*local_id);
 
                }
 
            }
src/protocol/tests/parser_binding.rs
Show inline comments
 
@@ -2,5 +2,82 @@ use super::*;
 

	
 
#[test]
 
fn test_correct_binding() {
 
    Tester::new_single_source_expect_ok("binding bare", )
 
    Tester::new_single_source_expect_ok("binding bare", "
 
        enum TestEnum{ A, B }
 
        union TestUnion{ A(u32), B }
 
        struct TestStruct{ u32 field }
 

	
 
        func foo() -> u32 {
 
            auto lit_enum_a = TestEnum::A;
 
            auto lit_enum_b = TestEnum::B;
 
            auto lit_union_a = TestUnion::A(0);
 
            auto lit_union_b = TestUnion::B;
 
            auto lit_struct = TestStruct{ field: 0 };
 

	
 
            if (let test_enum_a = lit_enum_a)   { auto can_use = test_enum_a; }
 
            if (let test_enum_b = lit_enum_b)   { auto can_use = test_enum_b; }
 
            if (let test_union_a = lit_union_a) { auto can_use = test_union_a; }
 
            if (let test_union_b = lit_union_b) { auto can_use = test_union_b; }
 
            if (let test_struct = lit_struct)   { auto can_use = test_struct; }
 

	
 
            return 0;
 
        }
 
    ").for_function("foo", |f| { f
 
        .for_variable("test_enum_a", |v| { v.assert_concrete_type("TestEnum"); })
 
        .for_variable("test_enum_b", |v| { v.assert_concrete_type("TestEnum"); })
 
        .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_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 || before binding", "
 
enum Test{ A, B }
 
func foo() -> u32 {
 
    if (let a = Test::A || 5 + 2 == 7) {
 
        auto mission_impossible = 5;
 
    }
 
    return 0;
 
}
 
    ");
 
}
 
\ No newline at end of file
src/protocol/tests/utils.rs
Show inline comments
 
@@ -465,14 +465,14 @@ impl<'a> FunctionTester<'a> {
 
    }
 

	
 
    pub(crate) fn for_variable<F: Fn(VariableTester)>(self, name: &str, f: F) -> Self {
 
        // Find the memory statement in order to find the local
 
        let mem_stmt_id = seek_stmt(
 
        // Seek through the blocks in order to find the variable
 
        let wrapping_block_id = seek_stmt(
 
            self.ctx.heap, self.def.body.upcast(),
 
            &|stmt| {
 
                if let Statement::Local(local) = stmt {
 
                    if let LocalStatement::Memory(memory) = local {
 
                        let local = &self.ctx.heap[memory.variable];
 
                        if local.identifier.value.as_str() == name {
 
                if let Statement::Block(block) = stmt {
 
                    for local_id in &block.locals {
 
                        let var = &self.ctx.heap[*local_id];
 
                        if var.identifier.value.as_str() == name {
 
                            return true;
 
                        }
 
                    }
 
@@ -482,43 +482,52 @@ impl<'a> FunctionTester<'a> {
 
            }
 
        );
 

	
 
        let mut found_local_id = None;
 
        if let Some(block_id) = wrapping_block_id {
 
            let block_stmt = self.ctx.heap[block_id].as_block();
 
            for local_id in &block_stmt.locals {
 
                let var = &self.ctx.heap[*local_id];
 
                if var.identifier.value.as_str() == name {
 
                    found_local_id = Some(*local_id);
 
                }
 
            }
 
        }
 

	
 
        assert!(
 
            mem_stmt_id.is_some(), "[{}] Failed to find variable '{}' in {}",
 
            found_local_id.is_some(), "[{}] Failed to find variable '{}' in {}",
 
            self.ctx.test_name, name, self.assert_postfix()
 
        );
 

	
 
        let mem_stmt_id = mem_stmt_id.unwrap();
 
        let local_id = self.ctx.heap[mem_stmt_id].as_memory().variable;
 
        let local = &self.ctx.heap[local_id];
 
        let local = &self.ctx.heap[found_local_id.unwrap()];
 

	
 
        // Find the assignment expression that follows it
 
        let assignment_id = seek_expr_in_stmt(
 
        // Find an instance of the variable expression so we can determine its
 
        // type.
 
        let var_expr = seek_expr_in_stmt(
 
            self.ctx.heap, self.def.body.upcast(),
 
            &|expr| {
 
                if let Expression::Assignment(assign_expr) = expr {
 
                    if let Expression::Variable(variable_expr) = &self.ctx.heap[assign_expr.left] {
 
                        if variable_expr.identifier.span.begin.offset == local.identifier.span.begin.offset {
 
                if let Expression::Variable(variable_expr) = expr {
 
                    if variable_expr.identifier.value.as_str() == name {
 
                        return true;
 
                    }
 
                }
 
                }
 

	
 
                false
 
            }
 
        );
 

	
 
        assert!(
 
            assignment_id.is_some(), "[{}] Failed to find assignment to variable '{}' in {}",
 
            var_expr.is_some(), "[{}] Failed to find variable expression of '{}' in {}",
 
            self.ctx.test_name, name, self.assert_postfix()
 
        );
 

	
 
        let assignment = &self.ctx.heap[assignment_id.unwrap()];
 
        let var_expr = &self.ctx.heap[var_expr.unwrap()];
 

	
 
        // Construct tester and pass to tester function
 
        let tester = VariableTester::new(
 
            self.ctx, self.def.this.upcast(), local,
 
            assignment.as_assignment()
 
            var_expr.as_variable()
 
        );
 

	
 
        f(tester);
 

	
 
        self
 
@@ -665,14 +674,14 @@ pub(crate) struct VariableTester<'a> {
 
    ctx: TestCtx<'a>,
 
    definition_id: DefinitionId,
 
    variable: &'a Variable,
 
    assignment: &'a AssignmentExpression,
 
    var_expr: &'a VariableExpression,
 
}
 

	
 
impl<'a> VariableTester<'a> {
 
    fn new(
 
        ctx: TestCtx<'a>, definition_id: DefinitionId, variable: &'a Variable, assignment: &'a AssignmentExpression
 
        ctx: TestCtx<'a>, definition_id: DefinitionId, variable: &'a Variable, var_expr: &'a VariableExpression
 
    ) -> Self {
 
        Self{ ctx, definition_id, variable, assignment }
 
        Self{ ctx, definition_id, variable, var_expr }
 
    }
 

	
 
    pub(crate) fn assert_parser_type(self, expected: &str) -> Self {
 
@@ -690,8 +699,7 @@ impl<'a> VariableTester<'a> {
 
    pub(crate) fn assert_concrete_type(self, expected: &str) -> Self {
 
        // Lookup concrete type in type table
 
        let mono_data = self.ctx.types.get_procedure_expression_data(&self.definition_id, 0);
 
        let lhs = self.ctx.heap[self.assignment.left].as_variable();
 
        let concrete_type = &mono_data.expr_data[lhs.unique_id_in_definition as usize].expr_type;
 
        let concrete_type = &mono_data.expr_data[self.var_expr.unique_id_in_definition as usize].expr_type;
 

	
 
        // Serialize and check
 
        let mut serialized = String::new();
0 comments (0 inline, 0 general)