diff --git a/crates/compiler/can/src/constraint.rs b/crates/compiler/can/src/constraint.rs index e22cb2fe453..4e9fc1a4c0d 100644 --- a/crates/compiler/can/src/constraint.rs +++ b/crates/compiler/can/src/constraint.rs @@ -64,6 +64,26 @@ pub type PExpectedTypeIndex = Index>; pub type TypeOrVar = EitherIndex; impl Constraints { + pub fn empty() -> Self { + Self { + constraints: Default::default(), + type_slices: Default::default(), + variables: Default::default(), + loc_symbols: Default::default(), + let_constraints: Default::default(), + categories: Default::default(), + pattern_categories: Default::default(), + expectations: Default::default(), + pattern_expectations: Default::default(), + includes_tags: Default::default(), + strings: Default::default(), + sketched_rows: Default::default(), + eq: Default::default(), + pattern_eq: Default::default(), + cycles: Default::default(), + } + } + pub fn new() -> Self { let constraints = Vec::new(); let type_slices = Vec::with_capacity(16); diff --git a/crates/compiler/late_solve/src/lib.rs b/crates/compiler/late_solve/src/lib.rs index 15098bda7aa..96afef7d067 100644 --- a/crates/compiler/late_solve/src/lib.rs +++ b/crates/compiler/late_solve/src/lib.rs @@ -5,6 +5,7 @@ use std::sync::{Arc, RwLock}; use bumpalo::Bump; use roc_can::abilities::AbilitiesStore; +use roc_can::constraint::Constraints; use roc_can::module::ExposedByModule; use roc_collections::MutMap; use roc_derive::SharedDerivedModule; @@ -12,13 +13,14 @@ use roc_error_macros::internal_error; use roc_module::symbol::ModuleId; use roc_module::symbol::Symbol; use roc_solve::ability::AbilityResolver; -use roc_solve::solve::Pools; -use roc_solve::specialize::{compact_lambda_sets_of_vars, DerivedEnv, Phase}; +use roc_solve::specialize::{compact_lambda_sets_of_vars, Phase}; +use roc_solve::Pools; +use roc_solve::{DerivedEnv, Env}; use roc_types::subs::{get_member_lambda_sets_at_region, Content, FlatType, LambdaSet}; use roc_types::subs::{ExposedTypesStorageSubs, Subs, Variable}; use roc_types::types::Polarity; use roc_unify::unify::MetaCollector; -use roc_unify::unify::{Env, Mode, Unified}; +use roc_unify::unify::{Env as UEnv, Mode, Unified}; pub use roc_solve::ability::{ResolveError, Resolved}; pub use roc_types::subs::instantiate_rigids; @@ -340,6 +342,19 @@ impl MetaCollector for ChangedVariableCollector { } } +std::thread_local! { + static SCRATCHPAD_FOR_OCCURS: std::cell::RefCell> = std::cell::RefCell::new(Some(Constraints::empty())); +} + +fn with_empty_solve_constraints(f: impl FnOnce(&Constraints) -> T) -> T { + SCRATCHPAD_FOR_OCCURS.with(|cell| { + let constr = cell.take().unwrap(); + let result = f(&constr); + cell.replace(Some(constr)); + result + }) +} + /// Unifies two variables and performs lambda set compaction. /// Ranks and other ability demands are disregarded. #[allow(clippy::too_many_arguments)] @@ -359,7 +374,7 @@ pub fn unify( "derived module can only unify its subs in its own context!" ); let unified = roc_unify::unify::unify_with_collector::( - &mut Env::new(subs), + &mut UEnv::new(subs), left, right, Mode::EQ, @@ -381,14 +396,17 @@ pub fn unify( exposed_types: exposed_by_module, }; - let must_implement_constraints = compact_lambda_sets_of_vars( - subs, - &derived_env, - arena, - &mut pools, - lambda_sets_to_specialize, - &late_phase, - ); + let must_implement_constraints = with_empty_solve_constraints(|c| { + let mut env = Env { + constraints: c, + subs, + derived_env: &derived_env, + arena, + pools: &mut pools, + }; + + compact_lambda_sets_of_vars(&mut env, lambda_sets_to_specialize, &late_phase) + }); // At this point we can't do anything with must-implement constraints, since we're no // longer solving. We must assume that they were totally caught during solving. // After we land https://github.com/roc-lang/roc/issues/3207 this concern should totally diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs index 7c0045af3c6..532802d9d91 100644 --- a/crates/compiler/load_internal/src/file.rs +++ b/crates/compiler/load_internal/src/file.rs @@ -56,7 +56,7 @@ use roc_region::all::{LineInfo, Loc, Region}; #[cfg(not(target_family = "wasm"))] use roc_reporting::report::to_https_problem_report_string; use roc_reporting::report::{to_file_problem_report_string, Palette, RenderTarget}; -use roc_solve::module::{extract_module_owned_implementations, Solved, SolvedModule}; +use roc_solve::module::{extract_module_owned_implementations, SolveConfig, Solved, SolvedModule}; use roc_solve_problem::TypeError; use roc_target::TargetInfo; use roc_types::subs::{CopiedImport, ExposedTypesStorageSubs, Subs, VarStore, Variable}; @@ -5061,6 +5061,14 @@ fn import_variable_for_symbol( } } +struct SolveResult { + solved: Solved, + solved_implementations: ResolvedImplementations, + exposed_vars_by_symbol: Vec<(Symbol, Variable)>, + problems: Vec, + abilities_store: AbilitiesStore, +} + #[allow(clippy::complexity)] fn run_solve_solve( exposed_for_module: ExposedForModule, @@ -5071,13 +5079,7 @@ fn run_solve_solve( var_store: VarStore, module: Module, derived_module: SharedDerivedModule, -) -> ( - Solved, - ResolvedImplementations, - Vec<(Symbol, Variable)>, - Vec, - AbilitiesStore, -) { +) -> SolveResult { let Module { exposed_symbols, aliases, @@ -5111,30 +5113,34 @@ fn run_solve_solve( &import_variables, ); - let mut solve_aliases = roc_solve::solve::Aliases::with_capacity(aliases.len()); + let mut solve_aliases = roc_solve::Aliases::with_capacity(aliases.len()); for (name, (_, alias)) in aliases.iter() { solve_aliases.insert(&mut types, *name, alias.clone()); } - let (solved_subs, solved_implementations, exposed_vars_by_symbol, problems, abilities_store) = { + let (solve_output, solved_implementations, exposed_vars_by_symbol) = { let module_id = module.module_id; - let (solved_subs, solved_env, problems, abilities_store) = roc_solve::module::run_solve( - module_id, + let solve_config = SolveConfig { + home: module_id, types, - &constraints, - actual_constraint, + constraints: &constraints, + root_constraint: actual_constraint, + pending_derives, + exposed_by_module: &exposed_for_module.exposed_by_module, + derived_module, + }; + + let solve_output = roc_solve::module::run_solve( + solve_config, rigid_variables, subs, solve_aliases, abilities_store, - pending_derives, - &exposed_for_module.exposed_by_module, - derived_module, ); let solved_implementations = - extract_module_owned_implementations(module_id, &abilities_store); + extract_module_owned_implementations(module_id, &solve_output.resolved_abilities_store); let is_specialization_symbol = |sym| { solved_implementations @@ -5147,7 +5153,8 @@ fn run_solve_solve( // Expose anything that is explicitly exposed by the header, or is a specialization of an // ability. - let exposed_vars_by_symbol: Vec<_> = solved_env + let exposed_vars_by_symbol: Vec<_> = solve_output + .scope .vars_by_symbol() .filter(|(k, _)| { exposed_symbols.contains(k) @@ -5156,22 +5163,23 @@ fn run_solve_solve( }) .collect(); - ( - solved_subs, - solved_implementations, - exposed_vars_by_symbol, - problems, - abilities_store, - ) + (solve_output, solved_implementations, exposed_vars_by_symbol) }; - ( - solved_subs, + let roc_solve::module::SolveOutput { + subs, + scope: _, + errors, + resolved_abilities_store, + } = solve_output; + + SolveResult { + solved: subs, solved_implementations, exposed_vars_by_symbol, - problems, - abilities_store, - ) + problems: errors, + abilities_store: resolved_abilities_store, + } } fn run_solve<'a>( @@ -5201,7 +5209,7 @@ fn run_solve<'a>( let loc_dbgs = std::mem::take(&mut module.loc_dbgs); let module = module; - let (solved_subs, solved_implementations, exposed_vars_by_symbol, problems, abilities_store) = { + let solve_result = { if module_id.is_builtin() { match cached_types.lock().remove(&module_id) { None => run_solve_solve( @@ -5219,13 +5227,13 @@ fn run_solve<'a>( exposed_vars_by_symbol, abilities, solved_implementations, - }) => ( - Solved(subs), + }) => SolveResult { + solved: Solved(subs), solved_implementations, exposed_vars_by_symbol, - vec![], - abilities, - ), + problems: vec![], + abilities_store: abilities, + }, } } else { run_solve_solve( @@ -5241,7 +5249,14 @@ fn run_solve<'a>( } }; - let mut solved_subs = solved_subs; + let SolveResult { + solved: mut solved_subs, + solved_implementations, + exposed_vars_by_symbol, + problems, + abilities_store, + } = solve_result; + let exposed_types = roc_solve::module::exposed_types_storage_subs( module_id, &mut solved_subs, diff --git a/crates/compiler/solve/src/ability.rs b/crates/compiler/solve/src/ability.rs index 47d0ba15a3d..c5ab1f60333 100644 --- a/crates/compiler/solve/src/ability.rs +++ b/crates/compiler/solve/src/ability.rs @@ -18,11 +18,11 @@ use roc_types::subs::{ TupleElems, Variable, }; use roc_types::types::{AliasKind, Category, MemberImpl, PatternCategory, Polarity, Types}; -use roc_unify::unify::{Env, MustImplementConstraints}; +use roc_unify::unify::{Env as UEnv, MustImplementConstraints}; use roc_unify::unify::{MustImplementAbility, Obligated}; -use crate::solve::type_to_var; -use crate::solve::{Aliases, Pools}; +use crate::env::Env; +use crate::{aliases::Aliases, to_var::type_to_var}; #[derive(Debug, Clone)] pub enum AbilityImplError { @@ -56,7 +56,7 @@ pub struct PendingDerivesTable( impl PendingDerivesTable { pub fn new( - subs: &mut Subs, + env: &mut Env, types: &mut Types, aliases: &mut Aliases, pending_derives: PendingDerives, @@ -81,17 +81,16 @@ impl PendingDerivesTable { // Neither rank nor pools should matter here. let typ = types.from_old_type(&typ); let opaque_var = type_to_var( - subs, + env, Rank::toplevel(), problems, abilities_store, obligation_cache, - &mut Pools::default(), types, aliases, typ, ); - let real_var = match subs.get_content_without_compacting(opaque_var) { + let real_var = match env.subs.get_content_without_compacting(opaque_var) { Content::Alias(_, _, real_var, AliasKind::Opaque) => real_var, _ => internal_error!("Non-opaque in derives table"), }; @@ -1284,7 +1283,7 @@ impl DerivableVisitor for DeriveEq { // Of the floating-point types, // only Dec implements Eq. - let mut env = Env::new(subs); + let mut env = UEnv::new(subs); let unified = unify( &mut env, content_var, @@ -1419,7 +1418,7 @@ pub fn resolve_ability_specialization( instantiate_rigids(subs, signature_var); let (_vars, must_implement_ability, _lambda_sets_to_specialize, _meta) = unify( - &mut Env::new(subs), + &mut UEnv::new(subs), specialization_var, signature_var, Mode::EQ, diff --git a/crates/compiler/solve/src/aliases.rs b/crates/compiler/solve/src/aliases.rs new file mode 100644 index 00000000000..ac8aca3b04e --- /dev/null +++ b/crates/compiler/solve/src/aliases.rs @@ -0,0 +1,329 @@ +use roc_can::abilities::AbilitiesStore; +use roc_collections::{soa::Index, MutMap}; +use roc_error_macros::internal_error; +use roc_module::symbol::Symbol; +use roc_solve_problem::TypeError; +use roc_types::{ + subs::{AliasVariables, Content, FlatType, Rank, Subs, SubsSlice, TagExt, UnionTags, Variable}, + types::{Alias, AliasKind, OptAbleVar, Type, TypeTag, Types}, +}; + +use crate::to_var::type_to_var_help; +use crate::{ability::ObligationCache, env::Env}; + +#[derive(Debug, Clone, Copy)] +struct DelayedAliasVariables { + start: u32, + type_variables_len: u8, + lambda_set_variables_len: u8, + recursion_variables_len: u8, + infer_ext_in_output_variables_len: u8, +} + +impl DelayedAliasVariables { + fn recursion_variables(self, variables: &mut [OptAbleVar]) -> &mut [OptAbleVar] { + let start = self.start as usize + + (self.type_variables_len + self.lambda_set_variables_len) as usize; + let length = self.recursion_variables_len as usize; + + &mut variables[start..][..length] + } + + fn lambda_set_variables(self, variables: &mut [OptAbleVar]) -> &mut [OptAbleVar] { + let start = self.start as usize + self.type_variables_len as usize; + let length = self.lambda_set_variables_len as usize; + + &mut variables[start..][..length] + } + + fn type_variables(self, variables: &mut [OptAbleVar]) -> &mut [OptAbleVar] { + let start = self.start as usize; + let length = self.type_variables_len as usize; + + &mut variables[start..][..length] + } + + fn infer_ext_in_output_variables(self, variables: &mut [OptAbleVar]) -> &mut [OptAbleVar] { + let start = self.start as usize + + (self.type_variables_len + + self.lambda_set_variables_len + + self.recursion_variables_len) as usize; + let length = self.infer_ext_in_output_variables_len as usize; + + &mut variables[start..][..length] + } +} + +#[derive(Debug, Default)] +pub struct Aliases { + aliases: Vec<(Symbol, Index, DelayedAliasVariables, AliasKind)>, + variables: Vec, +} + +impl Aliases { + pub fn with_capacity(cap: usize) -> Self { + Self { + aliases: Vec::with_capacity(cap), + variables: Vec::with_capacity(cap * 2), + } + } + + pub fn insert(&mut self, types: &mut Types, symbol: Symbol, alias: Alias) { + let alias_variables = + { + let start = self.variables.len() as _; + + self.variables.extend( + alias + .type_variables + .iter() + .map(|x| OptAbleVar::from(&x.value)), + ); + + self.variables.extend(alias.lambda_set_variables.iter().map( + |x| match x.as_inner() { + Type::Variable(v) => OptAbleVar::unbound(*v), + _ => unreachable!("lambda set type is not a variable"), + }, + )); + + let recursion_variables_len = alias.recursion_variables.len() as _; + self.variables.extend( + alias + .recursion_variables + .iter() + .copied() + .map(OptAbleVar::unbound), + ); + + self.variables.extend( + alias + .infer_ext_in_output_variables + .iter() + .map(|v| OptAbleVar::unbound(*v)), + ); + + DelayedAliasVariables { + start, + type_variables_len: alias.type_variables.len() as _, + lambda_set_variables_len: alias.lambda_set_variables.len() as _, + recursion_variables_len, + infer_ext_in_output_variables_len: alias.infer_ext_in_output_variables.len() + as _, + } + }; + + // TODO: can we construct Aliases from TypeTag directly? + let alias_typ = types.from_old_type(&alias.typ); + + self.aliases + .push((symbol, alias_typ, alias_variables, alias.kind)); + } + + fn instantiate_result_result( + env: &mut Env, + rank: Rank, + alias_variables: AliasVariables, + ) -> Variable { + let tag_names_slice = Subs::RESULT_TAG_NAMES; + + let err_slice = SubsSlice::new(alias_variables.variables_start + 1, 1); + let ok_slice = SubsSlice::new(alias_variables.variables_start, 1); + + let variable_slices = + SubsSlice::extend_new(&mut env.subs.variable_slices, [err_slice, ok_slice]); + + let union_tags = UnionTags::from_slices(tag_names_slice, variable_slices); + let ext_var = TagExt::Any(Variable::EMPTY_TAG_UNION); + let flat_type = FlatType::TagUnion(union_tags, ext_var); + let content = Content::Structure(flat_type); + + env.register(rank, content) + } + + /// Build an alias of the form `Num range := range` + fn build_num_opaque( + env: &mut Env, + rank: Rank, + symbol: Symbol, + range_var: Variable, + ) -> Variable { + let content = Content::Alias( + symbol, + AliasVariables::insert_into_subs(env.subs, [range_var], [], []), + range_var, + AliasKind::Opaque, + ); + + env.register(rank, content) + } + + fn instantiate_builtin_aliases_real_var( + &mut self, + env: &mut Env, + rank: Rank, + symbol: Symbol, + alias_variables: AliasVariables, + ) -> Option<(Variable, AliasKind)> { + match symbol { + Symbol::RESULT_RESULT => { + let var = Self::instantiate_result_result(env, rank, alias_variables); + + Some((var, AliasKind::Structural)) + } + Symbol::NUM_NUM | Symbol::NUM_INTEGER | Symbol::NUM_FLOATINGPOINT => { + // Num range := range | Integer range := range | FloatingPoint range := range + let range_var = env.subs.variables[alias_variables.variables_start as usize]; + Some((range_var, AliasKind::Opaque)) + } + Symbol::NUM_INT => { + // Int range : Num (Integer range) + // + // build `Integer range := range` + let integer_content_var = Self::build_num_opaque( + env, + rank, + Symbol::NUM_INTEGER, + env.subs.variables[alias_variables.variables_start as usize], + ); + + // build `Num (Integer range) := Integer range` + let num_content_var = + Self::build_num_opaque(env, rank, Symbol::NUM_NUM, integer_content_var); + + Some((num_content_var, AliasKind::Structural)) + } + Symbol::NUM_FRAC => { + // Frac range : Num (FloatingPoint range) + // + // build `FloatingPoint range := range` + let fpoint_content_var = Self::build_num_opaque( + env, + rank, + Symbol::NUM_FLOATINGPOINT, + env.subs.variables[alias_variables.variables_start as usize], + ); + + // build `Num (FloatingPoint range) := FloatingPoint range` + let num_content_var = + Self::build_num_opaque(env, rank, Symbol::NUM_NUM, fpoint_content_var); + + Some((num_content_var, AliasKind::Structural)) + } + Symbol::NUM_SIGNED8 => Some((Variable::SIGNED8, AliasKind::Opaque)), + Symbol::NUM_SIGNED16 => Some((Variable::SIGNED16, AliasKind::Opaque)), + Symbol::NUM_SIGNED32 => Some((Variable::SIGNED32, AliasKind::Opaque)), + Symbol::NUM_SIGNED64 => Some((Variable::SIGNED64, AliasKind::Opaque)), + Symbol::NUM_SIGNED128 => Some((Variable::SIGNED128, AliasKind::Opaque)), + Symbol::NUM_UNSIGNED8 => Some((Variable::UNSIGNED8, AliasKind::Opaque)), + Symbol::NUM_UNSIGNED16 => Some((Variable::UNSIGNED16, AliasKind::Opaque)), + Symbol::NUM_UNSIGNED32 => Some((Variable::UNSIGNED32, AliasKind::Opaque)), + Symbol::NUM_UNSIGNED64 => Some((Variable::UNSIGNED64, AliasKind::Opaque)), + Symbol::NUM_UNSIGNED128 => Some((Variable::UNSIGNED128, AliasKind::Opaque)), + Symbol::NUM_BINARY32 => Some((Variable::BINARY32, AliasKind::Opaque)), + Symbol::NUM_BINARY64 => Some((Variable::BINARY64, AliasKind::Opaque)), + _ => None, + } + } + + pub fn instantiate_real_var( + &mut self, + env: &mut Env, + rank: Rank, + problems: &mut Vec, + abilities_store: &AbilitiesStore, + obligation_cache: &mut ObligationCache, + arena: &bumpalo::Bump, + types: &mut Types, + symbol: Symbol, + alias_variables: AliasVariables, + ) -> (Variable, AliasKind) { + // hardcoded instantiations for builtin aliases + if let Some((var, kind)) = + self.instantiate_builtin_aliases_real_var(env, rank, symbol, alias_variables) + { + return (var, kind); + } + + let (typ, delayed_variables, kind) = + match self.aliases.iter().find(|(s, _, _, _)| *s == symbol) { + None => internal_error!( + "Alias {:?} not registered in delayed aliases! {:?}", + symbol, + &self.aliases + ), + Some(&(_, typ, delayed_variables, kind)) => (typ, delayed_variables, kind), + }; + + let mut substitutions: MutMap<_, _> = Default::default(); + + let old_type_variables = delayed_variables.type_variables(&mut self.variables); + let new_type_variables = &env.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); + } + } + + for OptAbleVar { + var: rec_var, + opt_abilities, + } in delayed_variables + .recursion_variables(&mut self.variables) + .iter_mut() + { + debug_assert!(opt_abilities.is_none()); + let new_var = env.subs.fresh_unnamed_flex_var(); + substitutions.insert(*rec_var, new_var); + } + + let old_lambda_set_variables = delayed_variables.lambda_set_variables(&mut self.variables); + let new_lambda_set_variables = + &env.subs.variables[alias_variables.lambda_set_variables().indices()]; + + for (old, new) in old_lambda_set_variables + .iter_mut() + .zip(new_lambda_set_variables) + { + debug_assert!(old.opt_abilities.is_none()); + if old.var != *new { + substitutions.insert(old.var, *new); + } + } + + let old_infer_ext_vars = + delayed_variables.infer_ext_in_output_variables(&mut self.variables); + let new_infer_ext_vars = + &env.subs.variables[alias_variables.infer_ext_in_output_variables().indices()]; + + for (old, new) in old_infer_ext_vars.iter_mut().zip(new_infer_ext_vars) { + debug_assert!(old.opt_abilities.is_none()); + if old.var != *new { + substitutions.insert(old.var, *new); + } + } + + let typ = if !substitutions.is_empty() { + types.clone_with_variable_substitutions(typ, &substitutions) + } else { + typ + }; + + let alias_variable = type_to_var_help( + env, + rank, + problems, + abilities_store, + obligation_cache, + arena, + self, + types, + typ, + false, + ); + (alias_variable, kind) + } +} diff --git a/crates/compiler/solve/src/deep_copy.rs b/crates/compiler/solve/src/deep_copy.rs new file mode 100644 index 00000000000..12eab6def5d --- /dev/null +++ b/crates/compiler/solve/src/deep_copy.rs @@ -0,0 +1,370 @@ +use std::ops::ControlFlow; + +use bumpalo::Bump; +use roc_error_macros::internal_error; +use roc_types::{ + subs::{ + self, AliasVariables, Content, Descriptor, FlatType, GetSubsSlice, Mark, OptVariable, Rank, + RecordFields, Subs, SubsSlice, TagExt, TupleElems, UnionLabels, Variable, + }, + types::{RecordField, Uls}, +}; + +use crate::env::Env; + +// TODO: eventually, we could possibly use the arena in Env instead. +pub(crate) fn deep_copy_var_in(env: &mut Env, rank: Rank, var: Variable, arena: &Bump) -> Variable { + let mut visited = bumpalo::collections::Vec::with_capacity_in(256, arena); + + let pool = env.pools.get_mut(rank); + + let var = env.subs.get_root_key(var); + match deep_copy_var_decision(env.subs, rank, var) { + ControlFlow::Break(copy) => copy, + ControlFlow::Continue(copy) => { + deep_copy_var_help(env.subs, rank, pool, &mut visited, var, copy); + + // we have tracked all visited variables, and can now traverse them + // in one go (without looking at the UnificationTable) and clear the copy field + for var in visited { + env.subs.set_copy_unchecked(var, OptVariable::NONE); + } + + copy + } + } +} + +#[inline] +fn has_trivial_copy(subs: &Subs, root_var: Variable) -> Option { + let existing_copy = subs.get_copy_unchecked(root_var); + + if let Some(copy) = existing_copy.into_variable() { + Some(copy) + } else if subs.get_rank_unchecked(root_var) != Rank::GENERALIZED { + Some(root_var) + } else { + None + } +} + +#[inline] +fn deep_copy_var_decision( + subs: &mut Subs, + max_rank: Rank, + var: Variable, +) -> ControlFlow { + let var = subs.get_root_key(var); + if let Some(copy) = has_trivial_copy(subs, var) { + ControlFlow::Break(copy) + } else { + let copy_descriptor = Descriptor { + content: Content::Structure(FlatType::EmptyTagUnion), + rank: max_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + let copy = subs.fresh(copy_descriptor); + + // Link the original variable to the new variable. This lets us + // avoid making multiple copies of the variable we are instantiating. + // + // Need to do this before recursively copying to avoid looping. + subs.set_mark_unchecked(var, Mark::NONE); + subs.set_copy_unchecked(var, copy.into()); + + ControlFlow::Continue(copy) + } +} + +fn deep_copy_var_help( + subs: &mut Subs, + max_rank: Rank, + pool: &mut Vec, + visited: &mut bumpalo::collections::Vec<'_, Variable>, + initial_source: Variable, + initial_copy: Variable, +) -> Variable { + use roc_types::subs::Content::*; + use roc_types::subs::FlatType::*; + + struct DeepCopyVarWork { + source: Variable, + copy: Variable, + } + + let initial = DeepCopyVarWork { + source: initial_source, + copy: initial_copy, + }; + let mut stack = vec![initial]; + + macro_rules! work { + ($variable:expr) => {{ + let var = subs.get_root_key($variable); + match deep_copy_var_decision(subs, max_rank, var) { + ControlFlow::Break(copy) => copy, + ControlFlow::Continue(copy) => { + stack.push(DeepCopyVarWork { source: var, copy }); + + copy + } + } + }}; + } + + macro_rules! copy_sequence { + ($length:expr, $variables:expr) => {{ + let new_variables = SubsSlice::reserve_into_subs(subs, $length as _); + for (target_index, var_index) in (new_variables.indices()).zip($variables) { + let var = subs[var_index]; + let copy_var = work!(var); + subs.variables[target_index] = copy_var; + } + + new_variables + }}; + } + + macro_rules! copy_union { + ($tags:expr) => {{ + let new_variable_slices = SubsSlice::reserve_variable_slices(subs, $tags.len()); + + let it = (new_variable_slices.indices()).zip($tags.variables()); + for (target_index, index) in it { + let slice = subs[index]; + + let new_variables = copy_sequence!(slice.len(), slice); + subs.variable_slices[target_index] = new_variables; + } + + UnionLabels::from_slices($tags.labels(), new_variable_slices) + }}; + } + + // When generalizing annotations with `Openness` extensions + // we want to promote them to `Any`, so that usages at + // specialized sites can grow unboundedly and are not bound to + // openness-polymorphism. + macro_rules! copy_tag_ext { + ($ext:expr) => { + TagExt::Any(work!($ext.var())) + }; + } + + while let Some(DeepCopyVarWork { source: var, copy }) = stack.pop() { + visited.push(var); + pool.push(copy); + + let content = *subs.get_content_unchecked(var); + + // Now we recursively copy the content of the variable. + // We have already marked the variable as copied, so we + // will not repeat this work or crawl this variable again. + match content { + Structure(flat_type) => { + let new_flat_type = match flat_type { + Apply(symbol, arguments) => { + let new_arguments = copy_sequence!(arguments.len(), arguments); + + Apply(symbol, new_arguments) + } + + Func(arguments, closure_var, ret_var) => { + let new_ret_var = work!(ret_var); + let new_closure_var = work!(closure_var); + + let new_arguments = copy_sequence!(arguments.len(), arguments); + + Func(new_arguments, new_closure_var, new_ret_var) + } + + same @ EmptyRecord | same @ EmptyTuple | same @ EmptyTagUnion => same, + + Record(fields, ext_var) => { + let record_fields = { + let new_variables = + copy_sequence!(fields.len(), fields.iter_variables()); + + // When copying a let-generalized record to a specialized region, rigid + // optionals just become optionals. + let field_types = subs.get_subs_slice(fields.record_fields()); + let has_rigid_optional_field = field_types + .iter() + .any(|f| matches!(f, RecordField::RigidOptional(..))); + + let new_field_types_start = if has_rigid_optional_field { + let field_types = field_types.to_vec(); + let slice = SubsSlice::extend_new( + &mut subs.record_fields, + field_types.into_iter().map(|f| match f { + RecordField::RigidOptional(()) + | RecordField::RigidRequired(()) => internal_error!("Rigid optional/required should be generalized to non-rigid by this point"), + + RecordField::Demanded(_) + | RecordField::Required(_) + | RecordField::Optional(_) => f, + }), + ); + slice.start + } else { + fields.field_types_start + }; + + RecordFields { + length: fields.length, + field_names_start: fields.field_names_start, + variables_start: new_variables.start, + field_types_start: new_field_types_start, + } + }; + + Record(record_fields, work!(ext_var)) + } + + Tuple(elems, ext_var) => { + let tuple_elems = { + let new_variables = copy_sequence!(elems.len(), elems.iter_variables()); + + TupleElems { + length: elems.length, + variables_start: new_variables.start, + elem_index_start: elems.elem_index_start, + } + }; + + Tuple(tuple_elems, work!(ext_var)) + } + + TagUnion(tags, ext_var) => { + let union_tags = copy_union!(tags); + + TagUnion(union_tags, copy_tag_ext!(ext_var)) + } + + FunctionOrTagUnion(tag_name, symbol, ext_var) => { + FunctionOrTagUnion(tag_name, symbol, copy_tag_ext!(ext_var)) + } + + RecursiveTagUnion(rec_var, tags, ext_var) => { + let union_tags = copy_union!(tags); + + RecursiveTagUnion(work!(rec_var), union_tags, copy_tag_ext!(ext_var)) + } + }; + + subs.set_content_unchecked(copy, Structure(new_flat_type)); + } + + FlexVar(_) | FlexAbleVar(_, _) | Error => { + subs.set_content_unchecked(copy, content); + } + + RecursionVar { + opt_name, + structure, + } => { + let content = RecursionVar { + opt_name, + structure: work!(structure), + }; + + subs.set_content_unchecked(copy, content); + } + + RigidVar(name) => { + subs.set_content_unchecked(copy, FlexVar(Some(name))); + } + + RigidAbleVar(name, ability) => { + subs.set_content_unchecked(copy, FlexAbleVar(Some(name), ability)); + } + + Alias(symbol, arguments, real_type_var, kind) => { + let new_variables = + copy_sequence!(arguments.all_variables_len, arguments.all_variables()); + + let new_arguments = AliasVariables { + variables_start: new_variables.start, + ..arguments + }; + + let new_real_type_var = work!(real_type_var); + let new_content = Alias(symbol, new_arguments, new_real_type_var, kind); + + subs.set_content_unchecked(copy, new_content); + } + + LambdaSet(subs::LambdaSet { + solved, + recursion_var, + unspecialized, + ambient_function: ambient_function_var, + }) => { + let lambda_set_var = copy; + + let new_solved = copy_union!(solved); + let new_rec_var = recursion_var.map(|v| work!(v)); + let new_unspecialized = SubsSlice::reserve_uls_slice(subs, unspecialized.len()); + + for (new_uls_index, uls_index) in + (new_unspecialized.into_iter()).zip(unspecialized.into_iter()) + { + let Uls(var, sym, region) = subs[uls_index]; + let new_var = work!(var); + + deep_copy_uls_precondition(subs, var, new_var); + + subs[new_uls_index] = Uls(new_var, sym, region); + + subs.uls_of_var.add(new_var, lambda_set_var); + } + + let new_ambient_function_var = work!(ambient_function_var); + debug_assert_ne!( + ambient_function_var, new_ambient_function_var, + "lambda set cloned but its ambient function wasn't?" + ); + + subs.set_content_unchecked( + lambda_set_var, + LambdaSet(subs::LambdaSet { + solved: new_solved, + recursion_var: new_rec_var, + unspecialized: new_unspecialized, + ambient_function: new_ambient_function_var, + }), + ); + } + + RangedNumber(range) => { + let new_content = RangedNumber(range); + + subs.set_content_unchecked(copy, new_content); + } + } + } + + initial_copy +} + +#[inline(always)] +fn deep_copy_uls_precondition(subs: &Subs, original_var: Variable, new_var: Variable) { + if cfg!(debug_assertions) { + let content = subs.get_content_without_compacting(original_var); + + debug_assert!( + matches!( + content, + Content::FlexAbleVar(..) | Content::RigidAbleVar(..) + ), + "var in unspecialized lamba set is not bound to an ability, it is {:?}", + roc_types::subs::SubsFmtContent(content, subs) + ); + debug_assert!( + original_var != new_var, + "unspecialized lamba set var was not instantiated" + ); + } +} diff --git a/crates/compiler/solve/src/env.rs b/crates/compiler/solve/src/env.rs new file mode 100644 index 00000000000..d55a571a904 --- /dev/null +++ b/crates/compiler/solve/src/env.rs @@ -0,0 +1,81 @@ +use bumpalo::Bump; +use roc_can::{constraint::Constraints, module::ExposedByModule}; +use roc_derive::SharedDerivedModule; +use roc_types::subs::{Content, Descriptor, Mark, OptVariable, Rank, Subs, Variable}; +use roc_unify::unify::Env as UEnv; + +use crate::Pools; + +pub struct DerivedEnv<'a> { + pub derived_module: &'a SharedDerivedModule, + /// Exposed types needed by the derived module. + pub exposed_types: &'a ExposedByModule, +} + +pub struct Env<'a> { + pub arena: &'a Bump, + pub constraints: &'a Constraints, + pub derived_env: &'a DerivedEnv<'a>, + pub subs: &'a mut Subs, + pub pools: &'a mut Pools, +} + +impl<'a> Env<'a> { + #[inline(always)] + pub fn register(&mut self, rank: Rank, content: Content) -> Variable { + let descriptor = Descriptor { + content, + rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + let var = self.subs.fresh(descriptor); + + self.pools.get_mut(rank).push(var); + + var + } + + /// Introduce some variables to Pools at the given rank. + /// Also, set each of their ranks in Subs to be the given rank. + pub fn introduce(&mut self, rank: Rank, vars: &[Variable]) { + let pool: &mut Vec = self.pools.get_mut(rank); + + for &var in vars.iter() { + self.subs.set_rank(var, rank); + } + + pool.extend(vars); + } + + #[inline(always)] + pub fn register_existing_var(&mut self, var: Variable) { + self.pools.get_mut(self.subs.get_rank(var)).push(var); + } + + pub fn register_with_known_var( + &mut self, + var: Variable, + rank: Rank, + content: Content, + ) -> Variable { + let descriptor = Descriptor { + content, + rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }; + + self.subs.set(var, descriptor); + + self.pools.get_mut(rank).push(var); + + var + } + + /// Retrieves an environment for unification. + pub fn uenv(&mut self) -> UEnv { + UEnv::new(self.subs) + } +} diff --git a/crates/compiler/solve/src/lib.rs b/crates/compiler/solve/src/lib.rs index fa30f79aad4..d43fc7bff41 100644 --- a/crates/compiler/solve/src/lib.rs +++ b/crates/compiler/solve/src/lib.rs @@ -3,8 +3,20 @@ #![warn(clippy::dbg_macro)] // See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant)] +// TODO to be removed +#![allow(clippy::too_many_arguments)] pub mod ability; pub mod module; pub mod solve; pub mod specialize; + +mod aliases; +mod deep_copy; +mod env; +mod pools; +mod to_var; + +pub use aliases::Aliases; +pub use env::{DerivedEnv, Env}; +pub use pools::Pools; diff --git a/crates/compiler/solve/src/module.rs b/crates/compiler/solve/src/module.rs index 1920f46eefe..628fabd6d35 100644 --- a/crates/compiler/solve/src/module.rs +++ b/crates/compiler/solve/src/module.rs @@ -1,6 +1,6 @@ -use crate::solve::{self, Aliases}; +use crate::{aliases::Aliases, solve}; use roc_can::abilities::{AbilitiesStore, ResolvedImpl}; -use roc_can::constraint::{Constraint as ConstraintSoa, Constraints}; +use roc_can::constraint::{Constraint, Constraints}; use roc_can::expr::PendingDerives; use roc_can::module::{ExposedByModule, ResolvedImplementations, RigidVariables}; use roc_collections::all::MutMap; @@ -53,20 +53,41 @@ pub struct SolvedModule { pub exposed_types: ExposedTypesStorageSubs, } -#[allow(clippy::too_many_arguments)] // TODO: put params in a context/env var +pub struct SolveConfig<'a> { + /// The module we are solving. + pub home: ModuleId, + pub constraints: &'a Constraints, + pub root_constraint: Constraint, + /// All types introduced in the module. Canonicalized, but not necessarily yet associated with + /// a variable substitution. + pub types: Types, + /// Table of types introduced in this module that claim to derive an ability implementation. + /// Due for checking and instantiation after the solver runs over the module. + pub pending_derives: PendingDerives, + /// Types exposed by other modules. + /// Available for late instantiation of imports, lambda sets, or ability types. + pub exposed_by_module: &'a ExposedByModule, + /// The unique `#Derived` module, used to generate and retrieve derived ability + /// implementations. + /// Needed during solving to resolve lambda sets from derived implementations that escape into + /// the user module. + pub derived_module: SharedDerivedModule, +} + +pub struct SolveOutput { + pub subs: Solved, + pub scope: solve::Scope, + pub errors: Vec, + pub resolved_abilities_store: AbilitiesStore, +} + pub fn run_solve( - home: ModuleId, - types: Types, - constraints: &Constraints, - constraint: ConstraintSoa, + config: SolveConfig<'_>, rigid_variables: RigidVariables, mut subs: Subs, mut aliases: Aliases, mut abilities_store: AbilitiesStore, - pending_derives: PendingDerives, - exposed_by_module: &ExposedByModule, - derived_module: SharedDerivedModule, -) -> (Solved, solve::Env, Vec, AbilitiesStore) { +) -> SolveOutput { for (var, name) in rigid_variables.named { subs.rigid_var(var, name); } @@ -84,21 +105,20 @@ pub fn run_solve( let mut problems = Vec::new(); // Run the solver to populate Subs. - let (solved_subs, solved_env) = solve::run( - home, - types, - constraints, + let (solved_subs, solved_scope) = solve::run( + config, &mut problems, subs, &mut aliases, - &constraint, - pending_derives, &mut abilities_store, - exposed_by_module, - derived_module, ); - (solved_subs, solved_env, problems, abilities_store) + SolveOutput { + subs: solved_subs, + scope: solved_scope, + errors: problems, + resolved_abilities_store: abilities_store, + } } /// Copies exposed types and all ability specializations, which may be implicitly exposed. diff --git a/crates/compiler/solve/src/pools.rs b/crates/compiler/solve/src/pools.rs new file mode 100644 index 00000000000..e3e86c20472 --- /dev/null +++ b/crates/compiler/solve/src/pools.rs @@ -0,0 +1,59 @@ +use roc_types::subs::{Rank, Variable}; + +const DEFAULT_POOLS: usize = 8; + +#[derive(Clone, Debug)] +pub struct Pools(Vec>); + +impl Default for Pools { + fn default() -> Self { + Pools::new(DEFAULT_POOLS) + } +} + +impl Pools { + pub fn new(num_pools: usize) -> Self { + Pools(vec![Vec::new(); num_pools]) + } + + pub fn len(&self) -> usize { + self.0.len() + } + + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + + pub fn get_mut(&mut self, rank: Rank) -> &mut Vec { + match self.0.get_mut(rank.into_usize()) { + Some(reference) => reference, + None => panic!("Compiler bug: could not find pool at rank {}", rank), + } + } + + pub fn get(&self, rank: Rank) -> &Vec { + match self.0.get(rank.into_usize()) { + Some(reference) => reference, + None => panic!("Compiler bug: could not find pool at rank {}", rank), + } + } + + pub fn iter(&self) -> std::slice::Iter<'_, Vec> { + self.0.iter() + } + + pub fn split_last(mut self) -> (Vec, Vec>) { + let last = self + .0 + .pop() + .unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools")); + + (last, self.0) + } + + pub fn extend_to(&mut self, n: usize) { + for _ in self.len()..n { + self.0.push(Vec::new()); + } + } +} diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index ba319525376..ea36ca5391b 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -1,47 +1,42 @@ -#![allow(clippy::too_many_arguments)] - use crate::ability::{ resolve_ability_specialization, type_implementing_specialization, AbilityImplError, CheckedDerives, ObligationCache, PendingDerivesTable, Resolved, }; -use crate::module::Solved; +use crate::deep_copy::deep_copy_var_in; +use crate::env::{DerivedEnv, Env}; +use crate::module::{SolveConfig, Solved}; +use crate::pools::Pools; use crate::specialize::{ - compact_lambda_sets_of_vars, AwaitingSpecializations, CompactionResult, DerivedEnv, SolvePhase, + compact_lambda_sets_of_vars, AwaitingSpecializations, CompactionResult, SolvePhase, }; +use crate::to_var::{either_type_index_to_var, type_to_var}; +use crate::Aliases; use bumpalo::Bump; use roc_can::abilities::{AbilitiesStore, MemberSpecializationInfo}; use roc_can::constraint::Constraint::{self, *}; -use roc_can::constraint::{Constraints, Cycle, LetConstraint, OpportunisticResolve, TypeOrVar}; +use roc_can::constraint::{Cycle, LetConstraint, OpportunisticResolve}; use roc_can::expected::{Expected, PExpected}; -use roc_can::expr::PendingDerives; -use roc_can::module::ExposedByModule; -use roc_collections::all::MutMap; -use roc_collections::soa::{Index, Slice}; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] use roc_debug_flags::ROC_VERIFY_RIGID_LET_GENERALIZED; -use roc_derive::SharedDerivedModule; use roc_error_macros::internal_error; -use roc_module::ident::TagName; -use roc_module::symbol::{ModuleId, Symbol}; +use roc_module::symbol::Symbol; use roc_problem::can::CycleEntry; use roc_region::all::Loc; use roc_solve_problem::TypeError; use roc_types::subs::{ - self, AliasVariables, Content, Descriptor, FlatType, GetSubsSlice, LambdaSet, Mark, - OptVariable, Rank, RecordFields, Subs, SubsSlice, TagExt, TupleElems, UlsOfVar, UnionLabels, - UnionLambdas, UnionTags, Variable, VariableSubsSlice, -}; -use roc_types::types::{ - gather_fields_unsorted_iter, gather_tuple_elems_unsorted_iter, AliasKind, AliasShared, - Category, ExtImplicitOpenness, OptAbleVar, Polarity, Reason, RecordField, Type, TypeExtension, - TypeTag, Types, Uls, + self, Content, FlatType, GetSubsSlice, Mark, OptVariable, Rank, Subs, TagExt, UlsOfVar, + Variable, }; +use roc_types::types::{Category, Polarity, Reason, RecordField, Type, TypeExtension, Types, Uls}; use roc_unify::unify::{ - unify, unify_introduced_ability_specialization, Env as UEnv, Mode, Obligated, - SpecializationLsetCollector, Unified::*, + unify, unify_introduced_ability_specialization, Mode, Obligated, SpecializationLsetCollector, + Unified::*, }; +mod scope; +pub use scope::Scope; + // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // https://github.com/elm/compiler // Thank you, Evan! @@ -92,465 +87,20 @@ use roc_unify::unify::{ // Ranks are used to limit the number of type variables considered for generalization. Only those inside // of the let (so those used in inferring the type of `\x -> x`) are considered. -use roc_types::types::Alias; - -#[derive(Debug, Clone, Copy)] -struct DelayedAliasVariables { - start: u32, - type_variables_len: u8, - lambda_set_variables_len: u8, - recursion_variables_len: u8, - infer_ext_in_output_variables_len: u8, -} - -impl DelayedAliasVariables { - fn recursion_variables(self, variables: &mut [OptAbleVar]) -> &mut [OptAbleVar] { - let start = self.start as usize - + (self.type_variables_len + self.lambda_set_variables_len) as usize; - let length = self.recursion_variables_len as usize; - - &mut variables[start..][..length] - } - - fn lambda_set_variables(self, variables: &mut [OptAbleVar]) -> &mut [OptAbleVar] { - let start = self.start as usize + self.type_variables_len as usize; - let length = self.lambda_set_variables_len as usize; - - &mut variables[start..][..length] - } - - fn type_variables(self, variables: &mut [OptAbleVar]) -> &mut [OptAbleVar] { - let start = self.start as usize; - let length = self.type_variables_len as usize; - - &mut variables[start..][..length] - } - - fn infer_ext_in_output_variables(self, variables: &mut [OptAbleVar]) -> &mut [OptAbleVar] { - let start = self.start as usize - + (self.type_variables_len - + self.lambda_set_variables_len - + self.recursion_variables_len) as usize; - let length = self.infer_ext_in_output_variables_len as usize; - - &mut variables[start..][..length] - } -} - -#[derive(Debug, Default)] -pub struct Aliases { - aliases: Vec<(Symbol, Index, DelayedAliasVariables, AliasKind)>, - variables: Vec, -} - -impl Aliases { - pub fn with_capacity(cap: usize) -> Self { - Self { - aliases: Vec::with_capacity(cap), - variables: Vec::with_capacity(cap * 2), - } - } - - pub fn insert(&mut self, types: &mut Types, symbol: Symbol, alias: Alias) { - let alias_variables = - { - let start = self.variables.len() as _; - - self.variables.extend( - alias - .type_variables - .iter() - .map(|x| OptAbleVar::from(&x.value)), - ); - - self.variables.extend(alias.lambda_set_variables.iter().map( - |x| match x.as_inner() { - Type::Variable(v) => OptAbleVar::unbound(*v), - _ => unreachable!("lambda set type is not a variable"), - }, - )); - - let recursion_variables_len = alias.recursion_variables.len() as _; - self.variables.extend( - alias - .recursion_variables - .iter() - .copied() - .map(OptAbleVar::unbound), - ); - - self.variables.extend( - alias - .infer_ext_in_output_variables - .iter() - .map(|v| OptAbleVar::unbound(*v)), - ); - - DelayedAliasVariables { - start, - type_variables_len: alias.type_variables.len() as _, - lambda_set_variables_len: alias.lambda_set_variables.len() as _, - recursion_variables_len, - infer_ext_in_output_variables_len: alias.infer_ext_in_output_variables.len() - as _, - } - }; - - // TODO: can we construct Aliases from TypeTag directly? - let alias_typ = types.from_old_type(&alias.typ); - - self.aliases - .push((symbol, alias_typ, alias_variables, alias.kind)); - } - - fn instantiate_result_result( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - alias_variables: AliasVariables, - ) -> Variable { - let tag_names_slice = Subs::RESULT_TAG_NAMES; - - let err_slice = SubsSlice::new(alias_variables.variables_start + 1, 1); - let ok_slice = SubsSlice::new(alias_variables.variables_start, 1); - - let variable_slices = - SubsSlice::extend_new(&mut subs.variable_slices, [err_slice, ok_slice]); - - let union_tags = UnionTags::from_slices(tag_names_slice, variable_slices); - let ext_var = TagExt::Any(Variable::EMPTY_TAG_UNION); - let flat_type = FlatType::TagUnion(union_tags, ext_var); - let content = Content::Structure(flat_type); - - register(subs, rank, pools, content) - } - - /// Build an alias of the form `Num range := range` - fn build_num_opaque( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - symbol: Symbol, - range_var: Variable, - ) -> Variable { - let content = Content::Alias( - symbol, - AliasVariables::insert_into_subs(subs, [range_var], [], []), - range_var, - AliasKind::Opaque, - ); - - register(subs, rank, pools, content) - } - - fn instantiate_builtin_aliases_real_var( - &mut self, - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - symbol: Symbol, - alias_variables: AliasVariables, - ) -> Option<(Variable, AliasKind)> { - match symbol { - Symbol::RESULT_RESULT => { - let var = Self::instantiate_result_result(subs, rank, pools, alias_variables); - - Some((var, AliasKind::Structural)) - } - Symbol::NUM_NUM | Symbol::NUM_INTEGER | Symbol::NUM_FLOATINGPOINT => { - // Num range := range | Integer range := range | FloatingPoint range := range - let range_var = subs.variables[alias_variables.variables_start as usize]; - Some((range_var, AliasKind::Opaque)) - } - Symbol::NUM_INT => { - // Int range : Num (Integer range) - // - // build `Integer range := range` - let integer_content_var = Self::build_num_opaque( - subs, - rank, - pools, - Symbol::NUM_INTEGER, - subs.variables[alias_variables.variables_start as usize], - ); - - // build `Num (Integer range) := Integer range` - let num_content_var = - Self::build_num_opaque(subs, rank, pools, Symbol::NUM_NUM, integer_content_var); - - Some((num_content_var, AliasKind::Structural)) - } - Symbol::NUM_FRAC => { - // Frac range : Num (FloatingPoint range) - // - // build `FloatingPoint range := range` - let fpoint_content_var = Self::build_num_opaque( - subs, - rank, - pools, - Symbol::NUM_FLOATINGPOINT, - subs.variables[alias_variables.variables_start as usize], - ); - - // build `Num (FloatingPoint range) := FloatingPoint range` - let num_content_var = - Self::build_num_opaque(subs, rank, pools, Symbol::NUM_NUM, fpoint_content_var); - - Some((num_content_var, AliasKind::Structural)) - } - Symbol::NUM_SIGNED8 => Some((Variable::SIGNED8, AliasKind::Opaque)), - Symbol::NUM_SIGNED16 => Some((Variable::SIGNED16, AliasKind::Opaque)), - Symbol::NUM_SIGNED32 => Some((Variable::SIGNED32, AliasKind::Opaque)), - Symbol::NUM_SIGNED64 => Some((Variable::SIGNED64, AliasKind::Opaque)), - Symbol::NUM_SIGNED128 => Some((Variable::SIGNED128, AliasKind::Opaque)), - Symbol::NUM_UNSIGNED8 => Some((Variable::UNSIGNED8, AliasKind::Opaque)), - Symbol::NUM_UNSIGNED16 => Some((Variable::UNSIGNED16, AliasKind::Opaque)), - Symbol::NUM_UNSIGNED32 => Some((Variable::UNSIGNED32, AliasKind::Opaque)), - Symbol::NUM_UNSIGNED64 => Some((Variable::UNSIGNED64, AliasKind::Opaque)), - Symbol::NUM_UNSIGNED128 => Some((Variable::UNSIGNED128, AliasKind::Opaque)), - Symbol::NUM_BINARY32 => Some((Variable::BINARY32, AliasKind::Opaque)), - Symbol::NUM_BINARY64 => Some((Variable::BINARY64, AliasKind::Opaque)), - _ => None, - } - } - - fn instantiate_real_var( - &mut self, - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - problems: &mut Vec, - abilities_store: &AbilitiesStore, - obligation_cache: &mut ObligationCache, - arena: &bumpalo::Bump, - types: &mut Types, - symbol: Symbol, - alias_variables: AliasVariables, - ) -> (Variable, AliasKind) { - // hardcoded instantiations for builtin aliases - if let Some((var, kind)) = Self::instantiate_builtin_aliases_real_var( - self, - subs, - rank, - pools, - symbol, - alias_variables, - ) { - return (var, kind); - } - - let (typ, delayed_variables, kind) = - match self.aliases.iter().find(|(s, _, _, _)| *s == symbol) { - None => internal_error!( - "Alias {:?} not registered in delayed aliases! {:?}", - symbol, - &self.aliases - ), - Some(&(_, typ, delayed_variables, kind)) => (typ, delayed_variables, kind), - }; - - 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()]; - - 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); - } - } - - for OptAbleVar { - var: rec_var, - opt_abilities, - } in delayed_variables - .recursion_variables(&mut self.variables) - .iter_mut() - { - debug_assert!(opt_abilities.is_none()); - let new_var = subs.fresh_unnamed_flex_var(); - substitutions.insert(*rec_var, new_var); - } - - let old_lambda_set_variables = delayed_variables.lambda_set_variables(&mut self.variables); - let new_lambda_set_variables = - &subs.variables[alias_variables.lambda_set_variables().indices()]; - - for (old, new) in old_lambda_set_variables - .iter_mut() - .zip(new_lambda_set_variables) - { - debug_assert!(old.opt_abilities.is_none()); - if old.var != *new { - substitutions.insert(old.var, *new); - } - } - - let old_infer_ext_vars = - delayed_variables.infer_ext_in_output_variables(&mut self.variables); - let new_infer_ext_vars = - &subs.variables[alias_variables.infer_ext_in_output_variables().indices()]; - - for (old, new) in old_infer_ext_vars.iter_mut().zip(new_infer_ext_vars) { - debug_assert!(old.opt_abilities.is_none()); - if old.var != *new { - substitutions.insert(old.var, *new); - } - } - - let typ = if !substitutions.is_empty() { - types.clone_with_variable_substitutions(typ, &substitutions) - } else { - typ - }; - - let alias_variable = type_to_variable( - subs, - rank, - pools, - problems, - abilities_store, - obligation_cache, - arena, - self, - types, - typ, - false, - ); - (alias_variable, kind) - } -} - -#[derive(Clone, Debug, Default)] -pub struct Env { - symbols: Vec, - variables: Vec, -} - -impl Env { - pub fn vars_by_symbol(&self) -> impl Iterator + '_ { - let it1 = self.symbols.iter().copied(); - let it2 = self.variables.iter().copied(); - - it1.zip(it2) - } - - #[inline(always)] - fn get_var_by_symbol(&self, symbol: &Symbol) -> Option { - self.symbols - .iter() - .position(|s| s == symbol) - .map(|index| self.variables[index]) - } - - #[inline(always)] - fn insert_symbol_var_if_vacant(&mut self, symbol: Symbol, var: Variable) { - match self.symbols.iter().position(|s| *s == symbol) { - None => { - // symbol is not in vars_by_symbol yet; insert it - self.symbols.push(symbol); - self.variables.push(var); - } - Some(_) => { - // do nothing - } - } - } -} - -const DEFAULT_POOLS: usize = 8; - -#[derive(Clone, Debug)] -pub struct Pools(Vec>); - -impl Default for Pools { - fn default() -> Self { - Pools::new(DEFAULT_POOLS) - } -} - -impl Pools { - pub fn new(num_pools: usize) -> Self { - Pools(vec![Vec::new(); num_pools]) - } - - pub fn len(&self) -> usize { - self.0.len() - } - - pub fn is_empty(&self) -> bool { - self.0.is_empty() - } - - pub fn get_mut(&mut self, rank: Rank) -> &mut Vec { - match self.0.get_mut(rank.into_usize()) { - Some(reference) => reference, - None => panic!("Compiler bug: could not find pool at rank {}", rank), - } - } - - pub fn get(&self, rank: Rank) -> &Vec { - match self.0.get(rank.into_usize()) { - Some(reference) => reference, - None => panic!("Compiler bug: could not find pool at rank {}", rank), - } - } - - pub fn iter(&self) -> std::slice::Iter<'_, Vec> { - self.0.iter() - } - - pub fn split_last(mut self) -> (Vec, Vec>) { - let last = self - .0 - .pop() - .unwrap_or_else(|| panic!("Attempted to split_last() on non-empty Pools")); - - (last, self.0) - } - - pub fn extend_to(&mut self, n: usize) { - for _ in self.len()..n { - self.0.push(Vec::new()); - } - } -} - #[derive(Clone)] struct State { - env: Env, + scope: Scope, mark: Mark, } -#[allow(clippy::too_many_arguments)] // TODO: put params in a context/env var pub fn run( - home: ModuleId, - types: Types, - constraints: &Constraints, + config: SolveConfig, problems: &mut Vec, mut subs: Subs, aliases: &mut Aliases, - constraint: &Constraint, - pending_derives: PendingDerives, abilities_store: &mut AbilitiesStore, - exposed_by_module: &ExposedByModule, - derived_module: SharedDerivedModule, -) -> (Solved, Env) { - let env = run_in_place( - home, - types, - constraints, - problems, - &mut subs, - aliases, - constraint, - pending_derives, - abilities_store, - exposed_by_module, - derived_module, - ); +) -> (Solved, Scope) { + let env = run_in_place(config, problems, &mut subs, aliases, abilities_store); (Solved(subs), env) } @@ -558,22 +108,26 @@ pub fn run( /// Modify an existing subs in-place instead #[allow(clippy::too_many_arguments)] // TODO: put params in a context/env var fn run_in_place( - _home: ModuleId, // TODO: remove me? - mut types: Types, - constraints: &Constraints, + config: SolveConfig, problems: &mut Vec, subs: &mut Subs, aliases: &mut Aliases, - constraint: &Constraint, - pending_derives: PendingDerives, abilities_store: &mut AbilitiesStore, - exposed_by_module: &ExposedByModule, - derived_module: SharedDerivedModule, -) -> Env { +) -> Scope { + let SolveConfig { + home: _, + constraints, + root_constraint, + mut types, + pending_derives, + exposed_by_module, + derived_module, + } = config; + let mut pools = Pools::default(); let state = State { - env: Env::default(), + scope: Scope::default(), mark: Mark::NONE.next(), }; let rank = Rank::toplevel(); @@ -582,8 +136,21 @@ fn run_in_place( let mut obligation_cache = ObligationCache::default(); let mut awaiting_specializations = AwaitingSpecializations::default(); - let pending_derives = PendingDerivesTable::new( + let derived_env = DerivedEnv { + derived_module: &derived_module, + exposed_types: exposed_by_module, + }; + + let mut env = Env { + arena: &arena, + constraints, + derived_env: &derived_env, subs, + pools: &mut pools, + }; + + let pending_derives = PendingDerivesTable::new( + &mut env, &mut types, aliases, pending_derives, @@ -594,45 +161,36 @@ fn run_in_place( let CheckedDerives { legal_derives: _, problems: derives_problems, - } = obligation_cache.check_derives(subs, abilities_store, pending_derives); + } = obligation_cache.check_derives(env.subs, abilities_store, pending_derives); problems.extend(derives_problems); - let derived_env = DerivedEnv { - derived_module: &derived_module, - exposed_types: exposed_by_module, - }; - let state = solve( - &arena, + &mut env, types, - constraints, state, rank, - &mut pools, problems, aliases, - subs, - constraint, + &root_constraint, abilities_store, &mut obligation_cache, &mut awaiting_specializations, - &derived_env, ); - state.env + state.scope } #[derive(Debug)] enum Work<'a> { Constraint { - env: &'a Env, + scope: &'a Scope, rank: Rank, constraint: &'a Constraint, }, CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc)>), /// The ret_con part of a let constraint that does NOT introduces rigid and/or flex variables LetConNoVariables { - env: &'a Env, + scope: &'a Scope, rank: Rank, let_con: &'a LetConstraint, @@ -647,7 +205,7 @@ enum Work<'a> { /// These introduced variables must be generalized, hence this variant /// is more complex than `LetConNoVariables`. LetConIntroducesVariables { - env: &'a Env, + scope: &'a Scope, rank: Rank, let_con: &'a LetConstraint, @@ -659,25 +217,20 @@ enum Work<'a> { }, } -#[allow(clippy::too_many_arguments)] fn solve( - arena: &Bump, + env: &mut Env, mut can_types: Types, - constraints: &Constraints, mut state: State, rank: Rank, - pools: &mut Pools, problems: &mut Vec, aliases: &mut Aliases, - subs: &mut Subs, constraint: &Constraint, abilities_store: &mut AbilitiesStore, obligation_cache: &mut ObligationCache, awaiting_specializations: &mut AwaitingSpecializations, - derived_env: &DerivedEnv, ) -> State { let initial = Work::Constraint { - env: &Env::default(), + scope: &Scope::default(), rank, constraint, }; @@ -685,57 +238,52 @@ fn solve( let mut stack = vec![initial]; while let Some(work_item) = stack.pop() { - let (env, rank, constraint) = match work_item { + let (scope, rank, constraint) = match work_item { Work::Constraint { - env, + scope, rank, constraint, } => { // the default case; actually solve this constraint - (env, rank, constraint) + (scope, rank, constraint) } Work::CheckForInfiniteTypes(def_vars) => { // after a LetCon, we must check if any of the variables that we introduced // loop back to themselves after solving the ret_constraint for (symbol, loc_var) in def_vars.iter() { - check_for_infinite_type(subs, pools, problems, *symbol, *loc_var); + check_for_infinite_type(env, problems, *symbol, *loc_var); } continue; } Work::LetConNoVariables { - env, + scope, rank, let_con, pool_variables, } => { // NOTE be extremely careful with shadowing here let offset = let_con.defs_and_ret_constraint.index(); - let ret_constraint = &constraints.constraints[offset + 1]; + let ret_constraint = &env.constraints.constraints[offset + 1]; // Add a variable for each def to new_vars_by_env. let local_def_vars = LocalDefVarsVec::from_def_types( - constraints, + env, rank, - pools, problems, abilities_store, obligation_cache, &mut can_types, aliases, - subs, let_con.def_types, ); - pools.get_mut(rank).extend(pool_variables); + env.pools.get_mut(rank).extend(pool_variables); - let mut new_env = env.clone(); + let mut new_scope = scope.clone(); for (symbol, loc_var) in local_def_vars.iter() { check_ability_specialization( - arena, - subs, - derived_env, - pools, + env, rank, abilities_store, obligation_cache, @@ -745,11 +293,11 @@ fn solve( *loc_var, ); - new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); + new_scope.insert_symbol_var_if_vacant(*symbol, loc_var.value); } stack.push(Work::Constraint { - env: arena.alloc(new_env), + scope: env.arena.alloc(new_scope), rank, constraint: ret_constraint, }); @@ -759,17 +307,17 @@ fn solve( continue; } Work::LetConIntroducesVariables { - env, + scope, rank, let_con, pool_variables, } => { // NOTE be extremely careful with shadowing here let offset = let_con.defs_and_ret_constraint.index(); - let ret_constraint = &constraints.constraints[offset + 1]; + let ret_constraint = &env.constraints.constraints[offset + 1]; let mark = state.mark; - let saved_env = state.env; + let saved_scope = state.scope; let young_mark = mark; let visit_mark = young_mark.next(); @@ -783,15 +331,13 @@ fn solve( // Add a variable for each def to local_def_vars. let local_def_vars = LocalDefVarsVec::from_def_types( - constraints, + env, intro_rank, - pools, problems, abilities_store, obligation_cache, &mut can_types, aliases, - subs, let_con.def_types, ); @@ -801,27 +347,28 @@ fn solve( // Otherwise, introduce all variables at the current rank; since none of them will // end up at the next rank, none will be generalized. if let_con.generalizable.0 { - pools.get_mut(rank.next()).extend(pool_variables); + env.pools.get_mut(rank.next()).extend(pool_variables); } else { - pools.get_mut(rank).extend(pool_variables); + env.pools.get_mut(rank).extend(pool_variables); } debug_assert_eq!( // Check that no variable ended up in a higher rank than the next rank.. that // would mean we generalized one level more than we need to! { - let offenders = pools + let offenders = env + .pools .get(rank.next()) .iter() .filter(|var| { - subs.get_rank(**var).into_usize() > rank.next().into_usize() + env.subs.get_rank(**var).into_usize() > rank.next().into_usize() }) .collect::>(); let result = offenders.len(); if result > 0 { - eprintln!("subs = {:?}", &subs); + eprintln!("subs = {:?}", &env.subs); eprintln!("offenders = {:?}", &offenders); eprintln!("let_con.def_types = {:?}", &let_con.def_types); } @@ -835,12 +382,12 @@ fn solve( // next rank. The variables introduced in the let-binding that are still at // that rank (intuitively, they did not "escape" into the lower level // before or after the let-binding) now get to be generalized. - generalize(subs, young_mark, visit_mark, rank.next(), pools); - debug_assert!(pools.get(rank.next()).is_empty(), "variables left over in let-binding scope, but they should all be in a lower scope or generalized now"); + generalize(env, young_mark, visit_mark, rank.next()); + debug_assert!(env.pools.get(rank.next()).is_empty(), "variables left over in let-binding scope, but they should all be in a lower scope or generalized now"); // check that things went well dbg_do!(ROC_VERIFY_RIGID_LET_GENERALIZED, { - let rigid_vars = &constraints.variables[let_con.rigid_vars.indices()]; + let rigid_vars = &env.constraints.variables[let_con.rigid_vars.indices()]; // NOTE the `subs.redundant` check does not come from elm. // It's unclear whether this is a bug with our implementation @@ -849,7 +396,8 @@ fn solve( let mut it = rigid_vars .iter() .filter(|&var| { - !subs.redundant(*var) && subs.get_rank(*var) != Rank::GENERALIZED + !env.subs.redundant(*var) + && env.subs.get_rank(*var) != Rank::GENERALIZED }) .peekable(); @@ -861,13 +409,10 @@ fn solve( } }); - let mut new_env = env.clone(); + let mut new_scope = scope.clone(); for (symbol, loc_var) in local_def_vars.iter() { check_ability_specialization( - arena, - subs, - derived_env, - pools, + env, rank, abilities_store, obligation_cache, @@ -877,20 +422,20 @@ fn solve( *loc_var, ); - new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); + new_scope.insert_symbol_var_if_vacant(*symbol, loc_var.value); } // Note that this vars_by_symbol is the one returned by the // previous call to solve() let state_for_ret_con = State { - env: saved_env, + scope: saved_scope, mark: final_mark, }; // Now solve the body, using the new vars_by_symbol which includes // the assignments' name-to-variable mappings. stack.push(Work::Constraint { - env: arena.alloc(new_env), + scope: env.arena.alloc(new_scope), rank, constraint: ret_constraint, }); @@ -908,17 +453,16 @@ fn solve( SaveTheEnvironment => { let mut copy = state; - copy.env = env.clone(); + copy.scope = scope.clone(); copy } Eq(roc_can::constraint::Eq(type_index, expectation_index, category_index, region)) => { - let category = &constraints.categories[category_index.index()]; + let category = &env.constraints.categories[category_index.index()]; let actual = either_type_index_to_var( - subs, + env, rank, - pools, problems, abilities_store, obligation_cache, @@ -927,11 +471,10 @@ fn solve( *type_index, ); - let expectation = &constraints.expectations[expectation_index.index()]; + let expectation = &env.constraints.expectations[expectation_index.index()]; let expected = either_type_index_to_var( - subs, + env, rank, - pools, problems, abilities_store, obligation_cache, @@ -941,7 +484,7 @@ fn solve( ); match unify( - &mut UEnv::new(subs), + &mut env.uenv(), actual, expected, Mode::EQ, @@ -953,11 +496,11 @@ fn solve( lambda_sets_to_specialize, extra_metadata: _, } => { - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); if !must_implement_ability.is_empty() { let new_problems = obligation_cache.check_obligations( - subs, + env.subs, abilities_store, must_implement_ability, AbilityImplError::BadExpr(*region, category.clone(), actual), @@ -965,21 +508,18 @@ fn solve( problems.extend(new_problems); } compact_lambdas_and_check_obligations( - arena, - pools, + env, problems, - subs, abilities_store, obligation_cache, awaiting_specializations, - derived_env, lambda_sets_to_specialize, ); state } Failure(vars, actual_type, expected_type, _bad_impls) => { - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); let problem = TypeError::BadExpr( *region, @@ -998,9 +538,8 @@ fn solve( // a special version of Eq that is used to store types in the AST. // IT DOES NOT REPORT ERRORS! let actual = either_type_index_to_var( - subs, + env, rank, - pools, &mut vec![], // don't report any extra errors abilities_store, obligation_cache, @@ -1009,12 +548,12 @@ fn solve( *source_index, ); - let actual_desc = subs.get(actual); - subs.union(*target, actual, actual_desc); + let actual_desc = env.subs.get(actual); + env.subs.union(*target, actual, actual_desc); state } Lookup(symbol, expectation_index, region) => { - match env.get_var_by_symbol(symbol) { + match scope.get_var_by_symbol(symbol) { Some(var) => { // Deep copy the vars associated with this symbol before unifying them. // Otherwise, suppose we have this: @@ -1037,13 +576,12 @@ fn solve( // then we copy from that module's Subs into our own. If the value // is being looked up in this module, then we use our Subs as both // the source and destination. - let actual = deep_copy_var_in(subs, rank, pools, var, arena); - let expectation = &constraints.expectations[expectation_index.index()]; + let actual = deep_copy_var_in(env, rank, var, env.arena); + let expectation = &env.constraints.expectations[expectation_index.index()]; let expected = either_type_index_to_var( - subs, + env, rank, - pools, problems, abilities_store, obligation_cache, @@ -1053,7 +591,7 @@ fn solve( ); match unify( - &mut UEnv::new(subs), + &mut env.uenv(), actual, expected, Mode::EQ, @@ -1065,11 +603,11 @@ fn solve( lambda_sets_to_specialize, extra_metadata: _, } => { - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); if !must_implement_ability.is_empty() { let new_problems = obligation_cache.check_obligations( - subs, + env.subs, abilities_store, must_implement_ability, AbilityImplError::BadExpr( @@ -1081,14 +619,11 @@ fn solve( problems.extend(new_problems); } compact_lambdas_and_check_obligations( - arena, - pools, + env, problems, - subs, abilities_store, obligation_cache, awaiting_specializations, - derived_env, lambda_sets_to_specialize, ); @@ -1096,7 +631,7 @@ fn solve( } Failure(vars, actual_type, expected_type, _bad_impls) => { - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); let problem = TypeError::BadExpr( *region, @@ -1119,10 +654,10 @@ fn solve( } } And(slice) => { - let it = constraints.constraints[slice.indices()].iter().rev(); + let it = env.constraints.constraints[slice.indices()].iter().rev(); for sub_constraint in it { stack.push(Work::Constraint { - env, + scope, rank, constraint: sub_constraint, }) @@ -1132,12 +667,11 @@ fn solve( } Pattern(type_index, expectation_index, category_index, region) | PatternPresence(type_index, expectation_index, category_index, region) => { - let category = &constraints.pattern_categories[category_index.index()]; + let category = &env.constraints.pattern_categories[category_index.index()]; let actual = either_type_index_to_var( - subs, + env, rank, - pools, problems, abilities_store, obligation_cache, @@ -1146,11 +680,10 @@ fn solve( *type_index, ); - let expectation = &constraints.pattern_expectations[expectation_index.index()]; + let expectation = &env.constraints.pattern_expectations[expectation_index.index()]; let expected = either_type_index_to_var( - subs, + env, rank, - pools, problems, abilities_store, obligation_cache, @@ -1165,7 +698,7 @@ fn solve( }; match unify( - &mut UEnv::new(subs), + &mut env.uenv(), actual, expected, mode, @@ -1177,11 +710,11 @@ fn solve( lambda_sets_to_specialize, extra_metadata: _, } => { - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); if !must_implement_ability.is_empty() { let new_problems = obligation_cache.check_obligations( - subs, + env.subs, abilities_store, must_implement_ability, AbilityImplError::BadPattern(*region, category.clone(), actual), @@ -1189,21 +722,18 @@ fn solve( problems.extend(new_problems); } compact_lambdas_and_check_obligations( - arena, - pools, + env, problems, - subs, abilities_store, obligation_cache, awaiting_specializations, - derived_env, lambda_sets_to_specialize, ); state } Failure(vars, actual_type, expected_type, _bad_impls) => { - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); let problem = TypeError::BadPattern( *region, @@ -1219,26 +749,26 @@ fn solve( } } Let(index, pool_slice) => { - let let_con = &constraints.let_constraints[index.index()]; + let let_con = &env.constraints.let_constraints[index.index()]; let offset = let_con.defs_and_ret_constraint.index(); - let defs_constraint = &constraints.constraints[offset]; - let ret_constraint = &constraints.constraints[offset + 1]; + let defs_constraint = &env.constraints.constraints[offset]; + let ret_constraint = &env.constraints.constraints[offset + 1]; - let flex_vars = &constraints.variables[let_con.flex_vars.indices()]; - let rigid_vars = &constraints.variables[let_con.rigid_vars.indices()]; + let flex_vars = &env.constraints.variables[let_con.flex_vars.indices()]; + let rigid_vars = &env.constraints.variables[let_con.rigid_vars.indices()]; - let pool_variables = &constraints.variables[pool_slice.indices()]; + let pool_variables = &env.constraints.variables[pool_slice.indices()]; if matches!(&ret_constraint, True) && let_con.rigid_vars.is_empty() { debug_assert!(pool_variables.is_empty()); - introduce(subs, rank, pools, flex_vars); + env.introduce(rank, flex_vars); // If the return expression is guaranteed to solve, // solve the assignments themselves and move on. stack.push(Work::Constraint { - env, + scope, rank, constraint: defs_constraint, }); @@ -1251,13 +781,13 @@ fn solve( // Note that the LetConSimple gets the current env and rank, // and not the env/rank from after solving the defs_constraint stack.push(Work::LetConNoVariables { - env, + scope, rank, let_con, pool_variables, }); stack.push(Work::Constraint { - env, + scope, rank, constraint: defs_constraint, }); @@ -1274,21 +804,21 @@ fn solve( }; // determine the next pool - if binding_rank.into_usize() < pools.len() { + if binding_rank.into_usize() < env.pools.len() { // Nothing to do, we already accounted for the next rank, no need to // adjust the pools } else { // we should be off by one at this point - debug_assert_eq!(binding_rank.into_usize(), 1 + pools.len()); - pools.extend_to(binding_rank.into_usize()); + debug_assert_eq!(binding_rank.into_usize(), 1 + env.pools.len()); + env.pools.extend_to(binding_rank.into_usize()); } - let pool: &mut Vec = pools.get_mut(binding_rank); + let pool: &mut Vec = env.pools.get_mut(binding_rank); // Introduce the variables of this binding, and extend the pool at our binding // rank. for &var in rigid_vars.iter().chain(flex_vars.iter()) { - subs.set_rank(var, binding_rank); + env.subs.set_rank(var, binding_rank); } pool.reserve(rigid_vars.len() + flex_vars.len()); pool.extend(rigid_vars.iter()); @@ -1304,13 +834,13 @@ fn solve( // That's because the defs constraints will be solved in next_rank if it is eligible for generalization. // The LetCon will then generalize variables that are at a higher rank than the rank of the current scope. stack.push(Work::LetConIntroducesVariables { - env, + scope, rank, let_con, pool_variables, }); stack.push(Work::Constraint { - env, + scope, rank: binding_rank, constraint: defs_constraint, }); @@ -1320,9 +850,8 @@ fn solve( } IsOpenType(type_index) => { let actual = either_type_index_to_var( - subs, + env, rank, - pools, problems, abilities_store, obligation_cache, @@ -1331,12 +860,12 @@ fn solve( *type_index, ); - open_tag_union(subs, pools, actual); + open_tag_union(env, actual); state } IncludesTag(index) => { - let includes_tag = &constraints.includes_tags[index.index()]; + let includes_tag = &env.constraints.includes_tags[index.index()]; let roc_can::constraint::IncludesTag { type_index, @@ -1346,12 +875,12 @@ fn solve( region, } = includes_tag; - let pattern_category = &constraints.pattern_categories[pattern_category.index()]; + let pattern_category = + &env.constraints.pattern_categories[pattern_category.index()]; let actual = either_type_index_to_var( - subs, + env, rank, - pools, problems, abilities_store, obligation_cache, @@ -1360,7 +889,7 @@ fn solve( *type_index, ); - let payload_types = constraints.variables[types.indices()] + let payload_types = env.constraints.variables[types.indices()] .iter() .map(|v| Type::Variable(*v)) .collect(); @@ -1370,19 +899,18 @@ fn solve( TypeExtension::Closed, )); let includes = type_to_var( - subs, + env, rank, problems, abilities_store, obligation_cache, - pools, &mut can_types, aliases, tag_ty, ); match unify( - &mut UEnv::new(subs), + &mut env.uenv(), actual, includes, Mode::PRESENT, @@ -1394,11 +922,11 @@ fn solve( lambda_sets_to_specialize, extra_metadata: _, } => { - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); if !must_implement_ability.is_empty() { let new_problems = obligation_cache.check_obligations( - subs, + env.subs, abilities_store, must_implement_ability, AbilityImplError::BadPattern( @@ -1410,21 +938,18 @@ fn solve( problems.extend(new_problems); } compact_lambdas_and_check_obligations( - arena, - pools, + env, problems, - subs, abilities_store, obligation_cache, awaiting_specializations, - derived_env, lambda_sets_to_specialize, ); state } Failure(vars, actual_type, expected_to_include_type, _bad_impls) => { - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); let problem = TypeError::BadPattern( *region, @@ -1453,8 +978,8 @@ fn solve( let (real_var, real_region, branches_var, category_and_expected) = match eq { Ok(eq) => { let roc_can::constraint::Eq(real_var, expected, category, real_region) = - constraints.eq[eq.index()]; - let expected = &constraints.expectations[expected.index()]; + env.constraints.eq[eq.index()]; + let expected = &env.constraints.expectations[expected.index()]; ( real_var, @@ -1469,8 +994,8 @@ fn solve( expected, category, real_region, - ) = constraints.pattern_eq[peq.index()]; - let expected = &constraints.pattern_expectations[expected.index()]; + ) = env.constraints.pattern_eq[peq.index()]; + let expected = &env.constraints.pattern_expectations[expected.index()]; ( real_var, @@ -1482,9 +1007,8 @@ fn solve( }; let real_var = either_type_index_to_var( - subs, + env, rank, - pools, problems, abilities_store, obligation_cache, @@ -1494,9 +1018,8 @@ fn solve( ); let branches_var = either_type_index_to_var( - subs, + env, rank, - pools, problems, abilities_store, obligation_cache, @@ -1512,16 +1035,16 @@ fn solve( Polarity::OF_PATTERN }; - let real_content = subs.get_content_without_compacting(real_var); - let branches_content = subs.get_content_without_compacting(branches_var); + let real_content = env.subs.get_content_without_compacting(real_var); + let branches_content = env.subs.get_content_without_compacting(branches_var); let already_have_error = matches!( (real_content, branches_content), (Content::Error, _) | (_, Content::Error) ); - let snapshot = subs.snapshot(); + let snapshot = env.subs.snapshot(); let unify_cond_and_patterns_outcome = unify( - &mut UEnv::new(subs), + &mut env.uenv(), branches_var, real_var, Mode::EQ, @@ -1538,25 +1061,22 @@ fn solve( lambda_sets_to_specialize, extra_metadata: _, } => { - subs.commit_snapshot(snapshot); + env.subs.commit_snapshot(snapshot); - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); problems.extend(obligation_cache.check_obligations( - subs, + env.subs, abilities_store, must_implement_ability, AbilityImplError::DoesNotImplement, )); compact_lambdas_and_check_obligations( - arena, - pools, + env, problems, - subs, abilities_store, obligation_cache, awaiting_specializations, - derived_env, lambda_sets_to_specialize, ); @@ -1566,15 +1086,15 @@ fn solve( } Failure(..) => { // Rollback and check for almost-equality. - subs.rollback_to(snapshot); + env.subs.rollback_to(snapshot); - let almost_eq_snapshot = subs.snapshot(); + let almost_eq_snapshot = env.subs.snapshot(); // TODO: turn this on for bidirectional exhaustiveness checking // open_tag_union(subs, real_var); - open_tag_union(subs, pools, branches_var); + open_tag_union(env, branches_var); let almost_eq = matches!( unify( - &mut UEnv::new(subs), + &mut env.uenv(), real_var, branches_var, Mode::EQ, @@ -1583,7 +1103,7 @@ fn solve( Success { .. } ); - subs.rollback_to(almost_eq_snapshot); + env.subs.rollback_to(almost_eq_snapshot); if almost_eq { // Case 3: almost equal, check exhaustiveness. @@ -1592,21 +1112,22 @@ fn solve( // Case 4: incompatible types, report type error. // Re-run first failed unification to get the type diff. match unify( - &mut UEnv::new(subs), + &mut env.uenv(), real_var, branches_var, Mode::EQ, cond_polarity, ) { Failure(vars, actual_type, expected_type, _bad_impls) => { - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); // Figure out the problem - it might be pattern or value // related. let problem = match category_and_expected { Ok((category, expected)) => { - let real_category = - constraints.categories[category.index()].clone(); + let real_category = env.constraints.categories + [category.index()] + .clone(); TypeError::BadExpr( real_region, real_category, @@ -1616,7 +1137,7 @@ fn solve( } Err((category, expected)) => { - let real_category = constraints.pattern_categories + let real_category = env.constraints.pattern_categories [category.index()] .clone(); TypeError::BadPattern( @@ -1637,7 +1158,7 @@ fn solve( } } - let sketched_rows = constraints.sketched_rows[sketched_rows.index()].clone(); + let sketched_rows = env.constraints.sketched_rows[sketched_rows.index()].clone(); if should_check_exhaustiveness { use roc_can::exhaustive::{check, ExhaustiveSummary}; @@ -1684,23 +1205,23 @@ fn solve( // TODO: this can likely be removed after remodelling tag extension types // (#4440). if cond_source_is_likely_positive_value && has_unification_error { - close_pattern_matched_tag_unions(subs, real_var); + close_pattern_matched_tag_unions(env.subs, real_var); } if let Ok(ExhaustiveSummary { errors, exhaustive, redundancies, - }) = check(subs, real_var, sketched_rows, context) + }) = check(env.subs, real_var, sketched_rows, context) { // Store information about whether the "when" is exhaustive, and // which (if any) of its branches are redundant. Codegen may use // this for branch-fixing and redundant elimination. if !exhaustive { - exhaustive_mark.set_non_exhaustive(subs); + exhaustive_mark.set_non_exhaustive(env.subs); } for redundant_mark in redundancies { - redundant_mark.set_redundant(subs); + redundant_mark.set_redundant(env.subs); } // Store the errors. @@ -1719,7 +1240,7 @@ fn solve( specialization_id, }) => { if let Ok(Resolved::Specialization(specialization)) = resolve_ability_specialization( - subs, + env.subs, abilities_store, member, specialization_variable, @@ -1733,8 +1254,8 @@ fn solve( let Cycle { def_names, expr_regions, - } = &constraints.cycles[cycle.index()]; - let symbols = &constraints.loc_symbols[def_names.indices()]; + } = &env.constraints.cycles[cycle.index()]; + let symbols = &env.constraints.loc_symbols[def_names.indices()]; // If the type of a symbol is not a function, that's an error. // Roc is strict, so only functions can be mutually recursive. @@ -1742,8 +1263,8 @@ fn solve( use Content::*; symbols.iter().any(|(s, _)| { - let var = env.get_var_by_symbol(s).expect("Symbol not solved!"); - let (_, underlying_content) = chase_alias_content(subs, var); + let var = scope.get_var_by_symbol(s).expect("Symbol not solved!"); + let (_, underlying_content) = chase_alias_content(env.subs, var); !matches!(underlying_content, Error | Structure(FlatType::Func(..))) }) @@ -1752,7 +1273,7 @@ fn solve( if any_is_bad { // expr regions are stored in loc_symbols (that turned out to be convenient). // The symbol is just a dummy, and should not be used - let expr_regions = &constraints.loc_symbols[expr_regions.indices()]; + let expr_regions = &env.constraints.loc_symbols[expr_regions.indices()]; let cycle = symbols .iter() @@ -1766,16 +1287,15 @@ fn solve( problems.push(TypeError::CircularDef(cycle)); - cycle_mark.set_illegal(subs); + cycle_mark.set_illegal(env.subs); } state } IngestedFile(type_index, file_path, bytes) => { let actual = either_type_index_to_var( - subs, + env, rank, - pools, problems, abilities_store, obligation_cache, @@ -1784,21 +1304,21 @@ fn solve( *type_index, ); - let snapshot = subs.snapshot(); + let snapshot = env.subs.snapshot(); if let Success { vars, must_implement_ability, lambda_sets_to_specialize, extra_metadata: _, } = unify( - &mut UEnv::new(subs), + &mut env.uenv(), actual, Variable::LIST_U8, Mode::EQ, Polarity::OF_VALUE, ) { // List U8 always valid. - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); debug_assert!( must_implement_ability.is_empty() && lambda_sets_to_specialize.is_empty(), @@ -1807,11 +1327,11 @@ fn solve( state } else { - subs.rollback_to(snapshot); + env.subs.rollback_to(snapshot); // We explicitly match on the last unify to get the type in the case it errors. match unify( - &mut UEnv::new(subs), + &mut env.uenv(), actual, Variable::STR, Mode::EQ, @@ -1823,7 +1343,7 @@ fn solve( lambda_sets_to_specialize, extra_metadata: _, } => { - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); debug_assert!( must_implement_ability.is_empty() && lambda_sets_to_specialize.is_empty(), @@ -1840,7 +1360,7 @@ fn solve( state } Failure(vars, actual_type, _, _) => { - introduce(subs, rank, pools, &vars); + env.introduce(rank, &vars); let problem = TypeError::IngestedFileUnsupportedType( file_path.clone(), @@ -1869,31 +1389,24 @@ fn chase_alias_content(subs: &Subs, mut var: Variable) -> (Variable, &Content) { } } -#[allow(clippy::too_many_arguments)] fn compact_lambdas_and_check_obligations( - arena: &Bump, - pools: &mut Pools, + env: &mut Env, problems: &mut Vec, - subs: &mut Subs, abilities_store: &mut AbilitiesStore, obligation_cache: &mut ObligationCache, awaiting_specialization: &mut AwaitingSpecializations, - derived_env: &DerivedEnv, lambda_sets_to_specialize: UlsOfVar, ) { let CompactionResult { obligations, awaiting_specialization: new_awaiting, } = compact_lambda_sets_of_vars( - subs, - derived_env, - arena, - pools, + env, lambda_sets_to_specialize, &SolvePhase { abilities_store }, ); problems.extend(obligation_cache.check_obligations( - subs, + env.subs, abilities_store, obligations, AbilityImplError::DoesNotImplement, @@ -1901,39 +1414,44 @@ fn compact_lambdas_and_check_obligations( awaiting_specialization.union(new_awaiting); } -fn open_tag_union(subs: &mut Subs, pools: &mut Pools, var: Variable) { +fn open_tag_union(env: &mut Env, var: Variable) { let mut stack = vec![var]; while let Some(var) = stack.pop() { use {Content::*, FlatType::*}; - let desc = subs.get(var); + let desc = env.subs.get(var); match desc.content { Structure(TagUnion(tags, ext)) => { - if let Structure(EmptyTagUnion) = subs.get_content_without_compacting(ext.var()) { - let new_ext_var = register(subs, desc.rank, pools, Content::FlexVar(None)); + if let Structure(EmptyTagUnion) = env.subs.get_content_without_compacting(ext.var()) + { + let new_ext_var = env.register(desc.rank, Content::FlexVar(None)); let new_union = Structure(TagUnion(tags, TagExt::Any(new_ext_var))); - subs.set_content(var, new_union); + env.subs.set_content(var, new_union); } // Also open up all nested tag unions. let all_vars = tags.variables().into_iter(); - stack.extend(all_vars.flat_map(|slice| subs[slice]).map(|var| subs[var])); + stack.extend( + all_vars + .flat_map(|slice| env.subs[slice]) + .map(|var| env.subs[var]), + ); } Structure(Record(fields, _)) => { // Open up all nested tag unions. - stack.extend(subs.get_subs_slice(fields.variables())); + stack.extend(env.subs.get_subs_slice(fields.variables())); } Structure(Tuple(elems, _)) => { // Open up all nested tag unions. - stack.extend(subs.get_subs_slice(elems.variables())); + stack.extend(env.subs.get_subs_slice(elems.variables())); } Structure(Apply(Symbol::LIST_LIST, args)) => { // Open up nested tag unions. - stack.extend(subs.get_subs_slice(args)); + stack.extend(env.subs.get_subs_slice(args)); } _ => { @@ -2025,14 +1543,10 @@ fn close_pattern_matched_tag_unions(subs: &mut Subs, var: Variable) { /// If a symbol claims to specialize an ability member, check that its solved type in fact /// does specialize the ability, and record the specialization. -#[allow(clippy::too_many_arguments)] // Aggressive but necessary - there aren't many usages. #[inline(always)] fn check_ability_specialization( - arena: &Bump, - subs: &mut Subs, - derived_env: &DerivedEnv, - pools: &mut Pools, + env: &mut Env, rank: Rank, abilities_store: &mut AbilitiesStore, obligation_cache: &mut ObligationCache, @@ -2057,10 +1571,10 @@ fn check_ability_specialization( // We need to freshly instantiate the root signature so that all unifications are reflected // in the specialization type, but not the original signature type. let root_signature_var = - deep_copy_var_in(subs, Rank::toplevel(), pools, root_signature_var, arena); - let snapshot = subs.snapshot(); + deep_copy_var_in(env, Rank::toplevel(), root_signature_var, env.arena); + let snapshot = env.subs.snapshot(); let unified = unify_introduced_ability_specialization( - &mut UEnv::new(subs), + &mut env.uenv(), root_signature_var, symbol_loc_var.value, Mode::EQ, @@ -2083,8 +1597,8 @@ fn check_ability_specialization( if opaque == impl_key.opaque { // It was! All is good. - subs.commit_snapshot(snapshot); - introduce(subs, rank, pools, &vars); + env.subs.commit_snapshot(snapshot); + env.introduce(rank, &vars); let specialization_lambda_sets = specialization_lambda_sets .into_iter() @@ -2095,14 +1609,11 @@ fn check_ability_specialization( .collect(); compact_lambdas_and_check_obligations( - arena, - pools, + env, problems, - subs, abilities_store, obligation_cache, awaiting_specializations, - derived_env, lambda_sets_to_specialize, ); @@ -2115,10 +1626,11 @@ fn check_ability_specialization( // error. // Commit so that the bad signature and its error persists in subs. - subs.commit_snapshot(snapshot); + env.subs.commit_snapshot(snapshot); - let _typ = - subs.var_to_error_type(symbol_loc_var.value, Polarity::OF_VALUE); + let _typ = env + .subs + .var_to_error_type(symbol_loc_var.value, Polarity::OF_VALUE); let problem = TypeError::WrongSpecialization { region: symbol_loc_var.region, @@ -2136,9 +1648,9 @@ fn check_ability_specialization( // This is a specialization of a structural type - never allowed. // Commit so that `var` persists in subs. - subs.commit_snapshot(snapshot); + env.subs.commit_snapshot(snapshot); - let typ = subs.var_to_error_type(var, Polarity::OF_VALUE); + let typ = env.subs.var_to_error_type(var, Polarity::OF_VALUE); let problem = TypeError::StructuralSpecialization { region: symbol_loc_var.region, @@ -2158,12 +1670,14 @@ fn check_ability_specialization( // Rollback the snapshot so we unlink the root signature with the specialization, // so we can have two separate error types. - subs.rollback_to(snapshot); + env.subs.rollback_to(snapshot); - let expected_type = - subs.var_to_error_type(root_signature_var, Polarity::OF_VALUE); - let actual_type = - subs.var_to_error_type(symbol_loc_var.value, Polarity::OF_VALUE); + let expected_type = env + .subs + .var_to_error_type(root_signature_var, Polarity::OF_VALUE); + let actual_type = env + .subs + .var_to_error_type(symbol_loc_var.value, Polarity::OF_VALUE); let reason = Reason::GeneralizedAbilityMemberSpecialization { member_name: ability_member, @@ -2185,8 +1699,8 @@ fn check_ability_specialization( } Failure(vars, expected_type, actual_type, unimplemented_abilities) => { - subs.commit_snapshot(snapshot); - introduce(subs, rank, pools, &vars); + env.subs.commit_snapshot(snapshot); + env.introduce(rank, &vars); let reason = Reason::InvalidAbilityMemberSpecialization { member_name: ability_member, @@ -2214,16 +1728,13 @@ fn check_ability_specialization( // Get the lambda sets that are ready for specialization because this ability member // specialization was resolved, and compact them. let new_lambda_sets_to_specialize = - awaiting_specializations.remove_for_specialized(subs, impl_key); + awaiting_specializations.remove_for_specialized(env.subs, impl_key); compact_lambdas_and_check_obligations( - arena, - pools, + env, problems, - subs, abilities_store, obligation_cache, awaiting_specializations, - derived_env, new_lambda_sets_to_specialize, ); debug_assert!( @@ -2267,27 +1778,24 @@ impl LocalDefVarsVec { impl LocalDefVarsVec<(Symbol, Loc)> { fn from_def_types( - constraints: &Constraints, + env: &mut Env, rank: Rank, - pools: &mut Pools, problems: &mut Vec, abilities_store: &mut AbilitiesStore, obligation_cache: &mut ObligationCache, types: &mut Types, aliases: &mut Aliases, - subs: &mut Subs, def_types_slice: roc_can::constraint::DefTypes, ) -> Self { - let type_indices_slice = &constraints.type_slices[def_types_slice.types.indices()]; - let loc_symbols_slice = &constraints.loc_symbols[def_types_slice.loc_symbols.indices()]; + let type_indices_slice = &env.constraints.type_slices[def_types_slice.types.indices()]; + let loc_symbols_slice = &env.constraints.loc_symbols[def_types_slice.loc_symbols.indices()]; let mut local_def_vars = Self::with_length(type_indices_slice.len()); for (&(symbol, region), typ_index) in (loc_symbols_slice.iter()).zip(type_indices_slice) { let var = either_type_index_to_var( - subs, + env, rank, - pools, problems, abilities_store, obligation_cache, @@ -2303,1334 +1811,46 @@ impl LocalDefVarsVec<(Symbol, Loc)> { } } -use std::cell::RefCell; -use std::ops::ControlFlow; -std::thread_local! { - /// Scratchpad arena so we don't need to allocate a new one all the time - static SCRATCHPAD: RefCell> = RefCell::new(Some(bumpalo::Bump::with_capacity(4 * 1024))); -} - -fn take_scratchpad() -> bumpalo::Bump { - SCRATCHPAD.with(|f| f.take().unwrap()) -} +fn check_for_infinite_type( + env: &mut Env, + problems: &mut Vec, + symbol: Symbol, + loc_var: Loc, +) { + let var = loc_var.value; -fn put_scratchpad(scratchpad: bumpalo::Bump) { - SCRATCHPAD.with(|f| { - f.replace(Some(scratchpad)); - }); -} + 'next_occurs_check: while let Err((_, chain)) = env.subs.occurs(var) { + // walk the chain till we find a tag union or lambda set, starting from the variable that + // occurred recursively, which is always at the end of the chain. + for &var in chain.iter().rev() { + match *env.subs.get_content_without_compacting(var) { + Content::Structure(FlatType::TagUnion(tags, ext_var)) => { + let rec_var = env.subs.mark_tag_union_recursive(var, tags, ext_var); + env.register_existing_var(rec_var); -fn either_type_index_to_var( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - problems: &mut Vec, - abilities_store: &mut AbilitiesStore, - obligation_cache: &mut ObligationCache, - types: &mut Types, - aliases: &mut Aliases, - either_type_index: TypeOrVar, -) -> Variable { - match either_type_index.split() { - Ok(type_index) => { - // Converts the celled type to a variable, emplacing the new variable for re-use. - let var = type_to_var( - subs, - rank, - problems, - abilities_store, - obligation_cache, - pools, - types, - aliases, - type_index, - ); + continue 'next_occurs_check; + } + Content::LambdaSet(subs::LambdaSet { + solved, + recursion_var: OptVariable::NONE, + unspecialized, + ambient_function: ambient_function_var, + }) => { + let rec_var = env.subs.mark_lambda_set_recursive( + var, + solved, + unspecialized, + ambient_function_var, + ); + env.register_existing_var(rec_var); - debug_assert!( - matches!(types[type_index], TypeTag::Variable(v) if v == var) - || matches!( - types[type_index], - TypeTag::EmptyRecord | TypeTag::EmptyTagUnion - ) - ); - var - } - Err(var_index) => { - // we cheat, and store the variable directly in the index - unsafe { Variable::from_index(var_index.index() as _) } + continue 'next_occurs_check; + } + _ => { /* fall through */ } + } } - } -} - -pub(crate) fn type_to_var( - subs: &mut Subs, - rank: Rank, - problems: &mut Vec, - abilities_store: &mut AbilitiesStore, - obligation_cache: &mut ObligationCache, - pools: &mut Pools, - types: &mut Types, - aliases: &mut Aliases, - typ: Index, -) -> Variable { - if let TypeTag::Variable(var) = types[typ] { - var - } else { - let mut arena = take_scratchpad(); - - let var = type_to_variable( - subs, - rank, - pools, - problems, - abilities_store, - obligation_cache, - &arena, - aliases, - types, - typ, - false, - ); - - arena.reset(); - put_scratchpad(arena); - - var - } -} - -enum RegisterVariable { - /// Based on the Type, we already know what variable this will be - Direct(Variable), - /// This Type needs more complicated Content. We reserve a Variable - /// for it, but put a placeholder Content in subs - Deferred, -} - -impl RegisterVariable { - fn from_type( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - arena: &'_ bumpalo::Bump, - types: &mut Types, - typ: Index, - ) -> Self { - use RegisterVariable::*; - - match types[typ] { - TypeTag::Variable(var) => Direct(var), - TypeTag::EmptyRecord => Direct(Variable::EMPTY_RECORD), - TypeTag::EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION), - TypeTag::DelayedAlias { shared } - | TypeTag::StructuralAlias { shared, .. } - | TypeTag::OpaqueAlias { shared, .. } - | TypeTag::HostExposedAlias { shared, .. } => { - let AliasShared { symbol, .. } = types[shared]; - if let Some(reserved) = Variable::get_reserved(symbol) { - let direct_var = if rank.is_generalized() { - // reserved variables are stored with rank NONE - reserved - } else { - // for any other rank, we need to copy; it takes care of adjusting the rank - deep_copy_var_in(subs, rank, pools, reserved, arena) - }; - // Safety: the `destination` will become the source-of-truth for the type index, since it - // was not already transformed before (if it was, we'd be in the Variable branch!) - let _old_typ = unsafe { types.emplace_variable(typ, direct_var) }; - return Direct(direct_var); - } - - Deferred - } - _ => Deferred, - } - } - - #[inline(always)] - fn with_stack( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - arena: &'_ bumpalo::Bump, - types: &mut Types, - typ_index: Index, - stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, - ) -> Variable { - match Self::from_type(subs, rank, pools, arena, types, typ_index) { - Self::Direct(var) => var, - Self::Deferred => { - let var = subs.fresh_unnamed_flex_var(); - // Safety: the `destination` will become the source-of-truth for the type index, since it - // was not already transformed before (if it was, it wouldn't be deferred!) - let typ = unsafe { types.emplace_variable(typ_index, var) }; - stack.push(TypeToVar::Defer { - typ, - typ_index, - destination: var, - ambient_function: AmbientFunctionPolicy::NoFunction, - }); - var - } - } - } -} - -/// Instantiation of ambient functions in unspecialized lambda sets is somewhat tricky due to other -/// optimizations we have in place. This struct tells us how they should be instantiated. -#[derive(Debug)] -enum AmbientFunctionPolicy { - /// We're not in a function. This variant may never hold for unspecialized lambda sets. - NoFunction, - /// We're in a known function. - Function(Variable), -} - -impl AmbientFunctionPolicy { - fn link_to_alias_lambda_set_var(&self, subs: &mut Subs, var: Variable) { - let ambient_function = match self { - AmbientFunctionPolicy::Function(var) => *var, - _ => { - // Might be linked at a deeper point in time, ignore for now - return; - } - }; - let content = subs.get_content_without_compacting(var); - let new_content = match content { - Content::LambdaSet(LambdaSet { - solved, - recursion_var, - unspecialized, - ambient_function: _, - }) => Content::LambdaSet(LambdaSet { - solved: *solved, - recursion_var: *recursion_var, - unspecialized: *unspecialized, - ambient_function, - }), - Content::FlexVar(_) => { - // Something like - // Encoder fmt : List U8, fmt -a-> List U8 | fmt has EncoderFormatting - // THEORY: Replace these with empty lambda sets. They will unify the same as a flex - // var does, but allows us to record the ambient function properly. - Content::LambdaSet(LambdaSet { - solved: UnionLabels::default(), - recursion_var: OptVariable::NONE, - unspecialized: SubsSlice::default(), - ambient_function, - }) - } - content => internal_error!("{:?}({:?}) not a lambda set", content, var), - }; - subs.set_content_unchecked(var, new_content); - } -} - -#[derive(Debug)] -enum TypeToVar { - Defer { - typ: TypeTag, - typ_index: Index, - destination: Variable, - ambient_function: AmbientFunctionPolicy, - }, -} - -#[allow(clippy::too_many_arguments)] -fn type_to_variable( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - problems: &mut Vec, - abilities_store: &AbilitiesStore, - obligation_cache: &mut ObligationCache, - arena: &bumpalo::Bump, - aliases: &mut Aliases, - types: &mut Types, - typ: Index, - // Helpers for instantiating ambient functions of lambda set variables from type aliases. - is_alias_lambda_set_arg: bool, -) -> Variable { - use bumpalo::collections::Vec; - - let mut stack = Vec::with_capacity_in(8, arena); - let mut bind_to_abilities = Vec::new_in(arena); - - macro_rules! helper { - ($typ:expr, $ambient_function_policy:expr) => {{ - match RegisterVariable::from_type(subs, rank, pools, arena, types, $typ) { - RegisterVariable::Direct(var) => { - // If the variable is just a type variable but we know we're in a lambda set - // context, try to link to the ambient function. - $ambient_function_policy.link_to_alias_lambda_set_var(subs, var); - - var - } - RegisterVariable::Deferred => { - let var = subs.fresh_unnamed_flex_var(); - - // Safety: the `destination` will become the source-of-truth for the type index, since it - // was not already transformed before (if it was, it wouldn't be deferred!) - let typ = unsafe { types.emplace_variable($typ, var) }; - - stack.push(TypeToVar::Defer { - typ, - typ_index: $typ, - destination: var, - ambient_function: $ambient_function_policy, - }); - - var - } - } - }}; - ($typ:expr) => {{ - helper!($typ, AmbientFunctionPolicy::NoFunction) - }}; - } - - let result = helper!(typ); - - while let Some(TypeToVar::Defer { - typ_index, - typ, - destination, - ambient_function, - }) = stack.pop() - { - use TypeTag::*; - match typ { - Variable(_) | EmptyRecord | EmptyTagUnion => { - unreachable!("This variant should never be deferred!",) - } - RangedNumber(range) => { - let content = Content::RangedNumber(range); - - register_with_known_var(subs, destination, rank, pools, content) - } - Apply { - symbol, - type_argument_regions: _, - region: _, - } => { - let arguments = types.get_type_arguments(typ_index); - let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - for (target_index, var_index) in - (new_arguments.indices()).zip(arguments.into_iter()) - { - let var = helper!(var_index); - subs.variables[target_index] = var; - } - - let flat_type = FlatType::Apply(symbol, new_arguments); - let content = Content::Structure(flat_type); - - register_with_known_var(subs, destination, rank, pools, content) - } - - ClosureTag { - name, - ambient_function, - } => { - let captures = types.get_type_arguments(typ_index); - let union_lambdas = create_union_lambda( - subs, rank, pools, arena, types, name, captures, &mut stack, - ); - - let content = Content::LambdaSet(subs::LambdaSet { - solved: union_lambdas, - // We may figure out the lambda set is recursive during solving, but it never - // is to begin with. - recursion_var: OptVariable::NONE, - unspecialized: SubsSlice::default(), - ambient_function, - }); - - register_with_known_var(subs, destination, rank, pools, content) - } - UnspecializedLambdaSet { unspecialized } => { - let unspecialized_slice = SubsSlice::extend_new( - &mut subs.unspecialized_lambda_sets, - std::iter::once(unspecialized), - ); - - // `ClosureTag` ambient functions are resolved during constraint generation. - // But `UnspecializedLambdaSet`s can only ever live in a type signature, and don't - // correspond to a expression, so they are never constrained. - // Instead, we resolve their ambient functions during type translation, observing - // the invariant that a lambda set can only ever appear under a function type. - let ambient_function = match ambient_function { - AmbientFunctionPolicy::NoFunction => { - debug_assert!(is_alias_lambda_set_arg); - // To be filled in during delayed type alias instantiation - roc_types::subs::Variable::NULL - } - AmbientFunctionPolicy::Function(var) => var, - }; - - let content = Content::LambdaSet(subs::LambdaSet { - unspecialized: unspecialized_slice, - solved: UnionLabels::default(), - recursion_var: OptVariable::NONE, - ambient_function, - }); - - register_with_known_var(subs, destination, rank, pools, content) - } - // This case is important for the rank of boolean variables - Function(closure_type, ret_type) => { - let arguments = types.get_type_arguments(typ_index); - let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - for (target_index, var_index) in - (new_arguments.indices()).zip(arguments.into_iter()) - { - let var = helper!(var_index); - subs.variables[target_index] = var; - } - - let ret_var = helper!(ret_type); - let closure_var = - helper!(closure_type, AmbientFunctionPolicy::Function(destination)); - let content = - Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var)); - - register_with_known_var(subs, destination, rank, pools, content) - } - Record(fields) => { - let ext_slice = types.get_type_arguments(typ_index); - - // An empty fields is inefficient (but would be correct) - // If hit, try to turn the value into an EmptyRecord in canonicalization - debug_assert!(!fields.is_empty() || !ext_slice.is_empty()); - - let mut field_vars = Vec::with_capacity_in(fields.len(), arena); - - let (fields_names, field_kinds, field_tys) = types.record_fields_slices(fields); - - for ((field, field_kind), field_type) in (fields_names.into_iter()) - .zip(field_kinds.into_iter()) - .zip(field_tys.into_iter()) - { - let field_var = { - let t = helper!(field_type); - types[field_kind].replace(t) - }; - - field_vars.push((types[field].clone(), field_var)); - } - - debug_assert!(ext_slice.len() <= 1); - let temp_ext_var = match ext_slice.into_iter().next() { - None => roc_types::subs::Variable::EMPTY_RECORD, - Some(ext) => helper!(ext), - }; - - let (it, new_ext_var) = - gather_fields_unsorted_iter(subs, RecordFields::empty(), temp_ext_var) - .expect("Something ended up weird in this record type"); - - let it = it - .into_iter() - .map(|(field, field_type)| (field.clone(), field_type)); - - field_vars.extend(it); - insertion_sort_by(&mut field_vars, RecordFields::compare); - - let record_fields = RecordFields::insert_into_subs(subs, field_vars); - - let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); - - register_with_known_var(subs, destination, rank, pools, content) - } - - Tuple(elems) => { - let ext_slice = types.get_type_arguments(typ_index); - - // Elems should never be empty; we don't support empty tuples - debug_assert!(!elems.is_empty() || !ext_slice.is_empty()); - - let mut elem_vars = Vec::with_capacity_in(elems.len(), arena); - - let (indices, elem_tys) = types.tuple_elems_slices(elems); - - for (index, elem_type) in indices.into_iter().zip(elem_tys.into_iter()) { - let elem_var = helper!(elem_type); - elem_vars.push((types[index], elem_var)); - } - - debug_assert!(ext_slice.len() <= 1); - let temp_ext_var = match ext_slice.into_iter().next() { - None => roc_types::subs::Variable::EMPTY_TUPLE, - Some(ext) => helper!(ext), - }; - - let (it, new_ext_var) = - gather_tuple_elems_unsorted_iter(subs, TupleElems::empty(), temp_ext_var) - .expect("Something ended up weird in this tuple type"); - - elem_vars.extend(it); - let tuple_elems = TupleElems::insert_into_subs(subs, elem_vars); - - let content = Content::Structure(FlatType::Tuple(tuple_elems, new_ext_var)); - - register_with_known_var(subs, destination, rank, pools, content) - } - - TagUnion(tags, ext_openness) => { - let ext_slice = types.get_type_arguments(typ_index); - - // An empty tags is inefficient (but would be correct) - // If hit, try to turn the value into an EmptyTagUnion in canonicalization - debug_assert!(!tags.is_empty() || !ext_slice.is_empty()); - - let (union_tags, ext) = type_to_union_tags( - subs, - rank, - pools, - arena, - types, - tags, - ext_slice, - ext_openness, - &mut stack, - ); - let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); - - register_with_known_var(subs, destination, rank, pools, content) - } - FunctionOrTagUnion(symbol, ext_openness) => { - let ext_slice = types.get_type_arguments(typ_index); - let tag_name = types.get_tag_name(&typ_index).clone(); - - debug_assert!(ext_slice.len() <= 1); - let temp_ext = match ext_slice.into_iter().next() { - Some(ext) => { - let var = helper!(ext); - TagExt::from_can(var, ext_openness) - } - None => TagExt::Any(roc_types::subs::Variable::EMPTY_TAG_UNION), - }; - - let (it, ext) = roc_types::types::gather_tags_unsorted_iter( - subs, - UnionTags::default(), - temp_ext, - ) - .expect("extension var could not be seen as a tag union"); - - for _ in it { - unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!"); - } - - let tag_names = SubsSlice::extend_new(&mut subs.tag_names, [tag_name]); - let symbols = SubsSlice::extend_new(&mut subs.symbol_names, [symbol]); - - let content = - Content::Structure(FlatType::FunctionOrTagUnion(tag_names, symbols, ext)); - - register_with_known_var(subs, destination, rank, pools, content) - } - RecursiveTagUnion(rec_var, tags, ext_openness) => { - let ext_slice = types.get_type_arguments(typ_index); - - // An empty tags is inefficient (but would be correct) - // If hit, try to turn the value into an EmptyTagUnion in canonicalization - debug_assert!(!tags.is_empty() || !ext_slice.is_empty()); - - let (union_tags, ext) = type_to_union_tags( - subs, - rank, - pools, - arena, - types, - tags, - ext_slice, - ext_openness, - &mut stack, - ); - let content = - Content::Structure(FlatType::RecursiveTagUnion(rec_var, union_tags, ext)); - - let tag_union_var = destination; - register_with_known_var(subs, tag_union_var, rank, pools, content); - - register_with_known_var( - subs, - rec_var, - rank, - pools, - Content::RecursionVar { - opt_name: None, - structure: tag_union_var, - }, - ); - - tag_union_var - } - - DelayedAlias { shared } => { - let AliasShared { - symbol, - type_argument_abilities, - type_argument_regions, - lambda_set_variables, - infer_ext_in_output_variables, - } = types[shared]; - - let type_arguments = types.get_type_arguments(typ_index); - - let alias_variables = { - let all_vars_length = type_arguments.len() - + lambda_set_variables.len() - + infer_ext_in_output_variables.len(); - let new_variables = VariableSubsSlice::reserve_into_subs(subs, all_vars_length); - - let type_arguments_offset = 0; - let lambda_set_vars_offset = type_arguments_offset + type_arguments.len(); - let infer_ext_vars_offset = lambda_set_vars_offset + lambda_set_variables.len(); - - for (((target_index, arg_type), arg_region), abilities) in - (new_variables.indices().skip(type_arguments_offset)) - .zip(type_arguments.into_iter()) - .zip(type_argument_regions.into_iter()) - .zip(type_argument_abilities.into_iter()) - { - let copy_var = helper!(arg_type); - subs.variables[target_index] = copy_var; - if !types[abilities].is_empty() { - let arg_region = types[arg_region]; - bind_to_abilities.push((Loc::at(arg_region, copy_var), abilities)); - } - } - - let it = (new_variables.indices().skip(lambda_set_vars_offset)) - .zip(lambda_set_variables.into_iter()); - for (target_index, ls) in it { - // We MUST do this now, otherwise when linking the ambient function during - // instantiation of the real var, there will be nothing to link against. - let copy_var = type_to_variable( - subs, - rank, - pools, - problems, - abilities_store, - obligation_cache, - arena, - aliases, - types, - ls, - true, - ); - subs.variables[target_index] = copy_var; - } - - let it = (new_variables.indices().skip(infer_ext_vars_offset)) - .zip(infer_ext_in_output_variables.into_iter()); - for (target_index, ext_typ) in it { - let copy_var = helper!(ext_typ); - subs.variables[target_index] = copy_var; - } - - AliasVariables { - variables_start: new_variables.start, - type_variables_len: type_arguments.len() as _, - lambda_set_variables_len: lambda_set_variables.len() as _, - all_variables_len: all_vars_length as _, - } - }; - - let (alias_variable, kind) = aliases.instantiate_real_var( - subs, - rank, - pools, - problems, - abilities_store, - obligation_cache, - arena, - types, - symbol, - alias_variables, - ); - - let content = Content::Alias(symbol, alias_variables, alias_variable, kind); - - register_with_known_var(subs, destination, rank, pools, content) - } - - StructuralAlias { shared, actual } | OpaqueAlias { shared, actual } => { - let kind = match typ { - StructuralAlias { .. } => AliasKind::Structural, - OpaqueAlias { .. } => AliasKind::Opaque, - _ => internal_error!(), - }; - - let AliasShared { - symbol, - type_argument_abilities, - type_argument_regions, - lambda_set_variables, - infer_ext_in_output_variables, - } = types[shared]; - - debug_assert!(roc_types::subs::Variable::get_reserved(symbol).is_none()); - - let type_arguments = types.get_type_arguments(typ_index); - - let alias_variables = { - let all_vars_length = type_arguments.len() - + lambda_set_variables.len() - + infer_ext_in_output_variables.len(); - - let type_arguments_offset = 0; - let lambda_set_vars_offset = type_arguments_offset + type_arguments.len(); - let infer_ext_vars_offset = lambda_set_vars_offset + lambda_set_variables.len(); - - let new_variables = VariableSubsSlice::reserve_into_subs(subs, all_vars_length); - - for (((target_index, typ), region), abilities) in - (new_variables.indices().skip(type_arguments_offset)) - .zip(type_arguments.into_iter()) - .zip(type_argument_regions.into_iter()) - .zip(type_argument_abilities.into_iter()) - { - let copy_var = helper!(typ); - subs.variables[target_index] = copy_var; - if !types[abilities].is_empty() { - let region = types[region]; - bind_to_abilities.push((Loc::at(region, copy_var), abilities)); - } - } - - let it = (new_variables.indices().skip(lambda_set_vars_offset)) - .zip(lambda_set_variables.into_iter()); - for (target_index, ls) in it { - let copy_var = helper!(ls); - subs.variables[target_index] = copy_var; - } - - let it = (new_variables.indices().skip(infer_ext_vars_offset)) - .zip(infer_ext_in_output_variables.into_iter()); - for (target_index, ext_typ) in it { - let copy_var = helper!(ext_typ); - subs.variables[target_index] = copy_var; - } - - AliasVariables { - variables_start: new_variables.start, - type_variables_len: type_arguments.len() as _, - lambda_set_variables_len: lambda_set_variables.len() as _, - all_variables_len: all_vars_length as _, - } - }; - - let alias_variable = if let Symbol::RESULT_RESULT = symbol { - roc_result_to_var(subs, rank, pools, arena, types, actual, &mut stack) - } else { - helper!(actual) - }; - let content = Content::Alias(symbol, alias_variables, alias_variable, kind); - - register_with_known_var(subs, destination, rank, pools, content) - } - HostExposedAlias { - shared, - actual_type: alias_type, - actual_variable: actual_var, - } => { - let AliasShared { - symbol, - type_argument_abilities: _, - type_argument_regions: _, - lambda_set_variables, - infer_ext_in_output_variables: _, // TODO - } = types[shared]; - - let type_arguments = types.get_type_arguments(typ_index); - - let alias_variables = { - let length = type_arguments.len() + lambda_set_variables.len(); - let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); - - for (target_index, arg_type) in - (new_variables.indices()).zip(type_arguments.into_iter()) - { - let copy_var = helper!(arg_type); - subs.variables[target_index] = copy_var; - } - let it = (new_variables.indices().skip(type_arguments.len())) - .zip(lambda_set_variables.into_iter()); - for (target_index, ls) in it { - // We MUST do this now, otherwise when linking the ambient function during - // instantiation of the real var, there will be nothing to link against. - let copy_var = type_to_variable( - subs, - rank, - pools, - problems, - abilities_store, - obligation_cache, - arena, - aliases, - types, - ls, - true, - ); - subs.variables[target_index] = copy_var; - } - - AliasVariables { - variables_start: new_variables.start, - type_variables_len: type_arguments.len() as _, - lambda_set_variables_len: lambda_set_variables.len() as _, - all_variables_len: length as _, - } - }; - - // cannot use helper! here because this variable may be involved in unification below - let alias_variable = type_to_variable( - subs, - rank, - pools, - problems, - abilities_store, - obligation_cache, - arena, - aliases, - types, - alias_type, - false, - ); - // TODO(opaques): I think host-exposed aliases should always be structural - // (when does it make sense to give a host an opaque type?) - let content = Content::Alias( - symbol, - alias_variables, - alias_variable, - AliasKind::Structural, - ); - let result = register_with_known_var(subs, destination, rank, pools, content); - - // We only want to unify the actual_var with the alias once - // if it's already redirected (and therefore, redundant) - // don't do it again - if !subs.redundant(actual_var) { - let descriptor = subs.get(result); - subs.union(result, actual_var, descriptor); - } - - result - } - Error => { - let content = Content::Error; - - register_with_known_var(subs, destination, rank, pools, content) - } - }; - } - - for (Loc { value: var, region }, abilities) in bind_to_abilities { - let abilities = &types[abilities]; - match *subs.get_content_unchecked(var) { - Content::RigidVar(a) => { - // TODO(multi-abilities): check run cache - let abilities_slice = - SubsSlice::extend_new(&mut subs.symbol_names, abilities.sorted_iter().copied()); - subs.set_content(var, Content::RigidAbleVar(a, abilities_slice)); - } - Content::RigidAbleVar(_, abs) - if (subs.get_subs_slice(abs).iter()).eq(abilities.sorted_iter()) => - { - // pass, already bound - } - _ => { - let abilities_slice = - SubsSlice::extend_new(&mut subs.symbol_names, abilities.sorted_iter().copied()); - - let flex_ability = register( - subs, - rank, - pools, - Content::FlexAbleVar(None, abilities_slice), - ); - - let category = Category::OpaqueArg; - match unify( - &mut UEnv::new(subs), - var, - flex_ability, - Mode::EQ, - Polarity::OF_VALUE, - ) { - Success { - vars: _, - must_implement_ability, - lambda_sets_to_specialize, - extra_metadata: _, - } => { - // No introduction needed - - if !must_implement_ability.is_empty() { - let new_problems = obligation_cache.check_obligations( - subs, - abilities_store, - must_implement_ability, - AbilityImplError::BadExpr(region, category, flex_ability), - ); - problems.extend(new_problems); - } - debug_assert!(lambda_sets_to_specialize - .drain() - .all(|(_, vals)| vals.is_empty())); - } - Failure(_vars, actual_type, expected_type, _bad_impls) => { - // No introduction needed - - let problem = TypeError::BadExpr( - region, - category, - actual_type, - Expected::NoExpectation(expected_type), - ); - - problems.push(problem); - } - } - } - } - } - - result -} - -#[inline(always)] -fn roc_result_to_var( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - arena: &'_ bumpalo::Bump, - types: &mut Types, - result_type: Index, - stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, -) -> Variable { - match types[result_type] { - TypeTag::TagUnion(tags, _ext_openness) => { - let ext_slice = types.get_type_arguments(result_type); - - debug_assert!(ext_slice.is_empty()); - debug_assert!(tags.len() == 2); - - let (tags_slice, payload_slices_slice) = types.union_tag_slices(tags); - - if let ([err, ok], [err_args, ok_args]) = - (&types[tags_slice], &types[payload_slices_slice]) - { - debug_assert_eq!(err, &subs.tag_names[0]); - debug_assert_eq!(ok, &subs.tag_names[1]); - - debug_assert_eq!(err_args.len(), 1); - debug_assert_eq!(ok_args.len(), 1); - - if let (Some(err_type), Some(ok_type)) = - (err_args.into_iter().next(), ok_args.into_iter().next()) - { - let err_var = RegisterVariable::with_stack( - subs, rank, pools, arena, types, err_type, stack, - ); - let ok_var = RegisterVariable::with_stack( - subs, rank, pools, arena, types, ok_type, stack, - ); - - let start = subs.variables.len() as u32; - let err_slice = SubsSlice::new(start, 1); - let ok_slice = SubsSlice::new(start + 1, 1); - - subs.variables.push(err_var); - subs.variables.push(ok_var); - - let variables = SubsSlice::new(subs.variable_slices.len() as _, 2); - subs.variable_slices.push(err_slice); - subs.variable_slices.push(ok_slice); - - let union_tags = UnionTags::from_slices(Subs::RESULT_TAG_NAMES, variables); - let ext = TagExt::Any(Variable::EMPTY_TAG_UNION); - - let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); - - return register(subs, rank, pools, content); - } - } - - unreachable!("invalid arguments to Result.Result; canonicalization should catch this!") - } - _ => unreachable!("not a valid type inside a Result.Result alias"), - } -} - -fn insertion_sort_by(arr: &mut [T], mut compare: F) -where - F: FnMut(&T, &T) -> std::cmp::Ordering, -{ - for i in 1..arr.len() { - let val = &arr[i]; - let mut j = i; - let pos = arr[..i] - .binary_search_by(|x| compare(x, val)) - .unwrap_or_else(|pos| pos); - // Swap all elements until specific position. - while j > pos { - arr.swap(j - 1, j); - j -= 1; - } - } -} - -fn sorted_no_duplicate_tags(tag_slices: &[TagName]) -> bool { - match tag_slices.split_first() { - None => true, - Some((first, rest)) => { - let mut current = first; - - for next in rest { - if current >= next { - return false; - } else { - current = next; - } - } - - true - } - } -} - -fn sort_and_deduplicate(tag_vars: &mut bumpalo::collections::Vec<(TagName, T)>) { - insertion_sort_by(tag_vars, |(a, _), (b, _)| a.cmp(b)); - - // deduplicate, keeping the right-most occurrence of a tag name - let mut i = 0; - - while i < tag_vars.len() { - match (tag_vars.get(i), tag_vars.get(i + 1)) { - (Some((t1, _)), Some((t2, _))) => { - if t1 == t2 { - tag_vars.remove(i); - } else { - i += 1; - } - } - _ => break, - } - } -} - -/// Find whether the current run of tag names is in the subs.tag_names array already. If so, -/// we take a SubsSlice to the existing tag names, so we don't have to add/clone those tag names -/// and keep subs memory consumption low -fn find_tag_name_run(slice: &[TagName], subs: &mut Subs) -> Option> { - use std::cmp::Ordering; - - let tag_name = slice.get(0)?; - - let mut result = None; - - // the `SubsSlice` that inserting `slice` into subs would give - let bigger_slice = SubsSlice::new(subs.tag_names.len() as _, slice.len() as _); - - match subs.tag_name_cache.get_mut(tag_name) { - Some(occupied) => { - let subs_slice = *occupied; - - let prefix_slice = SubsSlice::new(subs_slice.start, slice.len() as _); - - if slice.len() == 1 { - return Some(prefix_slice); - } - - match slice.len().cmp(&subs_slice.len()) { - Ordering::Less => { - // we might have a prefix - let tag_names = &subs.tag_names[subs_slice.start as usize..]; - - for (from_subs, from_slice) in tag_names.iter().zip(slice.iter()) { - if from_subs != from_slice { - return None; - } - } - - result = Some(prefix_slice); - } - Ordering::Equal => { - let tag_names = &subs.tag_names[subs_slice.indices()]; - - for (from_subs, from_slice) in tag_names.iter().zip(slice.iter()) { - if from_subs != from_slice { - return None; - } - } - - result = Some(subs_slice); - } - Ordering::Greater => { - // switch to the bigger slice that is not inserted yet, but will be soon - *occupied = bigger_slice; - } - } - } - None => { - subs.tag_name_cache.push(tag_name, bigger_slice); - } - } - - result -} - -#[inline(always)] -fn register_tag_arguments( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - arena: &'_ bumpalo::Bump, - types: &mut Types, - stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, - arguments: Slice, -) -> VariableSubsSlice { - if arguments.is_empty() { - VariableSubsSlice::default() - } else { - let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - let it = new_variables.indices().zip(arguments.into_iter()); - - for (target_index, argument) in it { - let var = - RegisterVariable::with_stack(subs, rank, pools, arena, types, argument, stack); - subs.variables[target_index] = var; - } - - new_variables - } -} - -/// Assumes that the tags are sorted and there are no duplicates! -fn insert_tags_fast_path( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - arena: &'_ bumpalo::Bump, - types: &mut Types, - union_tags: UnionTags, - stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, -) -> UnionTags { - let (tags, payload_slices) = types.union_tag_slices(union_tags); - - debug_assert_eq!(tags.len(), payload_slices.len()); - - if let [arguments_slice] = &types[payload_slices] { - let arguments_slice = *arguments_slice; - - let variable_slice = - register_tag_arguments(subs, rank, pools, arena, types, stack, arguments_slice); - - let new_variable_slices = - SubsSlice::extend_new(&mut subs.variable_slices, [variable_slice]); - - macro_rules! subs_tag_name { - ($tag_name_slice:expr) => { - return UnionTags::from_slices($tag_name_slice, new_variable_slices) - }; - } - - match types[tags][0].0.as_str() { - "Ok" => subs_tag_name!(Subs::TAG_NAME_OK.as_slice()), - "Err" => subs_tag_name!(Subs::TAG_NAME_ERR.as_slice()), - "InvalidNumStr" => subs_tag_name!(Subs::TAG_NAME_INVALID_NUM_STR.as_slice()), - "BadUtf8" => subs_tag_name!(Subs::TAG_NAME_BAD_UTF_8.as_slice()), - "OutOfBounds" => subs_tag_name!(Subs::TAG_NAME_OUT_OF_BOUNDS.as_slice()), - _other => {} - } - } - - let new_variable_slices = SubsSlice::reserve_variable_slices(subs, tags.len()); - match find_tag_name_run(&types[tags], subs) { - Some(new_tag_names) => { - let it = (new_variable_slices.indices()).zip(payload_slices.into_iter()); - - for (variable_slice_index, arguments_index) in it { - let arguments = types[arguments_index]; - subs.variable_slices[variable_slice_index] = - register_tag_arguments(subs, rank, pools, arena, types, stack, arguments); - } - - UnionTags::from_slices(new_tag_names, new_variable_slices) - } - None => { - let new_tag_names = SubsSlice::reserve_tag_names(subs, tags.len()); - - let it = (new_variable_slices.indices()) - .zip(new_tag_names.indices()) - .zip(tags.into_iter()) - .zip(payload_slices.into_iter()); - - for (((variable_slice_index, tag_name_index), tag_name), arguments_index) in it { - let arguments = types[arguments_index]; - subs.variable_slices[variable_slice_index] = - register_tag_arguments(subs, rank, pools, arena, types, stack, arguments); - - subs.tag_names[tag_name_index] = types[tag_name].clone(); - } - - UnionTags::from_slices(new_tag_names, new_variable_slices) - } - } -} - -fn insert_tags_slow_path( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - arena: &'_ bumpalo::Bump, - types: &mut Types, - union_tags: UnionTags, - mut tag_vars: bumpalo::collections::Vec<(TagName, VariableSubsSlice)>, - stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, -) -> UnionTags { - let (tags, payload_slices) = types.union_tag_slices(union_tags); - - for (tag_index, tag_argument_types_index) in (tags.into_iter()).zip(payload_slices.into_iter()) - { - let tag_argument_types = &types[tag_argument_types_index]; - - let new_slice = VariableSubsSlice::reserve_into_subs(subs, tag_argument_types.len()); - - for (i, arg) in (new_slice.indices()).zip(tag_argument_types.into_iter()) { - let var = RegisterVariable::with_stack(subs, rank, pools, arena, types, arg, stack); - subs.variables[i] = var; - } - - tag_vars.push((types[tag_index].clone(), new_slice)); - } - - sort_and_deduplicate(&mut tag_vars); - - UnionTags::insert_slices_into_subs(subs, tag_vars) -} - -fn type_to_union_tags( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - arena: &'_ bumpalo::Bump, - types: &mut Types, - union_tags: UnionTags, - opt_ext_slice: Slice, - ext_openness: ExtImplicitOpenness, - stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, -) -> (UnionTags, TagExt) { - use bumpalo::collections::Vec; - - let (tags, _) = types.union_tag_slices(union_tags); - - let sorted = tags.len() == 1 || sorted_no_duplicate_tags(&types[tags]); - - debug_assert!(opt_ext_slice.len() <= 1); - - match opt_ext_slice.into_iter().next() { - None => { - let ext = Variable::EMPTY_TAG_UNION; - - let union_tags = if sorted { - insert_tags_fast_path(subs, rank, pools, arena, types, union_tags, stack) - } else { - let tag_vars = Vec::with_capacity_in(tags.len(), arena); - insert_tags_slow_path(subs, rank, pools, arena, types, union_tags, tag_vars, stack) - }; - - (union_tags, TagExt::Any(ext)) - } - Some(ext) => { - let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); - - let temp_ext = { - let temp_ext_var = - RegisterVariable::with_stack(subs, rank, pools, arena, types, ext, stack); - TagExt::from_can(temp_ext_var, ext_openness) - }; - let (it, ext) = - roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext) - .expect("extension var could not be seen as tag union"); - - tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); - - let union_tags = if tag_vars.is_empty() && sorted { - insert_tags_fast_path(subs, rank, pools, arena, types, union_tags, stack) - } else { - insert_tags_slow_path(subs, rank, pools, arena, types, union_tags, tag_vars, stack) - }; - - (union_tags, ext) - } - } -} - -fn create_union_lambda( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - arena: &'_ bumpalo::Bump, - types: &mut Types, - closure: Symbol, - capture_types: Slice, - stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, -) -> UnionLambdas { - let variable_slice = - register_tag_arguments(subs, rank, pools, arena, types, stack, capture_types); - let new_variable_slices = SubsSlice::extend_new(&mut subs.variable_slices, [variable_slice]); - - let lambda_name_slice = SubsSlice::extend_new(&mut subs.symbol_names, [closure]); - - UnionLambdas::from_slices(lambda_name_slice, new_variable_slices) -} - -fn check_for_infinite_type( - subs: &mut Subs, - pools: &mut Pools, - problems: &mut Vec, - symbol: Symbol, - loc_var: Loc, -) { - let var = loc_var.value; - - 'next_occurs_check: while let Err((_, chain)) = subs.occurs(var) { - // walk the chain till we find a tag union or lambda set, starting from the variable that - // occurred recursively, which is always at the end of the chain. - for &var in chain.iter().rev() { - match *subs.get_content_without_compacting(var) { - Content::Structure(FlatType::TagUnion(tags, ext_var)) => { - let rec_var = subs.mark_tag_union_recursive(var, tags, ext_var); - register_to_pools(subs, rec_var, pools); - - continue 'next_occurs_check; - } - Content::LambdaSet(subs::LambdaSet { - solved, - recursion_var: OptVariable::NONE, - unspecialized, - ambient_function: ambient_function_var, - }) => { - let rec_var = subs.mark_lambda_set_recursive( - var, - solved, - unspecialized, - ambient_function_var, - ); - register_to_pools(subs, rec_var, pools); - - continue 'next_occurs_check; - } - _ => { /* fall through */ } - } - } - - circular_error(subs, problems, symbol, &loc_var); + + circular_error(env.subs, problems, symbol, &loc_var); } } @@ -3655,13 +1875,10 @@ fn circular_error( /// Ensures that variables introduced at the `young_rank`, but that should be /// stuck at a lower level, are marked at that level and not generalized at the /// present `young_rank`. See [adjust_rank]. -fn generalize( - subs: &mut Subs, - young_mark: Mark, - visit_mark: Mark, - young_rank: Rank, - pools: &mut Pools, -) { +fn generalize(env: &mut Env, young_mark: Mark, visit_mark: Mark, young_rank: Rank) { + let subs = &mut env.subs; + let pools = &mut env.pools; + let young_vars = std::mem::take(pools.get_mut(young_rank)); let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars); @@ -4089,419 +2306,3 @@ fn adjust_rank_content( RangedNumber(_) => group_rank, } } - -/// Introduce some variables to Pools at the given rank. -/// Also, set each of their ranks in Subs to be the given rank. -pub(crate) fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) { - let pool: &mut Vec = pools.get_mut(rank); - - for &var in vars.iter() { - subs.set_rank(var, rank); - } - - pool.extend(vars); -} - -pub(crate) fn deep_copy_var_in( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - var: Variable, - arena: &Bump, -) -> Variable { - let mut visited = bumpalo::collections::Vec::with_capacity_in(256, arena); - - let pool = pools.get_mut(rank); - - let var = subs.get_root_key(var); - match deep_copy_var_decision(subs, rank, var) { - ControlFlow::Break(copy) => copy, - ControlFlow::Continue(copy) => { - deep_copy_var_help(subs, rank, pool, &mut visited, var, copy); - - // we have tracked all visited variables, and can now traverse them - // in one go (without looking at the UnificationTable) and clear the copy field - for var in visited { - subs.set_copy_unchecked(var, OptVariable::NONE); - } - - copy - } - } -} - -#[inline] -fn has_trivial_copy(subs: &Subs, root_var: Variable) -> Option { - let existing_copy = subs.get_copy_unchecked(root_var); - - if let Some(copy) = existing_copy.into_variable() { - Some(copy) - } else if subs.get_rank_unchecked(root_var) != Rank::GENERALIZED { - Some(root_var) - } else { - None - } -} - -#[inline] -fn deep_copy_var_decision( - subs: &mut Subs, - max_rank: Rank, - var: Variable, -) -> ControlFlow { - let var = subs.get_root_key(var); - if let Some(copy) = has_trivial_copy(subs, var) { - ControlFlow::Break(copy) - } else { - let copy_descriptor = Descriptor { - content: Content::Structure(FlatType::EmptyTagUnion), - rank: max_rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }; - - let copy = subs.fresh(copy_descriptor); - - // Link the original variable to the new variable. This lets us - // avoid making multiple copies of the variable we are instantiating. - // - // Need to do this before recursively copying to avoid looping. - subs.set_mark_unchecked(var, Mark::NONE); - subs.set_copy_unchecked(var, copy.into()); - - ControlFlow::Continue(copy) - } -} - -fn deep_copy_var_help( - subs: &mut Subs, - max_rank: Rank, - pool: &mut Vec, - visited: &mut bumpalo::collections::Vec<'_, Variable>, - initial_source: Variable, - initial_copy: Variable, -) -> Variable { - use roc_types::subs::Content::*; - use roc_types::subs::FlatType::*; - - struct DeepCopyVarWork { - source: Variable, - copy: Variable, - } - - let initial = DeepCopyVarWork { - source: initial_source, - copy: initial_copy, - }; - let mut stack = vec![initial]; - - macro_rules! work { - ($variable:expr) => {{ - let var = subs.get_root_key($variable); - match deep_copy_var_decision(subs, max_rank, var) { - ControlFlow::Break(copy) => copy, - ControlFlow::Continue(copy) => { - stack.push(DeepCopyVarWork { source: var, copy }); - - copy - } - } - }}; - } - - macro_rules! copy_sequence { - ($length:expr, $variables:expr) => {{ - let new_variables = SubsSlice::reserve_into_subs(subs, $length as _); - for (target_index, var_index) in (new_variables.indices()).zip($variables) { - let var = subs[var_index]; - let copy_var = work!(var); - subs.variables[target_index] = copy_var; - } - - new_variables - }}; - } - - macro_rules! copy_union { - ($tags:expr) => {{ - let new_variable_slices = SubsSlice::reserve_variable_slices(subs, $tags.len()); - - let it = (new_variable_slices.indices()).zip($tags.variables()); - for (target_index, index) in it { - let slice = subs[index]; - - let new_variables = copy_sequence!(slice.len(), slice); - subs.variable_slices[target_index] = new_variables; - } - - UnionLabels::from_slices($tags.labels(), new_variable_slices) - }}; - } - - // When generalizing annotations with `Openness` extensions - // we want to promote them to `Any`, so that usages at - // specialized sites can grow unboundedly and are not bound to - // openness-polymorphism. - macro_rules! copy_tag_ext { - ($ext:expr) => { - TagExt::Any(work!($ext.var())) - }; - } - - while let Some(DeepCopyVarWork { source: var, copy }) = stack.pop() { - visited.push(var); - pool.push(copy); - - let content = *subs.get_content_unchecked(var); - - // Now we recursively copy the content of the variable. - // We have already marked the variable as copied, so we - // will not repeat this work or crawl this variable again. - match content { - Structure(flat_type) => { - let new_flat_type = match flat_type { - Apply(symbol, arguments) => { - let new_arguments = copy_sequence!(arguments.len(), arguments); - - Apply(symbol, new_arguments) - } - - Func(arguments, closure_var, ret_var) => { - let new_ret_var = work!(ret_var); - let new_closure_var = work!(closure_var); - - let new_arguments = copy_sequence!(arguments.len(), arguments); - - Func(new_arguments, new_closure_var, new_ret_var) - } - - same @ EmptyRecord | same @ EmptyTuple | same @ EmptyTagUnion => same, - - Record(fields, ext_var) => { - let record_fields = { - let new_variables = - copy_sequence!(fields.len(), fields.iter_variables()); - - // When copying a let-generalized record to a specialized region, rigid - // optionals just become optionals. - let field_types = subs.get_subs_slice(fields.record_fields()); - let has_rigid_optional_field = field_types - .iter() - .any(|f| matches!(f, RecordField::RigidOptional(..))); - - let new_field_types_start = if has_rigid_optional_field { - let field_types = field_types.to_vec(); - let slice = SubsSlice::extend_new( - &mut subs.record_fields, - field_types.into_iter().map(|f| match f { - RecordField::RigidOptional(()) - | RecordField::RigidRequired(()) => internal_error!("Rigid optional/required should be generalized to non-rigid by this point"), - - RecordField::Demanded(_) - | RecordField::Required(_) - | RecordField::Optional(_) => f, - }), - ); - slice.start - } else { - fields.field_types_start - }; - - RecordFields { - length: fields.length, - field_names_start: fields.field_names_start, - variables_start: new_variables.start, - field_types_start: new_field_types_start, - } - }; - - Record(record_fields, work!(ext_var)) - } - - Tuple(elems, ext_var) => { - let tuple_elems = { - let new_variables = copy_sequence!(elems.len(), elems.iter_variables()); - - TupleElems { - length: elems.length, - variables_start: new_variables.start, - elem_index_start: elems.elem_index_start, - } - }; - - Tuple(tuple_elems, work!(ext_var)) - } - - TagUnion(tags, ext_var) => { - let union_tags = copy_union!(tags); - - TagUnion(union_tags, copy_tag_ext!(ext_var)) - } - - FunctionOrTagUnion(tag_name, symbol, ext_var) => { - FunctionOrTagUnion(tag_name, symbol, copy_tag_ext!(ext_var)) - } - - RecursiveTagUnion(rec_var, tags, ext_var) => { - let union_tags = copy_union!(tags); - - RecursiveTagUnion(work!(rec_var), union_tags, copy_tag_ext!(ext_var)) - } - }; - - subs.set_content_unchecked(copy, Structure(new_flat_type)); - } - - FlexVar(_) | FlexAbleVar(_, _) | Error => { - subs.set_content_unchecked(copy, content); - } - - RecursionVar { - opt_name, - structure, - } => { - let content = RecursionVar { - opt_name, - structure: work!(structure), - }; - - subs.set_content_unchecked(copy, content); - } - - RigidVar(name) => { - subs.set_content_unchecked(copy, FlexVar(Some(name))); - } - - RigidAbleVar(name, ability) => { - subs.set_content_unchecked(copy, FlexAbleVar(Some(name), ability)); - } - - Alias(symbol, arguments, real_type_var, kind) => { - let new_variables = - copy_sequence!(arguments.all_variables_len, arguments.all_variables()); - - let new_arguments = AliasVariables { - variables_start: new_variables.start, - ..arguments - }; - - let new_real_type_var = work!(real_type_var); - let new_content = Alias(symbol, new_arguments, new_real_type_var, kind); - - subs.set_content_unchecked(copy, new_content); - } - - LambdaSet(subs::LambdaSet { - solved, - recursion_var, - unspecialized, - ambient_function: ambient_function_var, - }) => { - let lambda_set_var = copy; - - let new_solved = copy_union!(solved); - let new_rec_var = recursion_var.map(|v| work!(v)); - let new_unspecialized = SubsSlice::reserve_uls_slice(subs, unspecialized.len()); - - for (new_uls_index, uls_index) in - (new_unspecialized.into_iter()).zip(unspecialized.into_iter()) - { - let Uls(var, sym, region) = subs[uls_index]; - let new_var = work!(var); - - deep_copy_uls_precondition(subs, var, new_var); - - subs[new_uls_index] = Uls(new_var, sym, region); - - subs.uls_of_var.add(new_var, lambda_set_var); - } - - let new_ambient_function_var = work!(ambient_function_var); - debug_assert_ne!( - ambient_function_var, new_ambient_function_var, - "lambda set cloned but its ambient function wasn't?" - ); - - subs.set_content_unchecked( - lambda_set_var, - LambdaSet(subs::LambdaSet { - solved: new_solved, - recursion_var: new_rec_var, - unspecialized: new_unspecialized, - ambient_function: new_ambient_function_var, - }), - ); - } - - RangedNumber(range) => { - let new_content = RangedNumber(range); - - subs.set_content_unchecked(copy, new_content); - } - } - } - - initial_copy -} - -#[inline(always)] -fn deep_copy_uls_precondition(subs: &Subs, original_var: Variable, new_var: Variable) { - if cfg!(debug_assertions) { - let content = subs.get_content_without_compacting(original_var); - - debug_assert!( - matches!( - content, - Content::FlexAbleVar(..) | Content::RigidAbleVar(..) - ), - "var in unspecialized lamba set is not bound to an ability, it is {:?}", - roc_types::subs::SubsFmtContent(content, subs) - ); - debug_assert!( - original_var != new_var, - "unspecialized lamba set var was not instantiated" - ); - } -} - -#[inline(always)] -fn register(subs: &mut Subs, rank: Rank, pools: &mut Pools, content: Content) -> Variable { - let descriptor = Descriptor { - content, - rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }; - - let var = subs.fresh(descriptor); - - pools.get_mut(rank).push(var); - - var -} - -fn register_with_known_var( - subs: &mut Subs, - var: Variable, - rank: Rank, - pools: &mut Pools, - content: Content, -) -> Variable { - let descriptor = Descriptor { - content, - rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }; - - subs.set(var, descriptor); - - pools.get_mut(rank).push(var); - - var -} - -#[inline(always)] -fn register_to_pools(subs: &Subs, var: Variable, pools: &mut Pools) { - pools.get_mut(subs.get_rank(var)).push(var); -} diff --git a/crates/compiler/solve/src/solve/scope.rs b/crates/compiler/solve/src/solve/scope.rs new file mode 100644 index 00000000000..5acd041c968 --- /dev/null +++ b/crates/compiler/solve/src/solve/scope.rs @@ -0,0 +1,40 @@ +use roc_module::symbol::Symbol; +use roc_types::subs::Variable; + +/// The scope of the solver, as symbols are introduced. +#[derive(Clone, Debug, Default)] +pub struct Scope { + symbols: Vec, + variables: Vec, +} + +impl Scope { + pub fn vars_by_symbol(&self) -> impl Iterator + '_ { + let it1 = self.symbols.iter().copied(); + let it2 = self.variables.iter().copied(); + + it1.zip(it2) + } + + #[inline(always)] + pub fn get_var_by_symbol(&self, symbol: &Symbol) -> Option { + self.symbols + .iter() + .position(|s| s == symbol) + .map(|index| self.variables[index]) + } + + #[inline(always)] + pub fn insert_symbol_var_if_vacant(&mut self, symbol: Symbol, var: Variable) { + match self.symbols.iter().position(|s| *s == symbol) { + None => { + // symbol is not in vars_by_symbol yet; insert it + self.symbols.push(symbol); + self.variables.push(var); + } + Some(_) => { + // do nothing + } + } + } +} diff --git a/crates/compiler/solve/src/specialize.rs b/crates/compiler/solve/src/specialize.rs index d6344f70fc2..1ea79cff59a 100644 --- a/crates/compiler/solve/src/specialize.rs +++ b/crates/compiler/solve/src/specialize.rs @@ -2,16 +2,11 @@ use std::collections::VecDeque; -use bumpalo::Bump; -use roc_can::{ - abilities::{AbilitiesStore, ImplKey}, - module::ExposedByModule, -}; +use roc_can::abilities::{AbilitiesStore, ImplKey}; use roc_collections::{VecMap, VecSet}; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] use roc_debug_flags::ROC_TRACE_COMPACTION; -use roc_derive::SharedDerivedModule; use roc_derive_key::{DeriveError, DeriveKey}; use roc_error_macros::{internal_error, todo_abilities}; use roc_module::symbol::{ModuleId, Symbol}; @@ -22,11 +17,12 @@ use roc_types::{ }, types::{AliasKind, MemberImpl, Polarity, Uls}, }; -use roc_unify::unify::{unify, Env as UEnv, Mode, MustImplementConstraints}; +use roc_unify::unify::{unify, Mode, MustImplementConstraints}; use crate::{ ability::builtin_module_with_unlisted_ability_impl, - solve::{deep_copy_var_in, introduce, Pools}, + deep_copy::deep_copy_var_in, + env::{DerivedEnv, Env}, }; /// What phase in the compiler is reaching out to specialize lambda sets? @@ -121,12 +117,6 @@ impl Phase for SolvePhase<'_> { } } -pub struct DerivedEnv<'a> { - pub derived_module: &'a SharedDerivedModule, - /// Exposed types needed by the derived module. - pub exposed_types: &'a ExposedByModule, -} - #[derive(Default)] pub struct AwaitingSpecializations { // What variables' specialized lambda sets in `uls_of_var` will be unlocked for specialization @@ -305,10 +295,7 @@ fn unique_unspecialized_lambda(subs: &Subs, c_a: Variable, uls: &[Uls]) -> Optio #[must_use] pub fn compact_lambda_sets_of_vars( - subs: &mut Subs, - derived_env: &DerivedEnv, - arena: &Bump, - pools: &mut Pools, + env: &mut Env, uls_of_var: UlsOfVar, phase: &P, ) -> CompactionResult { @@ -320,7 +307,7 @@ pub fn compact_lambda_sets_of_vars( // Suppose a type variable `a` with `uls_of_var` mapping `uls_a = {l1, ... ln}` has been instantiated to a concrete type `C_a`. while let Some((c_a, uls_a)) = uls_of_var_queue.pop_front() { - let c_a = subs.get_root_key_without_compacting(c_a); + let c_a = env.subs.get_root_key_without_compacting(c_a); // 1. Let each `l` in `uls_a` be of form `[solved_lambdas + ... + C:f:r + ...]`. // NB: There may be multiple unspecialized lambdas of form `C:f:r, C:f1:r1, ..., C:fn:rn` in `l`. // In this case, let `t1, ... tm` be the other unspecialized lambdas not of form `C:_:_`, @@ -332,13 +319,13 @@ pub fn compact_lambda_sets_of_vars( let mut uls = uls_a.into_vec(); // De-duplicate lambdas by root key. - uls.iter_mut().for_each(|v| *v = subs.get_root_key(*v)); + uls.iter_mut().for_each(|v| *v = env.subs.get_root_key(*v)); uls.sort(); uls.dedup(); uls }; - trace_compact!(1. subs, c_a, &uls_a); + trace_compact!(1. env.subs, c_a, &uls_a); // The flattening step - remove lambda sets that don't reference the concrete var, and for // flatten lambda sets that reference it more than once. @@ -350,15 +337,15 @@ pub fn compact_lambda_sets_of_vars( recursion_var, unspecialized, ambient_function, - } = subs.get_lambda_set(lambda_set); - let lambda_set_rank = subs.get_rank(lambda_set); - let unspecialized = subs.get_subs_slice(unspecialized); + } = env.subs.get_lambda_set(lambda_set); + let lambda_set_rank = env.subs.get_rank(lambda_set); + let unspecialized = env.subs.get_subs_slice(unspecialized); // TODO: is it faster to traverse once, see if we only have one concrete lambda, and // bail in that happy-path, rather than always splitting? let (concrete, mut not_concrete): (Vec<_>, Vec<_>) = unspecialized .iter() .copied() - .partition(|Uls(var, _, _)| subs.equivalent_without_compacting(*var, c_a)); + .partition(|Uls(var, _, _)| env.subs.equivalent_without_compacting(*var, c_a)); if concrete.len() == 1 { // No flattening needs to be done, just return the lambda set as-is return vec![lambda_set]; @@ -373,7 +360,7 @@ pub fn compact_lambda_sets_of_vars( // lambdas, plus all other unspecialized lambdas. // l' = [solved_lambdas + t1 + ... + tm + C:f:r] let unspecialized = SubsSlice::extend_new( - &mut subs.unspecialized_lambda_sets, + &mut env.subs.unspecialized_lambda_sets, not_concrete .drain(..) .chain(std::iter::once(concrete_lambda)), @@ -384,10 +371,10 @@ pub fn compact_lambda_sets_of_vars( // lambdas. // ln = [[] + C:fn:rn] let unspecialized = SubsSlice::extend_new( - &mut subs.unspecialized_lambda_sets, + &mut env.subs.unspecialized_lambda_sets, [concrete_lambda], ); - let var = subs.fresh(Descriptor { + let var = env.subs.fresh(Descriptor { content: Content::Error, rank: lambda_set_rank, mark: Mark::NONE, @@ -396,7 +383,7 @@ pub fn compact_lambda_sets_of_vars( (var, unspecialized) }; - subs.set_content( + env.subs.set_content( var, Content::LambdaSet(LambdaSet { solved, @@ -414,11 +401,15 @@ pub fn compact_lambda_sets_of_vars( // 2. Now, each `l` in `uls_a` has a unique unspecialized lambda of form `C:f:r`. // Sort `uls_a` primarily by `f` (arbitrary order), and secondarily by `r` in descending order. uls_a.sort_by(|v1, v2| { - let unspec_1 = subs.get_subs_slice(subs.get_lambda_set(*v1).unspecialized); - let unspec_2 = subs.get_subs_slice(subs.get_lambda_set(*v2).unspecialized); + let unspec_1 = env + .subs + .get_subs_slice(env.subs.get_lambda_set(*v1).unspecialized); + let unspec_2 = env + .subs + .get_subs_slice(env.subs.get_lambda_set(*v2).unspecialized); - let Uls(_, f1, r1) = unique_unspecialized_lambda(subs, c_a, unspec_1).unwrap(); - let Uls(_, f2, r2) = unique_unspecialized_lambda(subs, c_a, unspec_2).unwrap(); + let Uls(_, f1, r1) = unique_unspecialized_lambda(env.subs, c_a, unspec_1).unwrap(); + let Uls(_, f2, r2) = unique_unspecialized_lambda(env.subs, c_a, unspec_2).unwrap(); match f1.cmp(&f2) { std::cmp::Ordering::Equal => { @@ -429,7 +420,7 @@ pub fn compact_lambda_sets_of_vars( } }); - trace_compact!(2. subs, &uls_a); + trace_compact!(2. env.subs, &uls_a); // 3. For each `l` in `uls_a` with unique unspecialized lambda `C:f:r`: // 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. Remove `C:f:r` from `t_f1`'s lambda set. @@ -439,8 +430,7 @@ pub fn compact_lambda_sets_of_vars( // 3. Unify `t_f1 ~ t_f2`. trace_compact!(3start.); for l in uls_a { - let compaction_result = - compact_lambda_set(subs, derived_env, arena, pools, c_a, l, phase); + let compaction_result = compact_lambda_set(env, c_a, l, phase); match compaction_result { OneCompactionResult::Compacted { @@ -474,10 +464,7 @@ enum OneCompactionResult { #[must_use] #[allow(clippy::too_many_arguments)] fn compact_lambda_set( - subs: &mut Subs, - derived_env: &DerivedEnv, - arena: &Bump, - pools: &mut Pools, + env: &mut Env, resolved_concrete: Variable, this_lambda_set: Variable, phase: &P, @@ -493,23 +480,24 @@ fn compact_lambda_set( recursion_var, unspecialized, ambient_function: t_f1, - } = subs.get_lambda_set(this_lambda_set); - let target_rank = subs.get_rank(this_lambda_set); + } = env.subs.get_lambda_set(this_lambda_set); + let target_rank = env.subs.get_rank(this_lambda_set); debug_assert!(!unspecialized.is_empty()); - let unspecialized = subs.get_subs_slice(unspecialized); + let unspecialized = env.subs.get_subs_slice(unspecialized); // 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. - let Uls(c, f, r) = unique_unspecialized_lambda(subs, resolved_concrete, unspecialized).unwrap(); + let Uls(c, f, r) = + unique_unspecialized_lambda(env.subs, resolved_concrete, unspecialized).unwrap(); - debug_assert!(subs.equivalent_without_compacting(c, resolved_concrete)); + debug_assert!(env.subs.equivalent_without_compacting(c, resolved_concrete)); // Now decide: do we // - proceed with specialization // - simply drop the specialization lambda set (due to an error) // - or do we need to wait, because we don't know enough information for the specialization yet? - let specialization_decision = make_specialization_decision(subs, phase, c, f); + let specialization_decision = make_specialization_decision(env.subs, phase, c, f); let specialization_key_or_drop = match specialization_decision { SpecializeDecision::Specialize(key) => Ok(key), SpecializeDecision::Drop => Err(()), @@ -522,7 +510,10 @@ fn compact_lambda_set( // 1b. Remove `C:f:r` from `t_f1`'s lambda set. let new_unspecialized: Vec<_> = unspecialized .iter() - .filter(|Uls(v, _, _)| !subs.equivalent_without_compacting(*v, resolved_concrete)) + .filter(|Uls(v, _, _)| { + !env.subs + .equivalent_without_compacting(*v, resolved_concrete) + }) .copied() .collect(); debug_assert_eq!(new_unspecialized.len(), unspecialized.len() - 1); @@ -530,12 +521,12 @@ fn compact_lambda_set( solved, recursion_var, unspecialized: SubsSlice::extend_new( - &mut subs.unspecialized_lambda_sets, + &mut env.subs.unspecialized_lambda_sets, new_unspecialized, ), ambient_function: t_f1, }; - subs.set_content( + env.subs.set_content( this_lambda_set, Content::LambdaSet(t_f1_lambda_set_without_concrete), ); @@ -545,7 +536,7 @@ fn compact_lambda_set( Err(()) => { // Do nothing other than to remove the concrete lambda to drop from the lambda set, // which we already did in 1b above. - trace_compact!(3iter_end_skipped.subs, t_f1); + trace_compact!(3iter_end_skipped. env.subs, t_f1); return OneCompactionResult::Compacted { new_obligations: Default::default(), new_lambda_sets_to_specialize: Default::default(), @@ -554,8 +545,8 @@ fn compact_lambda_set( }; let specialization_ambient_function_var = get_specialization_lambda_set_ambient_function( - subs, - derived_env, + env.subs, + env.derived_env, phase, f, r, @@ -568,7 +559,7 @@ fn compact_lambda_set( Err(()) => { // Do nothing other than to remove the concrete lambda to drop from the lambda set, // which we already did in 1b above. - trace_compact!(3iter_end_skipped.subs, t_f1); + trace_compact!(3iter_end_skipped. env.subs, t_f1); return OneCompactionResult::Compacted { new_obligations: Default::default(), new_lambda_sets_to_specialize: Default::default(), @@ -578,21 +569,21 @@ fn compact_lambda_set( // Ensure the specialized ambient function we'll unify with is not a generalized one, but one // at the rank of the lambda set being compacted. - let t_f2 = deep_copy_var_in(subs, target_rank, pools, t_f2, arena); + let t_f2 = deep_copy_var_in(env, target_rank, t_f2, env.arena); // 3. Unify `t_f1 ~ t_f2`. - trace_compact!(3iter_start.subs, this_lambda_set, t_f1, t_f2); + trace_compact!(3iter_start. env.subs, this_lambda_set, t_f1, t_f2); let (vars, new_obligations, new_lambda_sets_to_specialize, _meta) = unify( - &mut UEnv::new(subs), + &mut env.uenv(), t_f1, t_f2, Mode::LAMBDA_SET_SPECIALIZATION, Polarity::Pos, ) .expect_success("ambient functions don't unify"); - trace_compact!(3iter_end.subs, t_f1); + trace_compact!(3iter_end. env.subs, t_f1); - introduce(subs, target_rank, pools, &vars); + env.introduce(target_rank, &vars); OneCompactionResult::Compacted { new_obligations, diff --git a/crates/compiler/solve/src/to_var.rs b/crates/compiler/solve/src/to_var.rs new file mode 100644 index 00000000000..b49cddd1c1d --- /dev/null +++ b/crates/compiler/solve/src/to_var.rs @@ -0,0 +1,1292 @@ +use std::cell::RefCell; + +use roc_can::{abilities::AbilitiesStore, constraint::TypeOrVar, expected::Expected}; +use roc_collections::soa::{Index, Slice}; +use roc_error_macros::internal_error; +use roc_module::{ident::TagName, symbol::Symbol}; +use roc_region::all::Loc; +use roc_solve_problem::TypeError; +use roc_types::{ + subs::{ + self, AliasVariables, Content, FlatType, GetSubsSlice, LambdaSet, OptVariable, Rank, + RecordFields, Subs, SubsSlice, TagExt, TupleElems, UnionLabels, UnionLambdas, UnionTags, + Variable, VariableSubsSlice, + }, + types::{ + gather_fields_unsorted_iter, gather_tuple_elems_unsorted_iter, AliasKind, AliasShared, + Category, ExtImplicitOpenness, Polarity, TypeTag, Types, + }, +}; +use roc_unify::unify::{unify, Mode, Unified}; + +use crate::{ + ability::{AbilityImplError, ObligationCache}, + deep_copy::deep_copy_var_in, + env::Env, + Aliases, +}; + +std::thread_local! { + /// Scratchpad arena so we don't need to allocate a new one all the time + static SCRATCHPAD: RefCell> = RefCell::new(Some(bumpalo::Bump::with_capacity(4 * 1024))); +} + +fn take_scratchpad() -> bumpalo::Bump { + SCRATCHPAD.with(|f| f.take().unwrap()) +} + +fn put_scratchpad(scratchpad: bumpalo::Bump) { + SCRATCHPAD.with(|f| { + f.replace(Some(scratchpad)); + }); +} + +pub(crate) fn either_type_index_to_var( + env: &mut Env, + rank: Rank, + problems: &mut Vec, + abilities_store: &mut AbilitiesStore, + obligation_cache: &mut ObligationCache, + types: &mut Types, + aliases: &mut Aliases, + either_type_index: TypeOrVar, +) -> Variable { + match either_type_index.split() { + Ok(type_index) => { + // Converts the celled type to a variable, emplacing the new variable for re-use. + let var = type_to_var( + env, + rank, + problems, + abilities_store, + obligation_cache, + types, + aliases, + type_index, + ); + + debug_assert!( + matches!(types[type_index], TypeTag::Variable(v) if v == var) + || matches!( + types[type_index], + TypeTag::EmptyRecord | TypeTag::EmptyTagUnion + ) + ); + var + } + Err(var_index) => { + // we cheat, and store the variable directly in the index + unsafe { Variable::from_index(var_index.index() as _) } + } + } +} + +pub(crate) fn type_to_var( + env: &mut Env, + rank: Rank, + problems: &mut Vec, + abilities_store: &mut AbilitiesStore, + obligation_cache: &mut ObligationCache, + types: &mut Types, + aliases: &mut Aliases, + typ: Index, +) -> Variable { + if let TypeTag::Variable(var) = types[typ] { + var + } else { + let mut arena = take_scratchpad(); + + let var = type_to_var_help( + env, + rank, + problems, + abilities_store, + obligation_cache, + &arena, + aliases, + types, + typ, + false, + ); + + arena.reset(); + put_scratchpad(arena); + + var + } +} + +enum RegisterVariable { + /// Based on the Type, we already know what variable this will be + Direct(Variable), + /// This Type needs more complicated Content. We reserve a Variable + /// for it, but put a placeholder Content in subs + Deferred, +} + +impl RegisterVariable { + fn from_type( + env: &mut Env, + rank: Rank, + arena: &'_ bumpalo::Bump, + types: &mut Types, + typ: Index, + ) -> Self { + use RegisterVariable::*; + + match types[typ] { + TypeTag::Variable(var) => Direct(var), + TypeTag::EmptyRecord => Direct(Variable::EMPTY_RECORD), + TypeTag::EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION), + TypeTag::DelayedAlias { shared } + | TypeTag::StructuralAlias { shared, .. } + | TypeTag::OpaqueAlias { shared, .. } + | TypeTag::HostExposedAlias { shared, .. } => { + let AliasShared { symbol, .. } = types[shared]; + if let Some(reserved) = Variable::get_reserved(symbol) { + let direct_var = if rank.is_generalized() { + // reserved variables are stored with rank NONE + reserved + } else { + // for any other rank, we need to copy; it takes care of adjusting the rank + deep_copy_var_in(env, rank, reserved, arena) + }; + // Safety: the `destination` will become the source-of-truth for the type index, since it + // was not already transformed before (if it was, we'd be in the Variable branch!) + let _old_typ = unsafe { types.emplace_variable(typ, direct_var) }; + return Direct(direct_var); + } + + Deferred + } + _ => Deferred, + } + } + + #[inline(always)] + fn with_stack( + env: &mut Env, + rank: Rank, + arena: &'_ bumpalo::Bump, + types: &mut Types, + typ_index: Index, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, + ) -> Variable { + match Self::from_type(env, rank, arena, types, typ_index) { + Self::Direct(var) => var, + Self::Deferred => { + let var = env.subs.fresh_unnamed_flex_var(); + // Safety: the `destination` will become the source-of-truth for the type index, since it + // was not already transformed before (if it was, it wouldn't be deferred!) + let typ = unsafe { types.emplace_variable(typ_index, var) }; + stack.push(TypeToVar::Defer { + typ, + typ_index, + destination: var, + ambient_function: AmbientFunctionPolicy::NoFunction, + }); + var + } + } + } +} + +/// Instantiation of ambient functions in unspecialized lambda sets is somewhat tricky due to other +/// optimizations we have in place. This struct tells us how they should be instantiated. +#[derive(Debug)] +enum AmbientFunctionPolicy { + /// We're not in a function. This variant may never hold for unspecialized lambda sets. + NoFunction, + /// We're in a known function. + Function(Variable), +} + +impl AmbientFunctionPolicy { + fn link_to_alias_lambda_set_var(&self, subs: &mut Subs, var: Variable) { + let ambient_function = match self { + AmbientFunctionPolicy::Function(var) => *var, + _ => { + // Might be linked at a deeper point in time, ignore for now + return; + } + }; + let content = subs.get_content_without_compacting(var); + let new_content = match content { + Content::LambdaSet(LambdaSet { + solved, + recursion_var, + unspecialized, + ambient_function: _, + }) => Content::LambdaSet(LambdaSet { + solved: *solved, + recursion_var: *recursion_var, + unspecialized: *unspecialized, + ambient_function, + }), + Content::FlexVar(_) => { + // Something like + // Encoder fmt : List U8, fmt -a-> List U8 | fmt has EncoderFormatting + // THEORY: Replace these with empty lambda sets. They will unify the same as a flex + // var does, but allows us to record the ambient function properly. + Content::LambdaSet(LambdaSet { + solved: UnionLabels::default(), + recursion_var: OptVariable::NONE, + unspecialized: SubsSlice::default(), + ambient_function, + }) + } + content => internal_error!("{:?}({:?}) not a lambda set", content, var), + }; + subs.set_content_unchecked(var, new_content); + } +} + +#[derive(Debug)] +enum TypeToVar { + Defer { + typ: TypeTag, + typ_index: Index, + destination: Variable, + ambient_function: AmbientFunctionPolicy, + }, +} + +#[allow(clippy::too_many_arguments)] +pub(crate) fn type_to_var_help( + env: &mut Env, + rank: Rank, + problems: &mut Vec, + abilities_store: &AbilitiesStore, + obligation_cache: &mut ObligationCache, + arena: &bumpalo::Bump, + aliases: &mut Aliases, + types: &mut Types, + typ: Index, + // Helpers for instantiating ambient functions of lambda set variables from type aliases. + is_alias_lambda_set_arg: bool, +) -> Variable { + use bumpalo::collections::Vec; + + let mut stack = Vec::with_capacity_in(8, arena); + let mut bind_to_abilities = Vec::new_in(arena); + + macro_rules! helper { + ($typ:expr, $ambient_function_policy:expr) => {{ + match RegisterVariable::from_type(env, rank, arena, types, $typ) { + RegisterVariable::Direct(var) => { + // If the variable is just a type variable but we know we're in a lambda set + // context, try to link to the ambient function. + $ambient_function_policy.link_to_alias_lambda_set_var(env.subs, var); + + var + } + RegisterVariable::Deferred => { + let var = env.subs.fresh_unnamed_flex_var(); + + // Safety: the `destination` will become the source-of-truth for the type index, since it + // was not already transformed before (if it was, it wouldn't be deferred!) + let typ = unsafe { types.emplace_variable($typ, var) }; + + stack.push(TypeToVar::Defer { + typ, + typ_index: $typ, + destination: var, + ambient_function: $ambient_function_policy, + }); + + var + } + } + }}; + ($typ:expr) => {{ + helper!($typ, AmbientFunctionPolicy::NoFunction) + }}; + } + + let result = helper!(typ); + + while let Some(TypeToVar::Defer { + typ_index, + typ, + destination, + ambient_function, + }) = stack.pop() + { + use TypeTag::*; + match typ { + Variable(_) | EmptyRecord | EmptyTagUnion => { + unreachable!("This variant should never be deferred!",) + } + RangedNumber(range) => { + let content = Content::RangedNumber(range); + + env.register_with_known_var(destination, rank, content) + } + Apply { + symbol, + type_argument_regions: _, + region: _, + } => { + let arguments = types.get_type_arguments(typ_index); + let new_arguments = VariableSubsSlice::reserve_into_subs(env.subs, arguments.len()); + for (target_index, var_index) in + (new_arguments.indices()).zip(arguments.into_iter()) + { + let var = helper!(var_index); + env.subs.variables[target_index] = var; + } + + let flat_type = FlatType::Apply(symbol, new_arguments); + let content = Content::Structure(flat_type); + + env.register_with_known_var(destination, rank, content) + } + + ClosureTag { + name, + ambient_function, + } => { + let captures = types.get_type_arguments(typ_index); + let union_lambdas = + create_union_lambda(env, rank, arena, types, name, captures, &mut stack); + + let content = Content::LambdaSet(subs::LambdaSet { + solved: union_lambdas, + // We may figure out the lambda set is recursive during solving, but it never + // is to begin with. + recursion_var: OptVariable::NONE, + unspecialized: SubsSlice::default(), + ambient_function, + }); + + env.register_with_known_var(destination, rank, content) + } + UnspecializedLambdaSet { unspecialized } => { + let unspecialized_slice = SubsSlice::extend_new( + &mut env.subs.unspecialized_lambda_sets, + std::iter::once(unspecialized), + ); + + // `ClosureTag` ambient functions are resolved during constraint generation. + // But `UnspecializedLambdaSet`s can only ever live in a type signature, and don't + // correspond to a expression, so they are never constrained. + // Instead, we resolve their ambient functions during type translation, observing + // the invariant that a lambda set can only ever appear under a function type. + let ambient_function = match ambient_function { + AmbientFunctionPolicy::NoFunction => { + debug_assert!(is_alias_lambda_set_arg); + // To be filled in during delayed type alias instantiation + roc_types::subs::Variable::NULL + } + AmbientFunctionPolicy::Function(var) => var, + }; + + let content = Content::LambdaSet(subs::LambdaSet { + unspecialized: unspecialized_slice, + solved: UnionLabels::default(), + recursion_var: OptVariable::NONE, + ambient_function, + }); + + env.register_with_known_var(destination, rank, content) + } + // This case is important for the rank of boolean variables + Function(closure_type, ret_type) => { + let arguments = types.get_type_arguments(typ_index); + let new_arguments = VariableSubsSlice::reserve_into_subs(env.subs, arguments.len()); + for (target_index, var_index) in + (new_arguments.indices()).zip(arguments.into_iter()) + { + let var = helper!(var_index); + env.subs.variables[target_index] = var; + } + + let ret_var = helper!(ret_type); + let closure_var = + helper!(closure_type, AmbientFunctionPolicy::Function(destination)); + let content = + Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var)); + + env.register_with_known_var(destination, rank, content) + } + Record(fields) => { + let ext_slice = types.get_type_arguments(typ_index); + + // An empty fields is inefficient (but would be correct) + // If hit, try to turn the value into an EmptyRecord in canonicalization + debug_assert!(!fields.is_empty() || !ext_slice.is_empty()); + + let mut field_vars = Vec::with_capacity_in(fields.len(), arena); + + let (fields_names, field_kinds, field_tys) = types.record_fields_slices(fields); + + for ((field, field_kind), field_type) in (fields_names.into_iter()) + .zip(field_kinds.into_iter()) + .zip(field_tys.into_iter()) + { + let field_var = { + let t = helper!(field_type); + types[field_kind].replace(t) + }; + + field_vars.push((types[field].clone(), field_var)); + } + + debug_assert!(ext_slice.len() <= 1); + let temp_ext_var = match ext_slice.into_iter().next() { + None => roc_types::subs::Variable::EMPTY_RECORD, + Some(ext) => helper!(ext), + }; + + let (it, new_ext_var) = + gather_fields_unsorted_iter(env.subs, RecordFields::empty(), temp_ext_var) + .expect("Something ended up weird in this record type"); + + let it = it + .into_iter() + .map(|(field, field_type)| (field.clone(), field_type)); + + field_vars.extend(it); + insertion_sort_by(&mut field_vars, RecordFields::compare); + + let record_fields = RecordFields::insert_into_subs(env.subs, field_vars); + + let content = Content::Structure(FlatType::Record(record_fields, new_ext_var)); + + env.register_with_known_var(destination, rank, content) + } + + Tuple(elems) => { + let ext_slice = types.get_type_arguments(typ_index); + + // Elems should never be empty; we don't support empty tuples + debug_assert!(!elems.is_empty() || !ext_slice.is_empty()); + + let mut elem_vars = Vec::with_capacity_in(elems.len(), arena); + + let (indices, elem_tys) = types.tuple_elems_slices(elems); + + for (index, elem_type) in indices.into_iter().zip(elem_tys.into_iter()) { + let elem_var = helper!(elem_type); + elem_vars.push((types[index], elem_var)); + } + + debug_assert!(ext_slice.len() <= 1); + let temp_ext_var = match ext_slice.into_iter().next() { + None => roc_types::subs::Variable::EMPTY_TUPLE, + Some(ext) => helper!(ext), + }; + + let (it, new_ext_var) = + gather_tuple_elems_unsorted_iter(env.subs, TupleElems::empty(), temp_ext_var) + .expect("Something ended up weird in this tuple type"); + + elem_vars.extend(it); + let tuple_elems = TupleElems::insert_into_subs(env.subs, elem_vars); + + let content = Content::Structure(FlatType::Tuple(tuple_elems, new_ext_var)); + + env.register_with_known_var(destination, rank, content) + } + + TagUnion(tags, ext_openness) => { + let ext_slice = types.get_type_arguments(typ_index); + + // An empty tags is inefficient (but would be correct) + // If hit, try to turn the value into an EmptyTagUnion in canonicalization + debug_assert!(!tags.is_empty() || !ext_slice.is_empty()); + + let (union_tags, ext) = type_to_union_tags( + env, + rank, + arena, + types, + tags, + ext_slice, + ext_openness, + &mut stack, + ); + let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); + + env.register_with_known_var(destination, rank, content) + } + FunctionOrTagUnion(symbol, ext_openness) => { + let ext_slice = types.get_type_arguments(typ_index); + let tag_name = types.get_tag_name(&typ_index).clone(); + + debug_assert!(ext_slice.len() <= 1); + let temp_ext = match ext_slice.into_iter().next() { + Some(ext) => { + let var = helper!(ext); + TagExt::from_can(var, ext_openness) + } + None => TagExt::Any(roc_types::subs::Variable::EMPTY_TAG_UNION), + }; + + let (it, ext) = roc_types::types::gather_tags_unsorted_iter( + env.subs, + UnionTags::default(), + temp_ext, + ) + .expect("extension var could not be seen as a tag union"); + + for _ in it { + unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!"); + } + + let tag_names = SubsSlice::extend_new(&mut env.subs.tag_names, [tag_name]); + let symbols = SubsSlice::extend_new(&mut env.subs.symbol_names, [symbol]); + + let content = + Content::Structure(FlatType::FunctionOrTagUnion(tag_names, symbols, ext)); + + env.register_with_known_var(destination, rank, content) + } + RecursiveTagUnion(rec_var, tags, ext_openness) => { + let ext_slice = types.get_type_arguments(typ_index); + + // An empty tags is inefficient (but would be correct) + // If hit, try to turn the value into an EmptyTagUnion in canonicalization + debug_assert!(!tags.is_empty() || !ext_slice.is_empty()); + + let (union_tags, ext) = type_to_union_tags( + env, + rank, + arena, + types, + tags, + ext_slice, + ext_openness, + &mut stack, + ); + let content = + Content::Structure(FlatType::RecursiveTagUnion(rec_var, union_tags, ext)); + + let tag_union_var = destination; + env.register_with_known_var(tag_union_var, rank, content); + + env.register_with_known_var( + rec_var, + rank, + Content::RecursionVar { + opt_name: None, + structure: tag_union_var, + }, + ); + + tag_union_var + } + + DelayedAlias { shared } => { + let AliasShared { + symbol, + type_argument_abilities, + type_argument_regions, + lambda_set_variables, + infer_ext_in_output_variables, + } = types[shared]; + + let type_arguments = types.get_type_arguments(typ_index); + + let alias_variables = { + let all_vars_length = type_arguments.len() + + lambda_set_variables.len() + + infer_ext_in_output_variables.len(); + let new_variables = + VariableSubsSlice::reserve_into_subs(env.subs, all_vars_length); + + let type_arguments_offset = 0; + let lambda_set_vars_offset = type_arguments_offset + type_arguments.len(); + let infer_ext_vars_offset = lambda_set_vars_offset + lambda_set_variables.len(); + + for (((target_index, arg_type), arg_region), abilities) in + (new_variables.indices().skip(type_arguments_offset)) + .zip(type_arguments.into_iter()) + .zip(type_argument_regions.into_iter()) + .zip(type_argument_abilities.into_iter()) + { + let copy_var = helper!(arg_type); + env.subs.variables[target_index] = copy_var; + if !types[abilities].is_empty() { + let arg_region = types[arg_region]; + bind_to_abilities.push((Loc::at(arg_region, copy_var), abilities)); + } + } + + let it = (new_variables.indices().skip(lambda_set_vars_offset)) + .zip(lambda_set_variables.into_iter()); + for (target_index, ls) in it { + // We MUST do this now, otherwise when linking the ambient function during + // instantiation of the real var, there will be nothing to link against. + let copy_var = type_to_var_help( + env, + rank, + problems, + abilities_store, + obligation_cache, + arena, + aliases, + types, + ls, + true, + ); + env.subs.variables[target_index] = copy_var; + } + + let it = (new_variables.indices().skip(infer_ext_vars_offset)) + .zip(infer_ext_in_output_variables.into_iter()); + for (target_index, ext_typ) in it { + let copy_var = helper!(ext_typ); + env.subs.variables[target_index] = copy_var; + } + + AliasVariables { + variables_start: new_variables.start, + type_variables_len: type_arguments.len() as _, + lambda_set_variables_len: lambda_set_variables.len() as _, + all_variables_len: all_vars_length as _, + } + }; + + let (alias_variable, kind) = aliases.instantiate_real_var( + env, + rank, + problems, + abilities_store, + obligation_cache, + arena, + types, + symbol, + alias_variables, + ); + + let content = Content::Alias(symbol, alias_variables, alias_variable, kind); + + env.register_with_known_var(destination, rank, content) + } + + StructuralAlias { shared, actual } | OpaqueAlias { shared, actual } => { + let kind = match typ { + StructuralAlias { .. } => AliasKind::Structural, + OpaqueAlias { .. } => AliasKind::Opaque, + _ => internal_error!(), + }; + + let AliasShared { + symbol, + type_argument_abilities, + type_argument_regions, + lambda_set_variables, + infer_ext_in_output_variables, + } = types[shared]; + + debug_assert!(roc_types::subs::Variable::get_reserved(symbol).is_none()); + + let type_arguments = types.get_type_arguments(typ_index); + + let alias_variables = { + let all_vars_length = type_arguments.len() + + lambda_set_variables.len() + + infer_ext_in_output_variables.len(); + + let type_arguments_offset = 0; + let lambda_set_vars_offset = type_arguments_offset + type_arguments.len(); + let infer_ext_vars_offset = lambda_set_vars_offset + lambda_set_variables.len(); + + let new_variables = + VariableSubsSlice::reserve_into_subs(env.subs, all_vars_length); + + for (((target_index, typ), region), abilities) in + (new_variables.indices().skip(type_arguments_offset)) + .zip(type_arguments.into_iter()) + .zip(type_argument_regions.into_iter()) + .zip(type_argument_abilities.into_iter()) + { + let copy_var = helper!(typ); + env.subs.variables[target_index] = copy_var; + if !types[abilities].is_empty() { + let region = types[region]; + bind_to_abilities.push((Loc::at(region, copy_var), abilities)); + } + } + + let it = (new_variables.indices().skip(lambda_set_vars_offset)) + .zip(lambda_set_variables.into_iter()); + for (target_index, ls) in it { + let copy_var = helper!(ls); + env.subs.variables[target_index] = copy_var; + } + + let it = (new_variables.indices().skip(infer_ext_vars_offset)) + .zip(infer_ext_in_output_variables.into_iter()); + for (target_index, ext_typ) in it { + let copy_var = helper!(ext_typ); + env.subs.variables[target_index] = copy_var; + } + + AliasVariables { + variables_start: new_variables.start, + type_variables_len: type_arguments.len() as _, + lambda_set_variables_len: lambda_set_variables.len() as _, + all_variables_len: all_vars_length as _, + } + }; + + let alias_variable = if let Symbol::RESULT_RESULT = symbol { + roc_result_to_var(env, rank, arena, types, actual, &mut stack) + } else { + helper!(actual) + }; + let content = Content::Alias(symbol, alias_variables, alias_variable, kind); + + env.register_with_known_var(destination, rank, content) + } + HostExposedAlias { + shared, + actual_type: alias_type, + actual_variable: actual_var, + } => { + let AliasShared { + symbol, + type_argument_abilities: _, + type_argument_regions: _, + lambda_set_variables, + infer_ext_in_output_variables: _, // TODO + } = types[shared]; + + let type_arguments = types.get_type_arguments(typ_index); + + let alias_variables = { + let length = type_arguments.len() + lambda_set_variables.len(); + let new_variables = VariableSubsSlice::reserve_into_subs(env.subs, length); + + for (target_index, arg_type) in + (new_variables.indices()).zip(type_arguments.into_iter()) + { + let copy_var = helper!(arg_type); + env.subs.variables[target_index] = copy_var; + } + let it = (new_variables.indices().skip(type_arguments.len())) + .zip(lambda_set_variables.into_iter()); + for (target_index, ls) in it { + // We MUST do this now, otherwise when linking the ambient function during + // instantiation of the real var, there will be nothing to link against. + let copy_var = type_to_var_help( + env, + rank, + problems, + abilities_store, + obligation_cache, + arena, + aliases, + types, + ls, + true, + ); + env.subs.variables[target_index] = copy_var; + } + + AliasVariables { + variables_start: new_variables.start, + type_variables_len: type_arguments.len() as _, + lambda_set_variables_len: lambda_set_variables.len() as _, + all_variables_len: length as _, + } + }; + + // cannot use helper! here because this variable may be involved in unification below + let alias_variable = type_to_var_help( + env, + rank, + problems, + abilities_store, + obligation_cache, + arena, + aliases, + types, + alias_type, + false, + ); + // TODO(opaques): I think host-exposed aliases should always be structural + // (when does it make sense to give a host an opaque type?) + let content = Content::Alias( + symbol, + alias_variables, + alias_variable, + AliasKind::Structural, + ); + let result = env.register_with_known_var(destination, rank, content); + + // We only want to unify the actual_var with the alias once + // if it's already redirected (and therefore, redundant) + // don't do it again + if !env.subs.redundant(actual_var) { + let descriptor = env.subs.get(result); + env.subs.union(result, actual_var, descriptor); + } + + result + } + Error => { + let content = Content::Error; + + env.register_with_known_var(destination, rank, content) + } + }; + } + + for (Loc { value: var, region }, abilities) in bind_to_abilities { + let abilities = &types[abilities]; + match *env.subs.get_content_unchecked(var) { + Content::RigidVar(a) => { + // TODO(multi-abilities): check run cache + let abilities_slice = SubsSlice::extend_new( + &mut env.subs.symbol_names, + abilities.sorted_iter().copied(), + ); + env.subs + .set_content(var, Content::RigidAbleVar(a, abilities_slice)); + } + Content::RigidAbleVar(_, abs) + if (env.subs.get_subs_slice(abs).iter()).eq(abilities.sorted_iter()) => + { + // pass, already bound + } + _ => { + let abilities_slice = SubsSlice::extend_new( + &mut env.subs.symbol_names, + abilities.sorted_iter().copied(), + ); + + let flex_ability = env.register(rank, Content::FlexAbleVar(None, abilities_slice)); + + let category = Category::OpaqueArg; + match unify( + &mut env.uenv(), + var, + flex_ability, + Mode::EQ, + Polarity::OF_VALUE, + ) { + Unified::Success { + vars: _, + must_implement_ability, + lambda_sets_to_specialize, + extra_metadata: _, + } => { + // No introduction needed + + if !must_implement_ability.is_empty() { + let new_problems = obligation_cache.check_obligations( + env.subs, + abilities_store, + must_implement_ability, + AbilityImplError::BadExpr(region, category, flex_ability), + ); + problems.extend(new_problems); + } + debug_assert!(lambda_sets_to_specialize + .drain() + .all(|(_, vals)| vals.is_empty())); + } + Unified::Failure(_vars, actual_type, expected_type, _bad_impls) => { + // No introduction needed + + let problem = TypeError::BadExpr( + region, + category, + actual_type, + Expected::NoExpectation(expected_type), + ); + + problems.push(problem); + } + } + } + } + } + + result +} + +#[inline(always)] +fn roc_result_to_var( + env: &mut Env, + rank: Rank, + arena: &'_ bumpalo::Bump, + types: &mut Types, + result_type: Index, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, +) -> Variable { + match types[result_type] { + TypeTag::TagUnion(tags, _ext_openness) => { + let ext_slice = types.get_type_arguments(result_type); + + debug_assert!(ext_slice.is_empty()); + debug_assert!(tags.len() == 2); + + let (tags_slice, payload_slices_slice) = types.union_tag_slices(tags); + + if let ([err, ok], [err_args, ok_args]) = + (&types[tags_slice], &types[payload_slices_slice]) + { + debug_assert_eq!(err, &env.subs.tag_names[0]); + debug_assert_eq!(ok, &env.subs.tag_names[1]); + + debug_assert_eq!(err_args.len(), 1); + debug_assert_eq!(ok_args.len(), 1); + + if let (Some(err_type), Some(ok_type)) = + (err_args.into_iter().next(), ok_args.into_iter().next()) + { + let err_var = + RegisterVariable::with_stack(env, rank, arena, types, err_type, stack); + let ok_var = + RegisterVariable::with_stack(env, rank, arena, types, ok_type, stack); + + let start = env.subs.variables.len() as u32; + let err_slice = SubsSlice::new(start, 1); + let ok_slice = SubsSlice::new(start + 1, 1); + + env.subs.variables.push(err_var); + env.subs.variables.push(ok_var); + + let variables = SubsSlice::new(env.subs.variable_slices.len() as _, 2); + env.subs.variable_slices.push(err_slice); + env.subs.variable_slices.push(ok_slice); + + let union_tags = UnionTags::from_slices(Subs::RESULT_TAG_NAMES, variables); + let ext = TagExt::Any(Variable::EMPTY_TAG_UNION); + + let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); + + return env.register(rank, content); + } + } + + unreachable!("invalid arguments to Result.Result; canonicalization should catch this!") + } + _ => unreachable!("not a valid type inside a Result.Result alias"), + } +} + +fn insertion_sort_by(arr: &mut [T], mut compare: F) +where + F: FnMut(&T, &T) -> std::cmp::Ordering, +{ + for i in 1..arr.len() { + let val = &arr[i]; + let mut j = i; + let pos = arr[..i] + .binary_search_by(|x| compare(x, val)) + .unwrap_or_else(|pos| pos); + // Swap all elements until specific position. + while j > pos { + arr.swap(j - 1, j); + j -= 1; + } + } +} + +fn sorted_no_duplicate_tags(tag_slices: &[TagName]) -> bool { + match tag_slices.split_first() { + None => true, + Some((first, rest)) => { + let mut current = first; + + for next in rest { + if current >= next { + return false; + } else { + current = next; + } + } + + true + } + } +} + +fn sort_and_deduplicate(tag_vars: &mut bumpalo::collections::Vec<(TagName, T)>) { + insertion_sort_by(tag_vars, |(a, _), (b, _)| a.cmp(b)); + + // deduplicate, keeping the right-most occurrence of a tag name + let mut i = 0; + + while i < tag_vars.len() { + match (tag_vars.get(i), tag_vars.get(i + 1)) { + (Some((t1, _)), Some((t2, _))) => { + if t1 == t2 { + tag_vars.remove(i); + } else { + i += 1; + } + } + _ => break, + } + } +} + +/// Find whether the current run of tag names is in the subs.tag_names array already. If so, +/// we take a SubsSlice to the existing tag names, so we don't have to add/clone those tag names +/// and keep subs memory consumption low +fn find_tag_name_run(slice: &[TagName], subs: &mut Subs) -> Option> { + use std::cmp::Ordering; + + let tag_name = slice.get(0)?; + + let mut result = None; + + // the `SubsSlice` that inserting `slice` into subs would give + let bigger_slice = SubsSlice::new(subs.tag_names.len() as _, slice.len() as _); + + match subs.tag_name_cache.get_mut(tag_name) { + Some(occupied) => { + let subs_slice = *occupied; + + let prefix_slice = SubsSlice::new(subs_slice.start, slice.len() as _); + + if slice.len() == 1 { + return Some(prefix_slice); + } + + match slice.len().cmp(&subs_slice.len()) { + Ordering::Less => { + // we might have a prefix + let tag_names = &subs.tag_names[subs_slice.start as usize..]; + + for (from_subs, from_slice) in tag_names.iter().zip(slice.iter()) { + if from_subs != from_slice { + return None; + } + } + + result = Some(prefix_slice); + } + Ordering::Equal => { + let tag_names = &subs.tag_names[subs_slice.indices()]; + + for (from_subs, from_slice) in tag_names.iter().zip(slice.iter()) { + if from_subs != from_slice { + return None; + } + } + + result = Some(subs_slice); + } + Ordering::Greater => { + // switch to the bigger slice that is not inserted yet, but will be soon + *occupied = bigger_slice; + } + } + } + None => { + subs.tag_name_cache.push(tag_name, bigger_slice); + } + } + + result +} + +#[inline(always)] +fn register_tag_arguments( + env: &mut Env, + rank: Rank, + arena: &'_ bumpalo::Bump, + types: &mut Types, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, + arguments: Slice, +) -> VariableSubsSlice { + if arguments.is_empty() { + VariableSubsSlice::default() + } else { + let new_variables = VariableSubsSlice::reserve_into_subs(env.subs, arguments.len()); + let it = new_variables.indices().zip(arguments.into_iter()); + + for (target_index, argument) in it { + let var = RegisterVariable::with_stack(env, rank, arena, types, argument, stack); + env.subs.variables[target_index] = var; + } + + new_variables + } +} + +/// Assumes that the tags are sorted and there are no duplicates! +fn insert_tags_fast_path( + env: &mut Env, + rank: Rank, + arena: &'_ bumpalo::Bump, + types: &mut Types, + union_tags: UnionTags, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, +) -> UnionTags { + let (tags, payload_slices) = types.union_tag_slices(union_tags); + + debug_assert_eq!(tags.len(), payload_slices.len()); + + if let [arguments_slice] = &types[payload_slices] { + let arguments_slice = *arguments_slice; + + let variable_slice = + register_tag_arguments(env, rank, arena, types, stack, arguments_slice); + + let new_variable_slices = + SubsSlice::extend_new(&mut env.subs.variable_slices, [variable_slice]); + + macro_rules! subs_tag_name { + ($tag_name_slice:expr) => { + return UnionTags::from_slices($tag_name_slice, new_variable_slices) + }; + } + + match types[tags][0].0.as_str() { + "Ok" => subs_tag_name!(Subs::TAG_NAME_OK.as_slice()), + "Err" => subs_tag_name!(Subs::TAG_NAME_ERR.as_slice()), + "InvalidNumStr" => subs_tag_name!(Subs::TAG_NAME_INVALID_NUM_STR.as_slice()), + "BadUtf8" => subs_tag_name!(Subs::TAG_NAME_BAD_UTF_8.as_slice()), + "OutOfBounds" => subs_tag_name!(Subs::TAG_NAME_OUT_OF_BOUNDS.as_slice()), + _other => {} + } + } + + let new_variable_slices = SubsSlice::reserve_variable_slices(env.subs, tags.len()); + match find_tag_name_run(&types[tags], env.subs) { + Some(new_tag_names) => { + let it = (new_variable_slices.indices()).zip(payload_slices.into_iter()); + + for (variable_slice_index, arguments_index) in it { + let arguments = types[arguments_index]; + env.subs.variable_slices[variable_slice_index] = + register_tag_arguments(env, rank, arena, types, stack, arguments); + } + + UnionTags::from_slices(new_tag_names, new_variable_slices) + } + None => { + let new_tag_names = SubsSlice::reserve_tag_names(env.subs, tags.len()); + + let it = (new_variable_slices.indices()) + .zip(new_tag_names.indices()) + .zip(tags.into_iter()) + .zip(payload_slices.into_iter()); + + for (((variable_slice_index, tag_name_index), tag_name), arguments_index) in it { + let arguments = types[arguments_index]; + env.subs.variable_slices[variable_slice_index] = + register_tag_arguments(env, rank, arena, types, stack, arguments); + + env.subs.tag_names[tag_name_index] = types[tag_name].clone(); + } + + UnionTags::from_slices(new_tag_names, new_variable_slices) + } + } +} + +fn insert_tags_slow_path( + env: &mut Env, + rank: Rank, + arena: &'_ bumpalo::Bump, + types: &mut Types, + union_tags: UnionTags, + mut tag_vars: bumpalo::collections::Vec<(TagName, VariableSubsSlice)>, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, +) -> UnionTags { + let (tags, payload_slices) = types.union_tag_slices(union_tags); + + for (tag_index, tag_argument_types_index) in (tags.into_iter()).zip(payload_slices.into_iter()) + { + let tag_argument_types = &types[tag_argument_types_index]; + + let new_slice = VariableSubsSlice::reserve_into_subs(env.subs, tag_argument_types.len()); + + for (i, arg) in (new_slice.indices()).zip(tag_argument_types.into_iter()) { + let var = RegisterVariable::with_stack(env, rank, arena, types, arg, stack); + env.subs.variables[i] = var; + } + + tag_vars.push((types[tag_index].clone(), new_slice)); + } + + sort_and_deduplicate(&mut tag_vars); + + UnionTags::insert_slices_into_subs(env.subs, tag_vars) +} + +fn type_to_union_tags( + env: &mut Env, + rank: Rank, + arena: &'_ bumpalo::Bump, + types: &mut Types, + union_tags: UnionTags, + opt_ext_slice: Slice, + ext_openness: ExtImplicitOpenness, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, +) -> (UnionTags, TagExt) { + use bumpalo::collections::Vec; + + let (tags, _) = types.union_tag_slices(union_tags); + + let sorted = tags.len() == 1 || sorted_no_duplicate_tags(&types[tags]); + + debug_assert!(opt_ext_slice.len() <= 1); + + match opt_ext_slice.into_iter().next() { + None => { + let ext = Variable::EMPTY_TAG_UNION; + + let union_tags = if sorted { + insert_tags_fast_path(env, rank, arena, types, union_tags, stack) + } else { + let tag_vars = Vec::with_capacity_in(tags.len(), arena); + insert_tags_slow_path(env, rank, arena, types, union_tags, tag_vars, stack) + }; + + (union_tags, TagExt::Any(ext)) + } + Some(ext) => { + let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); + + let temp_ext = { + let temp_ext_var = + RegisterVariable::with_stack(env, rank, arena, types, ext, stack); + TagExt::from_can(temp_ext_var, ext_openness) + }; + let (it, ext) = roc_types::types::gather_tags_unsorted_iter( + env.subs, + UnionTags::default(), + temp_ext, + ) + .expect("extension var could not be seen as tag union"); + + tag_vars.extend(it.map(|(n, v)| (n.clone(), v))); + + let union_tags = if tag_vars.is_empty() && sorted { + insert_tags_fast_path(env, rank, arena, types, union_tags, stack) + } else { + insert_tags_slow_path(env, rank, arena, types, union_tags, tag_vars, stack) + }; + + (union_tags, ext) + } + } +} + +fn create_union_lambda( + env: &mut Env, + rank: Rank, + arena: &'_ bumpalo::Bump, + types: &mut Types, + closure: Symbol, + capture_types: Slice, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar>, +) -> UnionLambdas { + let variable_slice = register_tag_arguments(env, rank, arena, types, stack, capture_types); + let new_variable_slices = + SubsSlice::extend_new(&mut env.subs.variable_slices, [variable_slice]); + + let lambda_name_slice = SubsSlice::extend_new(&mut env.subs.symbol_names, [closure]); + + UnionLambdas::from_slices(lambda_name_slice, new_variable_slices) +} diff --git a/crates/compiler/test_derive/src/util.rs b/crates/compiler/test_derive/src/util.rs index 566d433c9c0..5d050827386 100644 --- a/crates/compiler/test_derive/src/util.rs +++ b/crates/compiler/test_derive/src/util.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; use bumpalo::Bump; use roc_packaging::cache::RocCacheDir; +use roc_solve::module::{SolveConfig, SolveOutput}; use ven_pretty::DocAllocator; use roc_can::{ @@ -418,18 +419,27 @@ fn check_derived_typechecks_and_golden( roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, std::env::set_var(roc_debug_flags::ROC_PRINT_UNIFICATIONS, "1") ); - let (mut solved_subs, _, problems, _) = roc_solve::module::run_solve( - test_module, + + let solve_config = SolveConfig { + home: test_module, + constraints: &constraints, + root_constraint: constr, types, - &constraints, - constr, + pending_derives: Default::default(), + exposed_by_module: &exposed_for_module.exposed_by_module, + derived_module: Default::default(), + }; + + let SolveOutput { + subs: mut solved_subs, + errors: problems, + .. + } = roc_solve::module::run_solve( + solve_config, RigidVariables::default(), test_subs, Default::default(), abilities_store, - Default::default(), - &exposed_for_module.exposed_by_module, - Default::default(), ); dbg_do!( roc_debug_flags::ROC_PRINT_UNIFICATIONS_DERIVED, diff --git a/crates/reporting/tests/helpers/mod.rs b/crates/reporting/tests/helpers/mod.rs index 187102105ea..a8bea3eeacb 100644 --- a/crates/reporting/tests/helpers/mod.rs +++ b/crates/reporting/tests/helpers/mod.rs @@ -15,7 +15,8 @@ use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds}; use roc_parse::parser::{SourceError, SyntaxError}; use roc_problem::can::Problem; use roc_region::all::Loc; -use roc_solve::solve::{self, Aliases}; +use roc_solve::module::SolveConfig; +use roc_solve::{solve, Aliases}; use roc_solve_problem::TypeError; use roc_types::subs::{Content, Subs, VarStore, Variable}; use roc_types::types::Types; @@ -33,26 +34,24 @@ pub fn infer_expr( problems: &mut Vec, types: Types, constraints: &Constraints, - constraint: &Constraint, + constraint: Constraint, pending_derives: PendingDerives, aliases: &mut Aliases, abilities_store: &mut AbilitiesStore, derived_module: SharedDerivedModule, expr_var: Variable, ) -> (Content, Subs) { - let (solved, _) = solve::run( - ModuleId::ATTR, + let config = SolveConfig { types, constraints, - problems, - subs, - aliases, - constraint, + root_constraint: constraint, + home: ModuleId::ATTR, pending_derives, - abilities_store, - &Default::default(), + exposed_by_module: &Default::default(), derived_module, - ); + }; + + let (solved, _) = solve::run(config, problems, subs, aliases, abilities_store); let content = *solved.inner().get_content_without_compacting(expr_var); diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 4d51d7c1907..aeed31a9fcc 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -258,7 +258,7 @@ mod test_reporting { subs.rigid_var(var.value, "*".into()); } - let mut solve_aliases = roc_solve::solve::Aliases::default(); + let mut solve_aliases = roc_solve::Aliases::default(); for (name, alias) in output.aliases { solve_aliases.insert(&mut types, name, alias); @@ -271,7 +271,7 @@ mod test_reporting { &mut unify_problems, types, &constraints, - &constraint, + constraint, // Use `new_report_problem_as` in order to get proper derives. // TODO: remove the non-new reporting test infra. PendingDerives::default(),