From 7ad55d67e277772881d63f577aa0ee7cc0260560 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 12:57:01 +0100 Subject: [PATCH 1/9] Make type_to_variable manually tail-recursive by using a work stack and reserving variables. fun stuff --- compiler/solve/src/solve.rs | 558 ++++++++++++++++++++---------------- 1 file changed, 318 insertions(+), 240 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 7a4efb1e48..2db56dbad6 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -865,14 +865,65 @@ fn type_to_var( _: &mut MutMap, typ: &Type, ) -> Variable { - let mut arena = take_scratchpad(); + if let Type::Variable(var) = typ { + *var + } else { + let mut arena = take_scratchpad(); - let var = type_to_variable(subs, rank, pools, &arena, typ); + // let var = type_to_variable(subs, rank, pools, &arena, typ); + let var = type_to_variable(subs, rank, pools, &arena, typ); - arena.reset(); - put_scratchpad(arena); + arena.reset(); + put_scratchpad(arena); - var + 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, + typ: &Type, + ) -> Self { + use RegisterVariable::*; + + match typ { + Variable(var) => Direct(*var), + EmptyRec => Direct(Variable::EMPTY_RECORD), + EmptyTagUnion => Direct(Variable::EMPTY_TAG_UNION), + Type::Alias { symbol, .. } => { + if let Some(reserved) = Variable::get_reserved(*symbol) { + if rank.is_none() { + // reserved variables are stored with rank NONE + return Direct(reserved); + } else { + // for any other rank, we need to copy; it takes care of adjusting the rank + let copied = deep_copy_var_in(subs, rank, pools, reserved, arena); + return Direct(copied); + } + } + + Deferred + } + _ => Deferred, + } + } +} + +#[derive(Debug)] +enum TypeToVar<'a> { + Defer(&'a Type, Variable), } fn type_to_variable<'a>( @@ -884,255 +935,280 @@ fn type_to_variable<'a>( ) -> Variable { use bumpalo::collections::Vec; - match typ { - Variable(var) => *var, - RangedNumber(typ, vars) => { - let ty_var = type_to_variable(subs, rank, pools, arena, typ); - let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); - let content = Content::RangedNumber(ty_var, vars); + let mut stack = Vec::with_capacity_in(8, arena); - register(subs, rank, pools, content) - } - Apply(symbol, arguments, _) => { - let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { - let var = type_to_variable(subs, rank, pools, arena, var_index); - subs.variables[target_index] = var; - } - - let flat_type = FlatType::Apply(*symbol, new_arguments); - let content = Content::Structure(flat_type); - - register(subs, rank, pools, content) - } - EmptyRec => Variable::EMPTY_RECORD, - EmptyTagUnion => Variable::EMPTY_TAG_UNION, - - ClosureTag { name, ext } => { - let tag_name = TagName::Closure(*name); - let tag_names = SubsSlice::new(subs.tag_names.len() as u32, 1); - - subs.tag_names.push(tag_name); - - // the first VariableSubsSlice in the array is a zero-length slice - let union_tags = UnionTags::from_slices(tag_names, SubsSlice::new(0, 1)); - - let content = Content::Structure(FlatType::TagUnion(union_tags, *ext)); - - register(subs, rank, pools, content) - } - - // This case is important for the rank of boolean variables - Function(arguments, closure_type, ret_type) => { - let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); - for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { - let var = type_to_variable(subs, rank, pools, arena, var_index); - subs.variables[target_index] = var; - } - - let ret_var = type_to_variable(subs, rank, pools, arena, ret_type); - let closure_var = type_to_variable(subs, rank, pools, arena, closure_type); - let content = Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var)); - - register(subs, rank, pools, content) - } - Record(fields, ext) => { - // 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.is_empty_record()); - - let mut field_vars = Vec::with_capacity_in(fields.len(), arena); - - for (field, field_type) in fields { - let field_var = - field_type.map(|typ| type_to_variable(subs, rank, pools, arena, typ)); - - field_vars.push((field.clone(), field_var)); - } - - let temp_ext_var = type_to_variable(subs, rank, pools, arena, 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(subs, rank, pools, content) - } - TagUnion(tags, ext) => { - // 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.is_empty_tag_union()); - - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); - let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); - - register(subs, rank, pools, content) - } - FunctionOrTagUnion(tag_name, symbol, ext) => { - let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); - - let (it, ext) = roc_types::types::gather_tags_unsorted_iter( - subs, - UnionTags::default(), - temp_ext_var, - ); - - for _ in it { - unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!"); - } - - let slice = SubsIndex::new(subs.tag_names.len() as u32); - subs.tag_names.push(tag_name.clone()); - - let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext)); - - register(subs, rank, pools, content) - } - RecursiveTagUnion(rec_var, tags, ext) => { - // 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.is_empty_tag_union()); - - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); - let content = - Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); - - let tag_union_var = register(subs, rank, pools, content); - - register_with_known_var( - subs, - *rec_var, - rank, - pools, - Content::RecursionVar { - opt_name: None, - structure: tag_union_var, - }, - ); - - tag_union_var - } - - Type::Alias { - symbol, - type_arguments, - actual, - lambda_set_variables, - kind, - } => { - if let Some(reserved) = Variable::get_reserved(*symbol) { - if rank.is_none() { - // reserved variables are stored with rank NONE - return reserved; - } else { - // for any other rank, we need to copy; it takes care of adjusting the rank - return deep_copy_var_in(subs, rank, pools, reserved, arena); + macro_rules! helper { + ($typ:expr) => {{ + match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { + RegisterVariable::Direct(var) => var, + RegisterVariable::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer($typ, var)); + var } } + }}; + } - let alias_variables = alias_to_var( - subs, - rank, - pools, - arena, - type_arguments, - lambda_set_variables, - ); + let result = helper!(typ); - let alias_variable = if let Symbol::RESULT_RESULT = *symbol { - roc_result_to_var(subs, rank, pools, arena, actual) - } else { - type_to_variable(subs, rank, pools, arena, actual) - }; - let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind); + while let Some(TypeToVar::Defer(typ, destination)) = stack.pop() { + match typ { + Variable(_) | EmptyRec | EmptyTagUnion => { + unreachable!("This variant should never be deferred!") + } + RangedNumber(typ, vars) => { + let ty_var = helper!(typ); + let vars = VariableSubsSlice::insert_into_subs(subs, vars.iter().copied()); + let content = Content::RangedNumber(ty_var, vars); - register(subs, rank, pools, content) - } - HostExposedAlias { - name: symbol, - type_arguments, - actual: alias_type, - actual_var, - lambda_set_variables, - .. - } => { - let alias_variables = alias_to_var( - subs, - rank, - pools, - arena, - type_arguments, - lambda_set_variables, - ); + register_with_known_var(subs, destination, rank, pools, content) + } + Apply(symbol, arguments, _) => { + let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); + for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { + let var = helper!(var_index); + subs.variables[target_index] = var; + } - let alias_variable = type_to_variable(subs, rank, pools, arena, alias_type); - // 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(subs, rank, pools, content); + let flat_type = FlatType::Apply(*symbol, new_arguments); + let content = Content::Structure(flat_type); - // 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); + register_with_known_var(subs, destination, rank, pools, content) } - result - } - Erroneous(problem) => { - let content = Content::Structure(FlatType::Erroneous(Box::new(problem.clone()))); + ClosureTag { name, ext } => { + let tag_name = TagName::Closure(*name); + let tag_names = SubsSlice::new(subs.tag_names.len() as u32, 1); - register(subs, rank, pools, content) - } - } -} + subs.tag_names.push(tag_name); -#[inline(always)] -fn alias_to_var<'a>( - subs: &mut Subs, - rank: Rank, - pools: &mut Pools, - arena: &'a bumpalo::Bump, - type_arguments: &[(roc_module::ident::Lowercase, Type)], - lambda_set_variables: &[roc_types::types::LambdaSet], -) -> AliasVariables { - let length = type_arguments.len() + lambda_set_variables.len(); - let new_variables = VariableSubsSlice::reserve_into_subs(subs, length); + // the first VariableSubsSlice in the array is a zero-length slice + let union_tags = UnionTags::from_slices(tag_names, SubsSlice::new(0, 1)); - for (target_index, (_, arg_type)) in (new_variables.indices()).zip(type_arguments) { - let copy_var = type_to_variable(subs, rank, pools, arena, arg_type); - subs.variables[target_index] = copy_var; + let content = Content::Structure(FlatType::TagUnion(union_tags, *ext)); + + register_with_known_var(subs, destination, rank, pools, content) + } + // This case is important for the rank of boolean variables + Function(arguments, closure_type, ret_type) => { + let new_arguments = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); + for (target_index, var_index) in (new_arguments.indices()).zip(arguments) { + let var = helper!(var_index); + subs.variables[target_index] = var; + } + + let ret_var = helper!(ret_type); + let closure_var = helper!(closure_type); + let content = + Content::Structure(FlatType::Func(new_arguments, closure_var, ret_var)); + + register_with_known_var(subs, destination, rank, pools, content) + } + Record(fields, ext) => { + // 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.is_empty_record()); + + let mut field_vars = Vec::with_capacity_in(fields.len(), arena); + + for (field, field_type) in fields { + let field_var = { + use roc_types::types::RecordField::*; + match &field_type { + Optional(t) => Optional(helper!(t)), + Required(t) => Required(helper!(t)), + Demanded(t) => Demanded(helper!(t)), + } + }; + + field_vars.push((field.clone(), field_var)); + } + + let temp_ext_var = 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) + } + + TagUnion(tags, ext) => { + // 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.is_empty_tag_union()); + + let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); + + register_with_known_var(subs, destination, rank, pools, content) + } + FunctionOrTagUnion(tag_name, symbol, ext) => { + let temp_ext_var = helper!(ext); + + let (it, ext) = roc_types::types::gather_tags_unsorted_iter( + subs, + UnionTags::default(), + temp_ext_var, + ); + + for _ in it { + unreachable!("we assert that the ext var is empty; otherwise we'd already know it was a tag union!"); + } + + let slice = SubsIndex::new(subs.tag_names.len() as u32); + subs.tag_names.push(tag_name.clone()); + + let content = Content::Structure(FlatType::FunctionOrTagUnion(slice, *symbol, ext)); + + register_with_known_var(subs, destination, rank, pools, content) + } + RecursiveTagUnion(rec_var, tags, ext) => { + // 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.is_empty_tag_union()); + + let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + 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 + } + + Type::Alias { + symbol, + type_arguments, + actual, + lambda_set_variables, + kind, + } => { + debug_assert!(Variable::get_reserved(*symbol).is_none()); + + 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) + { + 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); + for (target_index, ls) in it { + let copy_var = helper!(&ls.0); + subs.variables[target_index] = copy_var; + } + + AliasVariables { + variables_start: new_variables.start, + type_variables_len: type_arguments.len() as _, + all_variables_len: length as _, + } + }; + + let alias_variable = if let Symbol::RESULT_RESULT = *symbol { + roc_result_to_var(subs, rank, pools, arena, actual) + } else { + helper!(actual) + }; + let content = Content::Alias(*symbol, alias_variables, alias_variable, *kind); + + register_with_known_var(subs, destination, rank, pools, content) + } + HostExposedAlias { + name: symbol, + type_arguments, + actual: alias_type, + actual_var, + lambda_set_variables, + .. + } => { + 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) + { + 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); + for (target_index, ls) in it { + let copy_var = helper!(&ls.0); + subs.variables[target_index] = copy_var; + } + + AliasVariables { + variables_start: new_variables.start, + type_variables_len: type_arguments.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, arena, alias_type); + // 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(subs, rank, pools, content); + 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 + } + Erroneous(problem) => { + let content = Content::Structure(FlatType::Erroneous(Box::new(problem.clone()))); + + register_with_known_var(subs, destination, rank, pools, content) + } + }; } - let it = (new_variables.indices().skip(type_arguments.len())).zip(lambda_set_variables); - for (target_index, ls) in it { - let copy_var = type_to_variable(subs, rank, pools, arena, &ls.0); - subs.variables[target_index] = copy_var; - } - - AliasVariables { - variables_start: new_variables.start, - type_variables_len: type_arguments.len() as _, - all_variables_len: length as _, - } + result } #[inline(always)] @@ -2167,7 +2243,7 @@ fn register_with_known_var( rank: Rank, pools: &mut Pools, content: Content, -) { +) -> Variable { let descriptor = Descriptor { content, rank, @@ -2178,4 +2254,6 @@ fn register_with_known_var( subs.set(var, descriptor); pools.get_mut(rank).push(var); + + var } From a37a895016d3629b86651cbee6f41e2777851659 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 14:11:33 +0100 Subject: [PATCH 2/9] optimize tag name cache --- compiler/solve/src/solve.rs | 14 +++++------ compiler/types/src/subs.rs | 46 +++++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 9 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 2db56dbad6..2227dc41bf 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1321,16 +1321,16 @@ fn sort_and_deduplicate(tag_vars: &mut bumpalo::collections::Vec<(TagName, T) fn find_tag_name_run(slice: &[(TagName, T)], subs: &mut Subs) -> Option> { use std::cmp::Ordering; - let tag_name = slice.get(0)?.0.clone(); + let tag_name = &slice.get(0)?.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.entry(tag_name) { - Entry::Occupied(mut occupied) => { - let subs_slice = *occupied.get(); + 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 _); @@ -1364,12 +1364,12 @@ fn find_tag_name_run(slice: &[(TagName, T)], subs: &mut Subs) -> Option { // switch to the bigger slice that is not inserted yet, but will be soon - occupied.insert(bigger_slice); + *occupied = bigger_slice; } } } - Entry::Vacant(vacant) => { - vacant.insert(bigger_slice); + None => { + subs.tag_name_cache.push(tag_name, bigger_slice); } } diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index b636c1efa8..048d251e2f 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -68,7 +68,49 @@ pub struct Subs { pub field_names: Vec, pub record_fields: Vec>, pub variable_slices: Vec, - pub tag_name_cache: MutMap>, + pub tag_name_cache: TagNameCache, +} + +#[derive(Debug, Clone, Default)] +pub struct TagNameCache { + globals: Vec, + globals_slices: Vec>, + /// Currently private tags and closure tags; in the future just closure tags + symbols: Vec, + symbols_slices: Vec>, +} + +impl TagNameCache { + pub fn get_mut(&mut self, tag_name: &TagName) -> Option<&mut SubsSlice> { + match tag_name { + TagName::Global(uppercase) => { + // force into block + match self.globals.iter().position(|u| u == uppercase) { + Some(index) => Some(&mut self.globals_slices[index]), + None => None, + } + } + TagName::Private(symbol) | TagName::Closure(symbol) => { + match self.symbols.iter().position(|s| s == symbol) { + Some(index) => Some(&mut self.symbols_slices[index]), + None => None, + } + } + } + } + + pub fn push(&mut self, tag_name: &TagName, slice: SubsSlice) { + match tag_name { + TagName::Global(uppercase) => { + self.globals.push(uppercase.clone()); + self.globals_slices.push(slice); + } + TagName::Private(symbol) | TagName::Closure(symbol) => { + self.symbols.push(*symbol); + self.symbols_slices.push(slice); + } + } + } } impl Default for Subs { @@ -1248,7 +1290,7 @@ impl Subs { // store an empty slice at the first position // used for "TagOrFunction" variable_slices: vec![VariableSubsSlice::default()], - tag_name_cache: MutMap::default(), + tag_name_cache: TagNameCache::default(), }; // NOTE the utable does not (currently) have a with_capacity; using this as the next-best thing From 592a5ace193c5ed30846747e1ac836051d4fd91c Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 14:14:47 +0100 Subject: [PATCH 3/9] optimize recursive call --- compiler/solve/src/solve.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 2227dc41bf..0056bc68a8 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1466,8 +1466,7 @@ fn type_to_union_tags<'a>( let sorted = tags.len() == 1 || sorted_no_duplicates(tags); if ext.is_empty_tag_union() { - let ext = type_to_variable(subs, rank, pools, arena, &Type::EmptyTagUnion); - // let ext = Variable::EMPTY_TAG_UNION; + let ext = Variable::EMPTY_TAG_UNION; let union_tags = if sorted { insert_tags_fast_path(subs, rank, pools, arena, tags) From 8b526e4f58feb72ef09072a6074c2a22a27fce97 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 14:54:19 +0100 Subject: [PATCH 4/9] clippy --- compiler/solve/src/solve.rs | 1 - compiler/types/src/subs.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 0056bc68a8..5b446d1435 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -17,7 +17,6 @@ use roc_types::types::{ gather_fields_unsorted_iter, AliasKind, Category, ErrorType, PatternCategory, }; use roc_unify::unify::{unify, Mode, Unified::*}; -use std::collections::hash_map::Entry; // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // https://github.com/elm/compiler diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index 048d251e2f..52f9177e8f 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1,5 +1,5 @@ use crate::types::{name_type_var, AliasKind, ErrorType, Problem, RecordField, TypeExt}; -use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap}; +use roc_collections::all::{ImMap, ImSet, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName, Uppercase}; use roc_module::symbol::Symbol; use std::fmt; From eec92204f1d980e809f1e190ee9d8f88fc5ec689 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 15:13:39 +0100 Subject: [PATCH 5/9] optimize type_to_union_tags --- compiler/solve/src/solve.rs | 55 +++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 5b446d1435..8574ccabc3 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -1049,7 +1049,8 @@ fn type_to_variable<'a>( // If hit, try to turn the value into an EmptyTagUnion in canonicalization debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + let (union_tags, ext) = + type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); let content = Content::Structure(FlatType::TagUnion(union_tags, ext)); register_with_known_var(subs, destination, rank, pools, content) @@ -1079,7 +1080,8 @@ fn type_to_variable<'a>( // If hit, try to turn the value into an EmptyTagUnion in canonicalization debug_assert!(!tags.is_empty() || !ext.is_empty_tag_union()); - let (union_tags, ext) = type_to_union_tags(subs, rank, pools, arena, tags, ext); + let (union_tags, ext) = + type_to_union_tags(subs, rank, pools, arena, tags, ext, &mut stack); let content = Content::Structure(FlatType::RecursiveTagUnion(*rec_var, union_tags, ext)); @@ -1381,10 +1383,24 @@ fn insert_tags_fast_path<'a>( rank: Rank, pools: &mut Pools, arena: &'a bumpalo::Bump, - tags: &[(TagName, Vec)], + tags: &'a [(TagName, Vec)], + stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, ) -> UnionTags { let new_variable_slices = SubsSlice::reserve_variable_slices(subs, tags.len()); + macro_rules! helper { + ($typ:expr) => {{ + match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { + RegisterVariable::Direct(var) => var, + RegisterVariable::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer($typ, var)); + var + } + } + }}; + } + match find_tag_name_run(tags, subs) { Some(new_tag_names) => { let it = (new_variable_slices.indices()).zip(tags); @@ -1394,7 +1410,7 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = type_to_variable(subs, rank, pools, arena, argument); + let var = helper!(argument); subs.variables[target_index] = var; } @@ -1415,7 +1431,7 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = type_to_variable(subs, rank, pools, arena, argument); + let var = helper!(argument); subs.variables[target_index] = var; } @@ -1433,14 +1449,28 @@ fn insert_tags_slow_path<'a>( rank: Rank, pools: &mut Pools, arena: &'a bumpalo::Bump, - tags: &[(TagName, Vec)], + tags: &'a [(TagName, Vec)], mut tag_vars: bumpalo::collections::Vec<(TagName, VariableSubsSlice)>, + stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, ) -> UnionTags { + macro_rules! helper { + ($typ:expr) => {{ + match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { + RegisterVariable::Direct(var) => var, + RegisterVariable::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer($typ, var)); + var + } + } + }}; + } + for (tag, tag_argument_types) in tags { let new_slice = VariableSubsSlice::reserve_into_subs(subs, tag_argument_types.len()); for (i, arg) in (new_slice.indices()).zip(tag_argument_types) { - let var = type_to_variable(subs, rank, pools, arena, arg); + let var = helper!(arg); subs.variables[i] = var; } @@ -1457,8 +1487,9 @@ fn type_to_union_tags<'a>( rank: Rank, pools: &mut Pools, arena: &'a bumpalo::Bump, - tags: &[(TagName, Vec)], + tags: &'a [(TagName, Vec)], ext: &Type, + stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, ) -> (UnionTags, Variable) { use bumpalo::collections::Vec; @@ -1468,10 +1499,10 @@ fn type_to_union_tags<'a>( let ext = Variable::EMPTY_TAG_UNION; let union_tags = if sorted { - insert_tags_fast_path(subs, rank, pools, arena, tags) + insert_tags_fast_path(subs, rank, pools, arena, tags, stack) } else { let tag_vars = Vec::with_capacity_in(tags.len(), arena); - insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars) + insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) }; (union_tags, ext) @@ -1485,9 +1516,9 @@ fn type_to_union_tags<'a>( 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, tags) + insert_tags_fast_path(subs, rank, pools, arena, tags, stack) } else { - insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars) + insert_tags_slow_path(subs, rank, pools, arena, tags, tag_vars, stack) }; (union_tags, ext) From 0f5c339b4f5bd685e9a6ca7cfa21d30632a64fc5 Mon Sep 17 00:00:00 2001 From: Folkert Date: Sun, 6 Mar 2022 15:29:23 +0100 Subject: [PATCH 6/9] and roc_result_to_var --- compiler/solve/src/solve.rs | 82 ++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 8574ccabc3..b5550412e4 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -918,6 +918,25 @@ impl RegisterVariable { _ => Deferred, } } + + #[inline(always)] + fn with_stack<'a>( + subs: &mut Subs, + rank: Rank, + pools: &mut Pools, + arena: &'_ bumpalo::Bump, + typ: &'a Type, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, + ) -> Variable { + match Self::from_type(subs, rank, pools, arena, typ) { + Self::Direct(var) => var, + Self::Deferred => { + let var = subs.fresh_unnamed_flex_var(); + stack.push(TypeToVar::Defer(typ, var)); + var + } + } + } } #[derive(Debug)] @@ -1137,7 +1156,7 @@ fn type_to_variable<'a>( }; let alias_variable = if let Symbol::RESULT_RESULT = *symbol { - roc_result_to_var(subs, rank, pools, arena, actual) + roc_result_to_var(subs, rank, pools, arena, actual, &mut stack) } else { helper!(actual) }; @@ -1217,8 +1236,9 @@ fn roc_result_to_var<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, - result_type: &Type, + arena: &'_ bumpalo::Bump, + result_type: &'a Type, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> Variable { match result_type { Type::TagUnion(tags, ext) => { @@ -1230,8 +1250,10 @@ fn roc_result_to_var<'a>( debug_assert_eq!(ok, &subs.tag_names[1]); if let ([err_type], [ok_type]) = (err_args.as_slice(), ok_args.as_slice()) { - let err_var = type_to_variable(subs, rank, pools, arena, err_type); - let ok_var = type_to_variable(subs, rank, pools, arena, ok_type); + let err_var = + RegisterVariable::with_stack(subs, rank, pools, arena, err_type, stack); + let ok_var = + RegisterVariable::with_stack(subs, rank, pools, arena, ok_type, stack); let start = subs.variables.len() as u32; let err_slice = SubsSlice::new(start, 1); @@ -1382,25 +1404,12 @@ fn insert_tags_fast_path<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, + arena: &'_ bumpalo::Bump, tags: &'a [(TagName, Vec)], - stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> UnionTags { let new_variable_slices = SubsSlice::reserve_variable_slices(subs, tags.len()); - macro_rules! helper { - ($typ:expr) => {{ - match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { - RegisterVariable::Direct(var) => var, - RegisterVariable::Deferred => { - let var = subs.fresh_unnamed_flex_var(); - stack.push(TypeToVar::Defer($typ, var)); - var - } - } - }}; - } - match find_tag_name_run(tags, subs) { Some(new_tag_names) => { let it = (new_variable_slices.indices()).zip(tags); @@ -1410,7 +1419,8 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = helper!(argument); + let var = + RegisterVariable::with_stack(subs, rank, pools, arena, argument, stack); subs.variables[target_index] = var; } @@ -1431,7 +1441,8 @@ fn insert_tags_fast_path<'a>( let new_variables = VariableSubsSlice::reserve_into_subs(subs, arguments.len()); let it = (new_variables.indices()).zip(arguments); for (target_index, argument) in it { - let var = helper!(argument); + let var = + RegisterVariable::with_stack(subs, rank, pools, arena, argument, stack); subs.variables[target_index] = var; } @@ -1448,29 +1459,16 @@ fn insert_tags_slow_path<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, + arena: &'_ bumpalo::Bump, tags: &'a [(TagName, Vec)], mut tag_vars: bumpalo::collections::Vec<(TagName, VariableSubsSlice)>, - stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> UnionTags { - macro_rules! helper { - ($typ:expr) => {{ - match RegisterVariable::from_type(subs, rank, pools, arena, $typ) { - RegisterVariable::Direct(var) => var, - RegisterVariable::Deferred => { - let var = subs.fresh_unnamed_flex_var(); - stack.push(TypeToVar::Defer($typ, var)); - var - } - } - }}; - } - for (tag, tag_argument_types) in tags { let new_slice = VariableSubsSlice::reserve_into_subs(subs, tag_argument_types.len()); for (i, arg) in (new_slice.indices()).zip(tag_argument_types) { - let var = helper!(arg); + let var = RegisterVariable::with_stack(subs, rank, pools, arena, arg, stack); subs.variables[i] = var; } @@ -1486,10 +1484,10 @@ fn type_to_union_tags<'a>( subs: &mut Subs, rank: Rank, pools: &mut Pools, - arena: &'a bumpalo::Bump, + arena: &'_ bumpalo::Bump, tags: &'a [(TagName, Vec)], - ext: &Type, - stack: &mut bumpalo::collections::Vec<'a, TypeToVar<'a>>, + ext: &'a Type, + stack: &mut bumpalo::collections::Vec<'_, TypeToVar<'a>>, ) -> (UnionTags, Variable) { use bumpalo::collections::Vec; @@ -1509,7 +1507,7 @@ fn type_to_union_tags<'a>( } else { let mut tag_vars = Vec::with_capacity_in(tags.len(), arena); - let temp_ext_var = type_to_variable(subs, rank, pools, arena, ext); + let temp_ext_var = RegisterVariable::with_stack(subs, rank, pools, arena, ext, stack); let (it, ext) = roc_types::types::gather_tags_unsorted_iter(subs, UnionTags::default(), temp_ext_var); From 3bff99b0a23006d29e4113628cc61b292ae4cb1a Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 10:53:12 -0500 Subject: [PATCH 7/9] Register accessor closures when they are bound Previously we only registered record accessor closures in anonymous contexts, where we assume they must already be specialized based on the surrounding contexts. This is not true in general since one might bind an accessor to a name. Closes #2567 --- compiler/can/src/expr.rs | 2 + compiler/constrain/src/expr.rs | 24 +++++- compiler/mono/src/ir.rs | 122 ++++++++++++++++++++++----- compiler/solve/tests/solve_expr.rs | 14 +++ compiler/test_gen/src/gen_records.rs | 18 +++- 5 files changed, 154 insertions(+), 26 deletions(-) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index dc672693ff..fee1904ac4 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -144,6 +144,7 @@ pub enum Expr { name: Symbol, function_var: Variable, record_var: Variable, + closure_var: Variable, closure_ext_var: Variable, ext_var: Variable, field_var: Variable, @@ -740,6 +741,7 @@ pub fn canonicalize_expr<'a>( function_var: var_store.fresh(), record_var: var_store.fresh(), ext_var: var_store.fresh(), + closure_var: var_store.fresh(), closure_ext_var: var_store.fresh(), field_var: var_store.fresh(), field: (*field).into(), diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 0a97de4327..d064aa955a 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -769,7 +769,8 @@ pub fn constrain_expr( function_var, field, record_var, - closure_ext_var: closure_var, + closure_var, + closure_ext_var, ext_var, field_var, } => { @@ -795,16 +796,24 @@ pub fn constrain_expr( let lambda_set = Type::ClosureTag { name: *closure_name, - ext: *closure_var, + ext: *closure_ext_var, }; + let closure_type = Type::Variable(*closure_var); + let function_type = Type::Function( vec![record_type], - Box::new(lambda_set), + Box::new(closure_type.clone()), Box::new(field_type), ); let cons = [ + constraints.equal_types( + closure_type, + NoExpectation(lambda_set), + category.clone(), + region, + ), constraints.equal_types(function_type.clone(), expected, category.clone(), region), constraints.equal_types( function_type, @@ -816,7 +825,14 @@ pub fn constrain_expr( ]; constraints.exists_many( - [*record_var, *function_var, *closure_var, field_var, ext_var], + [ + *record_var, + *function_var, + *closure_var, + *closure_ext_var, + field_var, + ext_var, + ], cons, ) } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 559b9dae1f..f32541d591 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -816,6 +816,11 @@ impl<'a> Procs<'a> { ret_var: Variable, layout_cache: &mut LayoutCache<'a>, ) -> Result, RuntimeError> { + dbg!(env + .subs + .get_content_without_compacting(annotation) + .clone() + .dbg(env.subs)); let raw_layout = layout_cache .raw_from_var(env.arena, annotation, env.subs) .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); @@ -3088,6 +3093,53 @@ fn try_make_literal<'a>( } } +fn accessor_to_closure<'a>( + env: &mut Env<'a, '_>, + name: Symbol, + function_var: Variable, + record_var: Variable, + closure_var: Variable, + closure_ext_var: Variable, + ext_var: Variable, + field_var: Variable, + field: Lowercase, +) -> ClosureData { + // IDEA: convert accessor fromt + // + // .foo + // + // into + // + // (\r -> r.foo) + let record_symbol = env.unique_symbol(); + let body = roc_can::expr::Expr::Access { + record_var, + ext_var, + field_var, + loc_expr: Box::new(Loc::at_zero(roc_can::expr::Expr::Var(record_symbol))), + field, + }; + + let loc_body = Loc::at_zero(body); + + let arguments = vec![( + record_var, + Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)), + )]; + + ClosureData { + function_type: function_var, + closure_type: closure_var, + closure_ext_var, + return_type: field_var, + name, + captured_symbols: vec![], + recursive: roc_can::expr::Recursive::NotRecursive, + arguments, + loc_body: Box::new(loc_body), + } +} + pub fn with_hole<'a>( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, @@ -3886,40 +3938,36 @@ pub fn with_hole<'a>( name, function_var, record_var, - closure_ext_var: _, + closure_var, + closure_ext_var, ext_var, field_var, field, } => { - // IDEA: convert accessor fromt - // - // .foo - // - // into - // - // (\r -> r.foo) - let record_symbol = env.unique_symbol(); - let body = roc_can::expr::Expr::Access { + let ClosureData { + name, + function_type, + arguments, + loc_body, + .. + } = accessor_to_closure( + env, + name, + function_var, record_var, + closure_var, + closure_ext_var, ext_var, field_var, - loc_expr: Box::new(Loc::at_zero(roc_can::expr::Expr::Var(record_symbol))), field, - }; - - let loc_body = Loc::at_zero(body); - - let arguments = vec![( - record_var, - Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)), - )]; + ); match procs.insert_anonymous( env, name, - function_var, + function_type, arguments, - loc_body, + *loc_body, CapturedSymbols::None, field_var, layout_cache, @@ -5445,6 +5493,38 @@ pub fn from_can<'a>( return from_can(env, variable, cont.value, procs, layout_cache); } + roc_can::expr::Expr::Accessor { + name, + function_var, + record_var, + closure_var, + closure_ext_var, + ext_var, + field_var, + field, + } => { + let closure_data = accessor_to_closure( + env, + name, + function_var, + record_var, + closure_var, + closure_ext_var, + ext_var, + field_var, + field, + ); + + register_noncapturing_closure( + env, + procs, + layout_cache, + *symbol, + closure_data, + ); + + return from_can(env, variable, cont.value, procs, layout_cache); + } roc_can::expr::Expr::Var(original) => { // a variable is aliased, e.g. // diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 881a4556e9..70b321afcd 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -5529,4 +5529,18 @@ mod solve_expr { r#"a -> Effect a"#, ) } + + #[test] + fn generalized_accessor_function_applied() { + infer_eq_without_problem( + indoc!( + r#" + returnFoo = .foo + + returnFoo { foo: "foo" } + "# + ), + "Str", + ) + } } diff --git a/compiler/test_gen/src/gen_records.rs b/compiler/test_gen/src/gen_records.rs index 520db04b03..0a78ac2750 100644 --- a/compiler/test_gen/src/gen_records.rs +++ b/compiler/test_gen/src/gen_records.rs @@ -14,7 +14,7 @@ use crate::helpers::wasm::assert_evals_to; use indoc::indoc; #[cfg(test)] -use roc_std::RocList; +use roc_std::{RocList, RocStr}; #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))] @@ -1058,3 +1058,19 @@ fn call_with_bad_record_runtime_error() { "# )) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn generalized_accessor() { + assert_evals_to!( + indoc!( + r#" + returnFoo = .foo + + returnFoo { foo: "foo" } + "# + ), + RocStr::from("foo"), + RocStr + ); +} From 0692caf7ba2cad118b5907262f3750804b0b6269 Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 11:07:31 -0500 Subject: [PATCH 8/9] Consolidate `Accessor` data --- compiler/can/src/def.rs | 8 +-- compiler/can/src/expr.rs | 81 +++++++++++++++++++++----- compiler/constrain/src/expr.rs | 6 +- compiler/mono/src/ir.rs | 101 +++------------------------------ 4 files changed, 83 insertions(+), 113 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 1b5108dfe9..fad03f9099 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1085,7 +1085,7 @@ fn canonicalize_pending_def<'a>( // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. if let Pattern::Identifier(symbol) = loc_can_pattern.value { - if let &Closure(ClosureData { + if let Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1095,7 +1095,7 @@ fn canonicalize_pending_def<'a>( loc_body: ref body, ref captured_symbols, .. - }) = &loc_can_expr.value + }) = loc_can_expr.value { // Since everywhere in the code it'll be referred to by its defined name, // remove its generated name from the closure map. (We'll re-insert it later.) @@ -1225,7 +1225,7 @@ fn canonicalize_pending_def<'a>( // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. if let Pattern::Identifier(symbol) = loc_can_pattern.value { - if let &Closure(ClosureData { + if let Closure(ClosureData { function_type, closure_type, closure_ext_var, @@ -1235,7 +1235,7 @@ fn canonicalize_pending_def<'a>( loc_body: ref body, ref captured_symbols, .. - }) = &loc_can_expr.value + }) = loc_can_expr.value { // Since everywhere in the code it'll be referred to by its defined name, // remove its generated name from the closure map. (We'll re-insert it later.) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index fee1904ac4..8a54994f68 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -138,18 +138,7 @@ pub enum Expr { field: Lowercase, }, /// field accessor as a function, e.g. (.foo) expr - Accessor { - /// accessors are desugared to closures; they need to have a name - /// so the closure can have a correct lambda set - name: Symbol, - function_var: Variable, - record_var: Variable, - closure_var: Variable, - closure_ext_var: Variable, - ext_var: Variable, - field_var: Variable, - field: Lowercase, - }, + Accessor(AccessorData), Update { record_var: Variable, @@ -218,6 +207,70 @@ pub struct ClosureData { pub loc_body: Box>, } +/// A record accessor like `.foo`, which is equivalent to `\r -> r.foo` +/// Accessors are desugared to closures; they need to have a name +/// so the closure can have a correct lambda set. +/// +/// We distinguish them from closures so we can have better error messages +/// during constraint generation. +#[derive(Clone, Debug, PartialEq)] +pub struct AccessorData { + pub name: Symbol, + pub function_var: Variable, + pub record_var: Variable, + pub closure_var: Variable, + pub closure_ext_var: Variable, + pub ext_var: Variable, + pub field_var: Variable, + pub field: Lowercase, +} + +impl AccessorData { + pub fn to_closure_data(self, record_symbol: Symbol) -> ClosureData { + let AccessorData { + name, + function_var, + record_var, + closure_var, + closure_ext_var, + ext_var, + field_var, + field, + } = self; + + // IDEA: convert accessor from + // + // .foo + // + // into + // + // (\r -> r.foo) + let body = Expr::Access { + record_var, + ext_var, + field_var, + loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol))), + field, + }; + + let loc_body = Loc::at_zero(body); + + let arguments = vec![(record_var, Loc::at_zero(Pattern::Identifier(record_symbol)))]; + + ClosureData { + function_type: function_var, + closure_type: closure_var, + closure_ext_var, + return_type: field_var, + name, + captured_symbols: vec![], + recursive: Recursive::NotRecursive, + arguments, + loc_body: Box::new(loc_body), + } + } +} + #[derive(Clone, Debug, PartialEq)] pub struct Field { pub var: Variable, @@ -736,7 +789,7 @@ pub fn canonicalize_expr<'a>( ) } ast::Expr::AccessorFunction(field) => ( - Accessor { + Accessor(AccessorData { name: env.gen_unique_symbol(), function_var: var_store.fresh(), record_var: var_store.fresh(), @@ -745,7 +798,7 @@ pub fn canonicalize_expr<'a>( closure_ext_var: var_store.fresh(), field_var: var_store.fresh(), field: (*field).into(), - }, + }), Output::default(), ), ast::Expr::GlobalTag(tag) => { diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index d064aa955a..2913b5276a 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -8,7 +8,7 @@ use roc_can::def::{Declaration, Def}; use roc_can::expected::Expected::{self, *}; use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; -use roc_can::expr::{ClosureData, Field, WhenBranch}; +use roc_can::expr::{AccessorData, ClosureData, Field, WhenBranch}; use roc_can::pattern::Pattern; use roc_collections::all::{HumanIndex, ImMap, MutMap, SendMap}; use roc_module::ident::{Lowercase, TagName}; @@ -764,7 +764,7 @@ pub fn constrain_expr( [constraint, eq, record_con], ) } - Accessor { + Accessor(AccessorData { name: closure_name, function_var, field, @@ -773,7 +773,7 @@ pub fn constrain_expr( closure_ext_var, ext_var, field_var, - } => { + }) => { let ext_var = *ext_var; let ext_type = Variable(ext_var); let field_var = *field_var; diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index f32541d591..b352c0468e 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -3093,53 +3093,6 @@ fn try_make_literal<'a>( } } -fn accessor_to_closure<'a>( - env: &mut Env<'a, '_>, - name: Symbol, - function_var: Variable, - record_var: Variable, - closure_var: Variable, - closure_ext_var: Variable, - ext_var: Variable, - field_var: Variable, - field: Lowercase, -) -> ClosureData { - // IDEA: convert accessor fromt - // - // .foo - // - // into - // - // (\r -> r.foo) - let record_symbol = env.unique_symbol(); - let body = roc_can::expr::Expr::Access { - record_var, - ext_var, - field_var, - loc_expr: Box::new(Loc::at_zero(roc_can::expr::Expr::Var(record_symbol))), - field, - }; - - let loc_body = Loc::at_zero(body); - - let arguments = vec![( - record_var, - Loc::at_zero(roc_can::pattern::Pattern::Identifier(record_symbol)), - )]; - - ClosureData { - function_type: function_var, - closure_type: closure_var, - closure_ext_var, - return_type: field_var, - name, - captured_symbols: vec![], - recursive: roc_can::expr::Recursive::NotRecursive, - arguments, - loc_body: Box::new(loc_body), - } -} - pub fn with_hole<'a>( env: &mut Env<'a, '_>, can_expr: roc_can::expr::Expr, @@ -3934,33 +3887,17 @@ pub fn with_hole<'a>( stmt } - Accessor { - name, - function_var, - record_var, - closure_var, - closure_ext_var, - ext_var, - field_var, - field, - } => { + Accessor(accessor_data) => { + let field_var = accessor_data.field_var; + let fresh_record_symbol = env.unique_symbol(); + let ClosureData { name, function_type, arguments, loc_body, .. - } = accessor_to_closure( - env, - name, - function_var, - record_var, - closure_var, - closure_ext_var, - ext_var, - field_var, - field, - ); + } = accessor_data.to_closure_data(fresh_record_symbol); match procs.insert_anonymous( env, @@ -3975,7 +3912,7 @@ pub fn with_hole<'a>( Ok(_) => { let raw_layout = return_on_layout_error!( env, - layout_cache.raw_from_var(env.arena, function_var, env.subs) + layout_cache.raw_from_var(env.arena, function_type, env.subs) ); match raw_layout { @@ -5493,34 +5430,14 @@ pub fn from_can<'a>( return from_can(env, variable, cont.value, procs, layout_cache); } - roc_can::expr::Expr::Accessor { - name, - function_var, - record_var, - closure_var, - closure_ext_var, - ext_var, - field_var, - field, - } => { - let closure_data = accessor_to_closure( - env, - name, - function_var, - record_var, - closure_var, - closure_ext_var, - ext_var, - field_var, - field, - ); - + roc_can::expr::Expr::Accessor(accessor_data) => { + let fresh_record_symbol = env.unique_symbol(); register_noncapturing_closure( env, procs, layout_cache, *symbol, - closure_data, + accessor_data.to_closure_data(fresh_record_symbol), ); return from_can(env, variable, cont.value, procs, layout_cache); From cc8c94576236f4cbb7234fef783ea54140ca964b Mon Sep 17 00:00:00 2001 From: ayazhafiz Date: Sun, 6 Mar 2022 11:16:03 -0500 Subject: [PATCH 9/9] Remove stray dbg --- compiler/mono/src/ir.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index b352c0468e..67fd1b3432 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -816,11 +816,6 @@ impl<'a> Procs<'a> { ret_var: Variable, layout_cache: &mut LayoutCache<'a>, ) -> Result, RuntimeError> { - dbg!(env - .subs - .get_content_without_compacting(annotation) - .clone() - .dbg(env.subs)); let raw_layout = layout_cache .raw_from_var(env.arena, annotation, env.subs) .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));