From 54cd3e2d66398f008ba67747f05f87798fd75424 2021-05-26 12:23:06 From: mh Date: 2021-05-26 12:23:06 Subject: [PATCH] WIP on typing of binding expr --- diff --git a/src/protocol/ast.rs b/src/protocol/ast.rs index 863c5e613323d01dc4eb9ea19b6e9e54f4cc8561..47e5960bf8ccb2cbcb5c595aafe2d6ec02c81623 100644 --- a/src/protocol/ast.rs +++ b/src/protocol/ast.rs @@ -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 diff --git a/src/protocol/parser/pass_definitions.rs b/src/protocol/parser/pass_definitions.rs index 98badfc75223f404fb3e0c410b50dd216ebfc072..f84453b973b4dc068e726bfe3301004b236b99a2 100644 --- a/src/protocol/parser/pass_definitions.rs +++ b/src/protocol/parser/pass_definitions.rs @@ -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 }); diff --git a/src/protocol/parser/pass_typing.rs b/src/protocol/parser/pass_typing.rs index 8ad3e87682c4a630f0ede685ff1d2cb7998c45ae..6166b258fd36b0863b70a03bd8d409905af955a9 100644 --- a/src/protocol/parser/pass_typing.rs +++ b/src/protocol/parser/pass_typing.rs @@ -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(); - var_data.used_at.push(upcast_id); + + // 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]), diff --git a/src/protocol/parser/pass_validation_linking.rs b/src/protocol/parser/pass_validation_linking.rs index 6aaf18f205c2236ae851c169c3c918fc731bf0c4..8f61de4147aa2470da3fbb36f3be8bcbcb311de1 100644 --- a/src/protocol/parser/pass_validation_linking.rs +++ b/src/protocol/parser/pass_validation_linking.rs @@ -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); } } diff --git a/src/protocol/tests/parser_binding.rs b/src/protocol/tests/parser_binding.rs index 1966fd911cb64d8c4c1134750b55725115a94f58..44c6cbc44466eeb6b57d65756a9535f25a3a96e4 100644 --- a/src/protocol/tests/parser_binding.rs +++ b/src/protocol/tests/parser_binding.rs @@ -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 diff --git a/src/protocol/tests/utils.rs b/src/protocol/tests/utils.rs index 0b5224d1586dfecf47debbe70d09a9fb7e6b67db..5aecec7cd791e6f53e4aa3400d5b62cbb3f614d7 100644 --- a/src/protocol/tests/utils.rs +++ b/src/protocol/tests/utils.rs @@ -465,14 +465,14 @@ impl<'a> FunctionTester<'a> { } pub(crate) fn for_variable(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,24 +482,32 @@ 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 { - return true; - } + if let Expression::Variable(variable_expr) = expr { + if variable_expr.identifier.value.as_str() == name { + return true; } } @@ -508,17 +516,18 @@ impl<'a> FunctionTester<'a> { ); 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() + self.ctx, self.def.this.upcast(), local, + 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();