diff --git a/Gemfile.lock b/Gemfile.lock index 4c7a447beb..8d416f5f42 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -51,6 +51,7 @@ GEM PLATFORMS arm64-darwin-21 + arm64-darwin-22 x86_64-linux DEPENDENCIES diff --git a/doc/docs/references/language/built-ins.md b/doc/docs/references/language/built-ins.md index cf2f49e4a5..d8b4569c8a 100644 --- a/doc/docs/references/language/built-ins.md +++ b/doc/docs/references/language/built-ins.md @@ -27,6 +27,22 @@ It deletes a key from a map. \(map {k: v}, key k) {k: v} ``` +### `keys` + +It gets keys in a map. + +```pen +\(map {k: v}) [k] +``` + +### `values` + +It gets values in a map. + +```pen +\(map {k: v}) [v] +``` + ### `error` It creates an error with its source information. diff --git a/features/types/map.feature b/features/types/map.feature index af9664a86f..7301be9448 100644 --- a/features/types/map.feature +++ b/features/types/map.feature @@ -96,7 +96,7 @@ Feature: Map Given a file named "Foo.pen" with: """pen f = \(xs {string: number}) [string] { - [string k for k, _ in xs] + keys(xs) } """ When I successfully run `pen build` @@ -106,7 +106,7 @@ Feature: Map Given a file named "Foo.pen" with: """pen f = \(xs {string: number}) [number] { - [number v for _, v in xs] + values(xs) } """ When I successfully run `pen build` diff --git a/lib/hir-mir/src/built_in_call.rs b/lib/hir-mir/src/built_in_call.rs index 6400bc4432..497a5f7cd9 100644 --- a/lib/hir-mir/src/built_in_call.rs +++ b/lib/hir-mir/src/built_in_call.rs @@ -13,6 +13,7 @@ use hir::{ types, types::Type, }; +use position::Position; pub fn compile( context: &Context, @@ -82,6 +83,23 @@ pub fn compile( .into() } BuiltInFunctionName::Error => error_type::compile_error(arguments[0].clone()), + BuiltInFunctionName::Keys => { + let argument_type = &function_type.arguments()[0]; + + compile_map_iteration( + context, + &call.arguments()[0], + &context + .configuration()? + .map_type + .iteration + .key_function_name, + type_canonicalizer::canonicalize_map(argument_type, context.types())? + .ok_or_else(|| AnalysisError::MapExpected(argument_type.clone()))? + .key(), + position, + )? + } BuiltInFunctionName::Race => { const ELEMENT_NAME: &str = "$element"; @@ -216,17 +234,228 @@ pub fn compile( ) .into() } + BuiltInFunctionName::Values => { + let argument_type = &function_type.arguments()[0]; + + compile_map_iteration( + context, + &call.arguments()[0], + &context + .configuration()? + .map_type + .iteration + .value_function_name, + type_canonicalizer::canonicalize_map(argument_type, context.types())? + .ok_or_else(|| AnalysisError::MapExpected(argument_type.clone()))? + .value(), + position, + )? + } }) } +fn compile_map_iteration( + context: &Context, + argument: &Expression, + element_function_name: &str, + element_type: &Type, + position: &Position, +) -> Result { + const CLOSURE_NAME: &str = "$loop"; + + let list_type = type_::compile_list(context)?; + let definition = compile_map_iteration_function_definition( + context, + element_function_name, + element_type, + position, + )?; + + Ok(mir::ir::Call::new( + mir::types::Function::new( + vec![mir::types::Function::new(vec![], list_type.clone()).into()], + list_type.clone(), + ), + mir::ir::Variable::new(&context.configuration()?.list_type.lazy_function_name), + vec![mir::ir::LetRecursive::new( + mir::ir::FunctionDefinition::new( + CLOSURE_NAME, + vec![], + list_type.clone(), + mir::ir::LetRecursive::new( + definition.clone(), + mir::ir::Call::new( + mir::types::Function::new(vec![mir::types::Type::Variant], list_type), + mir::ir::Variable::new(definition.name()), + vec![mir::ir::Call::new( + mir::types::Function::new( + vec![type_::compile_map(context)?.into()], + mir::types::Type::Variant, + ), + mir::ir::Variable::new( + &context + .configuration()? + .map_type + .iteration + .iterate_function_name, + ), + vec![expression::compile(context, argument)?], + ) + .into()], + ), + ), + ), + mir::ir::Variable::new(CLOSURE_NAME), + ) + .into()], + ) + .into()) +} + +fn compile_map_iteration_function_definition( + context: &Context, + element_function_name: &str, + element_type: &Type, + position: &Position, +) -> Result { + const CLOSURE_NAME: &str = "$loop"; + const ITERATOR_NAME: &str = "$iterator"; + + let iteration_configuration = &context.configuration()?.map_type.iteration; + let any_type = Type::from(types::Any::new(position.clone())); + let iterator_type = Type::from(types::Reference::new( + &iteration_configuration.iterator_type_name, + position.clone(), + )); + let iterator_or_none_type = types::Union::new( + iterator_type.clone(), + types::None::new(position.clone()), + position.clone(), + ); + let iterator_variable = Variable::new(ITERATOR_NAME, position.clone()); + + Ok(mir::ir::FunctionDefinition::new( + CLOSURE_NAME, + vec![mir::ir::Argument::new( + ITERATOR_NAME, + mir::types::Type::Variant, + )], + type_::compile_list(context)?, + expression::compile( + context, + &IfType::new( + ITERATOR_NAME, + iterator_variable.clone(), + vec![IfTypeBranch::new( + iterator_type.clone(), + List::new( + element_type.clone(), + vec![ + ListElement::Single(downcast::compile( + context, + &any_type, + element_type, + &Call::new( + Some( + types::Function::new( + vec![iterator_type.clone()], + any_type.clone(), + position.clone(), + ) + .into(), + ), + Variable::new(element_function_name, position.clone()), + vec![iterator_variable.clone().into()], + position.clone(), + ) + .into(), + )?), + ListElement::Multiple( + Call::new( + Some( + types::Function::new( + vec![iterator_or_none_type.clone().into()], + types::List::new( + element_type.clone(), + position.clone(), + ), + position.clone(), + ) + .into(), + ), + Variable::new(CLOSURE_NAME, position.clone()), + vec![Call::new( + Some( + types::Function::new( + vec![iterator_type], + iterator_or_none_type, + position.clone(), + ) + .into(), + ), + Variable::new( + &iteration_configuration.rest_function_name, + position.clone(), + ), + vec![iterator_variable.into()], + position.clone(), + ) + .into()], + position.clone(), + ) + .into(), + ), + ], + position.clone(), + ), + )], + Some(ElseBranch::new( + Some(types::None::new(position.clone()).into()), + List::new(element_type.clone(), vec![], position.clone()), + position.clone(), + )), + position.clone(), + ) + .into(), + )?, + )) +} + #[cfg(test)] mod tests { use super::*; + use crate::compile_configuration::COMPILE_CONFIGURATION; use position::{test::PositionFake, Position}; fn compile_call(call: &Call) -> Result { compile( - &Context::dummy(Default::default(), Default::default()), + &Context::dummy( + [ + ( + COMPILE_CONFIGURATION.list_type.list_type_name.clone(), + types::None::new(Position::fake()).into(), + ), + ( + COMPILE_CONFIGURATION.map_type.context_type_name.clone(), + types::None::new(Position::fake()).into(), + ), + ( + COMPILE_CONFIGURATION.map_type.map_type_name.clone(), + types::None::new(Position::fake()).into(), + ), + ( + COMPILE_CONFIGURATION + .map_type + .iteration + .iterator_type_name + .clone(), + types::None::new(Position::fake()).into(), + ), + ] + .into_iter() + .collect(), + Default::default(), + ), call, if let Expression::BuiltInFunction(function) = call.function() { function @@ -259,6 +488,62 @@ mod tests { ))); } + #[test] + fn compile_keys() { + insta::assert_debug_snapshot!(compile_call(&Call::new( + Some( + types::Function::new( + vec![types::Map::new( + types::ByteString::new(Position::fake()), + types::Number::new(Position::fake()), + Position::fake() + ) + .into()], + types::List::new(types::ByteString::new(Position::fake()), Position::fake()), + Position::fake() + ) + .into() + ), + BuiltInFunction::new(BuiltInFunctionName::Keys, Position::fake()), + vec![Map::new( + types::ByteString::new(Position::fake()), + types::Number::new(Position::fake()), + vec![], + Position::fake() + ) + .into()], + Position::fake(), + ))); + } + + #[test] + fn compile_values() { + insta::assert_debug_snapshot!(compile_call(&Call::new( + Some( + types::Function::new( + vec![types::Map::new( + types::ByteString::new(Position::fake()), + types::Number::new(Position::fake()), + Position::fake() + ) + .into()], + types::List::new(types::ByteString::new(Position::fake()), Position::fake()), + Position::fake() + ) + .into() + ), + BuiltInFunction::new(BuiltInFunctionName::Values, Position::fake()), + vec![Map::new( + types::ByteString::new(Position::fake()), + types::Number::new(Position::fake()), + vec![], + Position::fake() + ) + .into()], + Position::fake(), + ))); + } + mod spawn { use super::*; use pretty_assertions::assert_eq; diff --git a/lib/hir-mir/src/lib.rs b/lib/hir-mir/src/lib.rs index 52402680a4..825c4b94ee 100644 --- a/lib/hir-mir/src/lib.rs +++ b/lib/hir-mir/src/lib.rs @@ -1819,6 +1819,72 @@ mod tests { ])) .unwrap(); } + + #[test] + fn compile_keys() { + let map_type = types::Map::new( + types::ByteString::new(Position::fake()), + types::Number::new(Position::fake()), + Position::fake(), + ); + + compile_module(&Module::empty().set_function_definitions(vec![ + FunctionDefinition::fake( + "f", + Lambda::new( + vec![], + types::List::new(map_type.key().clone(), Position::fake()), + Call::new( + None, + BuiltInFunction::new(BuiltInFunctionName::Keys, Position::fake()), + vec![Map::new( + map_type.key().clone(), + map_type.value().clone(), + vec![], + Position::fake(), + ).into()], + Position::fake(), + ), + Position::fake(), + ), + false, + ), + ])) + .unwrap(); + } + + #[test] + fn compile_values() { + let map_type = types::Map::new( + types::ByteString::new(Position::fake()), + types::Number::new(Position::fake()), + Position::fake(), + ); + + compile_module(&Module::empty().set_function_definitions(vec![ + FunctionDefinition::fake( + "f", + Lambda::new( + vec![], + types::List::new(map_type.value().clone(), Position::fake()), + Call::new( + None, + BuiltInFunction::new(BuiltInFunctionName::Values, Position::fake()), + vec![Map::new( + map_type.key().clone(), + map_type.value().clone(), + vec![], + Position::fake(), + ).into()], + Position::fake(), + ), + Position::fake(), + ), + false, + ), + ])) + .unwrap(); + } } mod reflect { diff --git a/lib/hir-mir/src/snapshots/hir_mir__built_in_call__tests__compile_keys.snap b/lib/hir-mir/src/snapshots/hir_mir__built_in_call__tests__compile_keys.snap new file mode 100644 index 0000000000..dfdc93be85 --- /dev/null +++ b/lib/hir-mir/src/snapshots/hir_mir__built_in_call__tests__compile_keys.snap @@ -0,0 +1,455 @@ +--- +source: lib/hir-mir/src/built_in_call.rs +expression: "compile_call(&Call::new(Some(types::Function::new(vec![types :: Map ::\n new(types :: ByteString :: new(Position :: fake()), types ::\n Number :: new(Position :: fake()), Position ::\n fake()).into()],\n types::List::new(types::ByteString::new(Position::fake()),\n Position::fake()), Position::fake()).into()),\n BuiltInFunction::new(BuiltInFunctionName::Keys, Position::fake()),\n vec![Map ::\n new(types :: ByteString :: new(Position :: fake()), types ::\n Number :: new(Position :: fake()), vec! [], Position ::\n fake()).into()], Position::fake()))" +--- +Ok( + Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + Function( + Function( + FunctionInner { + arguments: [], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + ), + ], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + function: Variable( + Variable { + name: "lazy", + }, + ), + arguments: [ + LetRecursive( + LetRecursive( + LetRecursiveInner { + definition: FunctionDefinition { + name: "$loop", + environment: [], + arguments: [], + result_type: Record( + Record { + name: "anyList", + }, + ), + body: LetRecursive( + LetRecursive( + LetRecursiveInner { + definition: FunctionDefinition { + name: "$loop", + environment: [], + arguments: [ + Argument { + name: "$iterator", + type_: Variant, + }, + ], + result_type: Record( + Record { + name: "anyList", + }, + ), + body: Case( + Case( + CaseInner { + argument: Variable( + Variable { + name: "$iterator", + }, + ), + alternatives: [ + Alternative { + types: [ + None, + ], + name: "$iterator", + expression: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + Function( + Function( + FunctionInner { + arguments: [], + result: Variant, + }, + ), + ), + None, + ], + result: None, + }, + ), + function: Variable( + Variable { + name: "prependToLists", + }, + ), + arguments: [ + LetRecursive( + LetRecursive( + LetRecursiveInner { + definition: FunctionDefinition { + name: "$thunk", + environment: [], + arguments: [], + result_type: Variant, + body: Variant( + Variant( + VariantInner { + type_: ByteString, + payload: Case( + Case( + CaseInner { + argument: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + None, + ], + result: Variant, + }, + ), + function: Variable( + Variable { + name: "mapIteratorKey", + }, + ), + arguments: [ + Variable( + Variable { + name: "$iterator", + }, + ), + ], + }, + ), + ), + alternatives: [ + Alternative { + types: [ + ByteString, + ], + name: "$value", + expression: Variable( + Variable { + name: "$value", + }, + ), + }, + ], + default_alternative: None, + }, + ), + ), + }, + ), + ), + type_: Function( + FunctionInner { + arguments: [], + result: Variant, + }, + ), + thunk: true, + }, + expression: Variable( + Variable { + name: "$thunk", + }, + ), + }, + ), + ), + Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + Function( + Function( + FunctionInner { + arguments: [], + result: None, + }, + ), + ), + ], + result: None, + }, + ), + function: Variable( + Variable { + name: "lazy", + }, + ), + arguments: [ + LetRecursive( + LetRecursive( + LetRecursiveInner { + definition: FunctionDefinition { + name: "$thunk", + environment: [], + arguments: [], + result_type: None, + body: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + None, + ], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + function: Variable( + Variable { + name: "$loop", + }, + ), + arguments: [ + Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + None, + ], + result: None, + }, + ), + function: Variable( + Variable { + name: "mapIteratorRest", + }, + ), + arguments: [ + Variable( + Variable { + name: "$iterator", + }, + ), + ], + }, + ), + ), + ], + }, + ), + ), + type_: Function( + FunctionInner { + arguments: [], + result: None, + }, + ), + thunk: true, + }, + expression: Variable( + Variable { + name: "$thunk", + }, + ), + }, + ), + ), + ], + }, + ), + ), + ], + }, + ), + ), + }, + Alternative { + types: [ + None, + ], + name: "$iterator", + expression: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [], + result: None, + }, + ), + function: Variable( + Variable { + name: "emptyList", + }, + ), + arguments: [], + }, + ), + ), + }, + ], + default_alternative: None, + }, + ), + ), + type_: Function( + FunctionInner { + arguments: [ + Variant, + ], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + thunk: false, + }, + expression: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + Variant, + ], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + function: Variable( + Variable { + name: "$loop", + }, + ), + arguments: [ + Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + Record( + Record { + name: "genericMap", + }, + ), + ], + result: Variant, + }, + ), + function: Variable( + Variable { + name: "iterateMap", + }, + ), + arguments: [ + Let( + Let( + LetInner { + name: "$ctx", + type_: None, + bound_expression: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [], + result: None, + }, + ), + function: Variable( + Variable { + name: "hir:map:context:9d7a635aafe1bc04", + }, + ), + arguments: [], + }, + ), + ), + expression: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [], + result: None, + }, + ), + function: Variable( + Variable { + name: "emptyMap", + }, + ), + arguments: [], + }, + ), + ), + }, + ), + ), + ], + }, + ), + ), + ], + }, + ), + ), + }, + ), + ), + type_: Function( + FunctionInner { + arguments: [], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + thunk: false, + }, + expression: Variable( + Variable { + name: "$loop", + }, + ), + }, + ), + ), + ], + }, + ), + ), +) diff --git a/lib/hir-mir/src/snapshots/hir_mir__built_in_call__tests__compile_values.snap b/lib/hir-mir/src/snapshots/hir_mir__built_in_call__tests__compile_values.snap new file mode 100644 index 0000000000..0ebb076c45 --- /dev/null +++ b/lib/hir-mir/src/snapshots/hir_mir__built_in_call__tests__compile_values.snap @@ -0,0 +1,455 @@ +--- +source: lib/hir-mir/src/built_in_call.rs +expression: "compile_call(&Call::new(Some(types::Function::new(vec![types :: Map ::\n new(types :: ByteString :: new(Position :: fake()), types ::\n Number :: new(Position :: fake()), Position ::\n fake()).into()],\n types::List::new(types::ByteString::new(Position::fake()),\n Position::fake()), Position::fake()).into()),\n BuiltInFunction::new(BuiltInFunctionName::Values,\n Position::fake()),\n vec![Map ::\n new(types :: ByteString :: new(Position :: fake()), types ::\n Number :: new(Position :: fake()), vec! [], Position ::\n fake()).into()], Position::fake()))" +--- +Ok( + Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + Function( + Function( + FunctionInner { + arguments: [], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + ), + ], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + function: Variable( + Variable { + name: "lazy", + }, + ), + arguments: [ + LetRecursive( + LetRecursive( + LetRecursiveInner { + definition: FunctionDefinition { + name: "$loop", + environment: [], + arguments: [], + result_type: Record( + Record { + name: "anyList", + }, + ), + body: LetRecursive( + LetRecursive( + LetRecursiveInner { + definition: FunctionDefinition { + name: "$loop", + environment: [], + arguments: [ + Argument { + name: "$iterator", + type_: Variant, + }, + ], + result_type: Record( + Record { + name: "anyList", + }, + ), + body: Case( + Case( + CaseInner { + argument: Variable( + Variable { + name: "$iterator", + }, + ), + alternatives: [ + Alternative { + types: [ + None, + ], + name: "$iterator", + expression: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + Function( + Function( + FunctionInner { + arguments: [], + result: Variant, + }, + ), + ), + None, + ], + result: None, + }, + ), + function: Variable( + Variable { + name: "prependToLists", + }, + ), + arguments: [ + LetRecursive( + LetRecursive( + LetRecursiveInner { + definition: FunctionDefinition { + name: "$thunk", + environment: [], + arguments: [], + result_type: Variant, + body: Variant( + Variant( + VariantInner { + type_: Number, + payload: Case( + Case( + CaseInner { + argument: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + None, + ], + result: Variant, + }, + ), + function: Variable( + Variable { + name: "mapIteratorValue", + }, + ), + arguments: [ + Variable( + Variable { + name: "$iterator", + }, + ), + ], + }, + ), + ), + alternatives: [ + Alternative { + types: [ + Number, + ], + name: "$value", + expression: Variable( + Variable { + name: "$value", + }, + ), + }, + ], + default_alternative: None, + }, + ), + ), + }, + ), + ), + type_: Function( + FunctionInner { + arguments: [], + result: Variant, + }, + ), + thunk: true, + }, + expression: Variable( + Variable { + name: "$thunk", + }, + ), + }, + ), + ), + Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + Function( + Function( + FunctionInner { + arguments: [], + result: None, + }, + ), + ), + ], + result: None, + }, + ), + function: Variable( + Variable { + name: "lazy", + }, + ), + arguments: [ + LetRecursive( + LetRecursive( + LetRecursiveInner { + definition: FunctionDefinition { + name: "$thunk", + environment: [], + arguments: [], + result_type: None, + body: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + None, + ], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + function: Variable( + Variable { + name: "$loop", + }, + ), + arguments: [ + Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + None, + ], + result: None, + }, + ), + function: Variable( + Variable { + name: "mapIteratorRest", + }, + ), + arguments: [ + Variable( + Variable { + name: "$iterator", + }, + ), + ], + }, + ), + ), + ], + }, + ), + ), + type_: Function( + FunctionInner { + arguments: [], + result: None, + }, + ), + thunk: true, + }, + expression: Variable( + Variable { + name: "$thunk", + }, + ), + }, + ), + ), + ], + }, + ), + ), + ], + }, + ), + ), + }, + Alternative { + types: [ + None, + ], + name: "$iterator", + expression: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [], + result: None, + }, + ), + function: Variable( + Variable { + name: "emptyList", + }, + ), + arguments: [], + }, + ), + ), + }, + ], + default_alternative: None, + }, + ), + ), + type_: Function( + FunctionInner { + arguments: [ + Variant, + ], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + thunk: false, + }, + expression: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + Variant, + ], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + function: Variable( + Variable { + name: "$loop", + }, + ), + arguments: [ + Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [ + Record( + Record { + name: "genericMap", + }, + ), + ], + result: Variant, + }, + ), + function: Variable( + Variable { + name: "iterateMap", + }, + ), + arguments: [ + Let( + Let( + LetInner { + name: "$ctx", + type_: None, + bound_expression: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [], + result: None, + }, + ), + function: Variable( + Variable { + name: "hir:map:context:9d7a635aafe1bc04", + }, + ), + arguments: [], + }, + ), + ), + expression: Call( + Call( + CallInner { + type_: Function( + FunctionInner { + arguments: [], + result: None, + }, + ), + function: Variable( + Variable { + name: "emptyMap", + }, + ), + arguments: [], + }, + ), + ), + }, + ), + ), + ], + }, + ), + ), + ], + }, + ), + ), + }, + ), + ), + type_: Function( + FunctionInner { + arguments: [], + result: Record( + Record { + name: "anyList", + }, + ), + }, + ), + thunk: false, + }, + expression: Variable( + Variable { + name: "$loop", + }, + ), + }, + ), + ), + ], + }, + ), + ), +) diff --git a/lib/hir/src/analysis/built_in_variable_transformer.rs b/lib/hir/src/analysis/built_in_variable_transformer.rs index f6891c591c..22db61ff50 100644 --- a/lib/hir/src/analysis/built_in_variable_transformer.rs +++ b/lib/hir/src/analysis/built_in_variable_transformer.rs @@ -11,11 +11,13 @@ pub fn transform(module: &Module) -> Module { "error" => BuiltInFunction::new(BuiltInFunctionName::Error, position.clone()).into(), "false" => Boolean::new(false, position.clone()).into(), "go" => BuiltInFunction::new(BuiltInFunctionName::Spawn, position.clone()).into(), + "keys" => BuiltInFunction::new(BuiltInFunctionName::Keys, position.clone()).into(), "none" => None::new(position.clone()).into(), "race" => BuiltInFunction::new(BuiltInFunctionName::Race, position.clone()).into(), "size" => BuiltInFunction::new(BuiltInFunctionName::Size, position.clone()).into(), "source" => BuiltInFunction::new(BuiltInFunctionName::Source, position.clone()).into(), "true" => Boolean::new(true, position.clone()).into(), + "values" => BuiltInFunction::new(BuiltInFunctionName::Values, position.clone()).into(), "_reflect_debug" => { BuiltInFunction::new(BuiltInFunctionName::ReflectDebug, position.clone()).into() } diff --git a/lib/hir/src/analysis/type_checker.rs b/lib/hir/src/analysis/type_checker.rs index 080265108d..37c590e519 100644 --- a/lib/hir/src/analysis/type_checker.rs +++ b/lib/hir/src/analysis/type_checker.rs @@ -545,9 +545,11 @@ fn check_built_in_call( } BuiltInFunctionName::Debug | BuiltInFunctionName::Error + | BuiltInFunctionName::Keys | BuiltInFunctionName::ReflectDebug | BuiltInFunctionName::ReflectEqual - | BuiltInFunctionName::Source => {} + | BuiltInFunctionName::Source + | BuiltInFunctionName::Values => {} } Ok(()) @@ -3432,7 +3434,7 @@ mod tests { } } - mod built_in_call { + mod built_in { use super::*; mod delete { @@ -3563,6 +3565,46 @@ mod tests { } } + mod keys { + use super::*; + + #[test] + fn check() { + let map_type = types::Map::new( + types::ByteString::new(Position::fake()), + types::Number::new(Position::fake()), + Position::fake(), + ); + let list_type = types::List::new(map_type.key().clone(), Position::fake()); + + check_module(&Module::empty().set_function_definitions( + vec![FunctionDefinition::fake( + "f", + Lambda::new( + vec![Argument::new("x", map_type.clone())], + list_type.element().clone(), + Call::new( + Some( + types::Function::new( + vec![map_type.into()], + list_type.element().clone(), + Position::fake(), + ) + .into(), + ), + BuiltInFunction::new(BuiltInFunctionName::Keys, Position::fake()), + vec![Variable::new("x", Position::fake()).into()], + Position::fake(), + ), + Position::fake(), + ), + false, + )], + )) + .unwrap(); + } + } + mod size { use super::*; @@ -3799,5 +3841,45 @@ mod tests { .unwrap(); } } + + mod values { + use super::*; + + #[test] + fn check() { + let map_type = types::Map::new( + types::ByteString::new(Position::fake()), + types::Number::new(Position::fake()), + Position::fake(), + ); + let list_type = types::List::new(map_type.value().clone(), Position::fake()); + + check_module(&Module::empty().set_function_definitions( + vec![FunctionDefinition::fake( + "f", + Lambda::new( + vec![Argument::new("x", map_type.clone())], + list_type.element().clone(), + Call::new( + Some( + types::Function::new( + vec![map_type.into()], + list_type.element().clone(), + Position::fake(), + ) + .into(), + ), + BuiltInFunction::new(BuiltInFunctionName::Values, Position::fake()), + vec![Variable::new("x", Position::fake()).into()], + Position::fake(), + ), + Position::fake(), + ), + false, + )], + )) + .unwrap(); + } + } } } diff --git a/lib/hir/src/analysis/type_inferrer.rs b/lib/hir/src/analysis/type_inferrer.rs index 29f7a68e2e..5ffd70e5fa 100644 --- a/lib/hir/src/analysis/type_inferrer.rs +++ b/lib/hir/src/analysis/type_inferrer.rs @@ -563,6 +563,23 @@ fn infer_built_in_call( types::Error::new(position.clone()), position.clone(), ), + BuiltInFunctionName::Keys => { + let [argument_type] = &argument_types[..] else { + return Err(AnalysisError::ArgumentCount(position.clone())); + }; + + types::Function::new( + vec![argument_type.clone()], + types::List::new( + type_canonicalizer::canonicalize_map(argument_type, context.types())? + .ok_or_else(|| AnalysisError::MapExpected(argument_type.clone()))? + .key() + .clone(), + position.clone(), + ), + position.clone(), + ) + } BuiltInFunctionName::Race => { let argument_type = argument_types .first() @@ -612,6 +629,23 @@ fn infer_built_in_call( types::Function::new(argument_types, result_type, position.clone()) } + BuiltInFunctionName::Values => { + let [argument_type] = &argument_types[..] else { + return Err(AnalysisError::ArgumentCount(position.clone())); + }; + + types::Function::new( + vec![argument_type.clone()], + types::List::new( + type_canonicalizer::canonicalize_map(argument_type, context.types())? + .ok_or_else(|| AnalysisError::MapExpected(argument_type.clone()))? + .value() + .clone(), + position.clone(), + ), + position.clone(), + ) + } } .into(), ), @@ -1304,7 +1338,7 @@ mod tests { "f", Lambda::new( vec![], - list_type.clone(), + list_type, ListComprehension::new( element_type.clone(), Let::new( @@ -1351,11 +1385,7 @@ mod tests { ) .into() ), - List::new( - element_type.clone(), - vec![], - Position::fake() - ), + List::new(element_type, vec![], Position::fake()), ) ], None, @@ -2417,6 +2447,60 @@ mod tests { ); } + #[test] + fn infer_keys() { + let map_type = types::Map::new( + types::ByteString::new(Position::fake()), + types::Number::new(Position::fake()), + Position::fake(), + ); + let list_type = types::List::new(map_type.key().clone(), Position::fake()); + + assert_eq!( + infer_module(&Module::empty().set_function_definitions(vec![ + FunctionDefinition::fake( + "f", + Lambda::new( + vec![Argument::new("xs", map_type.clone())], + list_type.clone(), + Call::new( + None, + BuiltInFunction::new(BuiltInFunctionName::Keys, Position::fake()), + vec![Variable::new("xs", Position::fake()).into()], + Position::fake() + ), + Position::fake(), + ), + false, + ) + ],)), + Ok( + Module::empty().set_function_definitions(vec![FunctionDefinition::fake( + "f", + Lambda::new( + vec![Argument::new("xs", map_type.clone())], + list_type.clone(), + Call::new( + Some( + types::Function::new( + vec![map_type.into()], + list_type, + Position::fake() + ) + .into() + ), + BuiltInFunction::new(BuiltInFunctionName::Keys, Position::fake()), + vec![Variable::new("xs", Position::fake()).into()], + Position::fake() + ), + Position::fake(), + ), + false, + )]) + ) + ); + } + #[test] fn infer_race() { let list_type = types::List::new( @@ -2700,5 +2784,59 @@ mod tests { ) ); } + + #[test] + fn infer_values() { + let map_type = types::Map::new( + types::ByteString::new(Position::fake()), + types::Number::new(Position::fake()), + Position::fake(), + ); + let list_type = types::List::new(map_type.value().clone(), Position::fake()); + + assert_eq!( + infer_module(&Module::empty().set_function_definitions(vec![ + FunctionDefinition::fake( + "f", + Lambda::new( + vec![Argument::new("xs", map_type.clone())], + list_type.clone(), + Call::new( + None, + BuiltInFunction::new(BuiltInFunctionName::Values, Position::fake()), + vec![Variable::new("xs", Position::fake()).into()], + Position::fake() + ), + Position::fake(), + ), + false, + ) + ],)), + Ok( + Module::empty().set_function_definitions(vec![FunctionDefinition::fake( + "f", + Lambda::new( + vec![Argument::new("xs", map_type.clone())], + list_type.clone(), + Call::new( + Some( + types::Function::new( + vec![map_type.into()], + list_type, + Position::fake() + ) + .into() + ), + BuiltInFunction::new(BuiltInFunctionName::Values, Position::fake()), + vec![Variable::new("xs", Position::fake()).into()], + Position::fake() + ), + Position::fake(), + ), + false, + )]) + ) + ); + } } } diff --git a/lib/hir/src/ir/built_in_function.rs b/lib/hir/src/ir/built_in_function.rs index 9f4d97650f..12d3c05b3d 100644 --- a/lib/hir/src/ir/built_in_function.rs +++ b/lib/hir/src/ir/built_in_function.rs @@ -5,12 +5,14 @@ pub enum BuiltInFunctionName { Debug, Delete, Error, + Keys, Race, ReflectDebug, ReflectEqual, Size, Source, Spawn, + Values, } #[derive(Clone, Debug, Eq, Hash, PartialEq)] diff --git a/test/prelude/map.test.pen b/test/prelude/map.test.pen index 057e7e55ff..be9ef826f6 100644 --- a/test/prelude/map.test.pen +++ b/test/prelude/map.test.pen @@ -8,18 +8,17 @@ type Bar {} type Union = Foo | Bar SetNoKey = \() none | error { - Assert'Equal(Number'Sum([number k for k, _ in {number: none}]), 0) + Assert'Equal(Number'Sum(keys({number: none})), 0) } SetKey = \() none | error { Assert'Equal( Number'Sum( - [number - k - for k, _ in {number: none + keys( + {number: none 1: none, - } - ], + }, + ), ), 1, ) @@ -28,13 +27,12 @@ SetKey = \() none | error { Set2Keys = \() none | error { Assert'Equal( Number'Sum( - [number - k - for k, _ in {number: none + keys( + {number: none 1: none, 2: none, - } - ], + }, + ), ), 3, ) @@ -43,14 +41,13 @@ Set2Keys = \() none | error { Set3Keys = \() none | error { Assert'Equal( Number'Sum( - [number - k - for k, _ in {number: none + keys( + {number: none 1: none, 2: none, 3: none, - } - ], + }, + ), ), 6, ) @@ -59,15 +56,14 @@ Set3Keys = \() none | error { Set4Keys = \() none | error { Assert'Equal( Number'Sum( - [number - k - for k, _ in {number: none + keys( + {number: none 1: none, 2: none, 3: none, 4: none, - } - ], + }, + ), ), 10, ) @@ -89,14 +85,14 @@ SetVeryManyKeys = \() none | error { IterateKey = \() none | error { Assert'Equal( - Number'Sum([number k for k, _ in {number: none 1: none}]), + Number'Sum(keys({number: none 1: none})), 1, ) } Iterate2Keys = \() none | error { Assert'Equal( - Number'Sum([number k for k, _ in {number: none 1: none, 2: none}]), + Number'Sum(keys({number: none 1: none, 2: none})), 3, ) } @@ -105,7 +101,7 @@ IterateManyKeys = \() none | error { ks = Number'Sequence(42) Assert'Equal( - Number'Sum([number k for k, _ in numberSet(ks)]), + Number'Sum(keys(numberSet(ks))), Number'Sum(ks), ) } @@ -114,7 +110,7 @@ IterateVeryManyKeys = \() none | error { ks = Number'Sequence(1024) Assert'Equal( - Number'Sum([number k for k, _ in numberSet(ks)]), + Number'Sum(keys(numberSet(ks))), Number'Sum(ks), ) } @@ -122,14 +118,13 @@ IterateVeryManyKeys = \() none | error { Set3KeysWith0 = \() none | error { Assert'Equal( Number'Sum( - [number - k - for k, _ in {number: none + keys( + {number: none 0: none, 1: none, 2: none, - } - ], + }, + ), ), 3, ) @@ -143,24 +138,14 @@ SetUnionKeys = \() none | error { DeleteKey = \() none | error { Assert'Equal( - Number'Sum( - [number - k - for k, _ in delete({number: none 1: none}, 1) - ], - ), + Number'Sum(keys(delete({number: none 1: none}, 1))), 0, ) } DeleteUnionKey = \() none | error { Assert'Equal( - size( - [number | none - k - for k, _ in delete({number | none: none 1: none}, 1) - ], - ), + size(keys(delete({number | none: none 1: none}, 1))), 0, ) } @@ -168,13 +153,12 @@ DeleteUnionKey = \() none | error { Merge2Maps = \() none | error { Assert'Equal( Number'Sum( - [number - k - for k, _ in {number: none + keys( + {number: none ...{number: none 1: none}, ...{number: none 2: none}, - } - ], + }, + ), ), 3, ) @@ -183,14 +167,13 @@ Merge2Maps = \() none | error { Merge3Maps = \() none | error { Assert'Equal( Number'Sum( - [number - k - for k, _ in {number: none + keys( + {number: none ...{number: none 1: none}, ...{number: none 2: none}, ...{number: none 3: none}, - } - ], + }, + ), ), 6, )