diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 2b9a176be5..da3fb071f5 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -149,7 +149,8 @@ pub fn gen_from_mono_module( // env.module.print_to_stderr(); // NOTE: If this fails, uncomment the above println to debug. panic!( - "Non-main function failed LLVM verification. Uncomment the above println to debug!" + r"Non-main function {:?} failed LLVM verification. Uncomment the above println to debug!", + fn_val.get_name(), ); } } diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 263c932ea9..afdf6bdd39 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -18,7 +18,7 @@ pub enum ExposedModuleTypes { } pub struct ConstrainedModule { - pub unused_imports: MutSet, + pub unused_imports: MutMap, pub constraint: Constraint, } @@ -132,7 +132,7 @@ pub fn constrain_imports( pub struct ConstrainableImports { pub imported_symbols: Vec, pub imported_aliases: MutMap, - pub unused_imports: MutSet, + pub unused_imports: MutMap, } /// Run this before constraining imports. @@ -143,7 +143,7 @@ pub struct ConstrainableImports { pub fn pre_constrain_imports( home: ModuleId, references: &MutSet, - imported_modules: MutSet, + imported_modules: MutMap, exposed_types: &mut SubsByModule, stdlib: &StdLib, ) -> ConstrainableImports { diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 335eb91c1e..00be57ef61 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -2249,12 +2249,10 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena); for layout in arguments { - argument_types.push(basic_type_from_layout( - arena, - context, - layout, - env.ptr_bytes, - )); + let arg_type = basic_type_from_layout(arena, context, layout, env.ptr_bytes); + let arg_ptr_type = arg_type.ptr_type(AddressSpace::Generic); + + argument_types.push(arg_ptr_type.into()); } let function_pointer_type = { @@ -2310,6 +2308,12 @@ pub fn build_closure_caller<'a, 'ctx, 'env>( let closure_data = builder.build_load(closure_data_ptr, "load_closure_data"); let mut parameters = parameters; + + for param in parameters.iter_mut() { + debug_assert!(param.is_pointer_value()); + *param = builder.build_load(param.into_pointer_value(), "load_param"); + } + parameters.push(closure_data); let call_result = invoke_and_catch(env, function_value, function_ptr, ¶meters, result_type); @@ -2357,12 +2361,10 @@ pub fn build_function_caller<'a, 'ctx, 'env>( let mut argument_types = Vec::with_capacity_in(arguments.len() + 3, env.arena); for layout in arguments { - argument_types.push(basic_type_from_layout( - arena, - context, - layout, - env.ptr_bytes, - )); + let arg_type = basic_type_from_layout(arena, context, layout, env.ptr_bytes); + let arg_ptr_type = arg_type.ptr_type(AddressSpace::Generic); + + argument_types.push(arg_ptr_type.into()); } let function_pointer_type = { @@ -2429,6 +2431,13 @@ pub fn build_function_caller<'a, 'ctx, 'env>( .build_bitcast(function_ptr, actual_function_type, "cast") .into_pointer_value(); + let mut parameters = parameters; + + for param in parameters.iter_mut() { + debug_assert!(param.is_pointer_value()); + *param = builder.build_load(param.into_pointer_value(), "load_param"); + } + let call_result = invoke_and_catch(env, function_value, function_ptr, ¶meters, result_type); builder.build_store(output, call_result); diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index af237965a0..fd61954feb 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -106,12 +106,12 @@ impl Dependencies { &mut self, module_id: ModuleId, opt_effect_module: Option, - dependencies: &MutSet, + dependencies: &MutMap, goal_phase: Phase, ) -> MutSet<(ModuleId, Phase)> { use Phase::*; - for dep in dependencies.iter().copied() { + for dep in dependencies.keys().copied() { // to parse and generate constraints, the headers of all dependencies must be loaded! // otherwise, we don't know whether an imported symbol is actually exposed self.add_dependency_help(module_id, dep, Phase::Parse, Phase::LoadHeader); @@ -146,7 +146,7 @@ impl Dependencies { let mut output = MutSet::default(); // all the dependencies can be loaded - for dep in dependencies { + for dep in dependencies.keys() { // TODO figure out how to "load" (because it doesn't exist on the file system) the Effect module if Some(*dep) != opt_effect_module { @@ -420,7 +420,7 @@ fn start_phase<'a>(module_id: ModuleId, phase: Phase, state: &mut State<'a>) -> let mut aliases = MutMap::default(); - for imported in parsed.imported_modules.iter() { + for imported in parsed.imported_modules.keys() { match state.module_cache.aliases.get(imported) { None => unreachable!( r"imported module {:?} did not register its aliases, so {:?} cannot use them", @@ -556,7 +556,7 @@ struct ModuleHeader<'a> { exposed_ident_ids: IdentIds, deps_by_name: MutMap, ModuleId>, packages: MutMap<&'a str, PackageOrPath<'a>>, - imported_modules: MutSet, + imported_modules: MutMap, exposes: Vec, exposed_imports: MutMap, src: &'a [u8], @@ -574,7 +574,7 @@ enum HeaderFor<'a> { struct ConstrainedModule { module: Module, declarations: Vec, - imported_modules: MutSet, + imported_modules: MutMap, constraint: Constraint, ident_ids: IdentIds, var_store: VarStore, @@ -631,7 +631,7 @@ struct ParsedModule<'a> { src: &'a str, module_timing: ModuleTiming, deps_by_name: MutMap, ModuleId>, - imported_modules: MutSet, + imported_modules: MutMap, exposed_ident_ids: IdentIds, exposed_imports: MutMap, parsed_defs: &'a [Located>], @@ -659,6 +659,7 @@ enum Msg<'a> { solved_subs: Solved, decls: Vec, module_timing: ModuleTiming, + unused_imports: MutMap, }, FinishedAllTypeChecking { solved_subs: Solved, @@ -870,6 +871,7 @@ enum BuildTask<'a> { constraint: Constraint, var_store: VarStore, declarations: Vec, + unused_imports: MutMap, }, BuildPendingSpecializations { module_timing: ModuleTiming, @@ -1607,6 +1609,7 @@ fn update<'a>( solved_subs, decls, mut module_timing, + mut unused_imports, } => { log!("solved types for {:?}", module_id); module_timing.end_time = SystemTime::now(); @@ -1616,6 +1619,15 @@ fn update<'a>( .type_problems .insert(module_id, solved_module.problems); + let existing = match state.module_cache.can_problems.entry(module_id) { + Vacant(entry) => entry.insert(std::vec::Vec::new()), + Occupied(entry) => entry.into_mut(), + }; + + for (unused, region) in unused_imports.drain() { + existing.push(roc_problem::can::Problem::UnusedImport(unused, region)); + } + let work = state.dependencies.notify(module_id, Phase::SolveTypes); // if there is a platform, the Pkg-Config module provides host-exposed, @@ -2350,7 +2362,7 @@ fn send_header<'a>( let mut imported: Vec<(QualifiedModuleName, Vec, Region)> = Vec::with_capacity(imports.len()); - let mut imported_modules: MutSet = MutSet::default(); + let mut imported_modules: MutMap = MutMap::default(); let mut scope_size = 0; for loc_entry in imports { @@ -2410,7 +2422,7 @@ fn send_header<'a>( }; let module_id = module_ids.get_or_insert(&pq_module_name); - imported_modules.insert(module_id); + imported_modules.insert(module_id, region); deps_by_name.insert(pq_module_name, module_id); @@ -2534,7 +2546,7 @@ fn send_header_two<'a>( let mut imported: Vec<(QualifiedModuleName, Vec, Region)> = Vec::with_capacity(imports.len()); - let mut imported_modules: MutSet = MutSet::default(); + let mut imported_modules: MutMap = MutMap::default(); let num_exposes = provides.len(); let mut deps_by_name: MutMap = @@ -2542,7 +2554,7 @@ fn send_header_two<'a>( // add standard imports // TODO add Effect by default - imported_modules.insert(app_module_id); + imported_modules.insert(app_module_id, Region::zero()); deps_by_name.insert( PQModuleName::Unqualified(ModuleName::APP.into()), app_module_id, @@ -2594,7 +2606,7 @@ fn send_header_two<'a>( }; let module_id = module_ids.get_or_insert(&pq_module_name); - imported_modules.insert(module_id); + imported_modules.insert(module_id, region); deps_by_name.insert(pq_module_name, module_id); @@ -2720,7 +2732,7 @@ impl<'a> BuildTask<'a> { module_timing: ModuleTiming, constraint: Constraint, var_store: VarStore, - imported_modules: MutSet, + imported_modules: MutMap, exposed_types: &mut SubsByModule, stdlib: &StdLib, declarations: Vec, @@ -2742,14 +2754,6 @@ impl<'a> BuildTask<'a> { stdlib, ); - if !unused_imports.is_empty() { - todo!( - "TODO gracefully handle unused import {:?} from module {:?}", - &unused_imports, - home, - ); - } - // Next, solve this module in the background. Self::Solve { module, @@ -2759,6 +2763,7 @@ impl<'a> BuildTask<'a> { var_store, declarations, module_timing, + unused_imports, } } } @@ -2772,6 +2777,7 @@ fn run_solve<'a>( constraint: Constraint, mut var_store: VarStore, decls: Vec, + unused_imports: MutMap, ) -> Msg<'a> { // We have more constraining work to do now, so we'll add it to our timings. let constrain_start = SystemTime::now(); @@ -2819,6 +2825,7 @@ fn run_solve<'a>( decls, solved_module, module_timing, + unused_imports, } } @@ -3071,7 +3078,7 @@ fn fabricate_effects_module<'a>( rigid_variables: module_output.rigid_variables, }; - let imported_modules = MutSet::default(); + let imported_modules = MutMap::default(); // Should a effect module ever have a ModuleDocumentation? let module_docs = ModuleDocumentation { @@ -3615,6 +3622,7 @@ fn run_task<'a>( var_store, ident_ids, declarations, + unused_imports, } => Ok(run_solve( module, ident_ids, @@ -3623,6 +3631,7 @@ fn run_task<'a>( constraint, var_store, declarations, + unused_imports, )), BuildPendingSpecializations { module_id, diff --git a/compiler/module/src/ident.rs b/compiler/module/src/ident.rs index 409600f226..48cacc84fe 100644 --- a/compiler/module/src/ident.rs +++ b/compiler/module/src/ident.rs @@ -64,7 +64,7 @@ impl TagName { impl ModuleName { // NOTE: After adding one of these, go to `impl ModuleId` and // add a corresponding ModuleId to there! - pub const APP: &'static str = ""; // app modules have no module name + pub const APP: &'static str = "#UserApp"; // app modules have this hardcoded name pub const BOOL: &'static str = "Bool"; pub const STR: &'static str = "Str"; pub const NUM: &'static str = "Num"; diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index dbacb9fe64..5ccfd4ee64 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -571,6 +571,12 @@ impl<'a> Procs<'a> { return; } + // If this is an imported symbol, let its home module make this specialization + if env.is_imported_symbol(name) { + add_needed_external(self, env, fn_var, name); + return; + } + // We're done with that tuple, so move layout back out to avoid cloning it. let (name, layout) = tuple; @@ -589,7 +595,7 @@ impl<'a> Procs<'a> { // TODO should pending_procs hold a Rc? let partial_proc = match self.partial_procs.get(&symbol) { Some(p) => p.clone(), - None => panic!("no partial_proc for {:?}", symbol), + None => panic!("no partial_proc for {:?} in module {:?}", symbol, env.home), }; // Mark this proc as in-progress, so if we're dealing with @@ -695,6 +701,10 @@ impl<'a, 'i> Env<'a, 'i> { Symbol::new(self.home, ident_id) } + + pub fn is_imported_symbol(&self, symbol: Symbol) -> bool { + symbol.module_id() != self.home && !symbol.is_builtin() + } } #[derive(Clone, Debug, PartialEq, Copy, Eq, Hash)] @@ -904,13 +914,16 @@ where D::Doc: Clone, A: Clone, { + use roc_module::ident::ModuleName; + if PRETTY_PRINT_IR_SYMBOLS { alloc.text(format!("{:?}", symbol)) } else { let text = format!("{}", symbol); - if text.starts_with('.') { - alloc.text("Test").append(text) + if text.starts_with(ModuleName::APP) { + let name: String = text.trim_start_matches(ModuleName::APP).into(); + alloc.text("Test").append(name) } else { alloc.text(text) } @@ -2129,6 +2142,96 @@ impl<'a> FunctionLayouts<'a> { } } +fn specialize_naked_symbol<'a>( + env: &mut Env<'a, '_>, + variable: Variable, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + assigned: Symbol, + hole: &'a Stmt<'a>, + symbol: Symbol, +) -> Stmt<'a> { + if procs.module_thunks.contains(&symbol) { + let partial_proc = procs.partial_procs.get(&symbol).unwrap(); + let fn_var = partial_proc.annotation; + + // This is a top-level declaration, which will code gen to a 0-arity thunk. + let result = call_by_name( + env, + procs, + fn_var, + symbol, + std::vec::Vec::new(), + layout_cache, + assigned, + env.arena.alloc(Stmt::Ret(assigned)), + ); + + return result; + } else if env.is_imported_symbol(symbol) { + match layout_cache.from_var(env.arena, variable, env.subs) { + Err(e) => panic!("invalid layout {:?}", e), + Ok(layout @ Layout::FunctionPointer(_, _)) => { + add_needed_external(procs, env, variable, symbol); + + match hole { + Stmt::Jump(_, _) => todo!("not sure what to do in this case yet"), + _ => { + let expr = Expr::FunctionPointer(symbol, layout.clone()); + let new_symbol = env.unique_symbol(); + return Stmt::Let( + new_symbol, + expr, + layout, + env.arena.alloc(Stmt::Ret(new_symbol)), + ); + } + } + } + Ok(_) => { + // this is a 0-arity thunk + let result = call_by_name( + env, + procs, + variable, + symbol, + std::vec::Vec::new(), + layout_cache, + assigned, + env.arena.alloc(Stmt::Ret(assigned)), + ); + return result; + } + } + } + + // A bit ugly, but it does the job + match hole { + Stmt::Jump(id, _) => Stmt::Jump(*id, env.arena.alloc([symbol])), + _ => { + let result = Stmt::Ret(symbol); + let original = symbol; + + // we don't have a more accurate variable available, which means the variable + // from the partial_proc will be used. So far that has given the correct + // result, but I'm not sure this will continue to be the case in more complex + // examples. + let opt_fn_var = None; + + // if this is a function symbol, ensure that it's properly specialized! + reuse_function_symbol( + env, + procs, + layout_cache, + opt_fn_var, + symbol, + result, + original, + ) + } + } +} + pub fn with_hole<'a>( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, @@ -2387,85 +2490,7 @@ pub fn with_hole<'a>( ) } Var(symbol) => { - if procs.module_thunks.contains(&symbol) { - let partial_proc = procs.partial_procs.get(&symbol).unwrap(); - let fn_var = partial_proc.annotation; - - // This is a top-level declaration, which will code gen to a 0-arity thunk. - let result = call_by_name( - env, - procs, - fn_var, - symbol, - std::vec::Vec::new(), - layout_cache, - assigned, - env.arena.alloc(Stmt::Ret(assigned)), - ); - - return result; - } else if symbol.module_id() != env.home && symbol.module_id() != ModuleId::ATTR { - match layout_cache.from_var(env.arena, variable, env.subs) { - Err(e) => panic!("invalid layout {:?}", e), - Ok(layout @ Layout::FunctionPointer(_, _)) => { - add_needed_external(procs, env, variable, symbol); - - match hole { - Stmt::Jump(_, _) => todo!("not sure what to do in this case yet"), - _ => { - let expr = Expr::FunctionPointer(symbol, layout.clone()); - let new_symbol = env.unique_symbol(); - return Stmt::Let( - new_symbol, - expr, - layout, - env.arena.alloc(Stmt::Ret(new_symbol)), - ); - } - } - } - Ok(_) => { - // this is a 0-arity thunk - let result = call_by_name( - env, - procs, - variable, - symbol, - std::vec::Vec::new(), - layout_cache, - assigned, - env.arena.alloc(Stmt::Ret(assigned)), - ); - return result; - } - } - } - - // A bit ugly, but it does the job - match hole { - Stmt::Jump(id, _) => Stmt::Jump(*id, env.arena.alloc([symbol])), - _ => { - let result = Stmt::Ret(symbol); - let original = symbol; - - // we don't have a more accurate variable available, which means the variable - // from the partial_proc will be used. So far that has given the correct - // result, but I'm not sure this will continue to be the case in more complex - // examples. - let opt_fn_var = None; - - // if this is a function symbol, ensure that it's properly specialized! - reuse_function_symbol( - env, - procs, - layout_cache, - opt_fn_var, - symbol, - result, - original, - ) - } - } + specialize_naked_symbol(env, variable, procs, layout_cache, assigned, hole, symbol) } Tag { variant_var, @@ -2636,17 +2661,28 @@ pub fn with_hole<'a>( let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena); let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena); - for (label, _, _) in sorted_fields.into_iter() { + enum Field { + Function(Symbol, Variable), + ValueSymbol, + Field(roc_can::expr::Field), + } + + for (label, variable, _) in sorted_fields.into_iter() { // TODO how should function pointers be handled here? + use ReuseSymbol::*; match fields.remove(&label) { - Some(field) => match can_reuse_symbol(procs, &field.loc_expr.value) { - Some(reusable) => { - field_symbols.push(reusable); - can_fields.push(None); + Some(field) => match can_reuse_symbol(env, procs, &field.loc_expr.value) { + Imported(symbol) | LocalFunction(symbol) => { + field_symbols.push(symbol); + can_fields.push(Field::Function(symbol, variable)); } - None => { + Value(reusable) => { + field_symbols.push(reusable); + can_fields.push(Field::ValueSymbol); + } + NotASymbol => { field_symbols.push(env.unique_symbol()); - can_fields.push(Some(field)); + can_fields.push(Field::Field(field)); } }, None => { @@ -2666,16 +2702,32 @@ pub fn with_hole<'a>( for (opt_field, symbol) in can_fields.into_iter().rev().zip(field_symbols.iter().rev()) { - if let Some(field) = opt_field { - stmt = with_hole( - env, - field.loc_expr.value, - field.var, - procs, - layout_cache, - *symbol, - env.arena.alloc(stmt), - ); + match opt_field { + Field::ValueSymbol => { + // this symbol is already defined; nothing to do + } + Field::Function(symbol, variable) => { + stmt = reuse_function_symbol( + env, + procs, + layout_cache, + Some(variable), + symbol, + stmt, + symbol, + ); + } + Field::Field(field) => { + stmt = with_hole( + env, + field.loc_expr.value, + field.var, + procs, + layout_cache, + *symbol, + env.arena.alloc(stmt), + ); + } } } @@ -3342,8 +3394,15 @@ pub fn with_hole<'a>( // if the function expression (loc_expr) is already a symbol, // re-use that symbol, and don't define its value again let mut result; - match can_reuse_symbol(procs, &loc_expr.value) { - Some(function_symbol) => { + use ReuseSymbol::*; + match can_reuse_symbol(env, procs, &loc_expr.value) { + LocalFunction(_) => { + unreachable!("if this was known to be a function, we would not be here") + } + Imported(_) => { + unreachable!("an imported value is never an anonymous function") + } + Value(function_symbol) => { if let Layout::Closure(_, closure_fields, _) = full_layout { // we're invoking a closure let closure_record_symbol = env.unique_symbol(); @@ -3436,7 +3495,7 @@ pub fn with_hole<'a>( ); } } - None => { + NotASymbol => { let function_symbol = env.unique_symbol(); result = Stmt::Let( @@ -4677,15 +4736,33 @@ fn store_record_destruct<'a>( /// We want to re-use symbols that are not function symbols /// for any other expression, we create a new symbol, and will /// later make sure it gets assigned the correct value. -fn can_reuse_symbol<'a>(procs: &Procs<'a>, expr: &roc_can::expr::Expr) -> Option { + +enum ReuseSymbol { + Imported(Symbol), + LocalFunction(Symbol), + Value(Symbol), + NotASymbol, +} + +fn can_reuse_symbol<'a>( + env: &mut Env<'a, '_>, + procs: &Procs<'a>, + expr: &roc_can::expr::Expr, +) -> ReuseSymbol { + use ReuseSymbol::*; + if let roc_can::expr::Expr::Var(symbol) = expr { - if procs.partial_procs.contains_key(&symbol) { - None + let symbol = *symbol; + + if env.is_imported_symbol(symbol) { + Imported(symbol) + } else if procs.partial_procs.contains_key(&symbol) { + LocalFunction(symbol) } else { - Some(*symbol) + Value(symbol) } } else { - None + NotASymbol } } @@ -4694,9 +4771,9 @@ fn possible_reuse_symbol<'a>( procs: &Procs<'a>, expr: &roc_can::expr::Expr, ) -> Symbol { - match can_reuse_symbol(procs, expr) { - Some(s) => s, - None => env.unique_symbol(), + match can_reuse_symbol(env, procs, expr) { + ReuseSymbol::Value(s) => s, + _ => env.unique_symbol(), } } @@ -4752,8 +4829,7 @@ fn reuse_function_symbol<'a>( ) -> Stmt<'a> { match procs.partial_procs.get(&original) { None => { - let is_imported = - !(env.home == original.module_id() || original.module_id() == ModuleId::ATTR); + let is_imported = env.is_imported_symbol(original); match arg_var { Some(arg_var) if is_imported => { @@ -4761,8 +4837,6 @@ fn reuse_function_symbol<'a>( .from_var(env.arena, arg_var, env.subs) .expect("creating layout does not fail"); - add_needed_external(procs, env, arg_var, original); - procs.insert_passed_by_name( env, arg_var, @@ -4774,7 +4848,7 @@ fn reuse_function_symbol<'a>( // an imported symbol is always a function pointer: // either it's a function, or a top-level 0-argument thunk let expr = Expr::FunctionPointer(original, layout.clone()); - return Stmt::Let(original, expr, layout, env.arena.alloc(result)); + return Stmt::Let(symbol, expr, layout, env.arena.alloc(result)); } _ => { // danger: a foreign symbol may not be specialized! @@ -4914,19 +4988,25 @@ fn assign_to_symbol<'a>( symbol: Symbol, result: Stmt<'a>, ) -> Stmt<'a> { - // if this argument is already a symbol, we don't need to re-define it - if let roc_can::expr::Expr::Var(original) = loc_arg.value { - reuse_function_symbol( - env, - procs, - layout_cache, - Some(arg_var), - symbol, - result, - original, - ) - } else { - with_hole( + use ReuseSymbol::*; + match can_reuse_symbol(env, procs, &loc_arg.value) { + Imported(original) | LocalFunction(original) => { + // for functions we must make sure they are specialized correctly + reuse_function_symbol( + env, + procs, + layout_cache, + Some(arg_var), + symbol, + result, + original, + ) + } + Value(_) => { + // symbol is already defined; nothing else to do here + result + } + NotASymbol => with_hole( env, loc_arg.value, arg_var, @@ -4934,7 +5014,7 @@ fn assign_to_symbol<'a>( layout_cache, symbol, env.arena.alloc(result), - ) + ), } } diff --git a/compiler/reporting/src/error/canonicalize.rs b/compiler/reporting/src/error/canonicalize.rs index f4275e3897..c216473b98 100644 --- a/compiler/reporting/src/error/canonicalize.rs +++ b/compiler/reporting/src/error/canonicalize.rs @@ -28,15 +28,22 @@ pub fn can_problem<'b>( .append(alloc.reflow(line)), ]) } - Problem::UnusedImport(module_id, region) => alloc.concat(vec![ - alloc.reflow("Nothing from "), - alloc.module(module_id), - alloc.reflow(" is used in this module."), - alloc.region(region), - alloc.reflow("Since "), - alloc.module(module_id), - alloc.reflow(" isn't used, you don't need to import it."), - ]), + Problem::UnusedImport(module_id, region) => { + alloc.stack(vec![ + alloc.concat(vec![ + alloc.reflow("Nothing from "), + alloc.module(module_id), + alloc.reflow(" is used in this module."), + ]), + alloc.region(region), + alloc.concat(vec![ + alloc.reflow("Since "), + alloc.module(module_id), + alloc.reflow(" isn't used, you don't need to import it."), + ]) + ]) + + } Problem::ExposedButNotDefined(symbol) => { alloc.stack(vec![ alloc diff --git a/examples/tea/Main.roc b/examples/tea/Main.roc index 6a02b55825..0019e85630 100644 --- a/examples/tea/Main.roc +++ b/examples/tea/Main.roc @@ -3,5 +3,29 @@ app "effect-example" imports [base.Cmd] provides [ main ] to base -main : I64 -main = 42 +Model : I64 + +Msg : [ Line Str ] + +main = { init, update } + +init : {} -> { model : Model, cmd : Cmd.Cmd Msg } +init = \{} -> + cmd = + Cmd.after (Cmd.putLine "Type a thing, and I'll say it back") \{} -> + Cmd.getLine (\l -> Line l) + + { model: 42, cmd } + + +update : Msg, Model -> { model : Model, cmd : Cmd.Cmd Msg } +update = \msg, model -> + when msg is + Line line -> + cmd = + Cmd.after (Cmd.putLine "You said:") \{} -> + Cmd.after (Cmd.putLine line) \{} -> + Cmd.after (Cmd.putLine "Type another thing, and I'll say it back") \{} -> + Cmd.getLine (\l -> Line l) + + { model: model + 1, cmd } diff --git a/examples/tea/platform/Pkg-Config.roc b/examples/tea/platform/Pkg-Config.roc index 6d9a0a0752..719b1ac581 100644 --- a/examples/tea/platform/Pkg-Config.roc +++ b/examples/tea/platform/Pkg-Config.roc @@ -1,5 +1,5 @@ platform folkertdev/foo - requires { main : Effect {} } + requires { main : {} } exposes [] packages {} imports [Cmd] @@ -11,31 +11,9 @@ platform folkertdev/foo getLine : Effect Str } - mainForHost : { init : ({} -> { model: I64, cmd : (Cmd.Cmd [ Line Str ]) as Fx }) as Init, update : ([ Line Str ], I64 -> { model: I64, cmd : Cmd.Cmd [ Line Str ] } ) as Update } -mainForHost = - { - init : \{} -> - { - model: 42, - cmd: - Cmd.after (Cmd.putLine "Type a thing, and I'll say it back") \{} -> - Cmd.getLine (\l -> Line l) - }, - update : \msg, model -> - when msg is - Line line -> - cmd = - Cmd.after (Cmd.putLine "You said:") \{} -> - Cmd.after (Cmd.putLine line) \{} -> - Cmd.after (Cmd.putLine "Type another thing, and I'll say it back") \{} -> - Cmd.getLine (\l -> Line l) - - { model: model + 1, cmd } - } - - +mainForHost = main diff --git a/examples/tea/platform/src/lib.rs b/examples/tea/platform/src/lib.rs index 3d9efe668d..11409beea2 100644 --- a/examples/tea/platform/src/lib.rs +++ b/examples/tea/platform/src/lib.rs @@ -17,7 +17,12 @@ extern "C" { fn roc_main_size() -> i64; #[link_name = "roc__mainForHost_1_Init_caller"] - fn call_Init(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> (); + fn _call_Init( + flags: &(), + function_pointer: *const u8, + closure_data: *const u8, + output: *mut u8, + ) -> (); #[link_name = "roc__mainForHost_1_Init_size"] fn size_Init() -> i64; @@ -27,8 +32,8 @@ extern "C" { #[link_name = "roc__mainForHost_1_Update_caller"] fn call_Update( - msg: Msg, - model: Model, + msg: &Msg, + model: &Model, function_pointer: *const u8, closure_data: *const u8, output: *mut u8, @@ -41,7 +46,12 @@ extern "C" { fn size_Update_result() -> i64; #[link_name = "roc__mainForHost_1_Fx_caller"] - fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> (); + fn _call_Fx( + unit: &(), + function_pointer: *const u8, + closure_data: *const u8, + output: *mut u8, + ) -> (); #[link_name = "roc__mainForHost_1_Fx_size"] fn size_Fx() -> i64; @@ -53,6 +63,17 @@ extern "C" { fn size_Model() -> i64; } +unsafe fn call_Fx(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> () { + // Fx (or Cmd on the roc side) is a thunk, so we know its first argument is a (pointer to) unit + _call_Fx(&(), function_pointer, closure_data, output) +} + +unsafe fn call_Init(function_pointer: *const u8, closure_data: *const u8, output: *mut u8) -> () { + // for now, we hardcode flags to be `()` (or `{}` on the roc side) + let flags = (); + _call_Init(&flags, function_pointer, closure_data, output) +} + #[no_mangle] pub fn roc_fx_putChar(foo: i64) -> () { let character = foo as u8 as char; @@ -152,9 +173,11 @@ unsafe fn run_update( let buffer: *mut std::ffi::c_void = buffer; let buffer: *mut u8 = buffer as *mut u8; + println!("let's try update!"); + call_Update( - msg, - model, + &msg, + &model, function_pointer, closure_data_ptr, buffer as *mut u8,