diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index 2823fe6c45..ab0ec700a5 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -122,6 +122,7 @@ comptime { exportStrFn(str.countSegments, "count_segments"); exportStrFn(str.countGraphemeClusters, "count_grapheme_clusters"); exportStrFn(str.countUtf8Bytes, "count_utf8_bytes"); + exportStrFn(str.getCapacity, "capacity"); exportStrFn(str.startsWith, "starts_with"); exportStrFn(str.startsWithScalar, "starts_with_scalar"); exportStrFn(str.endsWith, "ends_with"); diff --git a/crates/compiler/builtins/bitcode/src/str.zig b/crates/compiler/builtins/bitcode/src/str.zig index 13f015e31d..74fe6fb6d2 100644 --- a/crates/compiler/builtins/bitcode/src/str.zig +++ b/crates/compiler/builtins/bitcode/src/str.zig @@ -1210,6 +1210,10 @@ pub fn countUtf8Bytes(string: RocStr) callconv(.C) usize { return string.len(); } +pub fn getCapacity(string: RocStr) callconv(.C) usize { + return string.getCapacity(); +} + pub fn substringUnsafe(string: RocStr, start: usize, length: usize) callconv(.C) RocStr { const slice = string.asSlice()[start .. start + length]; return RocStr.fromSlice(slice); diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc index 7d46eb8f9c..ecfa0413ea 100644 --- a/crates/compiler/builtins/roc/Dict.roc +++ b/crates/compiler/builtins/roc/Dict.roc @@ -137,7 +137,6 @@ single : k, v -> Dict k v single = \key, value -> @Dict [Pair key value] - ## Returns a [List] of the dictionary's keys. keys : Dict k v -> List k keys = \@Dict list -> diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index 55472c4abc..d63e952638 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -314,6 +314,7 @@ pub const STR_STR_SPLIT: &str = "roc_builtins.str.str_split"; pub const STR_TO_SCALARS: &str = "roc_builtins.str.to_scalars"; pub const STR_COUNT_GRAPEHEME_CLUSTERS: &str = "roc_builtins.str.count_grapheme_clusters"; pub const STR_COUNT_UTF8_BYTES: &str = "roc_builtins.str.count_utf8_bytes"; +pub const STR_CAPACITY: &str = "roc_builtins.str.capacity"; pub const STR_STARTS_WITH: &str = "roc_builtins.str.starts_with"; pub const STR_STARTS_WITH_SCALAR: &str = "roc_builtins.str.starts_with_scalar"; pub const STR_ENDS_WITH: &str = "roc_builtins.str.ends_with"; diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs index 9ff34e49ea..4d9dfcbc59 100644 --- a/crates/compiler/can/src/builtins.rs +++ b/crates/compiler/can/src/builtins.rs @@ -124,6 +124,7 @@ map_symbol_to_lowlevel_and_arity! { StrAppendScalar; STR_APPEND_SCALAR_UNSAFE; 2, StrGetScalarUnsafe; STR_GET_SCALAR_UNSAFE; 2, StrToNum; STR_TO_NUM; 1, + StrGetCapacity; STR_CAPACITY; 1, ListLen; LIST_LEN; 1, ListWithCapacity; LIST_WITH_CAPACITY; 1, @@ -142,6 +143,7 @@ map_symbol_to_lowlevel_and_arity! { ListSublist; LIST_SUBLIST_LOWLEVEL; 3, ListDropAt; LIST_DROP_AT; 2, ListSwap; LIST_SWAP; 3, + ListGetCapacity; LIST_CAPACITY; 1, NumAdd; NUM_ADD; 2, NumAddWrap; NUM_ADD_WRAP; 2, diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs index d3da3fa7d9..10da76b43d 100644 --- a/crates/compiler/can/src/expr.rs +++ b/crates/compiler/can/src/expr.rs @@ -221,6 +221,9 @@ pub enum Expr { lambda_set_variables: Vec, }, + // Opaque as a function, e.g. @Id as a shorthand for \x -> @Id x + OpaqueWrapFunction(OpaqueWrapFunctionData), + // Test Expect { loc_condition: Box>, @@ -270,6 +273,9 @@ impl Expr { args_count: 0, }, &Self::OpaqueRef { name, .. } => Category::OpaqueWrap(name), + &Self::OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => { + Category::OpaqueWrap(opaque_name) + } Self::Expect { .. } => Category::Expect, // these nodes place no constraints on the expression's type @@ -381,6 +387,76 @@ impl AccessorData { } } +/// An opaque wrapper like `@Foo`, which is equivalent to `\p -> @Foo p` +/// These are desugared to closures, but we distinguish them so we can have +/// better error messages during constraint generation. +#[derive(Clone, Debug)] +pub struct OpaqueWrapFunctionData { + pub opaque_name: Symbol, + pub opaque_var: Variable, + // The following fields help link the concrete opaque type; see + // `Expr::OpaqueRef` for more info on how they're used. + pub specialized_def_type: Type, + pub type_arguments: Vec, + pub lambda_set_variables: Vec, + + pub function_name: Symbol, + pub function_var: Variable, + pub argument_var: Variable, + pub closure_var: Variable, +} + +impl OpaqueWrapFunctionData { + pub fn to_closure_data(self, argument_symbol: Symbol) -> ClosureData { + let OpaqueWrapFunctionData { + opaque_name, + opaque_var, + specialized_def_type, + type_arguments, + lambda_set_variables, + function_name, + function_var, + argument_var, + closure_var, + } = self; + + // IDEA: convert + // + // @Foo + // + // into + // + // (\p -> @Foo p) + let body = Expr::OpaqueRef { + opaque_var, + name: opaque_name, + argument: Box::new((argument_var, Loc::at_zero(Expr::Var(argument_symbol)))), + specialized_def_type: Box::new(specialized_def_type), + type_arguments, + lambda_set_variables, + }; + + let loc_body = Loc::at_zero(body); + + let arguments = vec![( + argument_var, + AnnotatedMark::known_exhaustive(), + Loc::at_zero(Pattern::Identifier(argument_symbol)), + )]; + + ClosureData { + function_type: function_var, + closure_type: closure_var, + return_type: opaque_var, + name: function_name, + captured_symbols: vec![], + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(loc_body), + } + } +} + #[derive(Clone, Debug)] pub struct Field { pub var: Variable, @@ -836,15 +912,41 @@ pub fn canonicalize_expr<'a>( Output::default(), ) } - ast::Expr::OpaqueRef(opaque_ref) => { + ast::Expr::OpaqueRef(name) => { // If we're here, the opaque reference is definitely not wrapping an argument - wrapped // arguments are handled in the Apply branch. - let problem = roc_problem::can::RuntimeError::OpaqueNotApplied(Loc::at( - region, - (*opaque_ref).into(), - )); - env.problem(Problem::RuntimeError(problem.clone())); - (RuntimeError(problem), Output::default()) + // Treat this as a function \payload -> @Opaque payload + match scope.lookup_opaque_ref(name, region) { + Err(runtime_error) => { + env.problem(Problem::RuntimeError(runtime_error.clone())); + (RuntimeError(runtime_error), Output::default()) + } + Ok((name, opaque_def)) => { + let mut output = Output::default(); + output.references.insert_type_lookup(name); + + let (type_arguments, lambda_set_variables, specialized_def_type) = + freshen_opaque_def(var_store, opaque_def); + + let fn_symbol = scope.gen_unique_symbol(); + + ( + OpaqueWrapFunction(OpaqueWrapFunctionData { + opaque_name: name, + opaque_var: var_store.fresh(), + specialized_def_type, + type_arguments, + lambda_set_variables, + + function_name: fn_symbol, + function_var: var_store.fresh(), + argument_var: var_store.fresh(), + closure_var: var_store.fresh(), + }), + output, + ) + } + } } ast::Expr::Expect(condition, continuation) => { let mut output = Output::default(); @@ -1421,7 +1523,8 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) -> | other @ AbilityMember(..) | other @ RunLowLevel { .. } | other @ TypedHole { .. } - | other @ ForeignCall { .. } => other, + | other @ ForeignCall { .. } + | other @ OpaqueWrapFunction(_) => other, List { elem_var, @@ -2420,7 +2523,8 @@ fn get_lookup_symbols(expr: &Expr, var_store: &mut VarStore) -> Vec<(Symbol, Var | Expr::SingleQuote(_) | Expr::EmptyRecord | Expr::TypedHole(_) - | Expr::RuntimeError(_) => {} + | Expr::RuntimeError(_) + | Expr::OpaqueWrapFunction(_) => {} } } diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs index 217d544466..c8f73f684b 100644 --- a/crates/compiler/can/src/module.rs +++ b/crates/compiler/can/src/module.rs @@ -967,5 +967,6 @@ fn fix_values_captured_in_closure_expr( let (_, loc_arg) = &mut **argument; fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols); } + OpaqueWrapFunction(_) => {} } } diff --git a/crates/compiler/can/src/traverse.rs b/crates/compiler/can/src/traverse.rs index 4c1d03676d..31ad25f1f2 100644 --- a/crates/compiler/can/src/traverse.rs +++ b/crates/compiler/can/src/traverse.rs @@ -7,7 +7,10 @@ use roc_types::subs::Variable; use crate::{ abilities::AbilitiesStore, def::{Annotation, Declaration, Def}, - expr::{self, AccessorData, AnnotatedMark, ClosureData, Declarations, Expr, Field}, + expr::{ + self, AccessorData, AnnotatedMark, ClosureData, Declarations, Expr, Field, + OpaqueWrapFunctionData, + }, pattern::{DestructType, Pattern, RecordDestruct}, }; @@ -220,6 +223,7 @@ pub fn walk_expr(visitor: &mut V, expr: &Expr, var: Variable) { ext_var: _, } => visitor.visit_expr(&loc_expr.value, loc_expr.region, *field_var), Expr::Accessor(AccessorData { .. }) => { /* terminal */ } + Expr::OpaqueWrapFunction(OpaqueWrapFunctionData { .. }) => { /* terminal */ } Expr::Update { record_var: _, ext_var: _, diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs index 2c568897a1..c31609088d 100644 --- a/crates/compiler/constrain/src/expr.rs +++ b/crates/compiler/constrain/src/expr.rs @@ -13,7 +13,7 @@ use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; use roc_can::expr::{ AccessorData, AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef, Field, - FunctionDef, WhenBranch, + FunctionDef, OpaqueWrapFunctionData, WhenBranch, }; use roc_can::pattern::Pattern; use roc_can::traverse::symbols_introduced_from_pattern; @@ -1114,6 +1114,100 @@ pub fn constrain_expr( constraints.exists_many(vars, [arg_con, opaque_con, link_type_variables_con]) } + OpaqueWrapFunction(OpaqueWrapFunctionData { + opaque_name, + opaque_var, + specialized_def_type, + type_arguments, + lambda_set_variables, + function_name, + function_var, + argument_var, + closure_var, + }) => { + let argument_type = Type::Variable(*argument_var); + + let opaque_type = Type::Alias { + symbol: *opaque_name, + type_arguments: type_arguments + .iter() + .map(|v| OptAbleType { + typ: Type::Variable(v.var), + opt_ability: v.opt_ability, + }) + .collect(), + lambda_set_variables: lambda_set_variables.clone(), + actual: Box::new(argument_type.clone()), + kind: AliasKind::Opaque, + }; + + // Tie the opaque type to the opaque_var + let opaque_con = constraints.equal_types_var( + *opaque_var, + Expected::NoExpectation(opaque_type), + Category::OpaqueWrap(*opaque_name), + region, + ); + + // Tie the type of the value wrapped by the opaque to the opaque's type variables. + let link_type_variables_con = constraints.equal_types( + argument_type.clone(), + Expected::NoExpectation((*specialized_def_type).clone()), + Category::OpaqueArg, + region, + ); + + let lambda_set = Type::ClosureTag { + name: *function_name, + captures: vec![], + ambient_function: *function_var, + }; + + let closure_type = Type::Variable(*closure_var); + + let opaque_type = Type::Variable(*opaque_var); + + let function_type = Type::Function( + vec![argument_type], + Box::new(closure_type), + Box::new(opaque_type), + ); + + let cons = [ + opaque_con, + link_type_variables_con, + constraints.equal_types_var( + *closure_var, + NoExpectation(lambda_set), + Category::OpaqueWrap(*opaque_name), + region, + ), + constraints.equal_types_var( + *function_var, + Expected::NoExpectation(function_type), + Category::OpaqueWrap(*opaque_name), + region, + ), + constraints.equal_types_var( + *function_var, + expected, + Category::OpaqueWrap(*opaque_name), + region, + ), + ]; + + let mut vars = vec![*argument_var, *opaque_var]; + + // Also add the fresh variables we created for the type argument and lambda sets + vars.extend(type_arguments.iter().map(|v| v.var)); + vars.extend(lambda_set_variables.iter().map(|v| { + v.0.expect_variable("all lambda sets should be fresh variables here") + })); + + vars.extend([*function_var, *closure_var]); + + constraints.exists_many(vars, cons) + } RunLowLevel { args, ret_var, op } => { // This is a modified version of what we do for function calls. diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs index fe22b00b6a..1bf1c1d59b 100644 --- a/crates/compiler/gen_llvm/src/llvm/build.rs +++ b/crates/compiler/gen_llvm/src/llvm/build.rs @@ -3,9 +3,9 @@ use crate::llvm::bitcode::{ call_str_bitcode_fn, call_void_bitcode_fn, }; use crate::llvm::build_list::{ - self, allocate_list, empty_polymorphic_list, list_append_unsafe, list_concat, list_drop_at, - list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4, list_prepend, - list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap, + self, allocate_list, empty_polymorphic_list, list_append_unsafe, list_capacity, list_concat, + list_drop_at, list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4, + list_prepend, list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap, list_symbol_to_c_abi, list_to_c_abi, list_with_capacity, pass_update_mode, }; use crate::llvm::build_str::{str_from_float, str_from_int}; @@ -5661,12 +5661,19 @@ fn run_low_level<'a, 'ctx, 'env>( call_bitcode_fn(env, &[string, index], bitcode::STR_GET_SCALAR_UNSAFE) } StrCountUtf8Bytes => { - // Str.countGraphemes : Str -> Nat + // Str.countUtf8Bytes : Str -> Nat debug_assert_eq!(args.len(), 1); let string = load_symbol(scope, &args[0]); call_bitcode_fn(env, &[string], bitcode::STR_COUNT_UTF8_BYTES) } + StrGetCapacity => { + // Str.capacity : Str -> Nat + debug_assert_eq!(args.len(), 1); + + let string = load_symbol(scope, &args[0]); + call_bitcode_fn(env, &[string], bitcode::STR_CAPACITY) + } StrSubstringUnsafe => { // Str.substringUnsafe : Str, Nat, Nat -> Str debug_assert_eq!(args.len(), 3); @@ -5714,13 +5721,21 @@ fn run_low_level<'a, 'ctx, 'env>( call_str_bitcode_fn(env, &[string], bitcode::STR_TRIM_RIGHT) } ListLen => { - // List.len : List * -> Int + // List.len : List * -> Nat debug_assert_eq!(args.len(), 1); let arg = load_symbol(scope, &args[0]); list_len(env.builder, arg.into_struct_value()).into() } + ListGetCapacity => { + // List.capacity : List * -> Nat + debug_assert_eq!(args.len(), 1); + + let arg = load_symbol(scope, &args[0]); + + list_capacity(env.builder, arg.into_struct_value()).into() + } ListWithCapacity => { // List.withCapacity : Nat -> List a debug_assert_eq!(args.len(), 1); diff --git a/crates/compiler/gen_llvm/src/llvm/build_dict.rs b/crates/compiler/gen_llvm/src/llvm/build_dict.rs index cce1f602b8..ffda8ac312 100644 --- a/crates/compiler/gen_llvm/src/llvm/build_dict.rs +++ b/crates/compiler/gen_llvm/src/llvm/build_dict.rs @@ -10,6 +10,7 @@ use crate::llvm::build_list::{layout_width, pass_as_opaque}; use crate::llvm::convert::{basic_type_from_layout, zig_dict_type}; use crate::llvm::refcounting::Mode; use inkwell::attributes::{Attribute, AttributeLoc}; +use inkwell::builder::Builder; use inkwell::context::Context; use inkwell::types::BasicType; use inkwell::values::{BasicValue, BasicValueEnum, FunctionValue, IntValue, StructValue}; @@ -679,6 +680,28 @@ pub fn dict_values<'a, 'ctx, 'env>( ) } +/// Dict.capacity : Dict * * -> Nat +pub fn dict_capacity<'ctx>( + builder: &Builder<'ctx>, + wrapper_struct: StructValue<'ctx>, +) -> IntValue<'ctx> { + builder + .build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "dict_capacity") + .unwrap() + .into_int_value() +} + +/// Set.capacity : Set * -> Nat +pub fn set_capacity<'ctx>( + builder: &Builder<'ctx>, + wrapper_struct: StructValue<'ctx>, +) -> IntValue<'ctx> { + builder + .build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "set_capacity") + .unwrap() + .into_int_value() +} + #[allow(clippy::too_many_arguments)] pub fn set_from_list<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/crates/compiler/gen_llvm/src/llvm/build_list.rs b/crates/compiler/gen_llvm/src/llvm/build_list.rs index b753cf687f..cb0975578c 100644 --- a/crates/compiler/gen_llvm/src/llvm/build_list.rs +++ b/crates/compiler/gen_llvm/src/llvm/build_list.rs @@ -351,7 +351,7 @@ fn bounds_check_comparison<'ctx>( builder.build_int_compare(IntPredicate::ULT, elem_index, len, "bounds_check") } -/// List.len : List elem -> Int +/// List.len : List * -> Nat pub fn list_len<'ctx>( builder: &Builder<'ctx>, wrapper_struct: StructValue<'ctx>, @@ -362,6 +362,17 @@ pub fn list_len<'ctx>( .into_int_value() } +/// List.capacity : List * -> Nat +pub fn list_capacity<'ctx>( + builder: &Builder<'ctx>, + wrapper_struct: StructValue<'ctx>, +) -> IntValue<'ctx> { + builder + .build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "list_capacity") + .unwrap() + .into_int_value() +} + /// List.sortWith : List a, (a, a -> Ordering) -> List a pub fn list_sort_with<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index ce87d7f1f8..1a1a1e83df 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -243,6 +243,7 @@ impl<'a> LowLevelCall<'a> { StrCountUtf8Bytes => { self.load_args_and_call_zig(backend, bitcode::STR_COUNT_UTF8_BYTES) } + StrGetCapacity => self.load_args_and_call_zig(backend, bitcode::STR_CAPACITY), StrToNum => { let number_layout = match self.ret_layout { Layout::Struct { field_layouts, .. } => field_layouts[0], @@ -308,7 +309,37 @@ impl<'a> LowLevelCall<'a> { let (local_id, offset) = location.local_and_offset(backend.storage.stack_frame_pointer); backend.code_builder.get_local(local_id); - backend.code_builder.i32_load(Align::Bytes4, offset + 4); + // List is stored as (pointer, length, capacity), + // with each of those fields being 4 bytes on wasm. + // So the length is 4 bytes after the start of the struct. + // + // WRAPPER_LEN represents the index of the length field + // (which is 1 as of the writing of this comment). If the field order + // ever changes, WRAPPER_LEN should be updated and this logic should + // continue to work even though this comment may become inaccurate. + backend + .code_builder + .i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_LEN)); + } + _ => internal_error!("invalid storage for List"), + }, + + ListGetCapacity => match backend.storage.get(&self.arguments[0]) { + StoredValue::StackMemory { location, .. } => { + let (local_id, offset) = + location.local_and_offset(backend.storage.stack_frame_pointer); + backend.code_builder.get_local(local_id); + // List is stored as (pointer, length, capacity), + // with each of those fields being 4 bytes on wasm. + // So the capacity is 8 bytes after the start of the struct. + // + // WRAPPER_CAPACITY represents the index of the capacity field + // (which is 2 as of the writing of this comment). If the field order + // ever changes, WRAPPER_CAPACITY should be updated and this logic should + // continue to work even though this comment may become inaccurate. + backend + .code_builder + .i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_CAPACITY)); } _ => internal_error!("invalid storage for List"), }, diff --git a/crates/compiler/gen_wasm/src/wasm_module/mod.rs b/crates/compiler/gen_wasm/src/wasm_module/mod.rs index 6e5fc593e0..64f1792f57 100644 --- a/crates/compiler/gen_wasm/src/wasm_module/mod.rs +++ b/crates/compiler/gen_wasm/src/wasm_module/mod.rs @@ -64,7 +64,9 @@ impl<'a> WasmModule<'a> { self.types.serialize(buffer); self.import.serialize(buffer); self.function.serialize(buffer); - self.table.serialize(buffer); + if !self.element.is_empty() { + self.table.serialize(buffer); + } self.memory.serialize(buffer); self.global.serialize(buffer); self.export.serialize(buffer); diff --git a/crates/compiler/gen_wasm/src/wasm_module/sections.rs b/crates/compiler/gen_wasm/src/wasm_module/sections.rs index 7ba7ef546b..dfb69bc7dd 100644 --- a/crates/compiler/gen_wasm/src/wasm_module/sections.rs +++ b/crates/compiler/gen_wasm/src/wasm_module/sections.rs @@ -1118,6 +1118,10 @@ impl<'a> ElementSection<'a> { pub fn size(&self) -> usize { self.segments.iter().map(|seg| seg.size()).sum() } + + pub fn is_empty(&self) -> bool { + self.segments.iter().all(|seg| seg.fn_indices.is_empty()) + } } impl<'a> Parse<&'a Bump> for ElementSection<'a> { @@ -1148,9 +1152,11 @@ impl<'a> Parse<&'a Bump> for ElementSection<'a> { impl<'a> Serialize for ElementSection<'a> { fn serialize(&self, buffer: &mut T) { - let header_indices = write_section_header(buffer, Self::ID); - self.segments.serialize(buffer); - update_section_size(buffer, header_indices); + if !self.is_empty() { + let header_indices = write_section_header(buffer, Self::ID); + self.segments.serialize(buffer); + update_section_size(buffer, header_indices); + } } } diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index 8f285a095d..e6fb72d6b7 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -2094,6 +2094,10 @@ fn update<'a>( .imported_modules .insert(ModuleId::DICT, Region::zero()); + header + .exposed_imports + .insert(Ident::from("Dict"), (Symbol::DICT_DICT, Region::zero())); + header .package_qualified_imported_modules .insert(PackageQualified::Unqualified(ModuleId::SET)); @@ -2102,6 +2106,10 @@ fn update<'a>( .imported_modules .insert(ModuleId::SET, Region::zero()); + header + .exposed_imports + .insert(Ident::from("Set"), (Symbol::SET_SET, Region::zero())); + header .package_qualified_imported_modules .insert(PackageQualified::Unqualified(ModuleId::LIST)); diff --git a/crates/compiler/module/src/low_level.rs b/crates/compiler/module/src/low_level.rs index 2c9ecc43ad..a166d0f760 100644 --- a/crates/compiler/module/src/low_level.rs +++ b/crates/compiler/module/src/low_level.rs @@ -29,6 +29,7 @@ pub enum LowLevel { StrReserve, StrAppendScalar, StrGetScalarUnsafe, + StrGetCapacity, ListLen, ListWithCapacity, ListReserve, @@ -46,6 +47,7 @@ pub enum LowLevel { ListDropAt, ListSwap, ListIsUnique, + ListGetCapacity, NumAdd, NumAddWrap, NumAddChecked, @@ -246,7 +248,9 @@ map_symbol_to_lowlevel! { StrAppendScalar <= STR_APPEND_SCALAR_UNSAFE, StrGetScalarUnsafe <= STR_GET_SCALAR_UNSAFE, StrToNum <= STR_TO_NUM, + StrGetCapacity <= STR_CAPACITY, ListLen <= LIST_LEN, + ListGetCapacity <= LIST_CAPACITY, ListWithCapacity <= LIST_WITH_CAPACITY, ListReserve <= LIST_RESERVE, ListIsUnique <= LIST_IS_UNIQUE, diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs index 1ceda35765..3299bb0527 100644 --- a/crates/compiler/module/src/symbol.rs +++ b/crates/compiler/module/src/symbol.rs @@ -1215,6 +1215,7 @@ define_builtins! { 46 STR_WALK_SCALARS_UNTIL: "walkScalarsUntil" 47 STR_TO_NUM: "strToNum" 48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel" + 49 STR_CAPACITY: "capacity" } 5 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias @@ -1285,6 +1286,7 @@ define_builtins! { 65 LIST_RESERVE: "reserve" 66 LIST_APPEND_UNSAFE: "appendUnsafe" 67 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel" + 68 LIST_CAPACITY: "capacity" } 6 RESULT: "Result" => { 0 RESULT_RESULT: "Result" // the Result.Result type alias @@ -1320,6 +1322,7 @@ define_builtins! { 14 DICT_REMOVE_ALL: "removeAll" // difference 15 DICT_WITH_CAPACITY: "withCapacity" + 16 DICT_CAPACITY: "capacity" } 8 SET: "Set" => { 0 SET_SET: "Set" // the Set.Set type alias @@ -1337,6 +1340,7 @@ define_builtins! { 12 SET_WALK_USER_FUNCTION: "#walk_user_function" 13 SET_CONTAINS: "contains" 14 SET_TO_DICT: "toDict" + 15 SET_CAPACITY: "capacity" } 9 BOX: "Box" => { 0 BOX_BOX_TYPE: "Box" imported // the Box.Box opaque type diff --git a/crates/compiler/mono/src/borrow.rs b/crates/compiler/mono/src/borrow.rs index ccfeead76a..f46b0ff022 100644 --- a/crates/compiler/mono/src/borrow.rs +++ b/crates/compiler/mono/src/borrow.rs @@ -874,9 +874,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] { // - other refcounted arguments are Borrowed match op { Unreachable => arena.alloc_slice_copy(&[irrelevant]), - ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes => { - arena.alloc_slice_copy(&[borrowed]) - } + ListLen | StrIsEmpty | StrToScalars | StrCountGraphemes | StrCountUtf8Bytes + | StrGetCapacity | ListGetCapacity => arena.alloc_slice_copy(&[borrowed]), ListWithCapacity => arena.alloc_slice_copy(&[irrelevant]), ListReplaceUnsafe => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), StrGetUnsafe | ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]), diff --git a/crates/compiler/mono/src/copy.rs b/crates/compiler/mono/src/copy.rs index e2dd5e0200..0417046c8c 100644 --- a/crates/compiler/mono/src/copy.rs +++ b/crates/compiler/mono/src/copy.rs @@ -1,6 +1,6 @@ use roc_can::{ def::Def, - expr::{AccessorData, ClosureData, Expr, Field, WhenBranch}, + expr::{AccessorData, ClosureData, Expr, Field, OpaqueWrapFunctionData, WhenBranch}, }; use roc_module::ident::{Lowercase, TagName}; use roc_types::{ @@ -543,6 +543,29 @@ fn deep_copy_type_vars_into_expr_help( lambda_set_variables: lambda_set_variables.clone(), }, + OpaqueWrapFunction(OpaqueWrapFunctionData { + opaque_name, + opaque_var, + specialized_def_type, + type_arguments, + lambda_set_variables, + function_name, + function_var, + argument_var, + closure_var, + }) => OpaqueWrapFunction(OpaqueWrapFunctionData { + opaque_name: *opaque_name, + opaque_var: sub!(*opaque_var), + function_name: *function_name, + function_var: sub!(*function_var), + argument_var: sub!(*argument_var), + closure_var: sub!(*closure_var), + // The following three are only used for constraining + specialized_def_type: specialized_def_type.clone(), + type_arguments: type_arguments.clone(), + lambda_set_variables: lambda_set_variables.clone(), + }), + Expect { loc_condition, loc_continuation, diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 33484777e1..0ea8364404 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -4387,6 +4387,60 @@ pub fn with_hole<'a>( } } + OpaqueWrapFunction(wrap_fn_data) => { + let opaque_var = wrap_fn_data.opaque_var; + let arg_symbol = env.unique_symbol(); + + let ClosureData { + name, + function_type, + arguments, + loc_body, + .. + } = wrap_fn_data.to_closure_data(arg_symbol); + + match procs.insert_anonymous( + env, + LambdaName::no_niche(name), + function_type, + arguments, + *loc_body, + CapturedSymbols::None, + opaque_var, + layout_cache, + ) { + Ok(_) => { + let raw_layout = return_on_layout_error!( + env, + layout_cache.raw_from_var(env.arena, function_type, env.subs), + "Expr::OpaqueWrapFunction" + ); + + match raw_layout { + RawFunctionLayout::Function(_, lambda_set, _) => { + let lambda_name = + find_lambda_name(env, layout_cache, lambda_set, name, &[]); + construct_closure_data( + env, + lambda_set, + lambda_name, + &[], + assigned, + hole, + ) + } + RawFunctionLayout::ZeroArgumentThunk(_) => { + internal_error!("should not be a thunk!") + } + } + } + + Err(_error) => Stmt::RuntimeError( + "TODO convert anonymous function error to a RuntimeError string", + ), + } + } + Update { record_var, symbol: structure, diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index e6f6e3b866..3f6c29793e 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -1809,7 +1809,7 @@ impl<'a> Builtin<'a> { pub const STR_WORDS: u32 = 3; pub const LIST_WORDS: u32 = 3; - /// Layout of collection wrapper for List and Str - a struct of (pointer, length, capacity). + /// Layout of collection wrapper for List, Str, Dict, and Set - a struct of (pointer, length, capacity). pub const WRAPPER_PTR: u32 = 0; pub const WRAPPER_LEN: u32 = 1; pub const WRAPPER_CAPACITY: u32 = 2; diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index 516040ffe3..0ea9dd3382 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -323,6 +323,43 @@ impl Aliases { let mut substitutions: MutMap<_, _> = Default::default(); + let old_type_variables = delayed_variables.type_variables(&mut self.variables); + let new_type_variables = &subs.variables[alias_variables.type_variables().indices()]; + + let some_new_vars_are_equivalent = { + // In practice the number of type variables is tiny, so just do a quadratic check + // without allocating. + let mut some_equivalent = false; + for (i, var) in new_type_variables.iter().enumerate() { + for j in (i + 1)..new_type_variables.len() { + let other_var = new_type_variables[j]; + some_equivalent = some_equivalent || *var == other_var; + } + } + some_equivalent + }; + + // If some type variables are equivalent, we have to work over a cloned type variable, + // otherwise we will leave in place an alias without preserving the property of unique + // type variables. + // + // For example, if a delayed alias `Foo a b` is instantiated with args `t1 t1` without cloning, + // then the delayed alias would be updated to `Foo t1 t1`, and now the distinction between the + // two type variables is lost. + let can_reuse_old_definition = !some_new_vars_are_equivalent; + + for (old, new) in old_type_variables.iter_mut().zip(new_type_variables) { + // if constraint gen duplicated a type these variables could be the same + // (happens very often in practice) + if old.var != *new { + substitutions.insert(old.var, *new); + + if can_reuse_old_definition { + old.var = *new; + } + } + } + for OptAbleVar { var: rec_var, opt_ability, @@ -333,19 +370,9 @@ impl Aliases { debug_assert!(opt_ability.is_none()); let new_var = subs.fresh_unnamed_flex_var(); substitutions.insert(*rec_var, new_var); - *rec_var = new_var; - } - let old_type_variables = delayed_variables.type_variables(&mut self.variables); - let new_type_variables = &subs.variables[alias_variables.type_variables().indices()]; - - for (old, new) in old_type_variables.iter_mut().zip(new_type_variables) { - // if constraint gen duplicated a type these variables could be the same - // (happens very often in practice) - if old.var != *new { - substitutions.insert(old.var, *new); - - old.var = *new; + if can_reuse_old_definition { + *rec_var = new_var; } } @@ -360,34 +387,44 @@ impl Aliases { debug_assert!(old.opt_ability.is_none()); if old.var != *new { substitutions.insert(old.var, *new); - old.var = *new; - } - } - if !substitutions.is_empty() { - typ.substitute_variables(&substitutions); - } - - let mut t = Type::EmptyRec; - - std::mem::swap(typ, &mut t); - - // assumption: an alias does not (transitively) syntactically contain itself - // (if it did it would have to be a recursive tag union, which we should have fixed up - // during canonicalization) - let alias_variable = type_to_variable(subs, rank, pools, arena, self, &t, false); - - { - match self.aliases.iter_mut().find(|(s, _, _, _)| *s == symbol) { - None => unreachable!(), - Some((_, typ, _, _)) => { - // swap typ back - std::mem::swap(typ, &mut t); + if can_reuse_old_definition { + old.var = *new; } } } - (alias_variable, kind) + if !can_reuse_old_definition { + let mut typ = typ.clone(); + typ.substitute_variables(&substitutions); + let alias_variable = type_to_variable(subs, rank, pools, arena, self, &typ, false); + (alias_variable, kind) + } else { + if !substitutions.is_empty() { + typ.substitute_variables(&substitutions); + } + + let mut t = Type::EmptyRec; + + std::mem::swap(typ, &mut t); + + // assumption: an alias does not (transitively) syntactically contain itself + // (if it did it would have to be a recursive tag union, which we should have fixed up + // during canonicalization) + let alias_variable = type_to_variable(subs, rank, pools, arena, self, &t, false); + + { + match self.aliases.iter_mut().find(|(s, _, _, _)| *s == symbol) { + None => unreachable!(), + Some((_, typ, _, _)) => { + // swap typ back + std::mem::swap(typ, &mut t); + } + } + } + + (alias_variable, kind) + } } } diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index 611ca9ffdd..e30a1431bd 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -7341,4 +7341,30 @@ mod solve_expr { "Rose I64", ); } + + #[test] + fn opaque_wrap_function() { + infer_eq_without_problem( + indoc!( + r#" + A := U8 + List.map [1, 2, 3] @A + "# + ), + "List A", + ); + } + + #[test] + fn opaque_wrap_function_with_inferred_arg() { + infer_eq_without_problem( + indoc!( + r#" + A a := a + List.map [1u8, 2u8, 3u8] @A + "# + ), + "List (A U8)", + ); + } } diff --git a/crates/compiler/test_derive/src/pretty_print.rs b/crates/compiler/test_derive/src/pretty_print.rs index ca8acaafe3..11054c7e06 100644 --- a/crates/compiler/test_derive/src/pretty_print.rs +++ b/crates/compiler/test_derive/src/pretty_print.rs @@ -2,7 +2,7 @@ use roc_can::def::Def; use roc_can::expr::Expr::{self, *}; -use roc_can::expr::{ClosureData, WhenBranch}; +use roc_can::expr::{ClosureData, OpaqueWrapFunctionData, WhenBranch}; use roc_can::pattern::{Pattern, RecordDestruct}; use roc_module::symbol::Interns; @@ -191,6 +191,9 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a, } => expr(c, CallArg, f, &loc_expr.value) .append(f.text(format!(".{}", field.as_str()))) .group(), + OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => { + f.text(format!("@{}", opaque_name.as_str(c.interns))) + } Accessor(_) => todo!(), Update { .. } => todo!(), Tag { .. } => todo!(), diff --git a/crates/compiler/test_gen/src/gen_dict.rs b/crates/compiler/test_gen/src/gen_dict.rs index f0f0232c97..0a70dc3ba6 100644 --- a/crates/compiler/test_gen/src/gen_dict.rs +++ b/crates/compiler/test_gen/src/gen_dict.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "gen-llvm")] +#![cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] #[cfg(feature = "gen-llvm")] use crate::helpers::llvm::assert_evals_to; @@ -6,8 +6,8 @@ use crate::helpers::llvm::assert_evals_to; // #[cfg(feature = "gen-dev")] // use crate::helpers::dev::assert_evals_to; -// #[cfg(feature = "gen-wasm")] -// use crate::helpers::wasm::assert_evals_to; +#[cfg(feature = "gen-wasm")] +use crate::helpers::wasm::assert_evals_to; use indoc::indoc; use roc_std::{RocList, RocStr}; @@ -18,9 +18,7 @@ fn dict_empty_len() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - - main = Dict.len Dict.empty + Dict.len Dict.empty "# ), 0, @@ -34,12 +32,9 @@ fn dict_insert_empty() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - - main = - Dict.empty - |> Dict.insert 42 32 - |> Dict.len + Dict.empty + |> Dict.insert 42 32 + |> Dict.len "# ), 1, @@ -53,13 +48,10 @@ fn dict_empty_contains() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - empty : Dict.Dict I64 F64 empty = Dict.empty - main = - Dict.contains empty 42 + Dict.contains empty 42 "# ), false, @@ -73,12 +65,10 @@ fn dict_nonempty_contains() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - empty : Dict.Dict I64 F64 empty = Dict.insert Dict.empty 42 1.23 - main = Dict.contains empty 42 + Dict.contains empty 42 "# ), true, @@ -92,15 +82,12 @@ fn dict_empty_remove() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - empty : Dict.Dict I64 F64 empty = Dict.empty - main = - empty - |> Dict.remove 42 - |> Dict.len + empty + |> Dict.remove 42 + |> Dict.len "# ), 0, @@ -114,15 +101,12 @@ fn dict_nonempty_remove() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - empty : Dict.Dict I64 F64 empty = Dict.insert Dict.empty 42 1.23 - main = - empty - |> Dict.remove 42 - |> Dict.len + empty + |> Dict.remove 42 + |> Dict.len "# ), 0, @@ -136,8 +120,6 @@ fn dict_nonempty_get() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - empty : Dict.Dict I64 F64 empty = Dict.insert Dict.empty 42 1.23 @@ -146,11 +128,10 @@ fn dict_nonempty_get() { Ok v -> v Err _ -> def - main = - empty - |> Dict.insert 42 1.23 - |> Dict.get 42 - |> withDefault 0 + empty + |> Dict.insert 42 1.23 + |> Dict.get 42 + |> withDefault 0 "# ), 1.23, @@ -160,18 +141,15 @@ fn dict_nonempty_get() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - withDefault = \x, def -> when x is Ok v -> v Err _ -> def - main = - Dict.empty - |> Dict.insert 42 1.23 - |> Dict.get 43 - |> withDefault 0 + Dict.empty + |> Dict.insert 42 1.23 + |> Dict.get 43 + |> withDefault 0 "# ), 0.0, @@ -180,13 +158,11 @@ fn dict_nonempty_get() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn keys() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - myDict : Dict.Dict I64 I64 myDict = Dict.empty @@ -195,7 +171,7 @@ fn keys() { |> Dict.insert 2 100 - main = Dict.keys myDict + Dict.keys myDict "# ), RocList::from_slice(&[0, 1, 2]), @@ -204,13 +180,11 @@ fn keys() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn values() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - myDict : Dict.Dict I64 I64 myDict = Dict.empty @@ -219,7 +193,7 @@ fn values() { |> Dict.insert 2 300 - main = Dict.values myDict + Dict.values myDict "# ), RocList::from_slice(&[100, 200, 300]), @@ -228,19 +202,17 @@ fn values() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn from_list_with_fold_simple() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - myDict : Dict.Dict I64 I64 myDict = [1,2,3] |> List.walk Dict.empty (\accum, value -> Dict.insert accum value value) - main = Dict.values myDict + Dict.values myDict "# ), RocList::from_slice(&[1, 2, 3]), @@ -249,13 +221,11 @@ fn from_list_with_fold_simple() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn from_list_with_fold_reallocates() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - range : I64, I64, List I64-> List I64 range = \low, high, accum -> if low < high then @@ -269,7 +239,7 @@ fn from_list_with_fold_reallocates() { range 0 25 [] |> List.walk Dict.empty (\accum, value -> Dict.insert accum value value) - main = Dict.values myDict + Dict.values myDict "# ), RocList::from_slice(&[ @@ -281,13 +251,11 @@ fn from_list_with_fold_reallocates() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn small_str_keys() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - myDict : Dict.Dict Str I64 myDict = Dict.empty @@ -296,7 +264,7 @@ fn small_str_keys() { |> Dict.insert "c" 100 - main = Dict.keys myDict + Dict.keys myDict "# ), RocList::from_slice(&["a".into(), "b".into(), "c".into(),],), @@ -305,13 +273,11 @@ fn small_str_keys() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn big_str_keys() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - myDict : Dict.Dict Str I64 myDict = Dict.empty @@ -319,7 +285,7 @@ fn big_str_keys() { |> Dict.insert "synopsis for high level overviews. Iterative approaches" 200 |> Dict.insert "to corporate strategy foster collaborative thinking to" 300 - main = Dict.keys myDict + Dict.keys myDict "# ), RocList::from_slice(&[ @@ -332,13 +298,11 @@ fn big_str_keys() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn big_str_values() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - myDict : Dict.Dict I64 Str myDict = Dict.empty @@ -346,7 +310,7 @@ fn big_str_values() { |> Dict.insert 200 "synopsis for high level overviews. Iterative approaches" |> Dict.insert 300 "to corporate strategy foster collaborative thinking to" - main = Dict.values myDict + Dict.values myDict "# ), RocList::from_slice(&[ @@ -364,8 +328,6 @@ fn unit_values() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - myDict : Dict.Dict I64 {} myDict = Dict.empty @@ -374,7 +336,7 @@ fn unit_values() { |> Dict.insert 2 {} |> Dict.insert 3 {} - main = Dict.len myDict + Dict.len myDict "# ), 4, @@ -388,13 +350,11 @@ fn single() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - myDict : Dict.Dict I64 {} myDict = Dict.single 0 {} - main = Dict.len myDict + Dict.len myDict "# ), 1, @@ -408,13 +368,11 @@ fn insert_all() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - - myDict : Dict.Dict I64 {} + myDict : Dict I64 {} myDict = Dict.insertAll (Dict.single 0 {}) (Dict.single 1 {}) - main = Dict.len myDict + Dict.len myDict "# ), 2, @@ -423,19 +381,17 @@ fn insert_all() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn insert_all_prefer_first() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - myDict : Dict.Dict I64 I64 myDict = (Dict.single 0 100) |> Dict.insertAll (Dict.single 0 200) - main = Dict.values myDict + Dict.values myDict "# ), RocList::from_slice(&[100]), @@ -449,8 +405,6 @@ fn keep_shared() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - dict1 : Dict.Dict I64 {} dict1 = Dict.empty @@ -467,9 +421,8 @@ fn keep_shared() { |> Dict.insert 2 {} |> Dict.insert 4 {} - main = - Dict.keepShared dict1 dict2 - |> Dict.len + Dict.keepShared dict1 dict2 + |> Dict.len "# ), 2, @@ -478,13 +431,11 @@ fn keep_shared() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn keep_shared_prefer_first() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - dict1 : Dict.Dict I64 I64 dict1 = Dict.empty @@ -501,9 +452,8 @@ fn keep_shared_prefer_first() { |> Dict.insert 2 200 |> Dict.insert 4 300 - main = - Dict.keepShared dict1 dict2 - |> Dict.values + Dict.keepShared dict1 dict2 + |> Dict.values "# ), RocList::from_slice(&[2, 4]), @@ -517,8 +467,6 @@ fn remove_all() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - dict1 : Dict.Dict I64 {} dict1 = Dict.empty @@ -535,9 +483,8 @@ fn remove_all() { |> Dict.insert 2 {} |> Dict.insert 4 {} - main = - Dict.removeAll dict1 dict2 - |> Dict.len + Dict.removeAll dict1 dict2 + |> Dict.len "# ), 3, @@ -551,8 +498,6 @@ fn remove_all_prefer_first() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - dict1 : Dict.Dict I64 I64 dict1 = Dict.empty @@ -569,9 +514,8 @@ fn remove_all_prefer_first() { |> Dict.insert 2 200 |> Dict.insert 4 300 - main = - Dict.removeAll dict1 dict2 - |> Dict.values + Dict.removeAll dict1 dict2 + |> Dict.values "# ), RocList::from_slice(&[1, 5, 3]), @@ -585,8 +529,6 @@ fn walk_sum_keys() { assert_evals_to!( indoc!( r#" - app "dict" imports [ Dict ] provides [main] to "./platform" - dict1 : Dict.Dict I64 I64 dict1 = Dict.empty @@ -596,7 +538,7 @@ fn walk_sum_keys() { |> Dict.insert 4 4 |> Dict.insert 5 5 - main = Dict.walk dict1 0 \k, _, a -> k + a + Dict.walk dict1 0 \k, _, a -> k + a "# ), 15, diff --git a/crates/compiler/test_gen/src/gen_set.rs b/crates/compiler/test_gen/src/gen_set.rs index ff82f2c90a..e898e2cc9a 100644 --- a/crates/compiler/test_gen/src/gen_set.rs +++ b/crates/compiler/test_gen/src/gen_set.rs @@ -18,9 +18,7 @@ fn empty_len() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = Set.len Set.empty + Set.len Set.empty "# ), 0, @@ -34,9 +32,7 @@ fn single_len() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = Set.len (Set.single 42) + Set.len (Set.single 42) "# ), 1, @@ -50,9 +46,7 @@ fn single_to_list() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = Set.toList (Set.single 42) + Set.toList (Set.single 42) "# ), RocList::from_slice(&[42]), @@ -62,9 +56,7 @@ fn single_to_list() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = Set.toList (Set.single 1) + Set.toList (Set.single 1) "# ), RocList::from_slice(&[1]), @@ -74,9 +66,7 @@ fn single_to_list() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = Set.toList (Set.single 1.0) + Set.toList (Set.single 1.0) "# ), RocList::from_slice(&[1.0]), @@ -90,14 +80,11 @@ fn insert() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = - Set.empty - |> Set.insert 0 - |> Set.insert 1 - |> Set.insert 2 - |> Set.toList + Set.empty + |> Set.insert 0 + |> Set.insert 1 + |> Set.insert 2 + |> Set.toList "# ), RocList::from_slice(&[0, 1, 2]), @@ -111,15 +98,12 @@ fn remove() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = - Set.empty - |> Set.insert 0 - |> Set.insert 1 - |> Set.remove 1 - |> Set.remove 2 - |> Set.toList + Set.empty + |> Set.insert 0 + |> Set.insert 1 + |> Set.remove 1 + |> Set.remove 2 + |> Set.toList "# ), RocList::from_slice(&[0]), @@ -133,17 +117,14 @@ fn union() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - set1 : Set.Set I64 set1 = Set.fromList [1,2] set2 : Set.Set I64 set2 = Set.fromList [1,3,4] - main = - Set.union set1 set2 - |> Set.toList + Set.union set1 set2 + |> Set.toList "# ), RocList::from_slice(&[1, 2, 3, 4]), @@ -157,17 +138,14 @@ fn difference() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - set1 : Set.Set I64 set1 = Set.fromList [1,2] set2 : Set.Set I64 set2 = Set.fromList [1,3,4] - main = - Set.difference set1 set2 - |> Set.toList + Set.difference set1 set2 + |> Set.toList "# ), RocList::from_slice(&[2]), @@ -181,17 +159,14 @@ fn intersection() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - set1 : Set.Set I64 set1 = Set.fromList [1,2] set2 : Set.Set I64 set2 = Set.fromList [1,3,4] - main = - Set.intersection set1 set2 - |> Set.toList + Set.intersection set1 set2 + |> Set.toList "# ), RocList::from_slice(&[1]), @@ -205,9 +180,7 @@ fn walk_sum() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = Set.walk (Set.fromList [1,2,3]) 0 (\x, y -> x + y) + Set.walk (Set.fromList [1,2,3]) 0 (\x, y -> x + y) "# ), 6, @@ -221,9 +194,7 @@ fn contains() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = Set.contains (Set.fromList [1,3,4]) 4 + Set.contains (Set.fromList [1,3,4]) 4 "# ), true, @@ -233,9 +204,7 @@ fn contains() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = Set.contains (Set.fromList [1,3,4]) 2 + Set.contains (Set.fromList [1,3,4]) 2 "# ), false, @@ -249,12 +218,9 @@ fn from_list() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = - [1,2,2,3,1,4] - |> Set.fromList - |> Set.toList + [1,2,2,3,1,4] + |> Set.fromList + |> Set.toList "# ), RocList::from_slice(&[1, 2, 3, 4]), @@ -264,15 +230,12 @@ fn from_list() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - empty : List I64 empty = [] - main = - empty - |> Set.fromList - |> Set.toList + empty + |> Set.fromList + |> Set.toList "# ), RocList::::default(), @@ -287,12 +250,9 @@ fn from_list_void() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - - main = - [] - |> Set.fromList - |> Set.toList + [] + |> Set.fromList + |> Set.toList "# ), RocList::::default(), @@ -306,16 +266,13 @@ fn from_list_result() { assert_evals_to!( indoc!( r#" - app "set" imports [ Set ] provides [main] to "./platform" - x : Result Str {} x = Ok "foo" - main = - [x] - |> Set.fromList - |> Set.toList - |> List.len + [x] + |> Set.fromList + |> Set.toList + |> List.len "# ), 1, diff --git a/crates/compiler/test_mono/generated/dict.txt b/crates/compiler/test_mono/generated/dict.txt index dd66c7d5b3..f76b9a9434 100644 --- a/crates/compiler/test_mono/generated/dict.txt +++ b/crates/compiler/test_mono/generated/dict.txt @@ -1,13 +1,17 @@ procedure Dict.1 (): - let Dict.25 : Dict [] [] = lowlevel DictEmpty ; - ret Dict.25; + let Dict.102 : List {[], []} = Array []; + ret Dict.102; -procedure Dict.7 (#Attr.2): - let Dict.24 : U64 = lowlevel DictSize #Attr.2; - dec #Attr.2; - ret Dict.24; +procedure Dict.7 (Dict.96): + let Dict.101 : U64 = CallByName List.6 Dict.96; + ret Dict.101; + +procedure List.6 (#Attr.2): + let List.295 : U64 = lowlevel ListLen #Attr.2; + ret List.295; procedure Test.0 (): - let Test.2 : Dict [] [] = CallByName Dict.1; + let Test.2 : List {[], []} = CallByName Dict.1; let Test.1 : U64 = CallByName Dict.7 Test.2; + dec Test.2; ret Test.1; diff --git a/crates/compiler/test_mono/generated/empty_list_of_function_type.txt b/crates/compiler/test_mono/generated/empty_list_of_function_type.txt index 6e0e9cb179..521dbfd3b7 100644 --- a/crates/compiler/test_mono/generated/empty_list_of_function_type.txt +++ b/crates/compiler/test_mono/generated/empty_list_of_function_type.txt @@ -1,8 +1,8 @@ -procedure List.2 (List.78, List.79): - let List.300 : U64 = CallByName List.6 List.78; - let List.297 : Int1 = CallByName Num.22 List.79 List.300; +procedure List.2 (List.79, List.80): + let List.300 : U64 = CallByName List.6 List.79; + let List.297 : Int1 = CallByName Num.22 List.80 List.300; if List.297 then - let List.299 : {} = CallByName List.60 List.78 List.79; + let List.299 : {} = CallByName List.60 List.79 List.80; let List.298 : [C {}, C {}] = TagId(1) List.299; ret List.298; else diff --git a/crates/compiler/test_mono/generated/encode.txt b/crates/compiler/test_mono/generated/encode.txt index e6b506b299..b63c00d749 100644 --- a/crates/compiler/test_mono/generated/encode.txt +++ b/crates/compiler/test_mono/generated/encode.txt @@ -1,7 +1,7 @@ -procedure List.4 (List.89, List.90): +procedure List.4 (List.90, List.91): let List.297 : U64 = 1i64; - let List.296 : List U8 = CallByName List.65 List.89 List.297; - let List.295 : List U8 = CallByName List.66 List.296 List.90; + let List.296 : List U8 = CallByName List.65 List.90 List.297; + let List.295 : List U8 = CallByName List.66 List.296 List.91; ret List.295; procedure List.65 (#Attr.2, #Attr.3): diff --git a/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt b/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt index a70da53aaf..469ab72a85 100644 --- a/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt +++ b/crates/compiler/test_mono/generated/issue_2583_specialize_errors_behind_unified_branches.txt @@ -2,11 +2,11 @@ procedure Bool.7 (#Attr.2, #Attr.3): let Bool.9 : Int1 = lowlevel Eq #Attr.2 #Attr.3; ret Bool.9; -procedure List.2 (List.78, List.79): - let List.309 : U64 = CallByName List.6 List.78; - let List.305 : Int1 = CallByName Num.22 List.79 List.309; +procedure List.2 (List.79, List.80): + let List.309 : U64 = CallByName List.6 List.79; + let List.305 : Int1 = CallByName Num.22 List.80 List.309; if List.305 then - let List.307 : I64 = CallByName List.60 List.78 List.79; + let List.307 : I64 = CallByName List.60 List.79 List.80; let List.306 : [C {}, C I64] = TagId(1) List.307; ret List.306; else @@ -22,15 +22,15 @@ procedure List.60 (#Attr.2, #Attr.3): let List.308 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3; ret List.308; -procedure List.9 (List.206): +procedure List.9 (List.207): let List.302 : U64 = 0i64; - let List.295 : [C {}, C I64] = CallByName List.2 List.206 List.302; + let List.295 : [C {}, C I64] = CallByName List.2 List.207 List.302; let List.299 : U8 = 1i64; let List.300 : U8 = GetTagId List.295; let List.301 : Int1 = lowlevel Eq List.299 List.300; if List.301 then - let List.207 : I64 = UnionAtIndex (Id 1) (Index 0) List.295; - let List.296 : [C Int1, C I64] = TagId(1) List.207; + let List.208 : I64 = UnionAtIndex (Id 1) (Index 0) List.295; + let List.296 : [C Int1, C I64] = TagId(1) List.208; ret List.296; else let List.298 : Int1 = true; @@ -41,27 +41,27 @@ procedure Num.22 (#Attr.2, #Attr.3): let Num.257 : Int1 = lowlevel NumLt #Attr.2 #Attr.3; ret Num.257; -procedure Str.27 (Str.88): - let Str.194 : [C Int1, C I64] = CallByName Str.67 Str.88; - ret Str.194; +procedure Str.27 (Str.89): + let Str.195 : [C Int1, C I64] = CallByName Str.68 Str.89; + ret Str.195; procedure Str.47 (#Attr.2): - let Str.202 : {I64, U8} = lowlevel StrToNum #Attr.2; - ret Str.202; + let Str.203 : {I64, U8} = lowlevel StrToNum #Attr.2; + ret Str.203; -procedure Str.67 (Str.189): - let Str.190 : {I64, U8} = CallByName Str.47 Str.189; - let Str.200 : U8 = StructAtIndex 1 Str.190; - let Str.201 : U8 = 0i64; - let Str.197 : Int1 = CallByName Bool.7 Str.200 Str.201; - if Str.197 then - let Str.199 : I64 = StructAtIndex 0 Str.190; - let Str.198 : [C Int1, C I64] = TagId(1) Str.199; - ret Str.198; +procedure Str.68 (Str.190): + let Str.191 : {I64, U8} = CallByName Str.47 Str.190; + let Str.201 : U8 = StructAtIndex 1 Str.191; + let Str.202 : U8 = 0i64; + let Str.198 : Int1 = CallByName Bool.7 Str.201 Str.202; + if Str.198 then + let Str.200 : I64 = StructAtIndex 0 Str.191; + let Str.199 : [C Int1, C I64] = TagId(1) Str.200; + ret Str.199; else - let Str.196 : Int1 = false; - let Str.195 : [C Int1, C I64] = TagId(0) Str.196; - ret Str.195; + let Str.197 : Int1 = false; + let Str.196 : [C Int1, C I64] = TagId(0) Str.197; + ret Str.196; procedure Test.0 (): let Test.4 : Int1 = true; diff --git a/crates/compiler/test_mono/generated/list_append.txt b/crates/compiler/test_mono/generated/list_append.txt index a41459c5c9..78a0252390 100644 --- a/crates/compiler/test_mono/generated/list_append.txt +++ b/crates/compiler/test_mono/generated/list_append.txt @@ -1,7 +1,7 @@ -procedure List.4 (List.89, List.90): +procedure List.4 (List.90, List.91): let List.297 : U64 = 1i64; - let List.296 : List I64 = CallByName List.65 List.89 List.297; - let List.295 : List I64 = CallByName List.66 List.296 List.90; + let List.296 : List I64 = CallByName List.65 List.90 List.297; + let List.295 : List I64 = CallByName List.66 List.296 List.91; ret List.295; procedure List.65 (#Attr.2, #Attr.3): diff --git a/crates/compiler/test_mono/generated/list_append_closure.txt b/crates/compiler/test_mono/generated/list_append_closure.txt index 0fd505d074..785db37142 100644 --- a/crates/compiler/test_mono/generated/list_append_closure.txt +++ b/crates/compiler/test_mono/generated/list_append_closure.txt @@ -1,7 +1,7 @@ -procedure List.4 (List.89, List.90): +procedure List.4 (List.90, List.91): let List.297 : U64 = 1i64; - let List.296 : List I64 = CallByName List.65 List.89 List.297; - let List.295 : List I64 = CallByName List.66 List.296 List.90; + let List.296 : List I64 = CallByName List.65 List.90 List.297; + let List.295 : List I64 = CallByName List.66 List.296 List.91; ret List.295; procedure List.65 (#Attr.2, #Attr.3): diff --git a/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt b/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt index e93b4e33e6..287e95c766 100644 --- a/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt +++ b/crates/compiler/test_mono/generated/list_cannot_update_inplace.txt @@ -1,18 +1,18 @@ -procedure List.3 (List.86, List.87, List.88): - let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88; +procedure List.3 (List.87, List.88, List.89): + let List.298 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89; let List.297 : List I64 = StructAtIndex 0 List.298; inc List.297; dec List.298; ret List.297; -procedure List.57 (List.83, List.84, List.85): - let List.303 : U64 = CallByName List.6 List.83; - let List.300 : Int1 = CallByName Num.22 List.84 List.303; +procedure List.57 (List.84, List.85, List.86): + let List.303 : U64 = CallByName List.6 List.84; + let List.300 : Int1 = CallByName Num.22 List.85 List.303; if List.300 then - let List.301 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85; + let List.301 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86; ret List.301; else - let List.299 : {List I64, I64} = Struct {List.83, List.85}; + let List.299 : {List I64, I64} = Struct {List.84, List.86}; ret List.299; procedure List.6 (#Attr.2): diff --git a/crates/compiler/test_mono/generated/list_get.txt b/crates/compiler/test_mono/generated/list_get.txt index 845164b74c..7e46e190a8 100644 --- a/crates/compiler/test_mono/generated/list_get.txt +++ b/crates/compiler/test_mono/generated/list_get.txt @@ -1,8 +1,8 @@ -procedure List.2 (List.78, List.79): - let List.300 : U64 = CallByName List.6 List.78; - let List.297 : Int1 = CallByName Num.22 List.79 List.300; +procedure List.2 (List.79, List.80): + let List.300 : U64 = CallByName List.6 List.79; + let List.297 : Int1 = CallByName Num.22 List.80 List.300; if List.297 then - let List.299 : I64 = CallByName List.60 List.78 List.79; + let List.299 : I64 = CallByName List.60 List.79 List.80; let List.298 : [C {}, C I64] = TagId(1) List.299; ret List.298; else diff --git a/crates/compiler/test_mono/generated/list_map_closure_borrows.txt b/crates/compiler/test_mono/generated/list_map_closure_borrows.txt index 6d876e8154..dac9b7d851 100644 --- a/crates/compiler/test_mono/generated/list_map_closure_borrows.txt +++ b/crates/compiler/test_mono/generated/list_map_closure_borrows.txt @@ -1,8 +1,8 @@ -procedure List.2 (List.78, List.79): - let List.300 : U64 = CallByName List.6 List.78; - let List.297 : Int1 = CallByName Num.22 List.79 List.300; +procedure List.2 (List.79, List.80): + let List.300 : U64 = CallByName List.6 List.79; + let List.297 : Int1 = CallByName Num.22 List.80 List.300; if List.297 then - let List.299 : Str = CallByName List.60 List.78 List.79; + let List.299 : Str = CallByName List.60 List.79 List.80; let List.298 : [C {}, C Str] = TagId(1) List.299; ret List.298; else @@ -27,12 +27,12 @@ procedure Num.22 (#Attr.2, #Attr.3): ret Num.257; procedure Str.16 (#Attr.2, #Attr.3): - let Str.194 : Str = lowlevel StrRepeat #Attr.2 #Attr.3; - ret Str.194; + let Str.195 : Str = lowlevel StrRepeat #Attr.2 #Attr.3; + ret Str.195; procedure Str.3 (#Attr.2, #Attr.3): - let Str.195 : Str = lowlevel StrConcat #Attr.2 #Attr.3; - ret Str.195; + let Str.196 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.196; procedure Test.1 (): let Test.21 : Str = "lllllllllllllllllllllooooooooooong"; diff --git a/crates/compiler/test_mono/generated/list_map_closure_owns.txt b/crates/compiler/test_mono/generated/list_map_closure_owns.txt index fe8f58ae70..1f891b32e9 100644 --- a/crates/compiler/test_mono/generated/list_map_closure_owns.txt +++ b/crates/compiler/test_mono/generated/list_map_closure_owns.txt @@ -1,8 +1,8 @@ -procedure List.2 (List.78, List.79): - let List.300 : U64 = CallByName List.6 List.78; - let List.297 : Int1 = CallByName Num.22 List.79 List.300; +procedure List.2 (List.79, List.80): + let List.300 : U64 = CallByName List.6 List.79; + let List.297 : Int1 = CallByName Num.22 List.80 List.300; if List.297 then - let List.299 : Str = CallByName List.60 List.78 List.79; + let List.299 : Str = CallByName List.60 List.79 List.80; let List.298 : [C {}, C Str] = TagId(1) List.299; ret List.298; else @@ -29,8 +29,8 @@ procedure Num.22 (#Attr.2, #Attr.3): ret Num.257; procedure Str.3 (#Attr.2, #Attr.3): - let Str.195 : Str = lowlevel StrConcat #Attr.2 #Attr.3; - ret Str.195; + let Str.196 : Str = lowlevel StrConcat #Attr.2 #Attr.3; + ret Str.196; procedure Test.1 (): let Test.21 : Str = "lllllllllllllllllllllooooooooooong"; diff --git a/crates/compiler/test_mono/generated/list_pass_to_function.txt b/crates/compiler/test_mono/generated/list_pass_to_function.txt index 0f30e9dc71..64381b2ad5 100644 --- a/crates/compiler/test_mono/generated/list_pass_to_function.txt +++ b/crates/compiler/test_mono/generated/list_pass_to_function.txt @@ -1,18 +1,18 @@ -procedure List.3 (List.86, List.87, List.88): - let List.296 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88; +procedure List.3 (List.87, List.88, List.89): + let List.296 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89; let List.295 : List I64 = StructAtIndex 0 List.296; inc List.295; dec List.296; ret List.295; -procedure List.57 (List.83, List.84, List.85): - let List.301 : U64 = CallByName List.6 List.83; - let List.298 : Int1 = CallByName Num.22 List.84 List.301; +procedure List.57 (List.84, List.85, List.86): + let List.301 : U64 = CallByName List.6 List.84; + let List.298 : Int1 = CallByName Num.22 List.85 List.301; if List.298 then - let List.299 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85; + let List.299 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86; ret List.299; else - let List.297 : {List I64, I64} = Struct {List.83, List.85}; + let List.297 : {List I64, I64} = Struct {List.84, List.86}; ret List.297; procedure List.6 (#Attr.2): diff --git a/crates/compiler/test_mono/generated/list_sort_asc.txt b/crates/compiler/test_mono/generated/list_sort_asc.txt index 7a1f26539c..0299a6d4e6 100644 --- a/crates/compiler/test_mono/generated/list_sort_asc.txt +++ b/crates/compiler/test_mono/generated/list_sort_asc.txt @@ -7,9 +7,9 @@ procedure List.28 (#Attr.2, #Attr.3): decref #Attr.2; ret List.297; -procedure List.54 (List.201): +procedure List.54 (List.202): let List.296 : {} = Struct {}; - let List.295 : List I64 = CallByName List.28 List.201 List.296; + let List.295 : List I64 = CallByName List.28 List.202 List.296; ret List.295; procedure Num.46 (#Attr.2, #Attr.3): diff --git a/crates/compiler/test_mono/generated/quicksort_swap.txt b/crates/compiler/test_mono/generated/quicksort_swap.txt index 4ccefa28db..af9b710ff7 100644 --- a/crates/compiler/test_mono/generated/quicksort_swap.txt +++ b/crates/compiler/test_mono/generated/quicksort_swap.txt @@ -1,8 +1,8 @@ -procedure List.2 (List.78, List.79): - let List.310 : U64 = CallByName List.6 List.78; - let List.307 : Int1 = CallByName Num.22 List.79 List.310; +procedure List.2 (List.79, List.80): + let List.310 : U64 = CallByName List.6 List.79; + let List.307 : Int1 = CallByName Num.22 List.80 List.310; if List.307 then - let List.309 : I64 = CallByName List.60 List.78 List.79; + let List.309 : I64 = CallByName List.60 List.79 List.80; let List.308 : [C {}, C I64] = TagId(1) List.309; ret List.308; else @@ -10,21 +10,21 @@ procedure List.2 (List.78, List.79): let List.305 : [C {}, C I64] = TagId(0) List.306; ret List.305; -procedure List.3 (List.86, List.87, List.88): - let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88; +procedure List.3 (List.87, List.88, List.89): + let List.298 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89; let List.297 : List I64 = StructAtIndex 0 List.298; inc List.297; dec List.298; ret List.297; -procedure List.57 (List.83, List.84, List.85): - let List.315 : U64 = CallByName List.6 List.83; - let List.312 : Int1 = CallByName Num.22 List.84 List.315; +procedure List.57 (List.84, List.85, List.86): + let List.315 : U64 = CallByName List.6 List.84; + let List.312 : Int1 = CallByName Num.22 List.85 List.315; if List.312 then - let List.313 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85; + let List.313 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86; ret List.313; else - let List.311 : {List I64, I64} = Struct {List.83, List.85}; + let List.311 : {List I64, I64} = Struct {List.84, List.86}; ret List.311; procedure List.6 (#Attr.2): diff --git a/crates/compiler/test_mono/generated/rigids.txt b/crates/compiler/test_mono/generated/rigids.txt index 1fdb2d7613..612e7e33e7 100644 --- a/crates/compiler/test_mono/generated/rigids.txt +++ b/crates/compiler/test_mono/generated/rigids.txt @@ -1,8 +1,8 @@ -procedure List.2 (List.78, List.79): - let List.310 : U64 = CallByName List.6 List.78; - let List.307 : Int1 = CallByName Num.22 List.79 List.310; +procedure List.2 (List.79, List.80): + let List.310 : U64 = CallByName List.6 List.79; + let List.307 : Int1 = CallByName Num.22 List.80 List.310; if List.307 then - let List.309 : I64 = CallByName List.60 List.78 List.79; + let List.309 : I64 = CallByName List.60 List.79 List.80; let List.308 : [C {}, C I64] = TagId(1) List.309; ret List.308; else @@ -10,21 +10,21 @@ procedure List.2 (List.78, List.79): let List.305 : [C {}, C I64] = TagId(0) List.306; ret List.305; -procedure List.3 (List.86, List.87, List.88): - let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88; +procedure List.3 (List.87, List.88, List.89): + let List.298 : {List I64, I64} = CallByName List.57 List.87 List.88 List.89; let List.297 : List I64 = StructAtIndex 0 List.298; inc List.297; dec List.298; ret List.297; -procedure List.57 (List.83, List.84, List.85): - let List.315 : U64 = CallByName List.6 List.83; - let List.312 : Int1 = CallByName Num.22 List.84 List.315; +procedure List.57 (List.84, List.85, List.86): + let List.315 : U64 = CallByName List.6 List.84; + let List.312 : Int1 = CallByName Num.22 List.85 List.315; if List.312 then - let List.313 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85; + let List.313 : {List I64, I64} = CallByName List.61 List.84 List.85 List.86; ret List.313; else - let List.311 : {List I64, I64} = Struct {List.83, List.85}; + let List.311 : {List I64, I64} = Struct {List.84, List.86}; ret List.311; procedure List.6 (#Attr.2): diff --git a/crates/compiler/types/src/pretty_print.rs b/crates/compiler/types/src/pretty_print.rs index 7895fa3bad..81793a7088 100644 --- a/crates/compiler/types/src/pretty_print.rs +++ b/crates/compiler/types/src/pretty_print.rs @@ -12,7 +12,7 @@ pub static WILDCARD: &str = "*"; static EMPTY_RECORD: &str = "{}"; static EMPTY_TAG_UNION: &str = "[]"; -/// Rerquirements for parentheses. +/// Requirements for parentheses. /// /// If we're inside a function (that is, this is either an argument or a return /// value), we may need to use parens. Examples: diff --git a/crates/repl_test/src/tests.rs b/crates/repl_test/src/tests.rs index 58479b5f76..7302cb7c9a 100644 --- a/crates/repl_test/src/tests.rs +++ b/crates/repl_test/src/tests.rs @@ -1179,3 +1179,16 @@ fn render_nullable_unwrapped_passing_through_alias() { "Cons (L (Cons (L (Cons (L Nil))))) : DeepList", ) } + +#[test] +fn opaque_wrap_function() { + expect_success( + indoc!( + r#" + A a := a + List.map [1u8, 2u8, 3u8] @A + "# + ), + "[1, 2, 3] : List (A U8)", + ); +} diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index aaea1b609a..758cbab1c1 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -7483,14 +7483,14 @@ All branches in an `if` must have the same type! // and checking it during can. The reason the error appears is because it is parsed as // Apply(Error(OtherModule), [@Age, 21]) @r###" - ── OPAQUE TYPE NOT APPLIED ─────────────────────────────── /code/proj/Main.roc ─ + ── OPAQUE TYPE NOT DEFINED ─────────────────────────────── /code/proj/Main.roc ─ - This opaque type is not applied to an argument: + The opaque type Age referenced here is not defined: 4│ OtherModule.@Age 21 ^^^^ - Note: Opaque types always wrap exactly one argument! + Note: It looks like there are no opaque types declared in this scope yet! ── SYNTAX PROBLEM ──────────────────────────────────────── /code/proj/Main.roc ─ @@ -9551,4 +9551,30 @@ All branches in an `if` must have the same type! an instance of this opaque type by doing @Age 23. "### ); + + test_report!( + opaque_wrap_function_mismatch, + indoc!( + r#" + A := U8 + List.map [1u16, 2u16, 3u16] @A + "# + ), + @r###" + ── TYPE MISMATCH ───────────────────────────────────────── /code/proj/Main.roc ─ + + The 2nd argument to `map` is not what I expect: + + 5│ List.map [1u16, 2u16, 3u16] @A + ^^ + + This A opaque wrapping has the type: + + U8 -> A + + But `map` needs the 2nd argument to be: + + U16 -> A + "### + ); } diff --git a/examples/benchmarks/AStar.roc b/examples/benchmarks/AStar.roc index 580989d27b..7151746ac1 100644 --- a/examples/benchmarks/AStar.roc +++ b/examples/benchmarks/AStar.roc @@ -16,7 +16,7 @@ initialModel : position -> Model position initialModel = \start -> { evaluated: Set.empty, openSet: Set.single start, - costs: Dict.single start 0.0, + costs: Dict.single start 0, cameFrom: Dict.empty, } diff --git a/examples/interactive/cli-platform/Path.roc b/examples/interactive/cli-platform/Path.roc new file mode 100644 index 0000000000..c0c90e109f --- /dev/null +++ b/examples/interactive/cli-platform/Path.roc @@ -0,0 +1,373 @@ +interface Path + exposes [ + Path, + PathComponent, + WindowsRoot, + toComponents, + walkComponents, + fromStr, + fromBytes, + ] + imports [] + +## You can canonicalize a [Path] using [Path.canonicalize]. +## +## Comparing canonical paths is often more reliable than comparing raw ones. +## For example, `Path.fromStr "foo/bar/../baz" == Path.fromStr "foo/baz"` will return `False`, +## because those are different paths even though their canonical equivalents would be equal. +## +## Also note that canonicalization reads from the file system (in order to resolve symbolic +## links, and to convert relative paths into absolute ones). This means that it is not only +## a [Task] (which can fail), but also that running [canonicalize] on the same [Path] twice +## may give different answers. An example of a way this could happen is if a symbolic link +## in the path changed on disk to point somewhere else in between the two [canonicalize] calls. +## +## Similarly, remember that canonical paths are not guaranteed to refer to a valid file. They +## might have referred to one when they were canonicalized, but that file may have moved or +## been deleted since the canonical path was created. So you might [canonicalize] a [Path], +## and then immediately use that [Path] to read a file from disk, and still get back an error +## because something relevant changed on the filesystem between the two operations. +## +## Also note that different filesystems have different rules for syntactically valid paths. +## Suppose you're on a machine with two disks, one formatted as ext4 and another as FAT32. +## It's possible to list the contents of a directory on the ext4 disk, and get a [CanPath] which +## is valid on that disk, but invalid on the other disk. One way this could happen is if the +## directory on the ext4 disk has a filename containing a `:` in it. `:` is allowed in ext4 +## paths but is considered invalid in FAT32 paths. +Path := [ + # We store these separately for two reasons: + # 1. If I'm calling an OS API, passing a path I got from the OS is definitely safe. + # However, passing a Path I got from a RocStr might be unsafe; it may contain \0 + # characters, which would result in the operation happening on a totally different + # path. As such, we need to check for \0s and fail without calling the OS API if we + # find one in the path. + # 2. If I'm converting the Path to a Str, doing that conversion on a Path that was + # created from a RocStr needs no further processing. However, if it came from the OS, + # then we need to know what charset to assume it had, in order to decode it properly. + # These come from the OS (e.g. when reading a directory, calling `canonicalize`, + # or reading an environment variable - which, incidentally, are nul-terminated), + # so we know they are both nul-terminated and do not contain interior nuls. + # As such, they can be passed directly to OS APIs. + # + # Note that the nul terminator byte is right after the end of the length (into the + # unused capacity), so this can both be compared directly to other `List U8`s that + # aren't nul-terminated, while also being able to be passed directly to OS APIs. + FromOperatingSystem (List U8), + + # These come from userspace (e.g. Path.fromBytes), so they need to be checked for interior + # nuls and then nul-terminated before the host can pass them to OS APIs. + ArbitraryBytes (List U8), + + # This was created as a RocStr, so it might have interior nul bytes but it's definitely UTF-8. + # That means we can `toStr` it trivially, but have to validate before sending it to OS + # APIs that expect a nul-terminated `char*`. + # + # Note that both UNIX and Windows APIs will accept UTF-8, because on Windows the host calls + # `_setmbcp(_MB_CP_UTF8);` to set the process's Code Page to UTF-8 before doing anything else. + # See https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page#-a-vs--w-apis + # and https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setmbcp?view=msvc-170 + # for more details on the UTF-8 Code Page in Windows. + FromStr Str, +] + +## Note that the path may not be valid depending on the filesystem where it is used. +## For example, paths containing `:` are valid on ext4 and NTFS filesystems, but not +## on FAT ones. So if you have multiple disks on the same machine, but they have +## different filesystems, then this path could be valid on one but invalid on another! +## +## It's safest to assume paths are invalid (even syntactically) until given to an operation +## which uses them to open a file. If that operation succeeds, then the path was valid +## (at the time). Otherwise, error handling can happen for that operation rather than validating +## up front for a false sense of security (given symlinks, parts of a path being renamed, etc.). +fromStr : Str -> Path +fromStr = \str -> @Path (FromStr str) + +## Not all filesystems use Unicode paths. This function can be used to create a path which +## is not valid Unicode (like a Roc [Str] is), but which is valid for a particular filesystem. +## +## Note that if the list contains any `0` bytes, sending this path to any file operations +## (e.g. [File.read] or [WriteStream.openPath]) will fail. +fromBytes : List U8 -> Path +fromBytes = \bytes -> @Path (ArbitraryBytes bytes) + +## Note that canonicalization reads from the file system (in order to resolve symbolic +## links, and to convert relative paths into absolute ones). This means that it is not only +## a [Task] (which can fail), but also that running [canonicalize] on the same [Path] twice +## may give different answers. An example of a way this could happen is if a symbolic link +## in the path changed on disk to point somewhere else in between the two [canonicalize] calls. +## +## Returns an effect type of `[Metadata, Cwd]` because it can resolve symbolic links +## and can access the current working directory by turning a relative path into an +## absolute one (which can prepend the absolute path of the current working directory to +## the relative path). +canonicalize : Path -> Task Path (CanonicalizeErr *) [Metadata, Read [Env]]* + +## Unfortunately, operating system paths do not include information about which charset +## they were originally encoded with. It's most common (but not guaranteed) that they will +## have been encoded with the same charset as the operating system's curent locale (which +## typically does not change after it is set during installation of the OS), so +## this should convert a [Path] to a valid string as long as the path was created +## with the given [Charset]. (Use [Env.charset] to get the current system charset.) +## +## For a conversion to [Str] that is lossy but does not return a [Result], see +## [displayUtf8]. +toInner : Path -> [Str Str, Bytes (List U8)] + +## Assumes a path is encoded as [UTF-8](https://en.wikipedia.org/wiki/UTF-8), +## and converts it to a string using [Str.displayUtf8]. +## +## This conversion is lossy because the path may contain invalid UTF-8 bytes. If that happens, +## any invalid bytes will be replaced with the [Unicode replacement character](https://unicode.org/glossary/#replacement_character) +## instead of returning an error. As such, it's rarely a good idea to use the [Str] returned +## by this function for any purpose other than displaying it to a user. +## +## When you don't know for sure what a path's encoding is, UTF-8 is a popular guess because +## it's the default on UNIX and also is the encoding used in Roc strings. This platform also +## automatically runs applications under the [UTF-8 code page](https://docs.microsoft.com/en-us/windows/apps/design/globalizing/use-utf8-code-page) +## on Windows. +## +## Converting paths to strings can be an unreliable operation, because operating systems +## don't record the paths' encodings. This means it's possible for the path to have been +## encoded with a different character set than UTF-8 even if UTF-8 is the system default, +## which means when [displayUtf8] converts them to a string, the string may include gibberish. +## [Here is an example.](https://unix.stackexchange.com/questions/667652/can-a-file-path-be-invalid-utf-8/667863#667863) +## +## If you happen to know the [Charset] that was used to encode the path, you can use +## [toStrUsingCharset] instead of [displayUtf8]. +displayUtf8 : Path -> Str +displayUtf8 = \@Path path -> + when path is + FromStr str -> str + NoInteriorNul bytes | ArbitraryBytes bytes -> + Str.displayUtf8 bytes + +isEq : Path, Path -> Bool +isEq = \@Path p1, @Path p2 -> + when p1 is + NoInteriorNul bytes1 | ArbitraryBytes bytes1 -> + when p2 is + NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> bytes1 == bytes2 + # We can't know the encoding that was originally used in the path, so we convert + # the string to bytes and see if those bytes are equal to the path's bytes. + # + # This may sound unreliable, but it's how all paths are compared; since the OS + # doesn't record which encoding was used to encode the path name, the only + # reasonable# definition for path equality is byte-for-byte equality. + FromStr str2 -> Str.isEqUtf8 str2 bytes1 + + FromStr str1 -> + when p2 is + NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Str.isEqUtf8 str1 bytes2 + FromStr str2 -> str1 == str2 + +compare : Path, Path -> [Lt, Eq, Gt] +compare = \@Path p1, @Path p2 -> + when p1 is + NoInteriorNul bytes1 | ArbitraryBytes bytes1 -> + when p2 is + NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Ord.compare bytes1 bytes2 + FromStr str2 -> Str.compareUtf8 str2 bytes1 + + FromStr str1 -> + when p2 is + NoInteriorNul bytes2 | ArbitraryBytes bytes2 -> Ord.compare str1 bytes2 + FromStr str2 -> str1 == str2 + +## ## Path Components +PathComponent : [ + ParentDir, # e.g. ".." on UNIX or Windows + CurrentDir, # e.g. "." on UNIX + Named Str, # e.g. "stuff" on UNIX + DirSep Str, # e.g. "/" on UNIX, "\" or "/" on Windows. Or, sometimes, "¥" on Windows - see + # https://docs.microsoft.com/en-us/windows/win32/intl/character-sets-used-in-file-names + # + # This is included as an option so if you're transforming part of a path, + # you can write back whatever separator was originally used. +] + +## Note that a root of Slash (`/`) has different meanings on UNIX and on Windows. +## * On UNIX, `/` at the beginning of the path refers to the filesystem root, and means the path is absolute. +## * On Windows, `/` at the beginning of the path refers to the current disk drive, and means the path is relative. +PathRoot : [ + WindowsSpecificRoot WindowsRoot, # e.g. "C:" on Windows + Slash, + None, +] + +# TODO see https://doc.rust-lang.org/std/path/enum.Prefix.html +WindowsRoot : [] + +## Returns the root of the path. +root : Path -> PathRoot + +components : Path -> { root : PathRoot, components : List PathComponent } + +## Walk over the path's [components]. +walk : + Path, + # None means it's a relative path + (PathRoot -> state), + (state, PathComponent -> state) + -> state + +## Returns the path without its last [`component`](#components). +## +## If the path was empty or contained only a [root](#PathRoot), returns the original path. +dropLast : Path -> Path + +# TODO see https://doc.rust-lang.org/std/path/struct.Path.html#method.join for +# the definition of the term "adjoin" - should we use that term? +append : Path, Path -> Path +append = \@Path prefix, @Path suffix -> + content = + when prefix is + NoInteriorNul prefixBytes -> + when suffix is + NoInteriorNul suffixBytes -> + # Neither prefix nor suffix had interior nuls, so the answer won't either + List.append prefixBytes suffixBytes + |> NoInteriorNul + + ArbitraryBytes suffixBytes -> + List.append prefixBytes suffixBytes + |> ArbitraryBytes + + FromStr suffixStr -> + # Append suffixStr by writing it to the end of prefixBytes + Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes) + |> ArbitraryBytes + + ArbitraryBytes prefixBytes -> + when suffix is + ArbitraryBytes suffixBytes | NoInteriorNul suffixBytes -> + List.append prefixBytes suffixBytes + |> ArbitraryBytes + + FromStr suffixStr -> + # Append suffixStr by writing it to the end of prefixBytes + Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes) + |> ArbitraryBytes + + FromStr prefixStr -> + when suffix is + ArbitraryBytes suffixBytes | NoInteriorNul suffixBytes -> + List.append (Str.toUtf8 prefixStr) suffixBytes + |> ArbitraryBytes + + FromStr suffixStr -> + Str.append prefixStr suffixStr + |> FromStr + + @Path content + +appendStr : Path, Str -> Path +appendStr = \@Path prefix, suffixStr -> + content = + when prefix is + NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes -> + # Append suffixStr by writing it to the end of prefixBytes + Str.writeUtf8 suffixStr prefixBytes (List.len prefixBytes) + |> ArbitraryBytes + + FromStr prefixStr -> + Str.append prefixStr suffixStr + |> FromStr + + @Path content + +## Returns `True` if the first path begins with the second. +startsWith : Path, Path -> Bool +startsWith = \@Path path, @Path prefix -> + when path is + NoInteriorNul pathBytes | ArbitraryBytes pathBytes -> + when prefix is + NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes -> + List.startsWith pathBytes prefixBytes + + FromStr prefixStr -> + strLen = Str.byteCount str + + if strLen == List.len pathBytes then + # Grab the first N bytes of the list, where N = byte length of string. + bytesPrefix = List.takeAt pathBytes 0 strLen + + # Compare the two for equality. + Str.isEqUtf8 prefixStr bytesPrefix + else + False + + FromStr pathStr -> + when prefix is + NoInteriorNul prefixBytes | ArbitraryBytes prefixBytes -> + Str.startsWithUtf8 pathStr prefixBytes + + FromStr prefixStr -> + Str.startsWith pathStr prefixStr + +## Returns `True` if the first path ends with the second. +endsWith : Path, Path -> Bool +endsWith = \@Path path, @Path prefix -> + when path is + NoInteriorNul pathBytes | ArbitraryBytes pathBytes -> + when suffix is + NoInteriorNul suffixBytes | ArbitraryBytes suffixBytes -> + List.endsWith pathBytes suffixBytes + + FromStr suffixStr -> + strLen = Str.byteCount suffixStr + + if strLen == List.len pathBytes then + # Grab the last N bytes of the list, where N = byte length of string. + bytesSuffix = List.takeAt pathBytes (strLen - 1) strLen + + # Compare the two for equality. + Str.startsWithUtf8 suffixStr bytesSuffix + else + False + + FromStr pathStr -> + when suffix is + NoInteriorNul suffixBytes | ArbitraryBytes suffixBytes -> + Str.endsWithUtf8 pathStr suffixBytes + + FromStr suffixStr -> + Str.endsWith pathStr suffixStr + +# TODO https://doc.rust-lang.org/std/path/struct.Path.html#method.strip_prefix +# TODO idea: what if it's File.openRead and File.openWrite? And then e.g. File.metadata, +# File.isDir, etc. +## If the last component of this path has no `.`, appends `.` followed by the given string. +## Otherwise, replaces everything after the last `.` with the given string. +## +## Examples: +## +## Path.fromStr "foo/bar/baz" |> Path.withExtension "txt" # foo/bar/baz.txt +## Path.fromStr "foo/bar/baz." |> Path.withExtension "txt" # foo/bar/baz.txt +## Path.fromStr "foo/bar/baz.xz" |> Path.withExtension "txt" # foo/bar/baz.txt +withExtension : Path, Str -> Path +withExtension = \@Path path, extension -> + when path is + NoInteriorNul bytes | ArbitraryBytes bytes -> + beforeDot = + when List.splitLast '.' is + Ok { before } -> before + Err NotFound -> list + + beforeDot + |> List.reserve (1 + List.len bytes) + |> List.append '.' + |> List.concat bytes + + FromStr str -> + beforeDot = + when Str.splitLast str "." is + Ok { before } -> before + Err NotFound -> str + + beforeDot + |> Str.reserve (1 + Str.byteCount str) + |> Str.append "." + |> Str.concat str + +# NOTE: no withExtensionBytes because it's too narrow. If you really need to get some +# non-Unicode in there, do it with diff --git a/www/build.sh b/www/build.sh index 8b4788484e..822fc7e76b 100755 --- a/www/build.sh +++ b/www/build.sh @@ -19,7 +19,9 @@ wget https://github.com/rtfeldman/elm-css/files/8849069/roc-source-code.zip # TODO: When roc repo is public, download it from nightly builds. wget https://github.com/brian-carroll/mock-repl/archive/refs/heads/deploy.zip unzip deploy.zip -mv mock-repl-deploy/* . +# Explicitly list the filenames, failing the build if they're not found +mv mock-repl-deploy/roc_repl_wasm.js repl/ +mv mock-repl-deploy/roc_repl_wasm_bg.wasm repl/ rmdir mock-repl-deploy rm deploy.zip diff --git a/www/public/repl/index.html b/www/public/repl/index.html index e2b17755b9..219c42bfdd 100644 --- a/www/public/repl/index.html +++ b/www/public/repl/index.html @@ -3,7 +3,7 @@ Roc REPL - + @@ -28,6 +28,6 @@ > - + diff --git a/www/public/repl/repl.js b/www/public/repl/repl.js index b6a48ba3df..d434c0bef4 100644 --- a/www/public/repl/repl.js +++ b/www/public/repl/repl.js @@ -41,7 +41,7 @@ const repl = { // Initialise repl.elemSourceInput.addEventListener("change", onInputChange); repl.elemSourceInput.addEventListener("keyup", onInputKeyup); -roc_repl_wasm.default('./roc_repl_wasm_bg.wasm').then((instance) => { +roc_repl_wasm.default('/repl/roc_repl_wasm_bg.wasm').then((instance) => { repl.compiler = instance; });