diff --git a/cli/tests/cli_run.rs b/cli/tests/cli_run.rs index 19c50a08f8..f89a64d8c3 100644 --- a/cli/tests/cli_run.rs +++ b/cli/tests/cli_run.rs @@ -332,4 +332,17 @@ mod cli_run { true, ); } + + #[test] + #[serial(effect)] + fn run_effect() { + check_output_with_stdin( + &example_file("effect", "Main.roc"), + "hello world how are you", + "effect-example", + &[], + "hello world how are you\n", + true, + ); + } } diff --git a/compiler/mono/src/alias_analysis.rs b/compiler/mono/src/alias_analysis.rs index ee78ea5860..e8b04ca1e9 100644 --- a/compiler/mono/src/alias_analysis.rs +++ b/compiler/mono/src/alias_analysis.rs @@ -1,10 +1,10 @@ use morphic_lib::TypeContext; use morphic_lib::{ BlockExpr, BlockId, CalleeSpecVar, ConstDefBuilder, ConstName, EntryPointName, ExprContext, - FuncDef, FuncDefBuilder, FuncName, ModDefBuilder, ModName, ProgramBuilder, Result, TypeId, - UpdateModeVar, ValueId, + FuncDef, FuncDefBuilder, FuncName, ModDefBuilder, ModName, ProgramBuilder, Result, + TypeDefBuilder, TypeId, TypeName, UpdateModeVar, ValueId, }; -use roc_collections::all::MutMap; +use roc_collections::all::{MutMap, MutSet}; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use std::convert::TryFrom; @@ -26,6 +26,26 @@ pub fn func_name_bytes(proc: &Proc) -> [u8; SIZE] { const DEBUG: bool = false; const SIZE: usize = if DEBUG { 50 } else { 16 }; +#[derive(Debug, Clone, Copy, Hash)] +struct TagUnionId(u64); + +fn recursive_tag_union_name_bytes(union_layout: &UnionLayout) -> TagUnionId { + use std::collections::hash_map::DefaultHasher; + use std::hash::Hash; + use std::hash::Hasher; + + let mut hasher = DefaultHasher::new(); + union_layout.hash(&mut hasher); + + TagUnionId(hasher.finish()) +} + +impl TagUnionId { + const fn as_bytes(&self) -> [u8; 8] { + self.0.to_ne_bytes() + } +} + pub fn func_name_bytes_help<'a, I>( symbol: Symbol, argument_layouts: I, @@ -134,6 +154,8 @@ where let entry_point_name = FuncName(ENTRY_POINT_NAME); m.add_func(entry_point_name, entry_point_function)?; + let mut type_definitions = MutSet::default(); + // all other functions for proc in procs { let bytes = func_name_bytes(proc); @@ -148,11 +170,32 @@ where ); } - let spec = proc_spec(proc)?; + let (spec, type_names) = proc_spec(proc)?; + + type_definitions.extend(type_names); m.add_func(func_name, spec)?; } + for union_layout in type_definitions { + let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + let mut builder = TypeDefBuilder::new(); + + let variant_types = build_variant_types(&mut builder, &union_layout)?; + let root_type = if let UnionLayout::NonNullableUnwrapped(_) = union_layout { + debug_assert_eq!(variant_types.len(), 1); + variant_types[0] + } else { + builder.add_union_type(&variant_types)? + }; + + let type_def = builder.build(root_type)?; + + m.add_named_type(type_name, type_def)?; + } + m.build()? }; @@ -195,7 +238,7 @@ fn build_entry_point(layout: crate::ir::ProcLayout, func_name: FuncName) -> Resu Ok(spec) } -fn proc_spec(proc: &Proc) -> Result { +fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet>)> { let mut builder = FuncDefBuilder::new(); let mut env = Env::default(); @@ -218,21 +261,22 @@ fn proc_spec(proc: &Proc) -> Result { let spec = builder.build(arg_type_id, ret_type_id, root)?; - Ok(spec) + Ok((spec, env.type_names)) } #[derive(Default)] -struct Env { +struct Env<'a> { symbols: MutMap, join_points: MutMap, + type_names: MutSet>, } -fn stmt_spec( +fn stmt_spec<'a>( builder: &mut FuncDefBuilder, - env: &mut Env, + env: &mut Env<'a>, block: BlockId, layout: &Layout, - stmt: &Stmt, + stmt: &Stmt<'a>, ) -> Result { use Stmt::*; @@ -420,7 +464,27 @@ fn build_tuple_value( builder.add_make_tuple(block, &value_ids) } -fn build_tuple_type(builder: &mut FuncDefBuilder, layouts: &[Layout]) -> Result { +#[derive(Clone, Debug, PartialEq)] +enum WhenRecursive<'a> { + Unreachable, + Loop(UnionLayout<'a>), +} + +fn build_recursive_tuple_type( + builder: &mut impl TypeContext, + layouts: &[Layout], + when_recursive: &WhenRecursive, +) -> Result { + let mut field_types = Vec::new(); + + for field in layouts.iter() { + field_types.push(layout_spec_help(builder, field, when_recursive)?); + } + + builder.add_tuple_type(&field_types) +} + +fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Result { let mut field_types = Vec::new(); for field in layouts.iter() { @@ -854,46 +918,95 @@ fn lowlevel_spec( } } +fn recursive_tag_variant( + builder: &mut impl TypeContext, + union_layout: &UnionLayout, + fields: &[Layout], +) -> Result { + let when_recursive = WhenRecursive::Loop(*union_layout); + + let data_id = build_recursive_tuple_type(builder, fields, &when_recursive)?; + let cell_id = builder.add_heap_cell_type(); + + builder.add_tuple_type(&[cell_id, data_id]) +} + fn build_variant_types( - builder: &mut FuncDefBuilder, + builder: &mut impl TypeContext, union_layout: &UnionLayout, ) -> Result> { use UnionLayout::*; - let mut result = Vec::new(); + let mut result; match union_layout { NonRecursive(tags) => { + result = Vec::with_capacity(tags.len()); + for tag in tags.iter() { result.push(build_tuple_type(builder, tag)?); } } - Recursive(_) => unreachable!(), - NonNullableUnwrapped(_) => unreachable!(), + Recursive(tags) => { + result = Vec::with_capacity(tags.len()); + + for tag in tags.iter() { + result.push(recursive_tag_variant(builder, union_layout, tag)?); + } + } + NonNullableUnwrapped(fields) => { + result = vec![recursive_tag_variant(builder, union_layout, fields)?]; + } NullableWrapped { - nullable_id: _, - other_tags: _, - } => unreachable!(), + nullable_id, + other_tags: tags, + } => { + result = Vec::with_capacity(tags.len() + 1); + + let cutoff = *nullable_id as usize; + + for tag in tags[..cutoff].iter() { + result.push(recursive_tag_variant(builder, union_layout, tag)?); + } + + let unit = builder.add_tuple_type(&[])?; + result.push(unit); + + for tag in tags[cutoff..].iter() { + result.push(recursive_tag_variant(builder, union_layout, tag)?); + } + } NullableUnwrapped { - nullable_id: _, - other_fields: _, - } => unreachable!(), + nullable_id, + other_fields: fields, + } => { + let unit = builder.add_tuple_type(&[])?; + let other_type = recursive_tag_variant(builder, union_layout, fields)?; + + if *nullable_id { + // nullable_id == 1 + result = vec![other_type, unit]; + } else { + result = vec![unit, other_type]; + } + } } Ok(result) } +#[allow(dead_code)] fn worst_case_type(context: &mut impl TypeContext) -> Result { let cell = context.add_heap_cell_type(); context.add_bag_type(cell) } -fn expr_spec( +fn expr_spec<'a>( builder: &mut FuncDefBuilder, - env: &mut Env, + env: &mut Env<'a>, block: BlockId, - layout: &Layout, - expr: &Expr, + layout: &Layout<'a>, + expr: &Expr<'a>, ) -> Result { use Expr::*; @@ -912,21 +1025,54 @@ fn expr_spec( tag_name: _, tag_id, arguments, - } => match tag_layout { - UnionLayout::NonRecursive(_) => { - let value_id = build_tuple_value(builder, env, block, arguments)?; - let variant_types = build_variant_types(builder, tag_layout)?; - builder.add_make_union(block, &variant_types, *tag_id as u32, value_id) - } - UnionLayout::Recursive(_) - | UnionLayout::NonNullableUnwrapped(_) - | UnionLayout::NullableWrapped { .. } - | UnionLayout::NullableUnwrapped { .. } => { - let result_type = worst_case_type(builder)?; - let value_id = build_tuple_value(builder, env, block, arguments)?; - builder.add_unknown_with(block, &[value_id], result_type) - } - }, + } => { + let variant_types = build_variant_types(builder, tag_layout)?; + + let data_id = build_tuple_value(builder, env, block, arguments)?; + let cell_id = builder.add_new_heap_cell(block)?; + + let value_id = match tag_layout { + UnionLayout::NonRecursive(_) => { + let value_id = build_tuple_value(builder, env, block, arguments)?; + return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id); + } + UnionLayout::NonNullableUnwrapped(_) => { + let value_id = builder.add_make_tuple(block, &[cell_id, data_id])?; + + let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + env.type_names.insert(*tag_layout); + + return builder.add_make_named(block, MOD_APP, type_name, value_id); + } + UnionLayout::Recursive(_) => builder.add_make_tuple(block, &[cell_id, data_id])?, + UnionLayout::NullableWrapped { nullable_id, .. } => { + if *tag_id == *nullable_id as u8 { + data_id + } else { + builder.add_make_tuple(block, &[cell_id, data_id])? + } + } + UnionLayout::NullableUnwrapped { nullable_id, .. } => { + if *tag_id == *nullable_id as u8 { + data_id + } else { + builder.add_make_tuple(block, &[cell_id, data_id])? + } + } + }; + + let union_id = + builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)?; + + let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + env.type_names.insert(*tag_layout); + + builder.add_make_named(block, MOD_APP, type_name, union_id) + } Struct(fields) => build_tuple_value(builder, env, block, fields), UnionAtIndex { index, @@ -942,11 +1088,45 @@ fn expr_spec( builder.add_get_tuple_field(block, tuple_value_id, index) } - _ => { - // for the moment recursive tag unions don't quite work - let value_id = env.symbols[structure]; - let result_type = layout_spec(builder, layout)?; - builder.add_unknown_with(block, &[value_id], result_type) + UnionLayout::Recursive(_) + | UnionLayout::NullableUnwrapped { .. } + | UnionLayout::NullableWrapped { .. } => { + let index = (*index) as u32; + let tag_value_id = env.symbols[structure]; + + let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?; + let variant_id = builder.add_unwrap_union(block, union_id, *tag_id as u32)?; + + // we're reading from this value, so touch the heap cell + let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?; + builder.add_touch(block, heap_cell)?; + + let tuple_value_id = builder.add_get_tuple_field(block, variant_id, 1)?; + + builder.add_get_tuple_field(block, tuple_value_id, index) + } + UnionLayout::NonNullableUnwrapped { .. } => { + let index = (*index) as u32; + debug_assert!(*tag_id == 0); + + let tag_value_id = env.symbols[structure]; + + let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + let variant_id = + builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?; + + // we're reading from this value, so touch the heap cell + let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?; + builder.add_touch(block, heap_cell)?; + + let tuple_value_id = builder.add_get_tuple_field(block, variant_id, 1)?; + + builder.add_get_tuple_field(block, tuple_value_id, index) } }, StructAtIndex { @@ -1017,34 +1197,66 @@ fn literal_spec( } } -fn layout_spec(builder: &mut FuncDefBuilder, layout: &Layout) -> Result { +fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result { + layout_spec_help(builder, layout, &WhenRecursive::Unreachable) +} + +fn layout_spec_help( + builder: &mut impl TypeContext, + layout: &Layout, + when_recursive: &WhenRecursive, +) -> Result { use Layout::*; match layout { - Builtin(builtin) => builtin_spec(builder, builtin), - Struct(fields) => build_tuple_type(builder, fields), - Union(union_layout) => match union_layout { - UnionLayout::NonRecursive(_) => { - let variant_types = build_variant_types(builder, union_layout)?; - builder.add_union_type(&variant_types) + Builtin(builtin) => builtin_spec(builder, builtin, when_recursive), + Struct(fields) => build_recursive_tuple_type(builder, fields, when_recursive), + Union(union_layout) => { + let variant_types = build_variant_types(builder, union_layout)?; + + match union_layout { + UnionLayout::NonRecursive(_) => builder.add_union_type(&variant_types), + UnionLayout::Recursive(_) + | UnionLayout::NullableUnwrapped { .. } + | UnionLayout::NullableWrapped { .. } + | UnionLayout::NonNullableUnwrapped(_) => { + let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + Ok(builder.add_named_type(MOD_APP, type_name)) + } } - UnionLayout::Recursive(_) => worst_case_type(builder), - UnionLayout::NonNullableUnwrapped(_) => worst_case_type(builder), - UnionLayout::NullableWrapped { - nullable_id: _, - other_tags: _, - } => worst_case_type(builder), - UnionLayout::NullableUnwrapped { - nullable_id: _, - other_fields: _, - } => worst_case_type(builder), + } + RecursivePointer => match when_recursive { + WhenRecursive::Unreachable => { + unreachable!() + } + WhenRecursive::Loop(union_layout) => match union_layout { + UnionLayout::NonRecursive(_) => unreachable!(), + UnionLayout::Recursive(_) + | UnionLayout::NullableUnwrapped { .. } + | UnionLayout::NullableWrapped { .. } + | UnionLayout::NonNullableUnwrapped(_) => { + let type_name_bytes = recursive_tag_union_name_bytes(union_layout).as_bytes(); + let type_name = TypeName(&type_name_bytes); + + Ok(builder.add_named_type(MOD_APP, type_name)) + } + }, }, - RecursivePointer => worst_case_type(builder), - Closure(_, lambda_set, _) => layout_spec(builder, &lambda_set.runtime_representation()), + Closure(_, lambda_set, _) => layout_spec_help( + builder, + &lambda_set.runtime_representation(), + when_recursive, + ), } } -fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result { +fn builtin_spec( + builder: &mut impl TypeContext, + builtin: &Builtin, + when_recursive: &WhenRecursive, +) -> Result { use Builtin::*; match builtin { @@ -1052,8 +1264,8 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result builder.add_tuple_type(&[]), Str | EmptyStr => str_type(builder), Dict(key_layout, value_layout) => { - let value_type = layout_spec(builder, value_layout)?; - let key_type = layout_spec(builder, key_layout)?; + let value_type = layout_spec_help(builder, value_layout, when_recursive)?; + let key_type = layout_spec_help(builder, key_layout, when_recursive)?; let element_type = builder.add_tuple_type(&[key_type, value_type])?; let cell = builder.add_heap_cell_type(); @@ -1062,7 +1274,7 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result { let value_type = builder.add_tuple_type(&[])?; - let key_type = layout_spec(builder, key_layout)?; + let key_type = layout_spec_help(builder, key_layout, when_recursive)?; let element_type = builder.add_tuple_type(&[key_type, value_type])?; let cell = builder.add_heap_cell_type(); @@ -1070,7 +1282,7 @@ fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result { - let element_type = layout_spec(builder, element_layout)?; + let element_type = layout_spec_help(builder, element_layout, when_recursive)?; let cell = builder.add_heap_cell_type(); let bag = builder.add_bag_type(element_type)?; diff --git a/compiler/mono/src/inc_dec.rs b/compiler/mono/src/inc_dec.rs index 9954707853..4cbc8b5bec 100644 --- a/compiler/mono/src/inc_dec.rs +++ b/compiler/mono/src/inc_dec.rs @@ -968,15 +968,6 @@ impl<'a> Context<'a> { // live vars of the whole expression let invoke_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default()); - // the result of an invoke should not be touched in the fail branch - // but it should be present in the pass branch (otherwise it would be dead) - // NOTE: we cheat a bit here to allow `invoke` when generating code for `expect` - let is_dead = !invoke_live_vars.contains(symbol); - - if is_dead && layout.is_refcounted() { - panic!("A variable of a reference-counted layout is dead; that's a bug!"); - } - let fail = { // TODO should we use ctor info like Lean? let ctx = self.clone(); diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index d02186ad8f..48cba39860 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4191,13 +4191,23 @@ fn convert_tag_union<'a>( let field_symbols_temp = sorted_field_symbols(env, procs, layout_cache, args); let field_symbols; - let opt_tag_id_symbol; + + // we must derive the union layout from the whole_var, building it up + // from `layouts` would unroll recursive tag unions, and that leads to + // problems down the line because we hash layouts and an unrolled + // version is not the same as the minimal version. + let union_layout = match return_on_layout_error!( + env, + layout_cache.from_var(env.arena, variant_var, env.subs) + ) { + Layout::Union(ul) => ul, + _ => unreachable!(), + }; use WrappedVariant::*; let (tag, union_layout) = match variant { Recursive { sorted_tag_layouts } => { debug_assert!(sorted_tag_layouts.len() > 1); - opt_tag_id_symbol = None; field_symbols = { let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena); @@ -4214,9 +4224,6 @@ fn convert_tag_union<'a>( layouts.push(arg_layouts); } - debug_assert!(layouts.len() > 1); - let union_layout = UnionLayout::Recursive(layouts.into_bump_slice()); - let tag = Expr::Tag { tag_layout: union_layout, tag_name, @@ -4227,13 +4234,11 @@ fn convert_tag_union<'a>( (tag, union_layout) } NonNullableUnwrapped { - fields, tag_name: wrapped_tag_name, + .. } => { debug_assert_eq!(tag_name, wrapped_tag_name); - opt_tag_id_symbol = None; - field_symbols = { let mut temp = Vec::with_capacity_in(field_symbols_temp.len(), arena); @@ -4242,8 +4247,6 @@ fn convert_tag_union<'a>( temp.into_bump_slice() }; - let union_layout = UnionLayout::NonNullableUnwrapped(fields); - let tag = Expr::Tag { tag_layout: union_layout, tag_name, @@ -4254,8 +4257,6 @@ fn convert_tag_union<'a>( (tag, union_layout) } NonRecursive { sorted_tag_layouts } => { - opt_tag_id_symbol = None; - field_symbols = { let mut temp = Vec::with_capacity_in(field_symbols_temp.len(), arena); @@ -4271,8 +4272,6 @@ fn convert_tag_union<'a>( layouts.push(arg_layouts); } - let union_layout = UnionLayout::NonRecursive(layouts.into_bump_slice()); - let tag = Expr::Tag { tag_layout: union_layout, tag_name, @@ -4283,12 +4282,8 @@ fn convert_tag_union<'a>( (tag, union_layout) } NullableWrapped { - nullable_id, - nullable_name: _, - sorted_tag_layouts, + sorted_tag_layouts, .. } => { - opt_tag_id_symbol = None; - field_symbols = { let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena); @@ -4304,11 +4299,6 @@ fn convert_tag_union<'a>( layouts.push(arg_layouts); } - let union_layout = UnionLayout::NullableWrapped { - nullable_id, - other_tags: layouts.into_bump_slice(), - }; - let tag = Expr::Tag { tag_layout: union_layout, tag_name, @@ -4318,16 +4308,7 @@ fn convert_tag_union<'a>( (tag, union_layout) } - NullableUnwrapped { - nullable_id, - nullable_name: _, - other_name: _, - other_fields, - } => { - // FIXME drop tag - let tag_id_symbol = env.unique_symbol(); - opt_tag_id_symbol = Some(tag_id_symbol); - + NullableUnwrapped { .. } => { field_symbols = { let mut temp = Vec::with_capacity_in(field_symbols_temp.len() + 1, arena); @@ -4336,11 +4317,6 @@ fn convert_tag_union<'a>( temp.into_bump_slice() }; - let union_layout = UnionLayout::NullableUnwrapped { - nullable_id, - other_fields, - }; - let tag = Expr::Tag { tag_layout: union_layout, tag_name, @@ -4352,26 +4328,14 @@ fn convert_tag_union<'a>( } }; - let mut stmt = Stmt::Let(assigned, tag, Layout::Union(union_layout), hole); + let stmt = Stmt::Let(assigned, tag, Layout::Union(union_layout), hole); let iter = field_symbols_temp .into_iter() .map(|x| x.2 .0) .rev() .zip(field_symbols.iter().rev()); - stmt = assign_to_symbols(env, procs, layout_cache, iter, stmt); - - if let Some(tag_id_symbol) = opt_tag_id_symbol { - // define the tag id - stmt = Stmt::Let( - tag_id_symbol, - Expr::Literal(Literal::Int(tag_id as i128)), - union_layout.tag_id_layout(), - arena.alloc(stmt), - ); - } - - stmt + assign_to_symbols(env, procs, layout_cache, iter, stmt) } } } @@ -7047,6 +7011,15 @@ fn from_can_pattern_help<'a>( temp }; + // we must derive the union layout from the whole_var, building it up + // from `layouts` would unroll recursive tag unions, and that leads to + // problems down the line because we hash layouts and an unrolled + // version is not the same as the minimal version. + let layout = match layout_cache.from_var(env.arena, *whole_var, env.subs) { + Ok(Layout::Union(ul)) => ul, + _ => unreachable!(), + }; + use WrappedVariant::*; match variant { NonRecursive { @@ -7091,18 +7064,6 @@ fn from_can_pattern_help<'a>( )); } - let layouts: Vec<&'a [Layout<'a>]> = { - let mut temp = Vec::with_capacity_in(tags.len(), env.arena); - - for (_, arg_layouts) in tags.into_iter() { - temp.push(*arg_layouts); - } - - temp - }; - - let layout = UnionLayout::NonRecursive(layouts.into_bump_slice()); - Pattern::AppliedTag { tag_name: tag_name.clone(), tag_id: tag_id as u8, @@ -7148,19 +7109,6 @@ fn from_can_pattern_help<'a>( )); } - let layouts: Vec<&'a [Layout<'a>]> = { - let mut temp = Vec::with_capacity_in(tags.len(), env.arena); - - for (_, arg_layouts) in tags.into_iter() { - temp.push(*arg_layouts); - } - - temp - }; - - debug_assert!(layouts.len() > 1); - let layout = UnionLayout::Recursive(layouts.into_bump_slice()); - Pattern::AppliedTag { tag_name: tag_name.clone(), tag_id: tag_id as u8, @@ -7204,8 +7152,6 @@ fn from_can_pattern_help<'a>( )); } - let layout = UnionLayout::NonNullableUnwrapped(fields); - Pattern::AppliedTag { tag_name: tag_name.clone(), tag_id: tag_id as u8, @@ -7279,21 +7225,6 @@ fn from_can_pattern_help<'a>( )); } - let layouts: Vec<&'a [Layout<'a>]> = { - let mut temp = Vec::with_capacity_in(tags.len(), env.arena); - - for (_, arg_layouts) in tags.into_iter() { - temp.push(*arg_layouts); - } - - temp - }; - - let layout = UnionLayout::NullableWrapped { - nullable_id, - other_tags: layouts.into_bump_slice(), - }; - Pattern::AppliedTag { tag_name: tag_name.clone(), tag_id: tag_id as u8, @@ -7350,11 +7281,6 @@ fn from_can_pattern_help<'a>( )); } - let layout = UnionLayout::NullableUnwrapped { - nullable_id, - other_fields, - }; - Pattern::AppliedTag { tag_name: tag_name.clone(), tag_id: tag_id as u8, diff --git a/compiler/reporting/src/error/parse.rs b/compiler/reporting/src/error/parse.rs index 646f675580..bf9da32395 100644 --- a/compiler/reporting/src/error/parse.rs +++ b/compiler/reporting/src/error/parse.rs @@ -3239,7 +3239,6 @@ fn to_requires_report<'a>( ERequires::Space(error, row, col) => to_space_report(alloc, filename, &error, row, col), ERequires::ListStart(row, col) => { - dbg!(row, col); let surroundings = Region::from_rows_cols(start_row, start_col, row, col); let region = Region::from_row_col(row, col); @@ -3263,6 +3262,34 @@ fn to_requires_report<'a>( } } + ERequires::Rigid(row, col) => { + let surroundings = Region::from_rows_cols(start_row, start_col, row, col); + let region = Region::from_row_col(row, col); + + let doc = alloc.stack(vec![ + alloc.reflow(r"I am partway through parsing a header, but I got stuck here:"), + alloc.region_with_subregion(surroundings, region), + alloc.concat(vec![ + alloc.reflow("I am expecting a list of rigids like "), + alloc.keyword("{}"), + alloc.reflow(" or "), + alloc.keyword("{model=>Model}"), + alloc.reflow(" next. A full "), + alloc.keyword("requires"), + alloc.reflow(" definition looks like"), + ]), + alloc + .parser_suggestion("requires {model=>Model, msg=>Msg} {main : Effect {}}") + .indent(4), + ]); + + Report { + filename, + doc, + title: "BAD REQUIRES RIGIDS".to_string(), + } + } + _ => todo!("unhandled parse error {:?}", parse_problem), } } @@ -3368,6 +3395,7 @@ fn to_space_report<'a>( title: "TAB CHARACTER".to_string(), } } + _ => todo!("unhandled type parse error: {:?}", &parse_problem), } } diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 56b309a30a..d3c7ce42d7 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -5929,6 +5929,44 @@ mod test_reporting { ) } + #[test] + fn platform_requires_rigids() { + report_header_problem_as( + indoc!( + r#" + platform folkertdev/foo + requires { main : Effect {} } + exposes [] + packages {} + imports [Task] + provides [ mainForHost ] + effects fx.Effect + { + putChar : I64 -> Effect {}, + putLine : Str -> Effect {}, + getLine : Effect Str + } + "# + ), + indoc!( + r#" + ── BAD REQUIRES RIGIDS ───────────────────────────────────────────────────────── + + I am partway through parsing a header, but I got stuck here: + + 1│ platform folkertdev/foo + 2│ requires { main : Effect {} } + ^ + + I am expecting a list of rigids like `{}` or `{model=>Model}` next. A full + `requires` definition looks like + + requires {model=>Model, msg=>Msg} {main : Effect {}} + "# + ), + ) + } + #[test] fn exposes_identifier() { report_header_problem_as( diff --git a/compiler/test_mono/generated/has_none.txt b/compiler/test_mono/generated/has_none.txt index cfbfaadfae..5341f99624 100644 --- a/compiler/test_mono/generated/has_none.txt +++ b/compiler/test_mono/generated/has_none.txt @@ -21,10 +21,8 @@ procedure Test.3 (Test.4): jump Test.13 Test.4; procedure Test.0 (): - let Test.28 = 0i64; - let Test.30 = 3i64; - let Test.26 = Just Test.30; - let Test.29 = 1i64; + let Test.28 = 3i64; + let Test.26 = Just Test.28; let Test.27 = Nil ; let Test.12 = Cons Test.26 Test.27; let Test.11 = CallByName Test.3 Test.12; diff --git a/compiler/test_mono/generated/is_nil.txt b/compiler/test_mono/generated/is_nil.txt index ec6a558c3a..55329f43fc 100644 --- a/compiler/test_mono/generated/is_nil.txt +++ b/compiler/test_mono/generated/is_nil.txt @@ -10,9 +10,7 @@ procedure Test.2 (Test.3): ret Test.11; procedure Test.0 (): - let Test.17 = 0i64; let Test.15 = 2i64; - let Test.18 = 1i64; let Test.16 = Nil ; let Test.9 = Cons Test.15 Test.16; let Test.8 = CallByName Test.2 Test.9; diff --git a/compiler/test_mono/generated/linked_list_length_twice.txt b/compiler/test_mono/generated/linked_list_length_twice.txt index 92aa6d9c20..38eea70396 100644 --- a/compiler/test_mono/generated/linked_list_length_twice.txt +++ b/compiler/test_mono/generated/linked_list_length_twice.txt @@ -17,7 +17,6 @@ procedure Test.3 (Test.5): ret Test.14; procedure Test.0 (): - let Test.21 = 1i64; let Test.2 = Nil ; let Test.8 = CallByName Test.3 Test.2; let Test.9 = CallByName Test.3 Test.2; diff --git a/compiler/test_mono/generated/peano.txt b/compiler/test_mono/generated/peano.txt index ba743a0f1d..49814c63b4 100644 --- a/compiler/test_mono/generated/peano.txt +++ b/compiler/test_mono/generated/peano.txt @@ -1,10 +1,6 @@ procedure Test.0 (): - let Test.9 = 0i64; - let Test.11 = 0i64; - let Test.13 = 0i64; - let Test.14 = 1i64; - let Test.12 = Z ; - let Test.10 = S Test.12; - let Test.8 = S Test.10; + let Test.10 = Z ; + let Test.9 = S Test.10; + let Test.8 = S Test.9; let Test.2 = S Test.8; ret Test.2; diff --git a/compiler/test_mono/generated/peano1.txt b/compiler/test_mono/generated/peano1.txt index df1d0183c7..bcf0d7a27d 100644 --- a/compiler/test_mono/generated/peano1.txt +++ b/compiler/test_mono/generated/peano1.txt @@ -1,11 +1,7 @@ procedure Test.0 (): - let Test.13 = 0i64; - let Test.15 = 0i64; - let Test.17 = 0i64; - let Test.18 = 1i64; - let Test.16 = Z ; - let Test.14 = S Test.16; - let Test.12 = S Test.14; + let Test.14 = Z ; + let Test.13 = S Test.14; + let Test.12 = S Test.13; let Test.2 = S Test.12; let Test.9 = 1i64; let Test.10 = GetTagId Test.2; diff --git a/compiler/test_mono/generated/peano2.txt b/compiler/test_mono/generated/peano2.txt index 4a1a5a234f..11ad76dbaf 100644 --- a/compiler/test_mono/generated/peano2.txt +++ b/compiler/test_mono/generated/peano2.txt @@ -1,11 +1,7 @@ procedure Test.0 (): - let Test.19 = 0i64; - let Test.21 = 0i64; - let Test.23 = 0i64; - let Test.24 = 1i64; - let Test.22 = Z ; - let Test.20 = S Test.22; - let Test.18 = S Test.20; + let Test.20 = Z ; + let Test.19 = S Test.20; + let Test.18 = S Test.19; let Test.2 = S Test.18; let Test.15 = 0i64; let Test.16 = GetTagId Test.2; diff --git a/examples/effect/Main.roc b/examples/effect/Main.roc index 9b10bb3bc8..9e3dd5f506 100644 --- a/examples/effect/Main.roc +++ b/examples/effect/Main.roc @@ -1,8 +1,8 @@ app "effect-example" packages { base: "thing/platform-dir" } - imports [base.Task] + imports [fx.Effect] provides [ main ] to base -main : Task.Task {} [] +main : Effect.Effect {} main = - Task.after Task.getLine \lineThisThing -> Task.putLine lineThisThing + Effect.after Effect.getLine \lineThisThing -> Effect.putLine lineThisThing diff --git a/examples/effect/thing/platform-dir/Cargo.lock b/examples/effect/thing/platform-dir/Cargo.lock deleted file mode 100644 index c386bb6c4a..0000000000 --- a/examples/effect/thing/platform-dir/Cargo.lock +++ /dev/null @@ -1,23 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "host" -version = "0.1.0" -dependencies = [ - "roc_std 0.1.0", -] - -[[package]] -name = "libc" -version = "0.2.79" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "roc_std" -version = "0.1.0" -dependencies = [ - "libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[metadata] -"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" diff --git a/examples/effect/thing/platform-dir/Cargo.toml b/examples/effect/thing/platform-dir/Cargo.toml deleted file mode 100644 index 9571ddc430..0000000000 --- a/examples/effect/thing/platform-dir/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "host" -version = "0.1.0" -authors = ["The Roc Contributors"] -license = "UPL-1.0" -edition = "2018" - -[lib] -crate-type = ["staticlib"] - -[dependencies] -roc_std = { path = "../../../../roc_std" } - -[workspace] diff --git a/examples/effect/thing/platform-dir/Package-Config.roc b/examples/effect/thing/platform-dir/Package-Config.roc index a9e40a3ddf..5e9516eeba 100644 --- a/examples/effect/thing/platform-dir/Package-Config.roc +++ b/examples/effect/thing/platform-dir/Package-Config.roc @@ -1,16 +1,16 @@ platform folkertdev/foo - requires { main : Effect {} } + requires {model=>Model, msg=>Msg} {main : Effect {}} exposes [] packages {} - imports [Task] + imports [fx.Effect] provides [ mainForHost ] effects fx.Effect { - putChar : I64 -> Effect {}, putLine : Str -> Effect {}, getLine : Effect Str } -mainForHost : Task.Task {} [] as Fx + +mainForHost : Effect.Effect {} as Fx mainForHost = main diff --git a/examples/effect/thing/platform-dir/Task.roc b/examples/effect/thing/platform-dir/Task.roc deleted file mode 100644 index 85a7f927c2..0000000000 --- a/examples/effect/thing/platform-dir/Task.roc +++ /dev/null @@ -1,31 +0,0 @@ -interface Task - exposes [ Task, after, always, fail, map, putLine, getLine ] - imports [ Effect ] - -Task a err : Effect.Effect (Result a err) - -always : a -> Task a * -always = \x -> Effect.always (Ok x) - -fail : err -> Task * err -fail = \x -> Effect.always (Err x) - -getLine : Task Str * -getLine = Effect.after Effect.getLine always - -putLine : Str -> Task {} * -putLine = \line -> Effect.map (Effect.putLine line) (\_ -> Ok {}) - -map : Task a err, (a -> b) -> Task b err -map = \task, transform -> - Effect.map task \res -> - when res is - Ok x -> Ok (transform x) - Err e -> Err e - -after : Task a err, (a -> Task b err) -> Task b err -after = \task, transform -> - Effect.after task \res -> - when res is - Ok x -> transform x - Err e -> Task.fail e diff --git a/examples/effect/thing/platform-dir/host.c b/examples/effect/thing/platform-dir/host.c deleted file mode 100644 index 0378c69589..0000000000 --- a/examples/effect/thing/platform-dir/host.c +++ /dev/null @@ -1,7 +0,0 @@ -#include - -extern int rust_main(); - -int main() { - return rust_main(); -} diff --git a/examples/effect/thing/platform-dir/host.zig b/examples/effect/thing/platform-dir/host.zig new file mode 100644 index 0000000000..307bd69a73 --- /dev/null +++ b/examples/effect/thing/platform-dir/host.zig @@ -0,0 +1,184 @@ +const std = @import("std"); +const str = @import("str"); +const RocStr = str.RocStr; +const testing = std.testing; +const expectEqual = testing.expectEqual; +const expect = testing.expect; +const maxInt = std.math.maxInt; + +comptime { + // This is a workaround for https://github.com/ziglang/zig/issues/8218 + // which is only necessary on macOS. + // + // Once that issue is fixed, we can undo the changes in + // 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing + // -fcompiler-rt in link.rs instead of doing this. Note that this + // workaround is present in many host.zig files, so make sure to undo + // it everywhere! + if (std.builtin.os.tag == .macos) { + _ = @import("compiler_rt"); + } +} + +const mem = std.mem; +const Allocator = mem.Allocator; + +extern fn roc__mainForHost_1_exposed([*]u8) void; +extern fn roc__mainForHost_size() i64; +extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void; +extern fn roc__mainForHost_1_Fx_size() i64; +extern fn roc__mainForHost_1_Fx_result_size() i64; + +extern fn malloc(size: usize) callconv(.C) ?*c_void; +extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; +extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void; + +export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { + return malloc(size); +} + +export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void { + return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size); +} + +export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void { + free(@alignCast(16, @ptrCast([*]u8, c_ptr))); +} + +const Unit = extern struct {}; + +pub export fn main() u8 { + const stdout = std.io.getStdOut().writer(); + const stderr = std.io.getStdErr().writer(); + + const size = @intCast(usize, roc__mainForHost_size()); + const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable; + var output = @ptrCast([*]u8, raw_output); + + defer { + std.heap.c_allocator.free(raw_output); + } + + var ts1: std.os.timespec = undefined; + std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; + + roc__mainForHost_1_exposed(output); + + const elements = @ptrCast([*]u64, @alignCast(8, output)); + + var flag = elements[0]; + + if (flag == 0) { + // all is well + const closure_data_pointer = @ptrCast([*]u8, output[8..size]); + + call_the_closure(closure_data_pointer); + } else { + const msg = @intToPtr([*:0]const u8, elements[1]); + stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable; + + return 0; + } + + var ts2: std.os.timespec = undefined; + std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; + + const delta = to_seconds(ts2) - to_seconds(ts1); + + stderr.print("runtime: {d:.3}ms\n", .{delta * 1000}) catch unreachable; + + return 0; +} + +fn to_seconds(tms: std.os.timespec) f64 { + return @intToFloat(f64, tms.tv_sec) + (@intToFloat(f64, tms.tv_nsec) / 1_000_000_000.0); +} + +fn call_the_closure(closure_data_pointer: [*]u8) void { + const size = roc__mainForHost_1_Fx_result_size(); + const raw_output = std.heap.c_allocator.alloc(u8, @intCast(usize, size)) catch unreachable; + var output = @ptrCast([*]u8, raw_output); + + defer { + std.heap.c_allocator.free(raw_output); + } + + const flags: u8 = 0; + + roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, output); + + const elements = @ptrCast([*]u64, @alignCast(8, output)); + + var flag = elements[0]; + + if (flag == 0) { + return; + } else { + unreachable; + } +} + +pub export fn roc_fx_getLine() str.RocStr { + if (roc_fx_getLine_help()) |value| { + return value; + } else |err| { + return str.RocStr.empty(); + } +} + +fn roc_fx_getLine_help() !RocStr { + const stdin = std.io.getStdIn().reader(); + var buf: [400]u8 = undefined; + + const line: []u8 = (try stdin.readUntilDelimiterOrEof(&buf, '\n')) orelse ""; + + return str.RocStr.init(@ptrCast([*]const u8, line), line.len); +} + +pub export fn roc_fx_putLine(rocPath: str.RocStr) i64 { + const stdout = std.io.getStdOut().writer(); + + for (rocPath.asSlice()) |char| { + stdout.print("{c}", .{char}) catch unreachable; + } + + stdout.print("\n", .{}) catch unreachable; + + return 0; +} + +const GetInt = extern struct { + value: i64, + error_code: u8, + is_error: bool, +}; + +pub export fn roc_fx_getInt() GetInt { + if (roc_fx_getInt_help()) |value| { + const get_int = GetInt{ .is_error = false, .value = value, .error_code = 0 }; + return get_int; + } else |err| switch (err) { + error.InvalidCharacter => { + return GetInt{ .is_error = true, .value = 0, .error_code = 0 }; + }, + else => { + return GetInt{ .is_error = true, .value = 0, .error_code = 1 }; + }, + } + + return 0; +} + +fn roc_fx_getInt_help() !i64 { + const stdin = std.io.getStdIn().reader(); + var buf: [40]u8 = undefined; + + const line: []u8 = (try stdin.readUntilDelimiterOrEof(&buf, '\n')) orelse ""; + + return std.fmt.parseInt(i64, line, 10); +} + +fn readLine() []u8 { + const stdin = std.io.getStdIn().reader(); + return (stdin.readUntilDelimiterOrEof(&line_buf, '\n') catch unreachable) orelse ""; +} diff --git a/examples/effect/thing/platform-dir/src/lib.rs b/examples/effect/thing/platform-dir/src/lib.rs deleted file mode 100644 index 2fe8147386..0000000000 --- a/examples/effect/thing/platform-dir/src/lib.rs +++ /dev/null @@ -1,158 +0,0 @@ -#![allow(non_snake_case)] - -use roc_std::alloca; -use roc_std::RocCallResult; -use roc_std::RocStr; -use std::alloc::Layout; -use std::ffi::c_void; -use std::time::SystemTime; - -extern "C" { - #[link_name = "roc__mainForHost_1_exposed"] - fn roc_main(output: *mut u8) -> (); - - #[link_name = "roc__mainForHost_1_size"] - fn roc_main_size() -> i64; - - #[link_name = "roc__mainForHost_1_Fx_caller"] - fn call_Fx( - flags: &(), - function_pointer: *const u8, - closure_data: *const u8, - output: *mut u8, - ) -> (); - - #[link_name = "roc__mainForHost_1_Fx_size"] - fn size_Fx() -> i64; - - #[link_name = "roc__mainForHost_1_Fx_result_size"] - fn size_Fx_result() -> i64; - - fn malloc(size: usize) -> *mut c_void; - fn realloc(c_ptr: *mut c_void, size: usize) -> *mut c_void; - fn free(c_ptr: *mut c_void); -} - -#[no_mangle] -pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { - return malloc(size); -} - -#[no_mangle] -pub unsafe fn roc_realloc( - c_ptr: *mut c_void, - new_size: usize, - _old_size: usize, - _alignment: u32, -) -> *mut c_void { - return realloc(c_ptr, new_size); -} - -#[no_mangle] -pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { - return free(c_ptr); -} - -#[no_mangle] -pub fn roc_fx_putChar(foo: i64) -> () { - let character = foo as u8 as char; - print!("{}", character); - - () -} - -#[no_mangle] -pub fn roc_fx_putLine(line: RocStr) -> () { - let bytes = line.as_slice(); - let string = unsafe { std::str::from_utf8_unchecked(bytes) }; - println!("{}", string); - - () -} - -#[no_mangle] -pub fn roc_fx_getLine() -> RocStr { - use std::io::{self, BufRead}; - - let stdin = io::stdin(); - let line1 = stdin.lock().lines().next().unwrap().unwrap(); - - RocStr::from_slice(line1.as_bytes()) -} - -unsafe fn call_the_closure(function_pointer: *const u8, closure_data_ptr: *const u8) -> i64 { - let size = size_Fx_result() as usize; - - alloca::with_stack_bytes(size, |buffer| { - let buffer: *mut std::ffi::c_void = buffer; - let buffer: *mut u8 = buffer as *mut u8; - - call_Fx( - &(), - function_pointer, - closure_data_ptr as *const u8, - buffer as *mut u8, - ); - - let output = &*(buffer as *mut RocCallResult<()>); - - match output.into() { - Ok(_) => 0, - Err(e) => panic!("failed with {}", e), - } - }) -} - -#[no_mangle] -pub fn rust_main() -> isize { - eprintln!("Running Roc closure"); - let start_time = SystemTime::now(); - - let size = unsafe { roc_main_size() } as usize; - let layout = Layout::array::(size).unwrap(); - let answer = unsafe { - let buffer = std::alloc::alloc(layout); - - roc_main(buffer); - - let output = &*(buffer as *mut RocCallResult<()>); - - match output.into() { - Ok(()) => { - let function_pointer = { - // this is a pointer to the location where the function pointer is stored - // we pass just the function pointer - let temp = buffer.offset(8) as *const i64; - - (*temp) as *const u8 - }; - - let closure_data_ptr = buffer.offset(16); - - let result = - call_the_closure(function_pointer as *const u8, closure_data_ptr as *const u8); - - std::alloc::dealloc(buffer, layout); - - result - } - Err(msg) => { - std::alloc::dealloc(buffer, layout); - - panic!("Roc failed with message: {}", msg); - } - } - }; - let end_time = SystemTime::now(); - let duration = end_time.duration_since(start_time).unwrap(); - - eprintln!( - "Roc closure took {:.4} ms to compute this answer: {:?}", - duration.as_secs_f64() * 1000.0, - // truncate the answer, so stdout is not swamped - answer - ); - - // Exit code - 0 -}