From b3c28b521ef69ad8bf3a9514aea9c5e7808a762b Mon Sep 17 00:00:00 2001 From: Yota Toyama Date: Sun, 25 Dec 2022 21:38:33 -0800 Subject: [PATCH] Parallel list comprehension (#1673) Close #1674. --- .cspell.json | 1 + features/types/list.feature | 10 + lib/ast-hir/src/module.rs | 57 +-- lib/ast/src/ast/list_comprehension_branch.rs | 27 +- lib/format/src/lib.rs | 81 +++- lib/hir-mir/src/built_in_call.rs | 9 +- lib/hir-mir/src/error.rs | 28 +- lib/hir-mir/src/generic_type_definition.rs | 29 +- lib/hir-mir/src/lib.rs | 205 +++++---- lib/hir-mir/src/list_comprehension.rs | 253 +++++++---- lib/hir-mir/src/variant_type_collection.rs | 38 +- lib/hir/src/analysis/error.rs | 20 + lib/hir/src/analysis/expression_visitor.rs | 4 +- .../src/analysis/try_operation_validator.rs | 50 ++- lib/hir/src/analysis/type_checker.rs | 404 +++++++++++++----- lib/hir/src/analysis/type_coercer.rs | 241 ++++++----- lib/hir/src/analysis/type_inferrer.rs | 383 ++++++++++++----- lib/hir/src/analysis/type_transformer.rs | 15 +- lib/hir/src/analysis/type_visitor.rs | 10 +- lib/hir/src/analysis/variable_transformer.rs | 84 ++-- lib/hir/src/ir.rs | 2 + lib/hir/src/ir/list_comprehension_branch.rs | 37 +- lib/hir/src/ir/list_comprehension_iteratee.rs | 30 ++ lib/hir/src/types/type_.rs | 8 + lib/parse/src/parser.rs | 62 +-- test/prelude/list.test.pen | 10 + 26 files changed, 1412 insertions(+), 686 deletions(-) create mode 100644 lib/hir/src/ir/list_comprehension_iteratee.rs diff --git a/.cspell.json b/.cspell.json index 99be5c366e..7207c8c052 100644 --- a/.cspell.json +++ b/.cspell.json @@ -37,6 +37,7 @@ "indoc", "insta", "intrinsics", + "iteratees", "itertools", "koka", "lcov", diff --git a/features/types/list.feature b/features/types/list.feature index e8b51f7176..315acbde31 100644 --- a/features/types/list.feature +++ b/features/types/list.feature @@ -122,6 +122,16 @@ Feature: List When I run `pen build` Then the exit status should be 0 + Scenario: Zip lists + Given a file named "Foo.pen" with: + """pen + f = \(xs [number], ys [number]) [number] { + [number x() + y() for x, y in xs, ys] + } + """ + When I run `pen build` + Then the exit status should be 0 + Scenario: Get a size of a list Given a file named "Foo.pen" with: """pen diff --git a/lib/ast-hir/src/module.rs b/lib/ast-hir/src/module.rs index 14bea575a3..b6fa5d4e63 100644 --- a/lib/ast-hir/src/module.rs +++ b/lib/ast-hir/src/module.rs @@ -284,10 +284,17 @@ fn compile_expression(expression: &ast::Expression) -> Result>()?, branch.condition().map(compile_expression).transpose()?, branch.position().clone(), )) @@ -497,16 +504,14 @@ mod tests { ast::Variable::new("x", Position::fake()), vec![ ast::ListComprehensionBranch::new( - "y", - None, - ast::Variable::new("x", Position::fake()), + vec!["y".into()], + vec![ast::Variable::new("x", Position::fake()).into()], None, Position::fake(), ), ast::ListComprehensionBranch::new( - "x", - None, - ast::Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ast::Variable::new("xs", Position::fake()).into()], None, Position::fake(), ), @@ -534,18 +539,20 @@ mod tests { ir::Variable::new("x", Position::fake()), vec![ ir::ListComprehensionBranch::new( - None, - "x", - None, - ir::Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ir::ListComprehensionIteratee::new( + None, + ir::Variable::new("xs", Position::fake()), + )], None, Position::fake(), ), ir::ListComprehensionBranch::new( - None, - "y", - None, - ir::Variable::new("x", Position::fake()), + vec!["y".into()], + vec![ir::ListComprehensionIteratee::new( + None, + ir::Variable::new("x", Position::fake()), + )], None, Position::fake(), ), @@ -580,9 +587,8 @@ mod tests { ast::types::Reference::new("number", Position::fake()), ast::Variable::new("x", Position::fake()), vec![ast::ListComprehensionBranch::new( - "x", - None, - ast::Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ast::Variable::new("xs", Position::fake()).into()], Some(ast::Variable::new("true", Position::fake()).into()), Position::fake(), ),], @@ -608,10 +614,11 @@ mod tests { types::Reference::new("number", Position::fake()), ir::Variable::new("x", Position::fake()), vec![ir::ListComprehensionBranch::new( - None, - "x", - None, - ir::Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ir::ListComprehensionIteratee::new( + None, + ir::Variable::new("xs", Position::fake()), + )], Some(ir::Variable::new("true", Position::fake()).into()), Position::fake(), )], diff --git a/lib/ast/src/ast/list_comprehension_branch.rs b/lib/ast/src/ast/list_comprehension_branch.rs index 8c0c04f71b..38ce75c7ad 100644 --- a/lib/ast/src/ast/list_comprehension_branch.rs +++ b/lib/ast/src/ast/list_comprehension_branch.rs @@ -4,40 +4,33 @@ use std::rc::Rc; #[derive(Clone, Debug, PartialEq)] pub struct ListComprehensionBranch { - primary_name: String, - secondary_name: Option, - iteratee: Rc, + names: Vec, + iteratees: Vec, condition: Option>, position: Position, } impl ListComprehensionBranch { pub fn new( - primary_name: impl Into, - secondary_name: Option, - iteratee: impl Into, + names: Vec, + iteratees: Vec, condition: Option, position: Position, ) -> Self { Self { - primary_name: primary_name.into(), - secondary_name, - iteratee: iteratee.into().into(), + names, + iteratees, condition: condition.map(From::from), position, } } - pub fn primary_name(&self) -> &str { - &self.primary_name + pub fn names(&self) -> &[String] { + &self.names } - pub fn secondary_name(&self) -> Option<&str> { - self.secondary_name.as_deref() - } - - pub fn iteratee(&self) -> &Expression { - &self.iteratee + pub fn iteratees(&self) -> &[Expression] { + &self.iteratees } pub fn condition(&self) -> Option<&Expression> { diff --git a/lib/format/src/lib.rs b/lib/format/src/lib.rs index 7e632ddace..5bf4b00b47 100644 --- a/lib/format/src/lib.rs +++ b/lib/format/src/lib.rs @@ -478,18 +478,26 @@ fn compile_expression(context: &mut Context, expression: &Expression) -> Documen .map(|branch| { compile_line_comment(context, branch.position(), |context| { sequence( - ["for ".into(), branch.primary_name().into()] + ["for ".into()] .into_iter() .chain( branch - .secondary_name() - .into_iter() - .flat_map(|name| [", ".into(), name.into()]), + .names() + .iter() + .map(|string| string.as_str().into()) + .intersperse(", ".into()), + ) + .chain([" in ".into()]) + .chain( + branch + .iteratees() + .iter() + .map(|iteratee| { + compile_expression(context, iteratee) + }) + .intersperse(", ".into()) + .collect::>(), ) - .chain([ - " in ".into(), - compile_expression(context, branch.iteratee()), - ]) .chain(branch.condition().map(|condition| { sequence([ " if ".into(), @@ -3107,9 +3115,8 @@ mod tests { types::Reference::new("none", Position::fake()), Variable::new("none", Position::fake()), vec![ListComprehensionBranch::new( - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![Variable::new("xs", Position::fake()).into()], None, Position::fake(), )], @@ -3129,9 +3136,8 @@ mod tests { types::Reference::new("none", Position::fake()), Variable::new("none", line_position(2)), vec![ListComprehensionBranch::new( - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![Variable::new("xs", Position::fake()).into()], None, line_position(2), )], @@ -3159,9 +3165,8 @@ mod tests { types::Reference::new("none", Position::fake()), Variable::new("none", line_position(2)), vec![ListComprehensionBranch::new( - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![Variable::new("xs", Position::fake()).into()], Some(Variable::new("true", Position::fake()).into()), line_position(2) )], @@ -3180,6 +3185,38 @@ mod tests { .trim() ); } + + #[test] + fn format_parallel() { + assert_eq!( + format( + &ListComprehension::new( + types::Reference::new("none", Position::fake()), + Variable::new("none", line_position(2)), + vec![ListComprehensionBranch::new( + vec!["x".into(), "y".into()], + vec![ + Variable::new("xs", Position::fake()).into(), + Variable::new("ys", Position::fake()).into() + ], + None, + line_position(2) + )], + line_position(1) + ) + .into() + ), + indoc!( + " + [none + none + for x, y in xs, ys + ] + " + ) + .trim() + ); + } } } @@ -3362,9 +3399,8 @@ mod tests { types::Reference::new("none", Position::fake()), Variable::new("none", Position::fake()), vec![ListComprehensionBranch::new( - "k", - Some("v".into()), - Variable::new("xs", Position::fake()), + vec!["k".into(), "v".into()], + vec![Variable::new("xs", Position::fake()).into()], None, Position::fake(), )], @@ -3384,9 +3420,8 @@ mod tests { types::Reference::new("none", Position::fake()), Variable::new("none", line_position(2)), vec![ListComprehensionBranch::new( - "k", - Some("v".into()), - Variable::new("xs", Position::fake()), + vec!["k".into(), "v".into()], + vec![Variable::new("xs", Position::fake()).into()], None, line_position(2) )], diff --git a/lib/hir-mir/src/built_in_call.rs b/lib/hir-mir/src/built_in_call.rs index 31730bc942..6400bc4432 100644 --- a/lib/hir-mir/src/built_in_call.rs +++ b/lib/hir-mir/src/built_in_call.rs @@ -107,10 +107,11 @@ pub fn compile( position.clone(), ), vec![ListComprehensionBranch::new( - Some(types::List::new(list_type, call.position().clone()).into()), - ELEMENT_NAME, - None, - call.arguments()[0].clone(), + vec![ELEMENT_NAME.into()], + vec![ListComprehensionIteratee::new( + Some(types::List::new(list_type, call.position().clone()).into()), + call.arguments()[0].clone(), + )], None, position.clone(), )], diff --git a/lib/hir-mir/src/error.rs b/lib/hir-mir/src/error.rs index 3b5ff9585f..d853f63358 100644 --- a/lib/hir-mir/src/error.rs +++ b/lib/hir-mir/src/error.rs @@ -16,6 +16,8 @@ pub enum CompileError { InvalidVariantType(Type), MainFunctionNotFound(Position), MirTypeCheck(mir::analysis::type_check::TypeCheckError), + MixedIterateesInListComprehension(Position), + MultipleMapsInListComprehension(Position), NewContextFunctionNotFound(Position), } @@ -33,23 +35,37 @@ impl Display for CompileError { position ) } + Self::InvalidVariantType(type_) => { + write!( + formatter, + "unsupported type information: {}\n{}", + type_formatter::format(type_), + type_.position() + ) + } Self::MainFunctionNotFound(position) => { write!(formatter, "main function not found\n{}", position) } Self::MirTypeCheck(error) => { write!(formatter, "failed to check types in MIR: {}", error) } - Self::NewContextFunctionNotFound(position) => { - write!(formatter, "new context function not found\n{}", position) + Self::MixedIterateesInListComprehension(position) => { + write!( + formatter, + "mixed iteratee types in list comprehension\n{}", + position + ) } - Self::InvalidVariantType(type_) => { + Self::MultipleMapsInListComprehension(position) => { write!( formatter, - "unsupported type information: {}\n{}", - type_formatter::format(type_), - type_.position() + "multiple maps in list comprehension\n{}", + position ) } + Self::NewContextFunctionNotFound(position) => { + write!(formatter, "new context function not found\n{}", position) + } } } } diff --git a/lib/hir-mir/src/generic_type_definition.rs b/lib/hir-mir/src/generic_type_definition.rs index 046509ffae..b8fb120813 100644 --- a/lib/hir-mir/src/generic_type_definition.rs +++ b/lib/hir-mir/src/generic_type_definition.rs @@ -407,10 +407,14 @@ mod tests { types::None::new(Position::fake()), None::new(Position::fake()), vec![ListComprehensionBranch::new( - Some(types::List::new(union_type.clone(), Position::fake()).into()), - "_", - None, - List::new(union_type, vec![], Position::fake()), + vec!["_".into()], + vec![ListComprehensionIteratee::new( + Some( + types::List::new(union_type.clone(), Position::fake()) + .into() + ), + List::new(union_type, vec![], Position::fake()), + )], None, Position::fake(), )], @@ -453,14 +457,15 @@ mod tests { union_type, None::new(Position::fake()), vec![ListComprehensionBranch::new( - Some(list_type.clone().into()), - "_", - None, - List::new( - types::None::new(Position::fake()), - vec![], - Position::fake() - ), + vec!["_".into()], + vec![ListComprehensionIteratee::new( + Some(list_type.clone().into()), + List::new( + types::None::new(Position::fake()), + vec![], + Position::fake() + ), + )], None, Position::fake(), )], diff --git a/lib/hir-mir/src/lib.rs b/lib/hir-mir/src/lib.rs index 0cf1c59cd7..52402680a4 100644 --- a/lib/hir-mir/src/lib.rs +++ b/lib/hir-mir/src/lib.rs @@ -838,10 +838,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("x", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("x", Position::fake()), + )], None, Position::fake(), )], @@ -884,30 +885,32 @@ mod tests { types::None::new(Position::fake()), None::new(Position::fake()), vec![ListComprehensionBranch::new( - None, - "x", - None, - Call::new( + vec!["x".into()], + vec![ListComprehensionIteratee::new( None, - Variable::new("xs", Position::fake()), - vec![], - Position::fake(), - ), + Call::new( + None, + Variable::new("xs", Position::fake()), + vec![], + Position::fake(), + ), + )], None, Position::fake(), )], Position::fake(), ), vec![ListComprehensionBranch::new( - None, - "xs", - None, - Call::new( + vec!["xs".into()], + vec![ListComprehensionIteratee::new( None, - Variable::new("g", Position::fake()), - vec![], - Position::fake(), - ), + Call::new( + None, + Variable::new("g", Position::fake()), + vec![], + Position::fake(), + ), + )], None, Position::fake(), )], @@ -940,10 +943,11 @@ mod tests { types::None::new(Position::fake()), None::new(Position::fake()), vec![ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], None, Position::fake(), )], @@ -978,18 +982,20 @@ mod tests { ), vec![ ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], None, Position::fake(), ), ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], None, Position::fake(), ), @@ -1038,18 +1044,20 @@ mod tests { ), vec![ ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], None, Position::fake(), ), ListComprehensionBranch::new( - None, - "y", - None, - Variable::new("ys", Position::fake()), + vec!["y".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("ys", Position::fake()), + )], None, Position::fake(), ), @@ -1086,23 +1094,25 @@ mod tests { ), vec![ ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], None, Position::fake(), ), ListComprehensionBranch::new( - None, - "y", - None, - Call::new( + vec!["y".into()], + vec![ListComprehensionIteratee::new( None, - Variable::new("x", Position::fake()), - vec![], - Position::fake(), - ), + Call::new( + None, + Variable::new("x", Position::fake()), + vec![], + Position::fake(), + ), + )], None, Position::fake(), ), @@ -1151,18 +1161,20 @@ mod tests { ), vec![ ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], None, Position::fake(), ), ListComprehensionBranch::new( - None, - "k", - Some("v".into()), - Variable::new("ys", Position::fake()), + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("ys", Position::fake()), + )], None, Position::fake(), ), @@ -1197,10 +1209,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], Some(Boolean::new(true, Position::fake()).into()), Position::fake(), )], @@ -1234,10 +1247,11 @@ mod tests { list_type.element().clone(), Variable::new("v", Position::fake()), vec![ListComprehensionBranch::new( - None, - "k", - Some("v".into()), - Variable::new("xs", Position::fake()), + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], Some(Boolean::new(true, Position::fake()).into()), Position::fake(), )], @@ -1250,6 +1264,52 @@ mod tests { ])) .unwrap(); } + + #[test] + fn compile_parallel() { + let list_type = + types::List::new(types::None::new(Position::fake()), Position::fake()); + let iteratee_list_types = [ + types::List::new(types::Number::new(Position::fake()), Position::fake()), + types::List::new(types::ByteString::new(Position::fake()), Position::fake()), + ]; + + compile_module(&Module::empty().set_function_definitions(vec![ + FunctionDefinition::fake( + "f", + Lambda::new( + vec![ + Argument::new("xs", iteratee_list_types[0].clone()), + Argument::new("ys", iteratee_list_types[1].clone()), + ], + list_type.clone(), + ListComprehension::new( + list_type.element().clone(), + None::new(Position::fake()), + vec![ListComprehensionBranch::new( + vec!["x".into(), "y".into()], + vec![ + ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + ), + ListComprehensionIteratee::new( + None, + Variable::new("ys", Position::fake()), + ), + ], + None, + Position::fake(), + )], + Position::fake(), + ), + Position::fake(), + ), + false, + ), + ])) + .unwrap(); + } } } @@ -1491,10 +1551,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - None, - "k", - Some("v".into()), - Variable::new("x", Position::fake()), + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("x", Position::fake()), + )], None, Position::fake(), )], diff --git a/lib/hir-mir/src/list_comprehension.rs b/lib/hir-mir/src/list_comprehension.rs index 030b9aa500..c4001b87af 100644 --- a/lib/hir-mir/src/list_comprehension.rs +++ b/lib/hir-mir/src/list_comprehension.rs @@ -10,9 +10,19 @@ pub fn compile( comprehension: &ListComprehension, ) -> Result { let [branch, ..] = comprehension.branches() else { unreachable!() }; - let iteratee_type = branch - .type_() - .ok_or_else(|| AnalysisError::TypeNotInferred(comprehension.position().clone()))?; + let iteratee_types = branch + .iteratees() + .iter() + .map(|iteratee| { + type_canonicalizer::canonicalize( + iteratee.type_().ok_or_else(|| { + AnalysisError::TypeNotInferred(comprehension.position().clone()) + })?, + context.types(), + ) + }) + .collect::, _>>()?; + // TODO Define a compile_element function. let element = if comprehension.branches().len() == 1 { ListElement::Single(comprehension.element().clone()) } else { @@ -27,30 +37,64 @@ pub fn compile( ) }; - match type_canonicalizer::canonicalize(iteratee_type, context.types())? { - Type::List(list_type) => compile_list(context, comprehension, branch, &list_type, element), - Type::Map(map_type) => compile_map(context, comprehension, branch, &map_type, element), - type_ => Err(AnalysisError::CollectionExpected( - type_.set_position(branch.iteratee().position().clone()), + if let [Type::Map(map_type)] = iteratee_types.as_slice() { + compile_map(context, comprehension, branch, map_type, element) + } else if iteratee_types + .iter() + .all(|type_| matches!(type_, Type::List(_))) + { + let list_types = iteratee_types + .iter() + .filter_map(|type_| type_.as_list()) + .collect::>(); + + compile_lists(context, comprehension, branch, &list_types, element) + } else if iteratee_types + .iter() + .all(|type_| matches!(type_, Type::Map(_))) + { + Err(CompileError::MultipleMapsInListComprehension( + comprehension.position().clone(), + )) + } else if iteratee_types + .iter() + .all(|type_| matches!(type_, Type::List(_) | Type::Map(_))) + { + Err(CompileError::MixedIterateesInListComprehension( + comprehension.position().clone(), + )) + } else { + let index = iteratee_types + .iter() + .position(|type_| !matches!(type_, Type::List(_) | Type::Map(_))) + .unwrap(); + + Err(AnalysisError::CollectionExpected( + iteratee_types[index] + .clone() + .set_position(branch.iteratees()[index].position().clone()), ) - .into()), + .into()) } } -fn compile_list( +fn compile_lists( context: &Context, comprehension: &ListComprehension, branch: &ListComprehensionBranch, - input_list_type: &types::List, + iteratee_types: &[&types::List], element: ListElement, ) -> Result { const CLOSURE_NAME: &str = "$loop"; - const LIST_NAME: &str = "$list"; - let position = comprehension.position(); - let input_element_type = input_list_type.element(); - let output_element_type = comprehension.type_(); let list_type = type_::compile_list(context)?; + let definition = compile_list_iteration_function_definition( + context, + comprehension, + branch, + iteratee_types, + element, + )?; Ok(mir::ir::Call::new( mir::types::Function::new( @@ -64,65 +108,22 @@ fn compile_list( vec![], list_type.clone(), mir::ir::LetRecursive::new( - mir::ir::FunctionDefinition::new( - CLOSURE_NAME, - vec![mir::ir::Argument::new(LIST_NAME, list_type.clone())], - list_type.clone(), - expression::compile( - context, - &IfList::new( - Some(input_element_type.clone()), - Variable::new(LIST_NAME, position.clone()), - branch.primary_name(), - LIST_NAME, - { - let rest = Call::new( - Some( - types::Function::new( - vec![types::List::new( - input_element_type.clone(), - position.clone(), - ) - .into()], - types::List::new( - output_element_type.clone(), - position.clone(), - ), - position.clone(), - ) - .into(), - ), - Variable::new(CLOSURE_NAME, position.clone()), - vec![Variable::new(LIST_NAME, position.clone()).into()], - position.clone(), - ); - let list = List::new( - output_element_type.clone(), - vec![element, ListElement::Multiple(rest.clone().into())], - position.clone(), - ); - - if let Some(condition) = branch.condition() { - Expression::from(If::new( - condition.clone(), - list, - rest, - branch.position().clone(), - )) - } else { - list.into() - } - }, - List::new(output_element_type.clone(), vec![], position.clone()), - position.clone(), - ) - .into(), - )?, - ), + definition.clone(), mir::ir::Call::new( - mir::types::Function::new(vec![list_type.clone().into()], list_type), - mir::ir::Variable::new(CLOSURE_NAME), - vec![expression::compile(context, branch.iteratee())?], + mir::types::Function::new( + branch + .iteratees() + .iter() + .map(|_| list_type.clone().into()) + .collect(), + list_type, + ), + mir::ir::Variable::new(definition.name()), + branch + .iteratees() + .iter() + .map(|iteratee| expression::compile(context, iteratee.expression())) + .collect::>()?, ), ), ), @@ -133,6 +134,91 @@ fn compile_list( .into()) } +fn compile_list_iteration_function_definition( + context: &Context, + comprehension: &ListComprehension, + branch: &ListComprehensionBranch, + iteratee_types: &[&types::List], + element: ListElement, +) -> Result { + const CLOSURE_NAME: &str = "$loop"; + const LIST_NAME: &str = "$list"; + + let position = comprehension.position(); + let list_type = type_::compile_list(context)?; + let iteratee_names = (0..branch.iteratees().len()) + .map(|index| format!("{}_{}", LIST_NAME, index)) + .collect::>(); + let arguments = iteratee_names + .iter() + .map(|name| mir::ir::Argument::new(name, list_type.clone())) + .collect(); + + let mut body = { + let rest = Call::new( + Some( + types::Function::new( + iteratee_types + .iter() + .map(|&type_| type_.clone().into()) + .collect(), + types::List::new(comprehension.type_().clone(), position.clone()), + position.clone(), + ) + .into(), + ), + Variable::new(CLOSURE_NAME, position.clone()), + iteratee_names + .iter() + .map(|name| Variable::new(name, position.clone()).into()) + .collect(), + position.clone(), + ); + let list = List::new( + comprehension.type_().clone(), + vec![element, ListElement::Multiple(rest.clone().into())], + position.clone(), + ); + + if let Some(condition) = branch.condition() { + Expression::from(If::new( + condition.clone(), + list, + rest, + branch.position().clone(), + )) + } else { + list.into() + } + }; + + for ((element_name, iteratee_name), type_) in branch + .names() + .iter() + .zip(iteratee_names) + .zip(iteratee_types) + .rev() + { + body = IfList::new( + Some(type_.element().clone()), + Variable::new(&iteratee_name, position.clone()), + element_name, + iteratee_name, + body, + List::new(comprehension.type_().clone(), vec![], position.clone()), + position.clone(), + ) + .into() + } + + Ok(mir::ir::FunctionDefinition::new( + CLOSURE_NAME, + arguments, + list_type, + expression::compile(context, &body)?, + )) +} + fn compile_map( context: &Context, comprehension: &ListComprehension, @@ -179,7 +265,11 @@ fn compile_map( .iteration .iterate_function_name, ), - vec![expression::compile(context, branch.iteratee())?], + branch + .iteratees() + .iter() + .map(|iteratee| expression::compile(context, iteratee.expression())) + .collect::>()?, ) .into()], ), @@ -253,7 +343,17 @@ fn compile_map_iteration_function_definition( vec![IfTypeBranch::new( iterator_type.clone(), Let::new( - Some(branch.primary_name().into()), + Some( + branch + .names() + .get(0) + .ok_or_else(|| { + AnalysisError::KeyNameNotDefined( + comprehension.position().clone(), + ) + })? + .into(), + ), Some(map_type.key().clone()), compile_key_value_function_call( &iteration_configuration.key_function_name, @@ -262,7 +362,8 @@ fn compile_map_iteration_function_definition( Let::new( Some( branch - .secondary_name() + .names() + .get(1) .ok_or_else(|| { AnalysisError::ValueNameNotDefined( comprehension.position().clone(), diff --git a/lib/hir-mir/src/variant_type_collection.rs b/lib/hir-mir/src/variant_type_collection.rs index 84a11cc26f..ead7c987e2 100644 --- a/lib/hir-mir/src/variant_type_collection.rs +++ b/lib/hir-mir/src/variant_type_collection.rs @@ -50,16 +50,18 @@ pub fn collect(context: &Context, module: &Module) -> Result, A } Expression::ListComprehension(comprehension) => { for branch in comprehension.branches() { - if let Some(type_) = branch.type_() { - match type_ { - Type::List(list_type) => { - lower_types.insert(list_type.element().clone()); + for iteratee in branch.iteratees() { + if let Some(type_) = iteratee.type_() { + match type_ { + Type::List(list_type) => { + lower_types.insert(list_type.element().clone()); + } + Type::Map(map_type) => { + lower_types.insert(map_type.key().clone()); + lower_types.insert(map_type.value().clone()); + } + _ => {} } - Type::Map(map_type) => { - lower_types.insert(map_type.key().clone()); - lower_types.insert(map_type.value().clone()); - } - _ => {} } } } @@ -349,10 +351,11 @@ mod tests { types::None::new(Position::fake()), None::new(Position::fake(),), vec![ListComprehensionBranch::new( - Some(input_list_type.clone().into()), - "x", - None, - Variable::new("x", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(input_list_type.clone().into()), + Variable::new("x", Position::fake()), + )], None, Position::fake(), )], @@ -393,10 +396,11 @@ mod tests { types::None::new(Position::fake()), None::new(Position::fake(),), vec![ListComprehensionBranch::new( - Some(input_map_type.clone().into()), - "k", - Some("v".into()), - Variable::new("x", Position::fake()), + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + Some(input_map_type.clone().into()), + Variable::new("x", Position::fake()), + )], None, Position::fake(), )], diff --git a/lib/hir/src/analysis/error.rs b/lib/hir/src/analysis/error.rs index 01244cc00b..c158eeb2f5 100644 --- a/lib/hir/src/analysis/error.rs +++ b/lib/hir/src/analysis/error.rs @@ -17,14 +17,18 @@ pub enum AnalysisError { CollectionExpected(Type), DuplicateFunctionNames(Position, Position), DuplicateTypeNames(Position, Position), + ElementNameNotDefined(Position), ErrorTypeUndefined, FunctionExpected(Type), ImpossibleRecord(Position), InvalidAdditionOperand(Position), InvalidTryOperation(Position), + KeyNameNotDefined(Position), + ListComprehensionIterateeCount(Position), ListExpected(Type), MapExpected(Type), MissingElseBlock(Position), + ParallelMapIteration(Position), RecordExpected(Type), RecordFieldNotFound(String, Position), RecordFieldPrivate(Position), @@ -103,6 +107,9 @@ impl Display for AnalysisError { Self::DuplicateTypeNames(one, other) => { write!(formatter, "duplicate type names\n{}\n{}", one, other) } + Self::ElementNameNotDefined(position) => { + write!(formatter, "element name not defined\n{}", position) + } Self::ErrorTypeUndefined => { write!(formatter, "error type undefined") } @@ -134,6 +141,16 @@ impl Display for AnalysisError { position ) } + Self::KeyNameNotDefined(position) => { + write!(formatter, "key name not defined\n{}", position) + } + Self::ListComprehensionIterateeCount(position) => { + write!( + formatter, + "unmatched iteratee count in list comprehension\n{}", + position + ) + } Self::ListExpected(type_) => { write!( formatter, @@ -155,6 +172,9 @@ impl Display for AnalysisError { position ) } + Self::ParallelMapIteration(position) => { + write!(formatter, "cannot iterate maps in parallel \n{}", position) + } Self::RecordExpected(type_) => { write!( formatter, diff --git a/lib/hir/src/analysis/expression_visitor.rs b/lib/hir/src/analysis/expression_visitor.rs index 0427027dae..9f348ea2d7 100644 --- a/lib/hir/src/analysis/expression_visitor.rs +++ b/lib/hir/src/analysis/expression_visitor.rs @@ -75,7 +75,9 @@ fn visit_expression<'a>(expression: &'a Expression, visit: &mut impl FnMut(&'a E } Expression::ListComprehension(comprehension) => { for branch in comprehension.branches() { - visit_expression(branch.iteratee()); + for iteratee in branch.iteratees() { + visit_expression(iteratee.expression()); + } } visit_expression(comprehension.element()); diff --git a/lib/hir/src/analysis/try_operation_validator.rs b/lib/hir/src/analysis/try_operation_validator.rs index f9d3108df9..879182ef90 100644 --- a/lib/hir/src/analysis/try_operation_validator.rs +++ b/lib/hir/src/analysis/try_operation_validator.rs @@ -79,7 +79,9 @@ fn validate_expression( } Expression::ListComprehension(comprehension) => { for branch in comprehension.branches() { - validate_expression(context, branch.iteratee(), None)?; + for iteratee in branch.iteratees() { + validate_expression(context, iteratee.expression(), None)?; + } if let Some(condition) = branch.condition() { validate_expression(context, condition, None)?; @@ -384,10 +386,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], None, Position::fake(), )], @@ -419,16 +422,17 @@ mod tests { types::None::new(Position::fake()), None::new(Position::fake()), vec![ListComprehensionBranch::new( - None, - "x", - None, - TryOperation::new( + vec!["x".into()], + vec![ListComprehensionIteratee::new( None, - Variable::new("xs", Position::fake()), - Position::fake(), - ), + TryOperation::new( + None, + Variable::new("xs", Position::fake()), + Position::fake(), + ), + )], None, - Position::fake() + Position::fake(), )], Position::fake() ), @@ -458,16 +462,20 @@ mod tests { types::None::new(Position::fake()), None::new(Position::fake()), vec![ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), - Some(TryOperation::new( + vec!["x".into()], + vec![ListComprehensionIteratee::new( None, Variable::new("xs", Position::fake()), - Position::fake(), - ).into()), - Position::fake() + )], + Some( + TryOperation::new( + None, + Variable::new("xs", Position::fake()), + Position::fake(), + ) + .into() + ), + Position::fake(), )], Position::fake() ), diff --git a/lib/hir/src/analysis/type_checker.rs b/lib/hir/src/analysis/type_checker.rs index 7f3006449a..080265108d 100644 --- a/lib/hir/src/analysis/type_checker.rs +++ b/lib/hir/src/analysis/type_checker.rs @@ -253,33 +253,76 @@ fn check_expression( let mut variables = variables.clone(); for branch in comprehension.branches() { - let iteratee_type = branch - .type_() - .ok_or_else(|| AnalysisError::TypeNotInferred(position.clone()))?; + if branch.iteratees().len() > 1 + && branch + .iteratees() + .iter() + .any(|iteratee| matches!(iteratee.type_(), Some(Type::Map(_)))) + { + return Err(AnalysisError::ParallelMapIteration( + branch.position().clone(), + )); + } else if branch.names().len() != branch.iteratees().len() + && branch + .iteratees() + .iter() + .all(|iteratee| matches!(iteratee.type_(), Some(Type::List(_)))) + { + return Err(AnalysisError::ListComprehensionIterateeCount( + branch.position().clone(), + )); + } - check_subsumption( - &check_expression(branch.iteratee(), &variables)?, - iteratee_type, - )?; + for (name, iteratee) in branch.names().iter().zip(branch.iteratees()) { + let iteratee_type = iteratee + .type_() + .ok_or_else(|| AnalysisError::TypeNotInferred(position.clone()))?; - variables = match type_canonicalizer::canonicalize(iteratee_type, context.types())? - { - Type::List(list_type) => variables.insert( - branch.primary_name().into(), - types::Function::new(vec![], list_type.element().clone(), position.clone()) - .into(), - ), - Type::Map(map_type) => variables.insert_iter( - [(branch.primary_name().into(), map_type.key().clone())] - .into_iter() - .chain( - branch - .secondary_name() - .map(|name| (name.into(), map_type.value().clone())), + check_subsumption( + &check_expression(iteratee.expression(), &variables)?, + iteratee_type, + )?; + + variables = + match type_canonicalizer::canonicalize(iteratee_type, context.types())? { + Type::List(list_type) => variables.insert( + name.into(), + types::Function::new( + vec![], + list_type.element().clone(), + position.clone(), + ) + .into(), ), - ), - _ => return Err(AnalysisError::TypeNotInferred(position.clone())), - }; + Type::Map(map_type) => variables.insert_iter([ + ( + branch + .names() + .get(0) + .ok_or_else(|| { + AnalysisError::KeyNameNotDefined( + branch.position().clone(), + ) + })? + .into(), + map_type.key().clone(), + ), + ( + branch + .names() + .get(1) + .ok_or_else(|| { + AnalysisError::ValueNameNotDefined( + branch.position().clone(), + ) + })? + .into(), + map_type.value().clone(), + ), + ]), + _ => return Err(AnalysisError::TypeNotInferred(position.clone())), + }; + } if let Some(condition) = branch.condition() { check_subsumption( @@ -2254,53 +2297,8 @@ mod tests { let element_type = types::None::new(Position::fake()); let list_type = types::List::new(element_type.clone(), Position::fake()); - check_module(&Module::empty().set_function_definitions(vec![ - FunctionDefinition::fake( - "f", - Lambda::new( - vec![], - types::List::new(element_type.clone(), Position::fake()), - ListComprehension::new( - element_type.clone(), - Call::new( - Some( - types::Function::new( - vec![], - element_type.clone(), - Position::fake(), - ) - .into(), - ), - Variable::new("x", Position::fake()), - vec![], - Position::fake(), - ), - vec![ListComprehensionBranch::new( - Some(list_type.into()), - "x", - None, - List::new(element_type, vec![], Position::fake()), - None, - Position::fake(), - )], - Position::fake(), - ), - Position::fake(), - ), - false, - ), - ])) - .unwrap(); - } - - #[test] - fn check_iteratee() { - let element_type = types::None::new(Position::fake()); - let list_type = types::List::new(element_type.clone(), Position::fake()); - - assert_eq!( - check_module(&Module::empty().set_function_definitions( - vec![FunctionDefinition::fake( + check_module(&Module::empty().set_function_definitions( + vec![FunctionDefinition::fake( "f", Lambda::new( vec![], @@ -2321,16 +2319,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - Some(list_type.into()), - "x", - None, - List::new( - element_type, - vec![ListElement::Single( - Number::new(42.0, Position::fake()).into(), - )], - Position::fake(), - ), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(list_type.into()), + List::new(element_type, vec![], Position::fake()), + )], None, Position::fake(), )], @@ -2339,7 +2332,59 @@ mod tests { Position::fake(), ), false, - )] + )], + )) + .unwrap(); + } + + #[test] + fn check_iteratee() { + let element_type = types::None::new(Position::fake()); + let list_type = types::List::new(element_type.clone(), Position::fake()); + + assert_eq!( + check_module(&Module::empty().set_function_definitions( + vec![FunctionDefinition::fake( + "f", + Lambda::new( + vec![], + types::List::new(element_type.clone(), Position::fake()), + ListComprehension::new( + element_type.clone(), + Call::new( + Some( + types::Function::new( + vec![], + element_type.clone(), + Position::fake(), + ) + .into(), + ), + Variable::new("x", Position::fake()), + vec![], + Position::fake(), + ), + vec![ListComprehensionBranch::new( + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(list_type.into()), + List::new( + element_type, + vec![ListElement::Single( + Number::new(42.0, Position::fake()).into(), + )], + Position::fake(), + ), + )], + None, + Position::fake(), + )], + Position::fake(), + ), + Position::fake(), + ), + false, + )] )), Err(AnalysisError::TypesNotMatched( types::Number::new(Position::fake()).into(), @@ -2364,10 +2409,11 @@ mod tests { element_type.clone(), None::new(Position::fake()), vec![ListComprehensionBranch::new( - Some(list_type.into()), - "x", - None, - List::new(element_type, vec![], Position::fake(),), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(list_type.into()), + List::new(element_type, vec![], Position::fake(),), + )], Some(None::new(Position::fake()).into()), Position::fake(), )], @@ -2384,6 +2430,135 @@ mod tests { )) ); } + + #[test] + fn check_parallel() { + let element_type = types::None::new(Position::fake()); + let list_type = types::List::new(element_type.clone(), Position::fake()); + + assert_eq!( + check_module(&Module::empty().set_function_definitions( + vec![FunctionDefinition::fake( + "f", + Lambda::new( + vec![], + types::List::new(element_type.clone(), Position::fake()), + ListComprehension::new( + element_type.clone(), + Call::new( + Some( + types::Function::new( + vec![], + element_type.clone(), + Position::fake(), + ) + .into(), + ), + Variable::new("x", Position::fake()), + vec![], + Position::fake(), + ), + vec![ListComprehensionBranch::new( + vec!["x".into(), "y".into()], + vec![ + ListComprehensionIteratee::new( + Some(list_type.clone().into()), + List::new( + element_type.clone(), + vec![], + Position::fake(), + ), + ), + ListComprehensionIteratee::new( + Some(list_type.into()), + List::new( + element_type, + vec![ListElement::Single( + Number::new(42.0, Position::fake()).into(), + )], + Position::fake(), + ), + ), + ], + None, + Position::fake(), + )], + Position::fake(), + ), + Position::fake(), + ), + false, + )] + )), + Err(AnalysisError::TypesNotMatched( + types::Number::new(Position::fake()).into(), + types::None::new(Position::fake()).into(), + )) + ); + } + + #[test] + fn check_iteratee_count() { + let element_type = types::None::new(Position::fake()); + let list_type = types::List::new(element_type.clone(), Position::fake()); + + assert_eq!( + check_module(&Module::empty().set_function_definitions( + vec![FunctionDefinition::fake( + "f", + Lambda::new( + vec![], + types::List::new(element_type.clone(), Position::fake()), + ListComprehension::new( + element_type.clone(), + Call::new( + Some( + types::Function::new( + vec![], + element_type.clone(), + Position::fake(), + ) + .into(), + ), + Variable::new("x", Position::fake()), + vec![], + Position::fake(), + ), + vec![ListComprehensionBranch::new( + vec!["x".into()], + vec![ + ListComprehensionIteratee::new( + Some(list_type.clone().into()), + List::new( + element_type.clone(), + vec![], + Position::fake(), + ), + ), + ListComprehensionIteratee::new( + Some(list_type.into()), + List::new( + element_type, + vec![], + Position::fake(), + ), + ), + ], + None, + Position::fake(), + )], + Position::fake(), + ), + Position::fake(), + ), + false, + )] + )), + Err(AnalysisError::ListComprehensionIterateeCount( + Position::fake(), + )) + ); + } } } @@ -2651,15 +2826,16 @@ mod tests { map_type.key().clone(), Variable::new("k", Position::fake()), vec![ListComprehensionBranch::new( - Some(map_type.clone().into()), - "k", - Some("v".into()), - Map::new( - map_type.key().clone(), - map_type.value().clone(), - vec![], - Position::fake(), - ), + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + Some(map_type.clone().into()), + Map::new( + map_type.key().clone(), + map_type.value().clone(), + vec![], + Position::fake(), + ), + )], None, Position::fake(), )], @@ -2691,15 +2867,16 @@ mod tests { map_type.value().clone(), Variable::new("v", Position::fake()), vec![ListComprehensionBranch::new( - Some(map_type.clone().into()), - "k", - Some("v".into()), - Map::new( - map_type.key().clone(), - map_type.value().clone(), - vec![], - Position::fake(), - ), + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + Some(map_type.clone().into()), + Map::new( + map_type.key().clone(), + map_type.value().clone(), + vec![], + Position::fake(), + ), + )], None, Position::fake(), )], @@ -2731,15 +2908,16 @@ mod tests { map_type.key().clone(), Variable::new("k", Position::fake()), vec![ListComprehensionBranch::new( - Some(map_type.clone().into()), - "k", - Some("v".into()), - Map::new( - map_type.key().clone(), - map_type.value().clone(), - vec![], - Position::fake(), - ), + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + Some(map_type.clone().into()), + Map::new( + map_type.key().clone(), + map_type.value().clone(), + vec![], + Position::fake(), + ), + )], None, Position::fake(), )], diff --git a/lib/hir/src/analysis/type_coercer.rs b/lib/hir/src/analysis/type_coercer.rs index 1cef255a2b..6a483819d9 100644 --- a/lib/hir/src/analysis/type_coercer.rs +++ b/lib/hir/src/analysis/type_coercer.rs @@ -282,36 +282,69 @@ fn transform_expression( let mut variables = variables.clone(); for branch in comprehension.branches() { - let iteratee = transform_expression(branch.iteratee(), &variables)?; + let iteratees = branch + .iteratees() + .iter() + .map(|iteratee| { + Ok(ListComprehensionIteratee::new( + iteratee.type_().cloned(), + transform_expression(iteratee.expression(), &variables)?, + )) + }) + .collect::>()?; - variables = match type_canonicalizer::canonicalize( + variables = variables.insert_iter( branch - .type_() - .ok_or_else(|| AnalysisError::TypeNotInferred(position.clone()))?, - context.types(), - )? { - Type::List(list_type) => variables.insert( - branch.primary_name().into(), - types::Function::new(vec![], list_type.element().clone(), position.clone()) - .into(), - ), - Type::Map(map_type) => variables.insert_iter( - [(branch.primary_name().into(), map_type.key().clone())] - .into_iter() - .chain( - branch - .secondary_name() - .map(|name| (name.into(), map_type.value().clone())), - ), - ), - type_ => return Err(AnalysisError::CollectionExpected(type_)), - }; + .iteratees() + .iter() + .enumerate() + .map(|(index, iteratee)| { + Ok( + match type_canonicalizer::canonicalize( + iteratee.type_().ok_or_else(|| { + AnalysisError::TypeNotInferred(position.clone()) + })?, + context.types(), + )? { + Type::List(list_type) => branch + .names() + .get(index) + .map(|name| { + ( + name.into(), + types::Function::new( + vec![], + list_type.element().clone(), + position.clone(), + ) + .into(), + ) + }) + .into_iter() + .collect::>(), + Type::Map(map_type) => { + branch + .names() + .get(0) + .map(|name| (name.into(), map_type.key().clone())) + .into_iter() + .chain(branch.names().get(1).map(|name| { + (name.into(), map_type.value().clone()) + })) + .collect() + } + type_ => return Err(AnalysisError::CollectionExpected(type_)), + }, + ) + }) + .collect::, _>>()? + .into_iter() + .flatten(), + ); branches.push(ListComprehensionBranch::new( - branch.type_().cloned(), - branch.primary_name(), - branch.secondary_name().map(String::from), - iteratee, + branch.names().to_vec(), + iteratees, branch .condition() .map(|expression| transform_expression(expression, &variables)) @@ -1460,10 +1493,11 @@ mod tests { union_type.clone(), None::new(Position::fake()), vec![ListComprehensionBranch::new( - Some(input_list_type.clone().into()), - "x", - None, - empty_list.clone(), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(input_list_type.clone().into()), + empty_list.clone(), + )], None, Position::fake(), )], @@ -1489,10 +1523,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - Some(input_list_type.into()), - "x", - None, - empty_list, + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(input_list_type.into()), + empty_list, + )], None, Position::fake(), )], @@ -1522,39 +1557,40 @@ mod tests { assert_eq!( coerce_module(&Module::empty().set_function_definitions( vec![FunctionDefinition::fake( - "f", - Lambda::new( - vec![], - output_list_type.clone(), - ListComprehension::new( - union_type.clone(), - Call::new( - Some( - types::Function::new( - vec![], - types::None::new(Position::fake()), - Position::fake(), - ) - .into() + "f", + Lambda::new( + vec![], + output_list_type.clone(), + ListComprehension::new( + union_type.clone(), + Call::new( + Some( + types::Function::new( + vec![], + types::None::new(Position::fake()), + Position::fake(), + ) + .into() + ), + Variable::new("x", Position::fake()), + vec![], + Position::fake(), ), - Variable::new("x", Position::fake()), - vec![], + vec![ListComprehensionBranch::new( + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(input_list_type.clone().into()), + empty_list.clone(), + )], + None, + Position::fake(), + )], Position::fake(), ), - vec![ListComprehensionBranch::new( - Some(input_list_type.clone().into()), - "x", - None, - empty_list.clone(), - None, - Position::fake(), - )], Position::fake(), ), - Position::fake(), - ), - false, - )], + false, + )], )), Ok( Module::empty().set_function_definitions(vec![FunctionDefinition::fake( @@ -1583,10 +1619,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - Some(input_list_type.into()), - "x", - None, - empty_list, + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(input_list_type.into()), + empty_list, + )], None, Position::fake(), )], @@ -1623,10 +1660,11 @@ mod tests { types::None::new(Position::fake()), None::new(Position::fake()), vec![ListComprehensionBranch::new( - Some(list_type.clone().into()), - "x", - None, - empty_list.clone(), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(list_type.clone().into()), + empty_list.clone(), + )], Some( EqualityOperation::new( Some(union_type.clone().into()), @@ -1656,10 +1694,11 @@ mod tests { types::None::new(Position::fake()), None::new(Position::fake()), vec![ListComprehensionBranch::new( - Some(list_type.into()), - "x", - None, - empty_list, + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(list_type.into()), + empty_list, + )], Some( EqualityOperation::new( Some(union_type.clone().into()), @@ -1986,10 +2025,11 @@ mod tests { union_type.clone(), None::new(Position::fake()), vec![ListComprehensionBranch::new( - Some(map_type.clone().into()), - "k", - Some("v".into()), - empty_map.clone(), + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + Some(map_type.clone().into()), + empty_map.clone(), + )], None, Position::fake(), )], @@ -2015,10 +2055,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - Some(map_type.into()), - "k", - Some("v".into()), - empty_map, + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + Some(map_type.into()), + empty_map, + )], None, Position::fake(), )], @@ -2063,10 +2104,11 @@ mod tests { union_type.clone(), Variable::new("k", Position::fake()), vec![ListComprehensionBranch::new( - Some(map_type.clone().into()), - "k", - Some("v".into()), - empty_map.clone(), + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + Some(map_type.clone().into()), + empty_map.clone(), + )], None, Position::fake(), )], @@ -2092,10 +2134,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - Some(map_type.into()), - "k", - Some("v".into()), - empty_map, + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + Some(map_type.into()), + empty_map, + )], None, Position::fake(), )], @@ -2140,10 +2183,11 @@ mod tests { union_type.clone(), Variable::new("v", Position::fake()), vec![ListComprehensionBranch::new( - Some(map_type.clone().into()), - "k", - Some("v".into()), - empty_map.clone(), + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + Some(map_type.clone().into()), + empty_map.clone(), + )], None, Position::fake(), )], @@ -2169,10 +2213,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - Some(map_type.into()), - "k", - Some("v".into()), - empty_map, + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + Some(map_type.into()), + empty_map, + )], None, Position::fake(), )], diff --git a/lib/hir/src/analysis/type_inferrer.rs b/lib/hir/src/analysis/type_inferrer.rs index dd833b26ec..29f7a68e2e 100644 --- a/lib/hir/src/analysis/type_inferrer.rs +++ b/lib/hir/src/analysis/type_inferrer.rs @@ -255,37 +255,80 @@ fn infer_expression( let mut branches = vec![]; for branch in comprehension.branches() { - let iteratee = infer_expression(branch.iteratee(), &variables)?; - let type_ = - type_extractor::extract_from_expression(context, &iteratee, &variables)?; + let iteratees = branch + .iteratees() + .iter() + .map(|iteratee| { + let expression = infer_expression(iteratee.expression(), &variables)?; + + Ok(ListComprehensionIteratee::new( + Some(type_extractor::extract_from_expression( + context, + &expression, + &variables, + )?), + expression, + )) + }) + .collect::, _>>()?; - variables = match type_canonicalizer::canonicalize(&type_, context.types())? { - Type::List(list_type) => variables.insert( - branch.primary_name().into(), - types::Function::new( - vec![], - list_type.element().clone(), - comprehension.position().clone(), - ) - .into(), - ), - Type::Map(map_type) => variables.insert_iter( - [(branch.primary_name().into(), map_type.key().clone())] - .into_iter() - .chain( - branch - .secondary_name() - .map(|name| (name.into(), map_type.value().clone())), - ), - ), - _ => return Err(AnalysisError::CollectionExpected(type_.clone())), - }; + variables = variables.insert_iter( + iteratees + .iter() + .enumerate() + .map(|(index, iteratee)| { + let type_ = iteratee.type_().ok_or_else(|| { + AnalysisError::TypeNotInferred(iteratee.position().clone()) + })?; + + Ok( + match type_canonicalizer::canonicalize(type_, context.types())? { + Type::List(list_type) => branch + .names() + .get(index) + .map(|name| { + ( + name.into(), + types::Function::new( + vec![], + list_type.element().clone(), + comprehension.position().clone(), + ) + .into(), + ) + }) + .into_iter() + .collect::>(), + Type::Map(map_type) => [ + branch + .names() + .get(0) + .map(|name| (name.into(), map_type.key().clone())), + branch + .names() + .get(1) + .map(|name| (name.into(), map_type.value().clone())), + ] + .into_iter() + .flatten() + .collect(), + _ => { + return Err(AnalysisError::CollectionExpected( + type_.clone(), + )) + } + }, + ) + }) + .collect::, _>>()? + .into_iter() + .flatten() + .collect::>(), + ); branches.push(ListComprehensionBranch::new( - Some(type_.clone()), - branch.primary_name(), - branch.secondary_name().map(String::from), - iteratee, + branch.names().to_vec(), + iteratees, branch .condition() .map(|expression| infer_expression(expression, &variables)) @@ -832,10 +875,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - None, - "x", - None, - List::new(element_type.clone(), vec![], Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + List::new(element_type.clone(), vec![], Position::fake()), + )], None, Position::fake(), )], @@ -874,10 +918,11 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - Some(list_type.into()), - "x", - None, - List::new(element_type, vec![], Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(list_type.into()), + List::new(element_type, vec![], Position::fake()), + )], None, Position::fake(), )], @@ -920,23 +965,25 @@ mod tests { ), vec![ ListComprehensionBranch::new( - None, - "xs", - None, - List::new(list_type.clone(), vec![], Position::fake()), + vec!["xs".into()], + vec![ListComprehensionIteratee::new( + None, + List::new(list_type.clone(), vec![], Position::fake()), + )], None, Position::fake(), ), ListComprehensionBranch::new( - None, - "x", - None, - Call::new( + vec!["x".into()], + vec![ListComprehensionIteratee::new( None, - Variable::new("xs", Position::fake()), - vec![], - Position::fake() - ), + Call::new( + None, + Variable::new("xs", Position::fake()), + vec![], + Position::fake() + ), + )], None, Position::fake(), ), @@ -977,30 +1024,32 @@ mod tests { ), vec![ ListComprehensionBranch::new( - Some(nested_list_type.into()), - "xs", - None, - List::new(list_type.clone(), vec![], Position::fake()), + vec!["xs".into()], + vec![ListComprehensionIteratee::new( + Some(nested_list_type.into()), + List::new(list_type.clone(), vec![], Position::fake()), + )], None, Position::fake(), ), ListComprehensionBranch::new( - Some(list_type.clone().into()), - "x", - None, - Call::new( - Some( - types::Function::new( - vec![], - list_type, - Position::fake() - ) - .into() + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(list_type.clone().into()), + Call::new( + Some( + types::Function::new( + vec![], + list_type, + Position::fake() + ) + .into() + ), + Variable::new("xs", Position::fake()), + vec![], + Position::fake() ), - Variable::new("xs", Position::fake()), - vec![], - Position::fake() - ), + )], None, Position::fake(), ), @@ -1031,26 +1080,28 @@ mod tests { types::None::new(Position::fake()), None::new(Position::fake()), vec![ListComprehensionBranch::new( - None, - "x", - None, - List::new( - list_type.element().clone(), - vec![], - Position::fake() - ), - Some(Let::new( - Some("y".into()), - None, - Call::new( - None, - Variable::new("x", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( None, List::new( + list_type.element().clone(), vec![], Position::fake() - ), - Variable::new("y", Position::fake()), - Position::fake(), - ).into()), + ) + , )], + Some( + Let::new( + Some("y".into()), + None, + Call::new( + None, + Variable::new("x", Position::fake()), + vec![], + Position::fake() + ), + Variable::new("y", Position::fake()), + Position::fake(), + ) + .into() + ), Position::fake(), )], Position::fake(), @@ -1070,14 +1121,15 @@ mod tests { types::None::new(Position::fake()), None::new(Position::fake()), vec![ListComprehensionBranch::new( - Some(list_type.clone().into()), - "x", - None, - List::new( - list_type.element().clone(), - vec![], - Position::fake() - ), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + Some(list_type.clone().into()), + List::new( + list_type.element().clone(), + vec![], + Position::fake() + ), + )], Some( Let::new( Some("y".into()), @@ -1145,12 +1197,10 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new(None, empty_map.clone(),)], None, - "k", - Some("v".into()), - empty_map.clone(), - None, - Position::fake() + Position::fake(), )], Position::fake(), ), @@ -1175,10 +1225,139 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - Some(map_type.clone().into()), - "k", - Some("v".into()), - empty_map, + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + Some(map_type.clone().into()), + empty_map, + )], + None, + Position::fake(), + )], + Position::fake(), + ), + Position::fake(), + ), + false, + )],) + ) + ); + } + + #[test] + fn infer_parallel() { + let element_type = types::Number::new(Position::fake()); + let list_type = types::List::new(element_type.clone(), Position::fake()); + + assert_eq!( + infer_module(&Module::empty().set_function_definitions(vec![ + FunctionDefinition::fake( + "f", + Lambda::new( + vec![], + list_type.clone(), + ListComprehension::new( + element_type.clone(), + Let::new( + Some("y".into()), + None, + Call::new( + None, + Variable::new("x", Position::fake()), + vec![], + Position::fake() + ), + Variable::new("y", Position::fake()), + Position::fake(), + ), + vec![ListComprehensionBranch::new( + vec!["_".into(), "x".into()], + vec![ + ListComprehensionIteratee::new( + None, + List::new( + types::None::new(Position::fake()), + vec![], + Position::fake() + ), + ), + ListComprehensionIteratee::new( + None, + List::new( + element_type.clone(), + vec![], + Position::fake() + ), + ) + ], + None, + Position::fake(), + )], + Position::fake(), + ), + Position::fake(), + ), + false, + ) + ])), + Ok( + Module::empty().set_function_definitions(vec![FunctionDefinition::fake( + "f", + Lambda::new( + vec![], + list_type.clone(), + ListComprehension::new( + element_type.clone(), + Let::new( + Some("y".into()), + Some(element_type.clone().into()), + Call::new( + Some( + types::Function::new( + vec![], + element_type.clone(), + Position::fake() + ) + .into() + ), + Variable::new("x", Position::fake()), + vec![], + Position::fake() + ), + Variable::new("y", Position::fake()), + Position::fake(), + ), + vec![ListComprehensionBranch::new( + vec!["_".into(), "x".into()], + vec![ + ListComprehensionIteratee::new( + Some( + types::List::new( + types::None::new(Position::fake()), + Position::fake() + ) + .into() + ), + List::new( + types::None::new(Position::fake()), + vec![], + Position::fake() + ), + ), + ListComprehensionIteratee::new( + Some( + types::List::new( + element_type.clone(), + Position::fake() + ) + .into() + ), + List::new( + element_type.clone(), + vec![], + Position::fake() + ), + ) + ], None, Position::fake(), )], diff --git a/lib/hir/src/analysis/type_transformer.rs b/lib/hir/src/analysis/type_transformer.rs index a55f6f244a..ed095808c3 100644 --- a/lib/hir/src/analysis/type_transformer.rs +++ b/lib/hir/src/analysis/type_transformer.rs @@ -240,10 +240,17 @@ fn transform_expression(expression: &Expression, transform: &impl Fn(&Type) -> T .iter() .map(|branch| { ListComprehensionBranch::new( - branch.type_().map(transform), - branch.primary_name(), - branch.secondary_name().map(String::from), - transform_expression(branch.iteratee()), + branch.names().to_vec(), + branch + .iteratees() + .iter() + .map(|iteratee| { + ListComprehensionIteratee::new( + iteratee.type_().map(transform), + transform_expression(iteratee.expression()), + ) + }) + .collect(), branch.condition().map(transform_expression), branch.position().clone(), ) diff --git a/lib/hir/src/analysis/type_visitor.rs b/lib/hir/src/analysis/type_visitor.rs index 576ad6750d..3e3ab63ca1 100644 --- a/lib/hir/src/analysis/type_visitor.rs +++ b/lib/hir/src/analysis/type_visitor.rs @@ -155,11 +155,13 @@ fn visit_expression<'a>(expression: &'a Expression, visit: &mut impl FnMut(&'a T visit_type(comprehension.type_(), visit); for branch in comprehension.branches() { - if let Some(type_) = branch.type_() { - visit_type(type_, visit); - } + for iteratee in branch.iteratees() { + if let Some(type_) = iteratee.type_() { + visit_type(type_, visit); + } - visit_expression(branch.iteratee(), visit); + visit_expression(iteratee.expression(), visit); + } } visit_expression(comprehension.element(), visit); diff --git a/lib/hir/src/analysis/variable_transformer.rs b/lib/hir/src/analysis/variable_transformer.rs index 5a55fd98e5..27cb2026f1 100644 --- a/lib/hir/src/analysis/variable_transformer.rs +++ b/lib/hir/src/analysis/variable_transformer.rs @@ -171,12 +171,19 @@ fn transform_expression( let mut transform: Box Expression> = Box::new(transform); for branch in comprehension.branches() { - let iteratee = transform_expression(branch.iteratee(), &transform); + let iteratees = branch + .iteratees() + .iter() + .map(|iteratee| { + ListComprehensionIteratee::new( + iteratee.type_().cloned(), + transform_expression(iteratee.expression(), &transform), + ) + }) + .collect(); transform = Box::new(move |variable| { - if branch.primary_name() == variable.name() - || branch.secondary_name() == Some(variable.name()) - { + if branch.names().iter().any(|name| name == variable.name()) { variable.clone().into() } else { transform(variable) @@ -184,10 +191,8 @@ fn transform_expression( }); branches.push(ListComprehensionBranch::new( - branch.type_().cloned(), - branch.primary_name(), - branch.secondary_name().map(String::from), - iteratee, + branch.names().to_vec(), + iteratees, branch .condition() .map(|expression| transform_expression(expression, &transform)), @@ -478,10 +483,11 @@ mod tests { types::Number::new(Position::fake()), Variable::new("x", Position::fake()), vec![ListComprehensionBranch::new( - None, - "x", - None, - Variable::new(name, Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new(name, Position::fake()), + )], None, Position::fake(), )], @@ -515,10 +521,11 @@ mod tests { types::Number::new(Position::fake()), Variable::new("x", Position::fake()), vec![ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], Some(Variable::new(name, Position::fake()).into()), Position::fake(), )], @@ -551,10 +558,11 @@ mod tests { types::Number::new(Position::fake()), Variable::new("x", Position::fake()), vec![ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], Some(Variable::new("x", Position::fake()).into()), Position::fake(), )], @@ -586,10 +594,11 @@ mod tests { types::None::new(Position::fake()), Variable::new("x", Position::fake()), vec![ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], None, Position::fake(), )], @@ -621,10 +630,11 @@ mod tests { types::Number::new(Position::fake()), Variable::new("v", Position::fake()), vec![ListComprehensionBranch::new( - None, - "k", - Some("v".into()), - Variable::new("xs", Position::fake()), + vec!["k".into(), "v".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], None, Position::fake(), )], @@ -657,18 +667,20 @@ mod tests { Variable::new("y", Position::fake()), vec![ ListComprehensionBranch::new( - None, - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("xs", Position::fake()), + )], None, Position::fake(), ), ListComprehensionBranch::new( - None, - "y", - None, - Variable::new("x", Position::fake()), + vec!["y".into()], + vec![ListComprehensionIteratee::new( + None, + Variable::new("x", Position::fake()), + )], None, Position::fake(), ), diff --git a/lib/hir/src/ir.rs b/lib/hir/src/ir.rs index e06f105591..5f1fd2399c 100644 --- a/lib/hir/src/ir.rs +++ b/lib/hir/src/ir.rs @@ -23,6 +23,7 @@ mod let_; mod list; mod list_comprehension; mod list_comprehension_branch; +mod list_comprehension_iteratee; mod list_element; mod map; mod map_element; @@ -70,6 +71,7 @@ pub use let_::*; pub use list::*; pub use list_comprehension::*; pub use list_comprehension_branch::*; +pub use list_comprehension_iteratee::*; pub use list_element::*; pub use map::*; pub use map_element::*; diff --git a/lib/hir/src/ir/list_comprehension_branch.rs b/lib/hir/src/ir/list_comprehension_branch.rs index 014d231e26..c3eb9024d5 100644 --- a/lib/hir/src/ir/list_comprehension_branch.rs +++ b/lib/hir/src/ir/list_comprehension_branch.rs @@ -1,51 +1,36 @@ -use super::expression::Expression; -use crate::types::Type; +use super::{expression::Expression, ListComprehensionIteratee}; use position::Position; use std::rc::Rc; #[derive(Clone, Debug, PartialEq)] pub struct ListComprehensionBranch { - type_: Option, - primary_name: String, - secondary_name: Option, - iteratee: Rc, + names: Vec, + iteratees: Vec, condition: Option>, position: Position, } impl ListComprehensionBranch { pub fn new( - type_: Option, - primary_name: impl Into, - secondary_name: Option, - iteratee: impl Into, + names: Vec, + iteratees: Vec, condition: Option, position: Position, ) -> Self { Self { - type_, - primary_name: primary_name.into(), - secondary_name, - iteratee: iteratee.into().into(), + names, + iteratees, condition: condition.map(From::from), position, } } - pub fn type_(&self) -> Option<&Type> { - self.type_.as_ref() + pub fn names(&self) -> &[String] { + &self.names } - pub fn primary_name(&self) -> &str { - &self.primary_name - } - - pub fn secondary_name(&self) -> Option<&str> { - self.secondary_name.as_deref() - } - - pub fn iteratee(&self) -> &Expression { - &self.iteratee + pub fn iteratees(&self) -> &[ListComprehensionIteratee] { + &self.iteratees } pub fn condition(&self) -> Option<&Expression> { diff --git a/lib/hir/src/ir/list_comprehension_iteratee.rs b/lib/hir/src/ir/list_comprehension_iteratee.rs new file mode 100644 index 0000000000..0cfebff265 --- /dev/null +++ b/lib/hir/src/ir/list_comprehension_iteratee.rs @@ -0,0 +1,30 @@ +use super::expression::Expression; +use crate::types::Type; +use position::Position; + +#[derive(Clone, Debug, PartialEq)] +pub struct ListComprehensionIteratee { + type_: Option, + expression: Expression, +} + +impl ListComprehensionIteratee { + pub fn new(type_: Option, expression: impl Into) -> Self { + Self { + type_, + expression: expression.into(), + } + } + + pub fn type_(&self) -> Option<&Type> { + self.type_.as_ref() + } + + pub fn expression(&self) -> &Expression { + &self.expression + } + + pub fn position(&self) -> &Position { + self.expression.position() + } +} diff --git a/lib/hir/src/types/type_.rs b/lib/hir/src/types/type_.rs index 100036bee0..9875aec51e 100644 --- a/lib/hir/src/types/type_.rs +++ b/lib/hir/src/types/type_.rs @@ -112,6 +112,14 @@ impl Type { pub fn is_variant(&self) -> bool { self.is_any() || self.is_union() } + + pub fn as_list(&self) -> Option<&List> { + if let Self::List(type_) = self { + Some(type_) + } else { + None + } + } } impl From for Type { diff --git a/lib/parse/src/parser.rs b/lib/parse/src/parser.rs index 6e7489137e..e4df418aaa 100644 --- a/lib/parse/src/parser.rs +++ b/lib/parse/src/parser.rs @@ -842,21 +842,14 @@ fn list_comprehension_branch(input: Input) -> IResult { position, keyword("for"), cut(tuple(( - identifier, - opt(preceded(sign(","), identifier)), + separated_or_terminated_list1(sign(","), identifier), keyword("in"), - expression, + separated_or_terminated_list1(sign(","), expression), opt(preceded(keyword("if"), expression)), ))), )), - |(position, _, (element_name, value_name, _, iterator, condition))| { - ListComprehensionBranch::new( - element_name, - value_name, - iterator, - condition, - position(), - ) + |(position, _, (element_names, _, iteratees, condition))| { + ListComprehensionBranch::new(element_names, iteratees, condition, position()) }, ), )(input) @@ -3178,9 +3171,8 @@ mod tests { types::Reference::new("none", Position::fake()), Variable::new("x", Position::fake()), vec![ListComprehensionBranch::new( - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![Variable::new("xs", Position::fake()).into()], None, Position::fake(), )], @@ -3201,9 +3193,8 @@ mod tests { Position::fake(), ), vec![ListComprehensionBranch::new( - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![Variable::new("xs", Position::fake()).into()], None, Position::fake(), )], @@ -3225,16 +3216,14 @@ mod tests { ), vec![ ListComprehensionBranch::new( - "y", - None, - Variable::new("x", Position::fake()), + vec!["y".into()], + vec![Variable::new("x", Position::fake()).into()], None, Position::fake(), ), ListComprehensionBranch::new( - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![Variable::new("xs", Position::fake()).into()], None, Position::fake(), ), @@ -3248,15 +3237,31 @@ mod tests { types::Reference::new("number", Position::fake()), Variable::new("x", Position::fake()), vec![ListComprehensionBranch::new( - "x", - None, - Variable::new("xs", Position::fake()), + vec!["x".into()], + vec![Variable::new("xs", Position::fake()).into()], Some(Variable::new("true", Position::fake()).into()), Position::fake(), )], Position::fake(), ), ), + ( + "[number x for x, y in xs, ys]", + ListComprehension::new( + types::Reference::new("number", Position::fake()), + Variable::new("x", Position::fake()), + vec![ListComprehensionBranch::new( + vec!["x".into(), "y".into()], + vec![ + Variable::new("xs", Position::fake()).into(), + Variable::new("ys", Position::fake()).into(), + ], + None, + Position::fake(), + )], + Position::fake(), + ), + ), ] { assert_eq!( list_comprehension(input(source, "")).unwrap().1, @@ -3349,9 +3354,8 @@ mod tests { types::Reference::new("none", Position::fake()), Variable::new("v", Position::fake()), vec![ListComprehensionBranch::new( - "k", - Some("v".into()), - Variable::new("xs", Position::fake()), + vec!["k".into(), "v".into()], + vec![Variable::new("xs", Position::fake()).into()], None, Position::fake(), )], diff --git a/test/prelude/list.test.pen b/test/prelude/list.test.pen index 231d1a051f..0696845aad 100644 --- a/test/prelude/list.test.pen +++ b/test/prelude/list.test.pen @@ -74,3 +74,13 @@ Permutate = \() none | error { [number 5, 7, 11, 10, 14, 22, 15, 21, 33], ) } + +Zip = \() none | error { + Assert'Equal( + [number + x() * y() + for x, y in [number 1, 2, 3], [number 4, 5, 6, 7] + ], + [number 4, 10, 18], + ) +}