From 6ffc8a507b2b24dbd95ae6fb4c674f13142ee8e3 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Wed, 27 Nov 2024 14:02:13 -0300 Subject: [PATCH 1/2] Allow suffixed pure functions that are exposed to the host --- crates/compiler/can/src/constraint.rs | 7 +++ crates/compiler/load/tests/helpers/mod.rs | 5 ++- crates/compiler/load/tests/test_reporting.rs | 45 +++----------------- crates/compiler/load_internal/src/file.rs | 19 +++++++++ crates/compiler/solve/src/module.rs | 4 +- crates/compiler/solve/src/solve.rs | 20 +++++++-- crates/compiler/test_derive/src/util.rs | 1 + crates/test_compile/src/help_solve.rs | 1 + 8 files changed, 57 insertions(+), 45 deletions(-) diff --git a/crates/compiler/can/src/constraint.rs b/crates/compiler/can/src/constraint.rs index dd9700a4abf..d71d56f0c03 100644 --- a/crates/compiler/can/src/constraint.rs +++ b/crates/compiler/can/src/constraint.rs @@ -957,6 +957,13 @@ impl FxSuffixKind { Self::UnsuffixedRecordField => IdentSuffix::None, } } + + pub fn symbol(&self) -> Option<&Symbol> { + match self { + Self::Let(symbol) | Self::Pattern(symbol) => Some(symbol), + Self::UnsuffixedRecordField => None, + } + } } #[derive(Debug, Clone, Copy, PartialEq)] diff --git a/crates/compiler/load/tests/helpers/mod.rs b/crates/compiler/load/tests/helpers/mod.rs index b5d2cc95182..89a3c6236eb 100644 --- a/crates/compiler/load/tests/helpers/mod.rs +++ b/crates/compiler/load/tests/helpers/mod.rs @@ -51,10 +51,11 @@ pub fn infer_expr( exposed_by_module: &Default::default(), derived_module, function_kind: FunctionKind::LambdaSet, - #[cfg(debug_assertions)] - checkmate: None, module_params: None, module_params_vars: Default::default(), + host_exposed_symbols: None, + #[cfg(debug_assertions)] + checkmate: None, }; let RunSolveOutput { solved, .. } = diff --git a/crates/compiler/load/tests/test_reporting.rs b/crates/compiler/load/tests/test_reporting.rs index 9d216db538f..541e3a04be6 100644 --- a/crates/compiler/load/tests/test_reporting.rs +++ b/crates/compiler/load/tests/test_reporting.rs @@ -14812,7 +14812,7 @@ All branches in an `if` must have the same type! Str.trim msg "# ), - @r###" + @r#" ── EFFECT IN PURE FUNCTION in /code/proj/Main.roc ────────────────────────────── This call to `Effect.putLine!` might produce an effect: @@ -14829,18 +14829,7 @@ All branches in an `if` must have the same type! You can still run the program with this error, which can be helpful when you're debugging. - - ── UNNECESSARY EXCLAMATION in /code/proj/Main.roc ────────────────────────────── - - This function is pure, but its name suggests otherwise: - - 5│ main! = \{} -> - ^^^^^ - - The exclamation mark at the end is reserved for effectful functions. - - Hint: Did you forget to run an effect? Is the type annotation wrong? - "### + "# ); test_report!( @@ -15423,7 +15412,7 @@ All branches in an `if` must have the same type! pureHigherOrder = \f, x -> f x "# ), - @r###" + @r#" ── TYPE MISMATCH in /code/proj/Main.roc ──────────────────────────────────────── This 1st argument to `pureHigherOrder` has an unexpected type: @@ -15438,18 +15427,7 @@ All branches in an `if` must have the same type! But `pureHigherOrder` needs its 1st argument to be: Str -> {} - - ── UNNECESSARY EXCLAMATION in /code/proj/Main.roc ────────────────────────────── - - This function is pure, but its name suggests otherwise: - - 5│ main! = \{} -> - ^^^^^ - - The exclamation mark at the end is reserved for effectful functions. - - Hint: Did you forget to run an effect? Is the type annotation wrong? - "### + "# ); test_report!( @@ -15467,7 +15445,7 @@ All branches in an `if` must have the same type! pureHigherOrder = \f, x -> f x "# ), - @r###" + @r#" ── TYPE MISMATCH in /code/proj/Main.roc ──────────────────────────────────────── This 1st argument to `pureHigherOrder` has an unexpected type: @@ -15482,17 +15460,6 @@ All branches in an `if` must have the same type! But `pureHigherOrder` needs its 1st argument to be: Str -> {} - - ── UNNECESSARY EXCLAMATION in /code/proj/Main.roc ────────────────────────────── - - This function is pure, but its name suggests otherwise: - - 5│ main! = \{} -> - ^^^^^ - - The exclamation mark at the end is reserved for effectful functions. - - Hint: Did you forget to run an effect? Is the type annotation wrong? - "### + "# ); } diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index ff7ce3a50cb..1899315113c 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -350,6 +350,8 @@ fn start_phase<'a>( None }; + let is_host_exposed = state.root_id == module.module_id; + BuildTask::solve_module( module, ident_ids, @@ -367,6 +369,7 @@ fn start_phase<'a>( state.cached_types.clone(), derived_module, state.exec_mode, + is_host_exposed, // #[cfg(debug_assertions)] checkmate, @@ -922,6 +925,7 @@ enum BuildTask<'a> { cached_subs: CachedTypeState, derived_module: SharedDerivedModule, exec_mode: ExecutionMode, + is_host_exposed: bool, #[cfg(debug_assertions)] checkmate: Option, @@ -4331,6 +4335,7 @@ impl<'a> BuildTask<'a> { cached_subs: CachedTypeState, derived_module: SharedDerivedModule, exec_mode: ExecutionMode, + is_host_exposed: bool, #[cfg(debug_assertions)] checkmate: Option, ) -> Self { @@ -4355,6 +4360,7 @@ impl<'a> BuildTask<'a> { cached_subs, derived_module, exec_mode, + is_host_exposed, #[cfg(debug_assertions)] checkmate, @@ -4661,6 +4667,7 @@ fn run_solve_solve( var_store: VarStore, module: Module, derived_module: SharedDerivedModule, + is_host_exposed: bool, #[cfg(debug_assertions)] checkmate: Option, ) -> SolveResult { @@ -4711,6 +4718,12 @@ fn run_solve_solve( let (solve_output, solved_implementations, exposed_vars_by_symbol) = { let module_id = module.module_id; + let host_exposed_idents = if is_host_exposed { + Some(&exposed_symbols) + } else { + None + }; + let solve_config = SolveConfig { home: module_id, types, @@ -4724,6 +4737,7 @@ fn run_solve_solve( checkmate, module_params, module_params_vars: imported_param_vars, + host_exposed_symbols: host_exposed_idents, }; let solve_output = roc_solve::module::run_solve( @@ -4800,6 +4814,7 @@ fn run_solve<'a>( cached_types: CachedTypeState, derived_module: SharedDerivedModule, exec_mode: ExecutionMode, + is_host_exposed: bool, #[cfg(debug_assertions)] checkmate: Option, ) -> Msg<'a> { @@ -4831,6 +4846,7 @@ fn run_solve<'a>( var_store, module, derived_module, + is_host_exposed, // #[cfg(debug_assertions)] checkmate, @@ -4863,6 +4879,7 @@ fn run_solve<'a>( var_store, module, derived_module, + is_host_exposed, // #[cfg(debug_assertions)] checkmate, @@ -6256,6 +6273,7 @@ fn run_task<'a>( cached_subs, derived_module, exec_mode, + is_host_exposed, #[cfg(debug_assertions)] checkmate, @@ -6275,6 +6293,7 @@ fn run_task<'a>( cached_subs, derived_module, exec_mode, + is_host_exposed, // #[cfg(debug_assertions)] checkmate, diff --git a/crates/compiler/solve/src/module.rs b/crates/compiler/solve/src/module.rs index e8a5e3a0b61..ea5a7b0b6d5 100644 --- a/crates/compiler/solve/src/module.rs +++ b/crates/compiler/solve/src/module.rs @@ -6,7 +6,7 @@ use roc_can::constraint::{Constraint, Constraints}; use roc_can::expr::PendingDerives; use roc_can::module::{ExposedByModule, ModuleParams, ResolvedImplementations, RigidVariables}; use roc_collections::all::MutMap; -use roc_collections::VecMap; +use roc_collections::{VecMap, VecSet}; use roc_derive::SharedDerivedModule; use roc_error_macros::internal_error; use roc_module::symbol::{ModuleId, Symbol}; @@ -76,6 +76,8 @@ pub struct SolveConfig<'a> { /// Needed during solving to resolve lambda sets from derived implementations that escape into /// the user module. pub derived_module: SharedDerivedModule, + /// + pub host_exposed_symbols: Option<&'a VecSet>, #[cfg(debug_assertions)] /// The checkmate collector for this module. diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index ca0df7cabe6..e45907f8ca5 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -19,7 +19,7 @@ use roc_can::constraint::{ }; use roc_can::expected::{Expected, PExpected}; use roc_can::module::ModuleParams; -use roc_collections::VecMap; +use roc_collections::{VecMap, VecSet}; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] use roc_debug_flags::ROC_VERIFY_RIGID_LET_GENERALIZED; @@ -136,6 +136,7 @@ fn run_help( function_kind, module_params, module_params_vars, + host_exposed_symbols, .. } = config; @@ -190,6 +191,7 @@ fn run_help( &mut awaiting_specializations, module_params, module_params_vars, + host_exposed_symbols, ); RunSolveOutput { @@ -249,6 +251,7 @@ fn solve( awaiting_specializations: &mut AwaitingSpecializations, module_params: Option, module_params_vars: VecMap, + host_exposed_symbols: Option<&VecSet>, ) -> State { let scope = Scope::new(module_params); @@ -455,6 +458,7 @@ fn solve( solve_suffix_fx( env, problems, + host_exposed_symbols, FxSuffixKind::Let(*symbol), loc_var.value, &loc_var.region, @@ -853,7 +857,7 @@ fn solve( *type_index, ); - solve_suffix_fx(env, problems, *kind, actual, region); + solve_suffix_fx(env, problems, host_exposed_symbols, *kind, actual, region); state } ExpectEffectful(variable, reason, region) => { @@ -1625,6 +1629,7 @@ fn solve( fn solve_suffix_fx( env: &mut InferenceEnv<'_>, problems: &mut Vec, + host_exposed_symbols: Option<&VecSet>, kind: FxSuffixKind, variable: Variable, region: &Region, @@ -1651,7 +1656,16 @@ fn solve_suffix_fx( let fx = *fx; match env.subs.get_content_without_compacting(fx) { Content::Pure => { - problems.push(TypeError::SuffixedPureFunction(*region, kind)); + match (kind.symbol(), host_exposed_symbols) { + (Some(sym), Some(host_exposed)) if host_exposed.contains(sym) => { + // If exposed to the platform, it's allowed to be suffixed but pure + // The platform might require a `main!` function that could perform + // effects, but that's not a requirement. + } + _ => { + problems.push(TypeError::SuffixedPureFunction(*region, kind)); + } + } } Content::FlexVar(_) => { env.subs.set_content(fx, Content::Effectful); diff --git a/crates/compiler/test_derive/src/util.rs b/crates/compiler/test_derive/src/util.rs index 811e6b117e9..d5897ec660f 100644 --- a/crates/compiler/test_derive/src/util.rs +++ b/crates/compiler/test_derive/src/util.rs @@ -439,6 +439,7 @@ fn check_derived_typechecks_and_golden( derived_module: Default::default(), module_params: None, module_params_vars: imported_param_vars, + host_exposed_symbols: None, #[cfg(debug_assertions)] checkmate: None, diff --git a/crates/test_compile/src/help_solve.rs b/crates/test_compile/src/help_solve.rs index 192a1ef24cb..7002e365348 100644 --- a/crates/test_compile/src/help_solve.rs +++ b/crates/test_compile/src/help_solve.rs @@ -49,6 +49,7 @@ impl SolvedExpr { derived_module: SharedDerivedModule::default(), module_params: None, module_params_vars: VecMap::default(), + host_exposed_symbols: None, #[cfg(debug_assertions)] checkmate: None, }; From 2e7e67019f302c66dbfdbf7ae2cc2c393d6c4499 Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Wed, 27 Nov 2024 14:15:08 -0300 Subject: [PATCH 2/2] Add doc comment to SolveConfig.host_exposed_symbols --- crates/compiler/solve/src/module.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/solve/src/module.rs b/crates/compiler/solve/src/module.rs index ea5a7b0b6d5..721e4b6c25a 100644 --- a/crates/compiler/solve/src/module.rs +++ b/crates/compiler/solve/src/module.rs @@ -76,7 +76,7 @@ pub struct SolveConfig<'a> { /// Needed during solving to resolve lambda sets from derived implementations that escape into /// the user module. pub derived_module: SharedDerivedModule, - /// + /// Symbols that are exposed to the host which might need special treatment. pub host_exposed_symbols: Option<&'a VecSet>, #[cfg(debug_assertions)]