diff --git a/src/protocol/ast.rs b/src/protocol/ast.rs index 3768723cfe9b4a64bf1f1ab077d2d1e3ba2ad71d..83ae094fbfd5a4aad116353aef5f60c9c8c6c1f5 100644 --- a/src/protocol/ast.rs +++ b/src/protocol/ast.rs @@ -1731,6 +1731,7 @@ pub enum Literal { Enum(LiteralEnum), Union(LiteralUnion), Array(Vec), + Tuple(Vec), } impl Literal { diff --git a/src/protocol/ast_printer.rs b/src/protocol/ast_printer.rs index 94a1de00f5443ddc88cad37734069f44eef935bc..ba45acba976625fff9d116b837774d4cd276901e 100644 --- a/src/protocol/ast_printer.rs +++ b/src/protocol/ast_printer.rs @@ -706,11 +706,19 @@ impl ASTWriter { self.kv(indent3).with_s_key("Value"); self.write_expr(heap, *value, indent4); } - } + }, Literal::Array(data) => { val.with_s_val("Array"); let indent4 = indent3 + 1; + self.kv(indent3).with_s_key("Elements"); + for expr_id in data { + self.write_expr(heap, *expr_id, indent4); + } + }, + Literal::Tuple(data) => { + val.with_s_val("Tuple"); + let indent4 = indent3 + 1; self.kv(indent3).with_s_key("Elements"); for expr_id in data { self.write_expr(heap, *expr_id, indent4); diff --git a/src/protocol/eval/executor.rs b/src/protocol/eval/executor.rs index 8a80bd8dce90a3dc81de4d03d545a90e2b1a5d27..8ea043156fec0af942858ff46cb79ce861946c6a 100644 --- a/src/protocol/eval/executor.rs +++ b/src/protocol/eval/executor.rs @@ -171,6 +171,12 @@ impl Frame { self.expr_stack.push_back(ExprInstruction::PushValToFront); self.serialize_expression(heap, *value_expr_id); } + }, + Literal::Tuple(value_expr_ids) => { + for value_expr_id in value_expr_ids { + self.expr_stack.push_back(ExprInstruction::PushValToFront); + self.serialize_expression(heap, *value_expr_id); + } } } }, @@ -553,6 +559,12 @@ impl Prompt { ); Value::Array(heap_pos) } + Literal::Tuple(lit_value) => { + let heap_pos = transfer_expression_values_front_into_heap( + cur_frame, &mut self.store, lit_value.len() + ); + Value::Tuple(heap_pos) + } }; cur_frame.expr_values.push_back(value); diff --git a/src/protocol/eval/value.rs b/src/protocol/eval/value.rs index d08d327fecbb3faaaa79ae2c9b7629ebb8ba8a4e..b6c8b42c3ca0ec60c9cbcc702bdd854b4cb5fc35 100644 --- a/src/protocol/eval/value.rs +++ b/src/protocol/eval/value.rs @@ -58,6 +58,7 @@ pub enum Value { SInt32(i32), SInt64(i64), Array(HeapPos), + Tuple(HeapPos), // Instances of user-defined types Enum(i64), Union(i64, HeapPos), @@ -94,6 +95,7 @@ impl_union_unpack_as_value!(as_sint16, Value::SInt16, i16); impl_union_unpack_as_value!(as_sint32, Value::SInt32, i32); impl_union_unpack_as_value!(as_sint64, Value::SInt64, i64); impl_union_unpack_as_value!(as_array, Value::Array, HeapPos); +impl_union_unpack_as_value!(as_tuple, Value::Tuple, HeapPos); impl_union_unpack_as_value!(as_enum, Value::Enum, i64); impl_union_unpack_as_value!(as_struct, Value::Struct, HeapPos); @@ -154,6 +156,7 @@ impl Value { Value::Message(v) => Some(*v), Value::String(v) => Some(*v), Value::Array(v) => Some(*v), + Value::Tuple(v) => Some(*v), Value::Union(_, v) => Some(*v), Value::Struct(v) => Some(*v), _ => None @@ -222,6 +225,7 @@ impl ValueGroup { Value::Message(_) => Value::Message(new_region_idx), Value::String(_) => Value::String(new_region_idx), Value::Array(_) => Value::Array(new_region_idx), + Value::Tuple(_) => Value::Tuple(new_region_idx), Value::Union(tag, _) => Value::Union(*tag, new_region_idx), Value::Struct(_) => Value::Struct(new_region_idx), _ => unreachable!(), @@ -268,6 +272,7 @@ impl ValueGroup { Value::Message(_) => Value::Message(to_heap_pos), Value::String(_) => Value::String(to_heap_pos), Value::Array(_) => Value::Array(to_heap_pos), + Value::Tuple(_) => Value::Tuple(to_heap_pos), Value::Union(tag, _) => Value::Union(*tag, to_heap_pos), Value::Struct(_) => Value::Struct(to_heap_pos), _ => unreachable!(), @@ -328,6 +333,7 @@ pub(crate) fn apply_assignment_operator(store: &mut Store, lhs: ValueId, op: Ass Value::SInt32(v) => { *v = rhs.as_sint32(); }, Value::SInt64(v) => { *v = rhs.as_sint64(); }, Value::Array(v) => { to_dealloc = Some(*v); *v = rhs.as_array(); }, + Value::Tuple(v) => { to_dealloc = Some(*v); *v = rhs.as_tuple(); }, Value::Enum(v) => { *v = rhs.as_enum(); }, Value::Union(lhs_tag, lhs_heap_pos) => { to_dealloc = Some(*lhs_heap_pos); @@ -790,6 +796,7 @@ pub(crate) fn apply_equality_operator(store: &Store, lhs: &Value, rhs: &Value) - Value::SInt32(v) => *v == rhs.as_sint32(), Value::SInt64(v) => *v == rhs.as_sint64(), Value::Array(lhs_pos) => eval_equality_heap(store, *lhs_pos, rhs.as_array()), + Value::Tuple(lhs_pos) => eval_equality_heap(store, *lhs_pos, rhs.as_tuple()), Value::Enum(v) => *v == rhs.as_enum(), Value::Union(lhs_tag, lhs_pos) => { let (rhs_tag, rhs_pos) = rhs.as_union(); @@ -844,6 +851,7 @@ pub(crate) fn apply_inequality_operator(store: &Store, lhs: &Value, rhs: &Value) Value::SInt32(v) => *v != rhs.as_sint32(), Value::SInt64(v) => *v != rhs.as_sint64(), Value::Array(lhs_pos) => eval_inequality_heap(store, *lhs_pos, rhs.as_array()), + Value::Tuple(lhs_pos) => eval_inequality_heap(store, *lhs_pos, rhs.as_tuple()), Value::Enum(v) => *v != rhs.as_enum(), Value::Union(lhs_tag, lhs_pos) => { let (rhs_tag, rhs_pos) = rhs.as_union(); @@ -909,6 +917,7 @@ pub(crate) fn apply_binding_operator(store: &mut Store, lhs: Value, rhs: Value) 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::Tuple(lhs_pos) => eval_binding_heap(store, lhs_pos, rhs.as_tuple()), Value::Enum(v) => v == rhs.as_enum(), Value::Union(lhs_tag, lhs_pos) => { let (rhs_tag, rhs_pos) = rhs.as_union(); diff --git a/src/protocol/input_source.rs b/src/protocol/input_source.rs index 93f133f9fd795e66c77b66bba97e7fad3b4f387e..fccb0372e7697ff8478444038d3fad561cb7e0f1 100644 --- a/src/protocol/input_source.rs +++ b/src/protocol/input_source.rs @@ -61,12 +61,6 @@ impl InputSource { } } - #[cfg(test)] - pub fn new_test(input: &str) -> Self { - let bytes = Vec::from(input.as_bytes()); - return Self::new(String::from("test"), bytes) - } - #[inline] pub fn pos(&self) -> InputPosition { InputPosition { line: self.line, offset: self.offset as u32 } diff --git a/src/protocol/parser/pass_definitions.rs b/src/protocol/parser/pass_definitions.rs index 0d15c82d1eab600fdb4e7ecd22714d90077f593d..ab2b18ad81aa014acecabc50db0c7b131aa6c945 100644 --- a/src/protocol/parser/pass_definitions.rs +++ b/src/protocol/parser/pass_definitions.rs @@ -402,8 +402,10 @@ impl PassDefinitions { let id = self.consume_if_statement(module, iter, ctx)?; section.push(id.upcast()); - let end_if = ctx.heap.alloc_end_if_statement(|this| EndIfStatement{ - this, start_if: id, next: StatementId::new_invalid() + let end_if = ctx.heap.alloc_end_if_statement(|this| EndIfStatement { + this, + start_if: id, + next: StatementId::new_invalid() }); section.push(end_if.upcast()); @@ -413,8 +415,10 @@ impl PassDefinitions { let id = self.consume_while_statement(module, iter, ctx)?; section.push(id.upcast()); - let end_while = ctx.heap.alloc_end_while_statement(|this| EndWhileStatement{ - this, start_while: id, next: StatementId::new_invalid() + let end_while = ctx.heap.alloc_end_while_statement(|this| EndWhileStatement { + this, + start_while: id, + next: StatementId::new_invalid() }); section.push(end_while.upcast()); @@ -443,7 +447,7 @@ impl PassDefinitions { let id = self.consume_fork_statement(module, iter, ctx)?; section.push(id.upcast()); - let end_fork = ctx.heap.alloc_end_fork_statement(|this| EndForkStatement{ + let end_fork = ctx.heap.alloc_end_fork_statement(|this| EndForkStatement { this, start_fork: id, next: StatementId::new_invalid(), @@ -478,6 +482,15 @@ impl PassDefinitions { section.push(id.upcast()); } } + } else if next == TokenKind::OpenParen { + // Same as above: memory statement or normal expression + if let Some((memory_stmt_id, assignment_stmt_id)) = self.maybe_consume_memory_statement(module, iter, ctx)? { + section.push(memory_stmt_id.upcast().upcast()); + section.push(assignment_stmt_id.upcast()); + } else { + let id = self.consume_expression_statement(module, iter, ctx)?; + section.push(id.upcast()); + } } else { let id = self.consume_expression_statement(module, iter, ctx)?; section.push(id.upcast()); @@ -1330,10 +1343,63 @@ impl PassDefinitions { let next = iter.next(); let result = if next == Some(TokenKind::OpenParen) { - // Expression between parentheses + // Something parenthesized. This can mean several things: we have + // a parenthesized expression or we have a tuple literal. They are + // ambiguous when the tuple has one member. But like the tuple type + // parsing we interpret all one-tuples as parenthesized expressions. + // + // Practically (to prevent unnecessary `consume_expression` calls) + // we distinguish the zero-tuple, the parenthesized expression, and + // the N-tuple (for N > 1). + let open_paren_pos = iter.next_start_position(); iter.consume(); - let result = self.consume_expression(module, iter, ctx)?; - consume_token(&module.source, iter, TokenKind::CloseParen)?; + let result = if Some(TokenKind::CloseParen) == iter.next() { + // Zero-tuple + let (_, close_paren_pos) = iter.next_positions(); + iter.consume(); + + let literal_id = ctx.heap.alloc_literal_expression(|this| LiteralExpression{ + this, + span: InputSpan::from_positions(open_paren_pos, close_paren_pos), + value: Literal::Tuple(Vec::new()), + parent: ExpressionParent::None, + unique_id_in_definition: -1, + }); + + literal_id.upcast() + } else { + // Start by consuming one expression, then check for a comma + let expr_id = self.consume_expression(module, iter, ctx)?; + if Some(TokenKind::Comma) == iter.next() && Some(TokenKind::CloseParen) != iter.peek() { + // Must be an N-tuple + iter.consume(); // the comma + let mut scoped_section = self.expressions.start_section(); + scoped_section.push(expr_id); + + let mut close_paren_pos = open_paren_pos; + consume_comma_separated_until( + TokenKind::CloseParen, &module.source, iter, ctx, + |_source, iter, ctx| self.consume_expression(module, iter, ctx), + &mut scoped_section, "an expression", Some(&mut close_paren_pos) + )?; + debug_assert!(scoped_section.len() > 1); // peeked token wasn't CloseParen, must be expression + + let literal_id = ctx.heap.alloc_literal_expression(|this| LiteralExpression{ + this, + span: InputSpan::from_positions(open_paren_pos, close_paren_pos), + value: Literal::Tuple(scoped_section.into_vec()), + parent: ExpressionParent::None, + unique_id_in_definition: -1, + }); + + literal_id.upcast() + } else { + // Assume we're dealing with a normal expression + consume_token(&module.source, iter, TokenKind::CloseParen)?; + + expr_id + } + }; result } else if next == Some(TokenKind::OpenCurly) { @@ -1385,6 +1451,7 @@ impl PassDefinitions { // May be a variable, a type instantiation or a function call. If we // have a single identifier that we cannot find in the type table // then we're going to assume that we're dealing with a variable. + let ident_span = iter.next_span(); let ident_text = module.source.section_at_span(ident_span); let symbol = ctx.symbols.get_symbol_by_name(SymbolScope::Module(module.root_id), ident_text); diff --git a/src/protocol/parser/pass_definitions_types.rs b/src/protocol/parser/pass_definitions_types.rs index 2f1a74fe8a86218c622676f0ca9805a9b43041b6..11603e2ffe11da4a97c02a6c51ee3186070a0b61 100644 --- a/src/protocol/parser/pass_definitions_types.rs +++ b/src/protocol/parser/pass_definitions_types.rs @@ -164,11 +164,13 @@ impl ParserTypeParser { } }, ParseState::TupleStart => { - // Allowed tokens: ident ) + // Allowed tokens: ident ( ) + // We'll strip the nested tuple later in this function match next { Some(TokenKind::Ident) => self.consume_type_idents( source, heap, symbols, wrapping_definition, poly_vars, cur_scope, allow_inference, iter )?, + Some(TokenKind::OpenParen) => self.consume_open_paren(iter), Some(TokenKind::CloseParen) => self.consume_close_paren(source, iter)?, _ => return Err(ParseError::new_error_str_at_pos( source, iter.last_valid_pos(), diff --git a/src/protocol/parser/pass_typing.rs b/src/protocol/parser/pass_typing.rs index a1ed224125d4cd4370298db0fdab3d51c4fcdd6a..ba38491017f0c0a32f7ecb48c41de6711f5f4475 100644 --- a/src/protocol/parser/pass_typing.rs +++ b/src/protocol/parser/pass_typing.rs @@ -1341,7 +1341,7 @@ impl Visitor for PassTyping { self.visit_expr(ctx, expr_id)?; } }, - Literal::Array(expressions) => { + Literal::Array(expressions) | Literal::Tuple(expressions) => { // TODO: @performance let expr_ids = expressions.clone(); for expr_id in expr_ids { @@ -1424,8 +1424,33 @@ impl PassTyping { fn resolve_types(&mut self, ctx: &mut Ctx, queue: &mut ResolveQueue) -> Result<(), ParseError> { // Keep inferring until we can no longer make any progress while !self.expr_queued.is_empty() { - let next_expr_idx = self.expr_queued.pop_front().unwrap(); - self.progress_expr(ctx, next_expr_idx)?; + // Make as much progress as possible without forced integer + // inference. + while !self.expr_queued.is_empty() { + let next_expr_idx = self.expr_queued.pop_front().unwrap(); + self.progress_expr(ctx, next_expr_idx)?; + } + + // Nothing is queued anymore. However we might have integer literals + // whose type cannot be inferred. For convenience's sake we'll + // infer these to be s32. + for (infer_expr_idx, infer_expr) in self.expr_types.iter_mut().enumerate() { + let expr_type = &mut infer_expr.expr_type; + if !expr_type.is_done && expr_type.parts.len() == 1 && expr_type.parts[0] == InferenceTypePart::IntegerLike { + // Force integer type to s32 + println!("DEBUG: Autoinferring (idx {}) {}", infer_expr.expr_id.index, String::from_utf8_lossy(ctx.module().source.section_at_span(ctx.heap[infer_expr.expr_id].full_span()))); + expr_type.parts[0] = InferenceTypePart::SInt32; + expr_type.is_done = true; + + // Requeue expression (and its parent, if it exists) + self.expr_queued.push_back(infer_expr_idx as i32); + + if let Some(parent_expr) = ctx.heap[infer_expr.expr_id].parent_expr_id() { + let parent_idx = ctx.heap[parent_expr].get_unique_id_in_definition(); + self.expr_queued.push_back(parent_idx); + } + } + } } // Helper for transferring polymorphic variables to concrete types and @@ -1477,22 +1502,15 @@ impl PassTyping { // Inference is now done. But we may still have uninferred types. So we // check for these. - for (infer_expr_idx, infer_expr) in self.expr_types.iter_mut().enumerate() { - let expr_type = &mut infer_expr.expr_type; - if !expr_type.is_done { - // Auto-infer numberlike/integerlike types to a regular int - if expr_type.parts.len() == 1 && expr_type.parts[0] == InferenceTypePart::IntegerLike { - expr_type.parts[0] = InferenceTypePart::SInt32; - self.expr_queued.push_back(infer_expr_idx as i32); - } else { - let expr = &ctx.heap[infer_expr.expr_id]; - return Err(ParseError::new_error_at_span( - &ctx.module().source, expr.full_span(), format!( - "could not fully infer the type of this expression (got '{}')", - expr_type.display_name(&ctx.heap) - ) - )); - } + for infer_expr in self.expr_types.iter_mut() { + if !infer_expr.expr_type.is_done { + let expr = &ctx.heap[infer_expr.expr_id]; + return Err(ParseError::new_error_at_span( + &ctx.module().source, expr.full_span(), format!( + "could not fully infer the type of this expression (got '{}')", + infer_expr.expr_type.display_name(&ctx.heap) + ) + )); } // Expression is fine, check if any extra data is attached @@ -1532,7 +1550,7 @@ impl PassTyping { // Not typechecked yet, so add an entry in the queue let reserved_idx = ctx.types.reserve_procedure_monomorph_index(&definition_id, concrete_type); infer_expr.field_or_monomorph_idx = reserved_idx; - queue.push(ResolveQueueElement{ + queue.push(ResolveQueueElement { root_id: ctx.heap[definition_id].defined_in(), definition_id, reserved_monomorph_idx: reserved_idx, @@ -1563,13 +1581,6 @@ impl PassTyping { } } - // If we did any implicit type forcing, then our queue isn't empty - // anymore - while !self.expr_queued.is_empty() { - let expr_idx = self.expr_queued.pop_back().unwrap(); - self.progress_expr(ctx, expr_idx)?; - } - // Every expression checked, and new monomorphs are queued. Transfer the // expression information to the type table. let procedure_arguments = match &self.definition_type { @@ -2461,10 +2472,53 @@ impl PassTyping { progress_expr }, + Literal::Tuple(data) => { + let expr_elements = data.clone(); // TODO: @performance + debug_log!("Tuple expr ({} elements): {}", expr_elements.len(), upcast_id.index); + debug_log!(" * Before:"); + debug_log!(" - Expr type: {}", self.debug_get_display_name(ctx, upcast_id)); + + // Initial tuple constraint + let num_members = expr_elements.len(); + let mut initial_type = Vec::with_capacity(num_members + 1); // TODO: @performance + initial_type.push(InferenceTypePart::Tuple(num_members as u32)); + for _ in 0..num_members { + initial_type.push(InferenceTypePart::Unknown); + } + let mut progress_expr = self.apply_template_constraint(ctx, upcast_id, &initial_type)?; + + // The elements of the tuple can have any type, but they must + // end up as arguments to the output tuple type. + debug_log!(" * During (checking expressions constituting tuple):"); + for (member_expr_index, member_expr_id) in expr_elements.iter().enumerate() { + // For the current expression index, (re)compute the + // position in the tuple type where the types should match. + let mut start_index = 1; // first element is Tuple type, second is the first child + for _ in 0..member_expr_index { + let tuple_expr_index = ctx.heap[id].unique_id_in_definition; + let tuple_type = &self.expr_types[tuple_expr_index as usize].expr_type; + start_index = InferenceType::find_subtree_end_idx(&tuple_type.parts, start_index); + debug_assert_ne!(start_index, tuple_type.parts.len()); // would imply less tuple type children than member expressions + } + + // Apply the constraint + let (member_progress_expr, member_progress) = self.apply_equal2_constraint( + ctx, upcast_id, upcast_id, start_index, *member_expr_id, 0 + )?; + debug_log!(" - Member {} type | {}", member_expr_index, self.debug_get_display_name(ctx, *member_expr_id)); + progress_expr = progress_expr || member_progress_expr; + + if member_progress { + self.queue_expr(ctx, *member_expr_id); + } + } + + progress_expr + } }; debug_log!(" * After:"); - debug_log!(" - Expr type: {}", self.debug_get_display_name(ctx, upcast_id)); + debug_log!(" - Expr type [{}]: {}", progress_expr, self.debug_get_display_name(ctx, upcast_id)); if progress_expr { self.queue_expr_parent(ctx, upcast_id); } @@ -3055,7 +3109,7 @@ impl PassTyping { _ => {} } - let mut progress = Vec::new(); + let mut progress = Vec::new(); // TODO: @Performance progress.resize(args.len(), false); // Do pairwise inference, keep track of the last entry we made progress diff --git a/src/protocol/parser/pass_validation_linking.rs b/src/protocol/parser/pass_validation_linking.rs index f7362e0996add4819ae92058adb09dff264b9f64..a2c27b912c13e69b7ecfaffe674d10d8cc5ea4db 100644 --- a/src/protocol/parser/pass_validation_linking.rs +++ b/src/protocol/parser/pass_validation_linking.rs @@ -1002,7 +1002,7 @@ impl Visitor for PassValidationLinking { expr_section.forget(); }, - Literal::Array(literal) => { + Literal::Array(literal) | Literal::Tuple(literal) => { // Visit all expressions in the array let upcast_id = id.upcast(); let expr_section = self.expression_buffer.start_section_initialized(literal); @@ -1225,7 +1225,7 @@ impl Visitor for PassValidationLinking { // subexpressions, so we're always fine if cfg!(debug_assertions) { match lit_expr.value { - Literal::Struct(_) | Literal::Union(_) | Literal::Array(_) => {}, + Literal::Struct(_) | Literal::Union(_) | Literal::Array(_) | Literal::Tuple(_) => {}, _ => unreachable!(), } } diff --git a/src/protocol/parser/token_parsing.rs b/src/protocol/parser/token_parsing.rs index 86176ef6bf8a7a5d9982069c185d4feb5a0aa2f0..3e265270f8f0375198d1a606c0cdf7c51fe4bcce 100644 --- a/src/protocol/parser/token_parsing.rs +++ b/src/protocol/parser/token_parsing.rs @@ -156,7 +156,8 @@ pub(crate) fn consume_token(source: &InputSource, iter: &mut TokenIter, expected Ok(span) } -/// Consumes a comma separated list until the closing delimiter is encountered +/// Consumes a comma separated list until the closing delimiter is encountered. +/// The closing delimiter is consumed as well. pub(crate) fn consume_comma_separated_until( close_delim: TokenKind, source: &InputSource, iter: &mut TokenIter, ctx: &mut PassCtx, mut consumer_fn: F, target: &mut E, item_name_and_article: &'static str, @@ -356,19 +357,20 @@ pub(crate) fn consume_character_literal( )); } + debug_assert!(char_text.len() >= 2); // always includes the bounding "'" match char_text.len() { - 0 => return Err(ParseError::new_error_str_at_span(source, span, "too little characters in character literal")), - 1 => { + 2 => return Err(ParseError::new_error_str_at_span(source, span, "too little characters in character literal")), + 3 => { // We already know the text is ascii, so just throw an error if we have the escape // character. - if char_text[0] == b'\\' { + if char_text[1] == b'\\' { return Err(ParseError::new_error_str_at_span(source, span, "escape character without subsequent character")); } - return Ok((char_text[0] as char, span)); + return Ok((char_text[1] as char, span)); }, - 2 => { - if char_text[0] == b'\\' { - let result = parse_escaped_character(source, span, char_text[1])?; + 4 => { + if char_text[1] == b'\\' { + let result = parse_escaped_character(source, span, char_text[2])?; return Ok((result, span)) } }, diff --git a/src/protocol/parser/type_table.rs b/src/protocol/parser/type_table.rs index babe84e1d9f86672c9f93a3653bcbb2fd72c5853..b3f6ed830b77fe7d8153a97872eec013dd851309 100644 --- a/src/protocol/parser/type_table.rs +++ b/src/protocol/parser/type_table.rs @@ -123,13 +123,6 @@ impl DefinedTypeVariant { } } - pub(crate) fn as_struct_mut(&mut self) -> &mut StructType { - match self { - DefinedTypeVariant::Struct(v) => v, - _ => unreachable!("Cannot convert {} to struct variant", self.type_class()) - } - } - pub(crate) fn as_enum(&self) -> &EnumType { match self { DefinedTypeVariant::Enum(v) => v, @@ -137,26 +130,12 @@ impl DefinedTypeVariant { } } - pub(crate) fn as_enum_mut(&mut self) -> &mut EnumType { - match self { - DefinedTypeVariant::Enum(v) => v, - _ => unreachable!("Cannot convert {} to enum variant", self.type_class()) - } - } - pub(crate) fn as_union(&self) -> &UnionType { match self { DefinedTypeVariant::Union(v) => v, _ => unreachable!("Cannot convert {} to union variant", self.type_class()) } } - - pub(crate) fn as_union_mut(&mut self) -> &mut UnionType { - match self { - DefinedTypeVariant::Union(v) => v, - _ => unreachable!("Cannot convert {} to union variant", self.type_class()) - } - } } pub struct PolymorphicVariable { @@ -269,13 +248,6 @@ pub(crate) enum MonomorphVariant { } impl MonomorphVariant { - fn as_struct(&self) -> &StructMonomorph { - match self { - MonomorphVariant::Struct(v) => v, - _ => unreachable!(), - } - } - fn as_struct_mut(&mut self) -> &mut StructMonomorph { match self { MonomorphVariant::Struct(v) => v, @@ -297,13 +269,6 @@ impl MonomorphVariant { } } - fn as_tuple(&self) -> &TupleMonomorph { - match self { - MonomorphVariant::Tuple(v) => v, - _ => unreachable!(), - } - } - fn as_tuple_mut(&mut self) -> &mut TupleMonomorph { match self { MonomorphVariant::Tuple(v) => v, diff --git a/src/protocol/tests/eval_binding.rs b/src/protocol/tests/eval_binding.rs index f4241920636e5f82b23b095b5208ad0e6399017d..1d61b48dcda8a059da5b67fdf085926bd67aa84d 100644 --- a/src/protocol/tests/eval_binding.rs +++ b/src/protocol/tests/eval_binding.rs @@ -129,6 +129,40 @@ fn test_binding_from_union() { }); } +#[test] +fn test_binding_from_tuple() { + Tester::new_single_source_expect_ok("tuple binding", " + func foo() -> u32 { + u64 value = 2000; + auto tuple = (\"hello\", value, 21); + + bool success1 = false; + if (let (\"hello\", value, 21) = tuple && let (a, b, c) = tuple) { + success1 = a == \"hello\" && b == value && c == 21; + } + + bool success2 = true; + if (let (\"nope\", a, b) = tuple) success2 = false; + if (let (\"hello\", 2001, 21) = tuple) success2 = false; + if (let (a, 2001, b) = tuple) success2 = false; + if (let (a, b, 22) = tuple) success2 = false; + + bool success3 = false; + if (let (\"hello\", v2a, v3a) = tuple && let (v1a, 2000, v3b) = tuple && let (v1b, v2b, 21) = tuple) { + success3 = v1a == v1b && v2a == v2b && v3a == v3b; + } + + if (success1 && success2 && success3 && let (\"hello\", a, b) = tuple) { + return cast(a) + b; + } + + return 0; + } + ").for_function("foo", |f| { f + .call_ok(Some(Value::UInt32(2021))); + }); +} + #[test] fn test_binding_fizz_buzz() { Tester::new_single_source_expect_ok("am I employable?", " diff --git a/src/protocol/tests/eval_operators.rs b/src/protocol/tests/eval_operators.rs index 3bc0130c6d58105648dea9d99a0759cc9014c2d4..de63537538834df9eb6d2c0582077ccc7d5a0168 100644 --- a/src/protocol/tests/eval_operators.rs +++ b/src/protocol/tests/eval_operators.rs @@ -146,6 +146,38 @@ fn test_binary_integer_operators() { ); } +#[test] +fn test_tuple_operators() { + Tester::new_single_source_expect_ok("tuple equality", " + func test_func() -> bool { + auto a1 = (8, 16, 32); + (u8, u16, u32) a2 = (8, 16, 32); + auto b1 = (); + () b2 = (); + + return a1 == a2 && a2 == (8, 16, 32) && b1 == b2 && b2 == (); + } + ").for_function("test_func", |f| { f + .call_ok(Some(Value::Bool(true))); + }); + + Tester::new_single_source_expect_ok("tuple inequality", " + func test_func() -> bool { + auto a = (8, 16, 32); + (u8, u16, u32) a_same = (8, 16, 32); + auto a_diff = (0b111, 0b1111, 0b11111); + auto b = (); + return + !(a != a_same) && + a != a_diff && + a != (8, 16, 320) && + !(b != ()); + } + ").for_function("test_func", |f| { f + .call_ok(Some(Value::Bool(true))); + }); +} + #[test] fn test_string_operators() { Tester::new_single_source_expect_ok("string concatenation", " diff --git a/src/protocol/tests/parser_literals.rs b/src/protocol/tests/parser_literals.rs index 29ab95774c778f867b8ecbc46072e01ff5e39f90..de343928abfb66f8f8b6cc0399badfa476c205b4 100644 --- a/src/protocol/tests/parser_literals.rs +++ b/src/protocol/tests/parser_literals.rs @@ -67,4 +67,58 @@ fn test_string_literals() { Tester::new_single_source_expect_err("non-ASCII string", " func test() -> string { return \"💧\"; } ").error(|e| { e.assert_msg_has(0, "non-ASCII character in string literal"); }); +} + +#[test] +fn test_tuple_literals() { + Tester::new_single_source_expect_ok("zero tuples", " + func test() -> () { + // Looks like lisp :) + auto t1 = (); + () t2 = (); + auto t3 = (()); + () t4 = (()); + auto t5 = ((((())))); + ((())) t6 = ((((())))); + + return (); + } + ").for_function("test", |f| { f + .for_variable("t1", |v| { v.assert_concrete_type("()"); }) + .for_variable("t2", |v| { v.assert_concrete_type("()"); }) + .for_variable("t3", |v| { v.assert_concrete_type("()"); }) + .for_variable("t4", |v| { v.assert_concrete_type("()"); }) + .for_variable("t5", |v| { v.assert_concrete_type("()"); }) + .for_variable("t6", |v| { v.assert_concrete_type("()"); }); + }); + + // All one-tuples (T) are transformed into T to prevent ambiguity + Tester::new_single_source_expect_ok("one tuples", " + func test() -> (u32) { + auto a = (0); + (s32) b = (1); + ((((s32)))) c = ((2)); + } + ").for_function("test", |f| { f + .for_variable("a", |v| { v.assert_concrete_type("s32"); }) + .for_variable("b", |v| { v.assert_concrete_type("s32"); }) + .for_variable("c", |v| { v.assert_concrete_type("s32"); }); + }); + + Tester::new_single_source_expect_ok("actual tuples", " + func test() -> (u32, u32) { + (u8,u16,u32) a = (0, 1, 2); + auto b = a; + auto c = (3, 4, 5); + ((auto, auto)) d = (a, c); + auto e = (\"hello\", 'c', 5 + 2); + return ((0), (1)); + } + ").for_function("test", |f| { f + .for_variable("a", |v| { v.assert_concrete_type("(u8,u16,u32)"); }) + .for_variable("b", |v| { v.assert_concrete_type("(u8,u16,u32)"); }) + .for_variable("c", |v| { v.assert_concrete_type("(s32,s32,s32)"); }) + .for_variable("d", |v| { v.assert_concrete_type("((u8,u16,u32),(s32,s32,s32))"); }) + .for_variable("e", |v| { v.assert_concrete_type("(string,char,s32)"); }); + }); } \ No newline at end of file diff --git a/src/protocol/tests/utils.rs b/src/protocol/tests/utils.rs index 84dde7c2d467633829f7e2df5863b42aee9524b7..fa7153214d697f2f57231e79511d844c581a5442 100644 --- a/src/protocol/tests/utils.rs +++ b/src/protocol/tests/utils.rs @@ -405,7 +405,7 @@ impl<'a> EnumTester<'a> { pub(crate) fn assert_size_alignment(mut self, serialized_monomorph: &str, size: usize, alignment: usize) -> Self { self = self.assert_has_monomorph(serialized_monomorph); - let (has_monomorph, serialized) = has_monomorph(self.ctx, self.def.this.upcast(), serialized_monomorph); + let (has_monomorph, _) = has_monomorph(self.ctx, self.def.this.upcast(), serialized_monomorph); let mono_index = has_monomorph.unwrap(); let mono = self.ctx.types.get_monomorph(mono_index); @@ -753,7 +753,7 @@ impl<'a> VariableTester<'a> { let concrete_type = &mono_data.expr_data[self.var_expr.unique_id_in_definition as usize].expr_type; // Serialize and check - let mut serialized = concrete_type.display_name(self.ctx.heap); + let serialized = concrete_type.display_name(self.ctx.heap); assert_eq!( expected, &serialized, @@ -788,7 +788,7 @@ impl<'a> ExpressionTester<'a> { let concrete_type = &mono_data.expr_data[expr_index as usize].expr_type; // Serialize and check type - let mut serialized = concrete_type.display_name(self.ctx.heap); + let serialized = concrete_type.display_name(self.ctx.heap); assert_eq!( expected, &serialized, @@ -925,7 +925,6 @@ impl<'a> ErrorTester<'a> { fn has_equal_num_monomorphs(ctx: TestCtx, num: usize, definition_id: DefinitionId) -> (bool, usize) { // Again: inefficient, but its testing code - let type_def = ctx.types.get_base_definition(&definition_id).unwrap(); let mut num_on_type = 0; for mono in &ctx.types.mono_lookup.monomorphs {