use crate::abilities::AbilityMemberData; use crate::abilities::MemberVariables; use crate::abilities::PendingMemberType; use crate::annotation::canonicalize_annotation; use crate::annotation::find_type_def_symbols; use crate::annotation::make_apply_symbol; use crate::annotation::IntroducedVariables; use crate::annotation::OwnedNamedOrAble; use crate::env::Env; use crate::expr::AnnotatedMark; use crate::expr::ClosureData; use crate::expr::Declarations; use crate::expr::Expr::{self, *}; use crate::expr::{canonicalize_expr, Output, Recursive}; use crate::pattern::{canonicalize_def_header_pattern, BindingsFromPattern, Pattern}; use crate::procedure::References; use crate::scope::create_alias; use crate::scope::Scope; use roc_collections::ReferenceMatrix; use roc_collections::VecMap; use roc_collections::{ImSet, MutMap, SendMap}; use roc_error_macros::internal_error; use roc_module::ident::Ident; use roc_module::ident::Lowercase; use roc_module::symbol::IdentId; use roc_module::symbol::ModuleId; use roc_module::symbol::Symbol; use roc_parse::ast; use roc_parse::ast::AbilityMember; use roc_parse::ast::Defs; use roc_parse::ast::ExtractSpaces; use roc_parse::ast::TypeHeader; use roc_parse::pattern::PatternType; use roc_problem::can::ShadowKind; use roc_problem::can::{CycleEntry, Problem, RuntimeError}; use roc_region::all::{Loc, Region}; use roc_types::subs::IllegalCycleMark; use roc_types::subs::{VarStore, Variable}; use roc_types::types::AliasCommon; use roc_types::types::AliasKind; use roc_types::types::AliasVar; use roc_types::types::LambdaSet; use roc_types::types::{Alias, Type}; use std::fmt::Debug; #[derive(Clone, Debug)] pub struct Def { pub loc_pattern: Loc, pub loc_expr: Loc, pub expr_var: Variable, pub pattern_vars: SendMap, pub annotation: Option, } impl Def { pub fn region(&self) -> Region { let head_region = match &self.annotation { Some(ann) => { if ann.region.start() < self.loc_pattern.region.start() { ann.region } else { // Happens with annotation-only bodies like foo : T, since `T` is after the // pattern. self.loc_pattern.region } } None => self.loc_pattern.region, }; Region::span_across(&head_region, &self.loc_expr.region) } } #[derive(Clone, Debug)] pub struct Annotation { pub signature: Type, pub introduced_variables: IntroducedVariables, pub aliases: SendMap, pub region: Region, } #[derive(Debug)] pub(crate) struct CanDefs { defs: Vec>, expects: Expects, def_ordering: DefOrdering, aliases: VecMap, } #[derive(Clone, Debug)] pub struct Expects { pub conditions: Vec, pub regions: Vec, pub preceding_comment: Vec, } impl Expects { fn with_capacity(capacity: usize) -> Self { Self { conditions: Vec::with_capacity(capacity), regions: Vec::with_capacity(capacity), preceding_comment: Vec::with_capacity(capacity), } } } /// A Def that has had patterns and type annnotations canonicalized, /// but no Expr canonicalization has happened yet. Also, it has had spaces /// and nesting resolved, and knows whether annotations are standalone or not. #[derive(Debug, Clone)] enum PendingValueDef<'a> { /// A standalone annotation with no body AnnotationOnly( &'a Loc>, Loc, &'a Loc>, ), /// A body with no type annotation Body( &'a Loc>, Loc, &'a Loc>, ), /// A body with a type annotation TypedBody( &'a Loc>, Loc, &'a Loc>, &'a Loc>, ), } impl PendingValueDef<'_> { fn loc_pattern(&self) -> &Loc { match self { PendingValueDef::AnnotationOnly(_, loc_pattern, _) => loc_pattern, PendingValueDef::Body(_, loc_pattern, _) => loc_pattern, PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern, } } } #[derive(Debug, Clone)] enum PendingTypeDef<'a> { /// A structural type alias, e.g. `Ints : List Int` Alias { name: Loc, vars: Vec>, ann: &'a Loc>, }, /// An opaque type alias, e.g. `Age := U32`. Opaque { name: Loc, vars: Vec>, ann: &'a Loc>, derived: Option<&'a Loc>>, }, Ability { name: Loc, members: &'a [ast::AbilityMember<'a>], }, /// An invalid alias, that is ignored in the rest of the pipeline /// e.g. a definition like `MyAlias 1 : Int` /// with an incorrect pattern InvalidAlias { #[allow(dead_code)] kind: AliasKind, symbol: Symbol, region: Region, }, /// An alias with a name that shadows another symbol ShadowedAlias, /// An invalid ability, that is ignored in the rest of the pipeline. /// E.g. a shadowed ability, or with a bad definition. InvalidAbility { symbol: Symbol, region: Region, }, AbilityNotOnToplevel, AbilityShadows, } impl PendingTypeDef<'_> { fn introduction(&self) -> Option<(Symbol, Region)> { match self { PendingTypeDef::Alias { name, ann, .. } => { let region = Region::span_across(&name.region, &ann.region); Some((name.value, region)) } PendingTypeDef::Opaque { name, vars: _, ann, derived, } => { let end = derived.map(|d| d.region).unwrap_or(ann.region); let region = Region::span_across(&name.region, &end); Some((name.value, region)) } PendingTypeDef::Ability { name, .. } => Some((name.value, name.region)), PendingTypeDef::InvalidAlias { symbol, region, .. } => Some((*symbol, *region)), PendingTypeDef::ShadowedAlias { .. } => None, PendingTypeDef::InvalidAbility { symbol, region } => Some((*symbol, *region)), PendingTypeDef::AbilityNotOnToplevel => None, PendingTypeDef::AbilityShadows => None, } } } // See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check. #[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum Declaration { Declare(Def), DeclareRec(Vec, IllegalCycleMark), Builtin(Def), Expects(Expects), /// If we know a cycle is illegal during canonicalization. /// Otherwise we will try to detect this during solving; see [`IllegalCycleMark`]. InvalidCycle(Vec), } impl Declaration { pub fn def_count(&self) -> usize { use Declaration::*; match self { Declare(_) => 1, DeclareRec(defs, _) => defs.len(), InvalidCycle { .. } => 0, Builtin(_) => 0, Expects(_) => 0, } } pub fn region(&self) -> Region { match self { Declaration::Declare(def) => def.region(), Declaration::DeclareRec(defs, _) => Region::span_across( &defs.first().unwrap().region(), &defs.last().unwrap().region(), ), Declaration::Builtin(def) => def.region(), Declaration::InvalidCycle(cycles) => Region::span_across( &cycles.first().unwrap().expr_region, &cycles.last().unwrap().expr_region, ), Declaration::Expects(expects) => Region::span_across( expects.regions.first().unwrap(), expects.regions.last().unwrap(), ), } } } /// Returns a topologically sorted sequence of alias/opaque names fn sort_type_defs_before_introduction( referenced_symbols: VecMap>, ) -> Vec { let capacity = referenced_symbols.len(); let mut matrix = ReferenceMatrix::new(capacity); let (symbols, referenced) = referenced_symbols.unzip(); for (index, references) in referenced.iter().enumerate() { for referenced in references { match symbols.iter().position(|k| k == referenced) { None => { /* not defined in this scope */ } Some(ref_index) => matrix.set_row_col(index, ref_index, true), } } } // find the strongly connected components and their relations matrix .strongly_connected_components_all() .groups() .flat_map(|group| group.iter_ones()) .map(|index| symbols[index]) .collect() } #[inline(always)] #[allow(clippy::too_many_arguments)] fn canonicalize_alias<'a>( env: &mut Env<'a>, output: &mut Output, var_store: &mut VarStore, scope: &mut Scope, pending_abilities_in_scope: &[Symbol], name: Loc, ann: &'a Loc>, vars: &[Loc], kind: AliasKind, ) -> Result { let symbol = name.value; let can_ann = canonicalize_annotation( env, scope, &ann.value, ann.region, var_store, pending_abilities_in_scope, ); // Record all the annotation's references in output.references.lookups for symbol in can_ann.references { output.references.insert_type_lookup(symbol); } let mut can_vars: Vec> = Vec::with_capacity(vars.len()); let mut is_phantom = false; let IntroducedVariables { named, able, wildcards, inferred, .. } = can_ann.introduced_variables; let mut named: Vec<_> = (named.into_iter().map(OwnedNamedOrAble::Named)) .chain(able.into_iter().map(OwnedNamedOrAble::Able)) .collect(); for loc_lowercase in vars.iter() { let opt_index = named .iter() .position(|nv| nv.ref_name() == &loc_lowercase.value); match opt_index { Some(index) => { // This is a valid lowercase rigid var for the type def. let named_variable = named.swap_remove(index); let var = named_variable.variable(); let opt_bound_ability = named_variable.opt_ability(); let name = named_variable.name(); can_vars.push(Loc { value: AliasVar { name, var, opt_bound_ability, }, region: loc_lowercase.region, }); } None => { is_phantom = true; env.problems.push(Problem::PhantomTypeArgument { typ: symbol, variable_region: loc_lowercase.region, variable_name: loc_lowercase.value.clone(), }); } } } if is_phantom { // Bail out return Err(()); } let num_unbound = named.len() + wildcards.len() + inferred.len(); if num_unbound > 0 { let one_occurrence = named .iter() .map(|nv| Loc::at(nv.first_seen(), nv.variable())) .chain(wildcards) .chain(inferred) .next() .unwrap() .region; env.problems.push(Problem::UnboundTypeVariable { typ: symbol, num_unbound, one_occurrence, kind, }); // Bail out return Err(()); } Ok(create_alias( symbol, name.region, can_vars.clone(), can_ann.typ, kind, )) } #[inline(always)] #[allow(clippy::too_many_arguments)] fn canonicalize_opaque<'a>( env: &mut Env<'a>, output: &mut Output, var_store: &mut VarStore, scope: &mut Scope, pending_abilities_in_scope: &[Symbol], name: Loc, ann: &'a Loc>, vars: &[Loc], derives: Option<&'a Loc>>, ) -> Result { let alias = canonicalize_alias( env, output, var_store, scope, pending_abilities_in_scope, name, ann, vars, AliasKind::Opaque, )?; if let Some(derives) = derives { let derives = derives.value.collection(); let mut can_derives = vec![]; for derived in derives.items { let region = derived.region; match derived.value.extract_spaces().item { ast::TypeAnnotation::Apply(module_name, ident, []) => { match make_apply_symbol(env, region, scope, module_name, ident) { Ok(ability) if ability.is_builtin_ability() => { can_derives.push(Loc::at(region, ability)); } Ok(_) => { // Register the problem but keep going, we may still be able to compile the // program even if a derive is missing. env.problem(Problem::IllegalDerive(region)); } Err(_) => { // This is bad apply; an error will have been reported for it // already. } } } _ => { // Register the problem but keep going, we may still be able to compile the // program even if a derive is missing. env.problem(Problem::IllegalDerive(region)); } } } if !can_derives.is_empty() { // Fresh instance of this opaque to be checked for derivability during solving. let fresh_inst = Type::DelayedAlias(AliasCommon { symbol: name.value, type_arguments: alias .type_variables .iter() .map(|_| Type::Variable(var_store.fresh())) .collect(), lambda_set_variables: alias .lambda_set_variables .iter() .map(|_| LambdaSet(Type::Variable(var_store.fresh()))) .collect(), }); let old = output .pending_derives .insert(name.value, (fresh_inst, can_derives)); debug_assert!(old.is_none()); } } Ok(alias) } #[inline(always)] pub(crate) fn canonicalize_defs<'a>( env: &mut Env<'a>, mut output: Output, var_store: &mut VarStore, scope: &mut Scope, loc_defs: &'a mut roc_parse::ast::Defs<'a>, pattern_type: PatternType, ) -> (CanDefs, Output, MutMap) { // Canonicalizing defs while detecting shadowing involves a multi-step process: // // 1. Go through each of the patterns. // 2. For each identifier pattern, get the scope.symbol() for the ident. (That symbol will use the home module for its module.) // 3. If that symbol is already in scope, then we're about to shadow it. Error! // 4. Otherwise, add it to the scope immediately, so we can detect shadowing within the same // pattern (e.g. (Foo a a) = ...) // 5. Add this canonicalized pattern and its corresponding ast::Expr to pending_exprs. // 5. Once every pattern has been processed and added to scope, go back and canonicalize the exprs from // pending_exprs, this time building up a canonical def for each one. // // This way, whenever any expr is doing lookups, it knows everything that's in scope - // even defs that appear after it in the source. // // This naturally handles recursion too, because a given expr which refers // to itself won't be processed until after its def has been added to scope. let mut pending_type_defs = Vec::with_capacity(loc_defs.type_defs.len()); let mut value_defs = Vec::with_capacity(loc_defs.value_defs.len()); for (index, either_index) in loc_defs.tags.iter().enumerate() { match either_index.split() { Ok(type_index) => { let type_def = &loc_defs.type_defs[type_index.index()]; pending_type_defs.push(to_pending_type_def(env, type_def, scope, pattern_type)); } Err(value_index) => { let value_def = &loc_defs.value_defs[value_index.index()]; let region = loc_defs.regions[index]; value_defs.push(Loc::at(region, value_def)); } } } if cfg!(debug_assertions) { scope.register_debug_idents(); } let (aliases, symbols_introduced) = canonicalize_type_defs( env, &mut output, var_store, scope, pending_type_defs, pattern_type, ); // Now that we have the scope completely assembled, and shadowing resolved, // we're ready to canonicalize any body exprs. canonicalize_value_defs( env, output, var_store, scope, &value_defs, pattern_type, aliases, symbols_introduced, ) } #[allow(clippy::too_many_arguments)] fn canonicalize_value_defs<'a>( env: &mut Env<'a>, mut output: Output, var_store: &mut VarStore, scope: &mut Scope, value_defs: &[Loc<&'a roc_parse::ast::ValueDef<'a>>], pattern_type: PatternType, mut aliases: VecMap, mut symbols_introduced: MutMap, ) -> (CanDefs, Output, MutMap) { // Canonicalize all the patterns, record shadowing problems, and store // the ast::Expr values in pending_exprs for further canonicalization // once we've finished assembling the entire scope. let mut pending_value_defs = Vec::with_capacity(value_defs.len()); let mut pending_expects = Vec::with_capacity(value_defs.len()); for loc_def in value_defs { let mut new_output = Output::default(); let pending = to_pending_value_def( env, var_store, loc_def.value, scope, &mut new_output, pattern_type, ); match pending { PendingValue::Def(pending_def) => { // Record the ast::Expr for later. We'll do another pass through these // once we have the entire scope assembled. If we were to canonicalize // the exprs right now, they wouldn't have symbols in scope from defs // that get would have gotten added later in the defs list! pending_value_defs.push(pending_def); output.union(new_output); } PendingValue::SignatureDefMismatch => { /* skip */ } PendingValue::Expect(pending_expect) => { pending_expects.push(pending_expect); } } } let mut symbol_to_index: Vec<(IdentId, u32)> = Vec::with_capacity(pending_value_defs.len()); for (def_index, pending_def) in pending_value_defs.iter().enumerate() { for (s, r) in BindingsFromPattern::new(pending_def.loc_pattern()) { // store the top-level defs, used to ensure that closures won't capture them if let PatternType::TopLevelDef = pattern_type { env.top_level_symbols.insert(s); } symbols_introduced.insert(s, r); debug_assert_eq!(env.home, s.module_id()); debug_assert!( !symbol_to_index.iter().any(|(id, _)| *id == s.ident_id()), "{:?}", s ); symbol_to_index.push((s.ident_id(), def_index as u32)); } } let capacity = pending_value_defs.len(); let mut defs = Vec::with_capacity(capacity); let mut def_ordering = DefOrdering::from_symbol_to_id(env.home, symbol_to_index, capacity); for (def_id, pending_def) in pending_value_defs.into_iter().enumerate() { let temp_output = canonicalize_pending_value_def( env, pending_def, output, scope, var_store, pattern_type, &mut aliases, ); output = temp_output.output; defs.push(Some(temp_output.def)); def_ordering.insert_symbol_references(def_id as u32, &temp_output.references) } let mut expects = Expects::with_capacity(pending_expects.len()); for pending in pending_expects { let (loc_can_condition, can_output) = canonicalize_expr( env, var_store, scope, pending.condition.region, &pending.condition.value, ); expects.conditions.push(loc_can_condition.value); expects.regions.push(loc_can_condition.region); expects.preceding_comment.push(pending.preceding_comment); output.union(can_output); } let can_defs = CanDefs { defs, expects, def_ordering, aliases, }; (can_defs, output, symbols_introduced) } fn canonicalize_type_defs<'a>( env: &mut Env<'a>, output: &mut Output, var_store: &mut VarStore, scope: &mut Scope, pending_type_defs: Vec>, pattern_type: PatternType, ) -> (VecMap, MutMap) { enum TypeDef<'a> { Alias( Loc, Vec>, &'a Loc>, ), Opaque( Loc, Vec>, &'a Loc>, Option<&'a Loc>>, ), Ability(Loc, &'a [AbilityMember<'a>]), } let mut type_defs = MutMap::default(); let mut pending_abilities_in_scope = Vec::new(); let mut referenced_type_symbols = VecMap::default(); // Determine which idents we introduced in the course of this process. let mut symbols_introduced = MutMap::default(); for pending_def in pending_type_defs.into_iter() { if let Some((symbol, region)) = pending_def.introduction() { symbols_introduced.insert(symbol, region); } match pending_def { PendingTypeDef::Alias { name, vars, ann } => { let referenced_symbols = find_type_def_symbols(scope, &ann.value); referenced_type_symbols.insert(name.value, referenced_symbols); type_defs.insert(name.value, TypeDef::Alias(name, vars, ann)); } PendingTypeDef::Opaque { name, vars, ann, derived, } => { let referenced_symbols = find_type_def_symbols(scope, &ann.value); referenced_type_symbols.insert(name.value, referenced_symbols); // Don't need to insert references for derived types, because these can only contain // builtin abilities, and hence do not affect the type def sorting. We'll insert // references of usages when canonicalizing the derives. type_defs.insert(name.value, TypeDef::Opaque(name, vars, ann, derived)); } PendingTypeDef::Ability { name, members } => { let mut referenced_symbols = Vec::with_capacity(2); for member in members.iter() { // Add the referenced type symbols of each member function. We need to make // sure those are processed first before we resolve the whole ability // definition. referenced_symbols.extend(find_type_def_symbols(scope, &member.typ.value)); } referenced_type_symbols.insert(name.value, referenced_symbols); type_defs.insert(name.value, TypeDef::Ability(name, members)); pending_abilities_in_scope.push(name.value); } PendingTypeDef::InvalidAlias { .. } | PendingTypeDef::InvalidAbility { .. } | PendingTypeDef::AbilityShadows | PendingTypeDef::ShadowedAlias { .. } | PendingTypeDef::AbilityNotOnToplevel => { /* ignore */ } } } let sorted = sort_type_defs_before_introduction(referenced_type_symbols); let mut aliases = VecMap::default(); let mut abilities = MutMap::default(); for type_name in sorted { match type_defs.remove(&type_name).unwrap() { TypeDef::Alias(name, vars, ann) => { let alias = canonicalize_alias( env, output, var_store, scope, &pending_abilities_in_scope, name, ann, &vars, AliasKind::Structural, ); if let Ok(alias) = alias { aliases.insert(name.value, alias); } } TypeDef::Opaque(name, vars, ann, derived) => { let alias_and_derives = canonicalize_opaque( env, output, var_store, scope, &pending_abilities_in_scope, name, ann, &vars, derived, ); if let Ok(alias) = alias_and_derives { aliases.insert(name.value, alias); } } TypeDef::Ability(name, members) => { // For now we enforce that aliases cannot reference abilities, so let's wait to // resolve ability definitions until aliases are resolved and in scope below. abilities.insert(name.value, (name, members)); } } } // Now that we know the alias dependency graph, we can try to insert recursion variables // where aliases are recursive tag unions, or detect illegal recursions. let aliases = correct_mutual_recursive_type_alias(env, aliases, var_store); for (symbol, alias) in aliases.iter() { scope.add_alias( *symbol, alias.region, alias.type_variables.clone(), alias.typ.clone(), alias.kind, ); } // Resolve all pending abilities, to add them to scope. resolve_abilities( env, output, var_store, scope, abilities, &pending_abilities_in_scope, pattern_type, ); (aliases, symbols_introduced) } /// Resolve all pending abilities, to add them to scope. #[allow(clippy::too_many_arguments)] fn resolve_abilities<'a>( env: &mut Env<'a>, output: &mut Output, var_store: &mut VarStore, scope: &mut Scope, abilities: MutMap, &[AbilityMember])>, pending_abilities_in_scope: &[Symbol], pattern_type: PatternType, ) { for (loc_ability_name, members) in abilities.into_values() { let mut can_members = Vec::with_capacity(members.len()); for member in members { let member_annot = canonicalize_annotation( env, scope, &member.typ.value, member.typ.region, var_store, pending_abilities_in_scope, ); // Record all the annotation's references in output.references.lookups for symbol in member_annot.references { output.references.insert_type_lookup(symbol); } let name_region = member.name.region; let member_name = member.name.extract_spaces().item; let member_sym = match scope.introduce(member_name.into(), name_region) { Ok(sym) => sym, Err((original_region, shadow, _new_symbol)) => { env.problem(roc_problem::can::Problem::Shadowing { original_region, shadow, kind: ShadowKind::Variable, }); // Pretend the member isn't a part of the ability continue; } }; if pattern_type == PatternType::TopLevelDef { env.top_level_symbols.insert(member_sym); } // What variables in the annotation are bound to the parent ability, and what variables // are bound to some other ability? let (variables_bound_to_ability, _variables_bound_to_other_abilities): ( Vec<_>, Vec<_>, ) = member_annot .introduced_variables .able .iter() .partition(|av| av.ability == loc_ability_name.value); let var_bound_to_ability = match variables_bound_to_ability.as_slice() { [one] => one.variable, [] => { // There are no variables bound to the parent ability - then this member doesn't // need to be a part of the ability. env.problem(Problem::AbilityMemberMissingHasClause { member: member_sym, ability: loc_ability_name.value, region: name_region, }); // Pretend the member isn't a part of the ability continue; } [..] => { // There is more than one variable bound to the member signature, so something like // Eq has eq : a, b -> Bool | a has Eq, b has Eq // We have no way of telling what type implements a particular instance of Eq in // this case (a or b?), so disallow it. let span_has_clauses = Region::across_all( variables_bound_to_ability.iter().map(|v| &v.first_seen), ); let bound_var_names = variables_bound_to_ability .iter() .map(|v| v.name.clone()) .collect(); env.problem(Problem::AbilityMemberMultipleBoundVars { member: member_sym, ability: loc_ability_name.value, span_has_clauses, bound_var_names, }); // Pretend the member isn't a part of the ability continue; } }; // The introduced variables are good; add them to the output. output .introduced_variables .union(&member_annot.introduced_variables); let iv = member_annot.introduced_variables; let variables = MemberVariables { able_vars: iv.collect_able(), rigid_vars: iv.collect_rigid(), flex_vars: iv.collect_flex(), }; let signature = { let mut signature = member_annot.typ; signature .instantiate_lambda_sets_as_unspecialized(var_bound_to_ability, member_sym); signature }; can_members.push(( member_sym, AbilityMemberData { parent_ability: loc_ability_name.value, region: name_region, typ: PendingMemberType::Local { variables, signature, signature_var: var_store.fresh(), }, }, )); } // Store what symbols a type must define implementations for to have this ability. scope .abilities_store .register_ability(loc_ability_name.value, can_members); } } #[derive(Debug)] struct DefOrdering { home: ModuleId, symbol_to_id: Vec<(IdentId, u32)>, // an length x length matrix indicating who references who references: ReferenceMatrix, // references without looking into closure bodies. // Used to spot definitely-wrong recursion direct_references: ReferenceMatrix, } impl DefOrdering { fn from_symbol_to_id( home: ModuleId, symbol_to_id: Vec<(IdentId, u32)>, capacity: usize, ) -> Self { // NOTE: because of `Pair a b = someDef` patterns, we can have more symbols than defs // but because `_ = someDef` we can also have more defs than symbols Self { home, symbol_to_id, references: ReferenceMatrix::new(capacity), direct_references: ReferenceMatrix::new(capacity), } } fn insert_symbol_references(&mut self, def_id: u32, def_references: &DefReferences) { match def_references { DefReferences::Value(references) => { let it = references.value_lookups().chain(references.calls()); for referenced in it { if let Some(ref_id) = self.get_id(*referenced) { self.references .set_row_col(def_id as usize, ref_id as usize, true); self.direct_references .set_row_col(def_id as usize, ref_id as usize, true); } } } DefReferences::Function(references) => { let it = references.value_lookups().chain(references.calls()); for referenced in it { if let Some(ref_id) = self.get_id(*referenced) { self.references .set_row_col(def_id as usize, ref_id as usize, true); } } } DefReferences::AnnotationWithoutBody => { // annotatations without bodies don't reference any other definitions } } } fn get_id(&self, symbol: Symbol) -> Option { if symbol.module_id() != self.home { return None; } let target = symbol.ident_id(); for (ident_id, def_id) in self.symbol_to_id.iter() { if target == *ident_id { return Some(*def_id); } } None } fn get_symbol(&self, id: usize) -> Option { for (ident_id, def_id) in self.symbol_to_id.iter() { if id as u32 == *def_id { return Some(Symbol::new(self.home, *ident_id)); } } None } } #[inline(always)] pub(crate) fn sort_can_defs_new( env: &mut Env<'_>, scope: &mut Scope, var_store: &mut VarStore, defs: CanDefs, mut output: Output, ) -> (Declarations, Output) { let CanDefs { defs, expects, def_ordering, aliases, } = defs; // TODO: inefficient, but I want to make this what CanDefs contains in the future let mut defs: Vec<_> = defs.into_iter().map(|x| x.unwrap()).collect(); let mut declarations = Declarations::with_capacity(defs.len()); for (symbol, alias) in aliases.into_iter() { output.aliases.insert(symbol, alias); } // We first perform SCC based on any reference, both variable usage and calls // considering both value definitions and function bodies. This will spot any // recursive relations between any 2 definitions. let sccs = def_ordering.references.strongly_connected_components_all(); sccs.reorder(&mut defs); for group in sccs.groups().rev() { match group.count_ones() { 1 => { // a group with a single Def, nice and simple let def = defs.pop().unwrap(); let index = group.first_one().unwrap(); if def_ordering.direct_references.get_row_col(index, index) { // a definition like `x = x + 1`, which is invalid in roc let symbol = def_ordering.get_symbol(index).unwrap(); let entries = vec![make_cycle_entry(symbol, &def)]; let problem = Problem::RuntimeError(RuntimeError::CircularDef(entries.clone())); env.problem(problem); // Declaration::InvalidCycle(entries) todo!("InvalidCycle: {:?}", entries) } else if def_ordering.references.get_row_col(index, index) { // this function calls itself, and must be typechecked as a recursive def match def.loc_pattern.value { Pattern::Identifier(symbol) => match def.loc_expr.value { Closure(closure_data) => { declarations.push_recursive_def( Loc::at(def.loc_pattern.region, symbol), Loc::at(def.loc_expr.region, closure_data), def.expr_var, def.annotation, None, ); } _ => todo!(), }, Pattern::AbilityMemberSpecialization { ident: symbol, specializes, } => match def.loc_expr.value { Closure(closure_data) => { declarations.push_recursive_def( Loc::at(def.loc_pattern.region, symbol), Loc::at(def.loc_expr.region, closure_data), def.expr_var, def.annotation, Some(specializes), ); } _ => todo!(), }, _ => todo!("{:?}", &def.loc_pattern.value), } } else { match def.loc_pattern.value { Pattern::Identifier(symbol) => match def.loc_expr.value { Closure(closure_data) => { declarations.push_function_def( Loc::at(def.loc_pattern.region, symbol), Loc::at(def.loc_expr.region, closure_data), def.expr_var, def.annotation, None, ); } _ => { declarations.push_value_def( Loc::at(def.loc_pattern.region, symbol), def.loc_expr, def.expr_var, def.annotation, None, ); } }, Pattern::AbilityMemberSpecialization { ident: symbol, specializes, } => match def.loc_expr.value { Closure(closure_data) => { declarations.push_function_def( Loc::at(def.loc_pattern.region, symbol), Loc::at(def.loc_expr.region, closure_data), def.expr_var, def.annotation, Some(specializes), ); } _ => { declarations.push_value_def( Loc::at(def.loc_pattern.region, symbol), def.loc_expr, def.expr_var, def.annotation, Some(specializes), ); } }, _ => { declarations.push_destructure_def( def.loc_pattern, def.loc_expr, def.expr_var, def.annotation, def.pattern_vars.into_iter().collect(), ); } } } } group_length => { let group_defs = defs.split_off(defs.len() - group_length); // push the "header" for this group of recursive definitions let cycle_mark = IllegalCycleMark::new(var_store); declarations.push_recursive_group(group_length as u16, cycle_mark); // then push the definitions of this group for def in group_defs { let (symbol, specializes) = match def.loc_pattern.value { Pattern::Identifier(symbol) => (symbol, None), Pattern::AbilityMemberSpecialization { ident, specializes } => { (ident, Some(specializes)) } _ => { internal_error!("destructures cannot participate in a recursive group; it's always a type error") } }; match def.loc_expr.value { Closure(closure_data) => { declarations.push_recursive_def( Loc::at(def.loc_pattern.region, symbol), Loc::at(def.loc_expr.region, closure_data), def.expr_var, def.annotation, specializes, ); } _ => { declarations.push_value_def( Loc::at(def.loc_pattern.region, symbol), def.loc_expr, def.expr_var, def.annotation, specializes, ); } } } } } } for (condition, region) in expects.conditions.into_iter().zip(expects.regions) { // an `expect` does not have a user-defined name, but we'll need a name to call the expectation let name = scope.gen_unique_symbol(); declarations.push_expect(name, Loc::at(region, condition)); } (declarations, output) } #[inline(always)] pub(crate) fn sort_can_defs( env: &mut Env<'_>, var_store: &mut VarStore, defs: CanDefs, mut output: Output, ) -> (Vec, Output) { let CanDefs { mut defs, expects, def_ordering, aliases, } = defs; for (symbol, alias) in aliases.into_iter() { output.aliases.insert(symbol, alias); } macro_rules! take_def { ($index:expr) => { match defs[$index].take() { Some(def) => def, None => { // NOTE: a `_ = someDef` can mean we don't have a symbol here let symbol = def_ordering.get_symbol($index); roc_error_macros::internal_error!("def not available {:?}", symbol) } } }; } // We first perform SCC based on any reference, both variable usage and calls // considering both value definitions and function bodies. This will spot any // recursive relations between any 2 definitions. let sccs = def_ordering.references.strongly_connected_components_all(); let mut declarations = Vec::with_capacity(defs.len()); for group in sccs.groups() { if group.count_ones() == 1 { // a group with a single Def, nice and simple let index = group.iter_ones().next().unwrap(); let def = take_def!(index); let is_specialization = matches!( def.loc_pattern.value, Pattern::AbilityMemberSpecialization { .. } ); let declaration = if def_ordering.direct_references.get_row_col(index, index) { // a definition like `x = x + 1`, which is invalid in roc let symbol = def_ordering.get_symbol(index).unwrap(); let entries = vec![make_cycle_entry(symbol, &def)]; let problem = Problem::RuntimeError(RuntimeError::CircularDef(entries.clone())); env.problem(problem); Declaration::InvalidCycle(entries) } else if def_ordering.references.get_row_col(index, index) { debug_assert!(!is_specialization, "Self-recursive specializations can only be determined during solving - but it was determined for {:?} now, that's a bug!", def); // this function calls itself, and must be typechecked as a recursive def Declaration::DeclareRec(vec![mark_def_recursive(def)], IllegalCycleMark::empty()) } else { Declaration::Declare(def) }; declarations.push(declaration); } else { // There is something recursive going on between the Defs of this group. // Now we use the direct_references to see if it is clearly invalid recursion, e.g. // // x = y // y = x // // We allow indirect recursion (behind a lambda), e.g. // // boom = \{} -> boom {} // // In general we cannot spot faulty recursion (halting problem), so this is our // purely-syntactic heuristic. We'll have a second attempt once we know the types in // the cycle. let direct_sccs = def_ordering .direct_references .strongly_connected_components_subset(group); debug_assert!( !group.iter_ones().any(|index| matches!((&defs[index]).as_ref().unwrap().loc_pattern.value, Pattern::AbilityMemberSpecialization{..})), "A specialization is involved in a recursive cycle - this should not be knowable until solving"); let declaration = if direct_sccs.groups().count() == 1 { // all defs are part of the same direct cycle, that is invalid! let mut entries = Vec::with_capacity(group.count_ones()); for index in group.iter_ones() { let def = take_def!(index); let symbol = def_ordering.get_symbol(index).unwrap(); entries.push(make_cycle_entry(symbol, &def)) } let problem = Problem::RuntimeError(RuntimeError::CircularDef(entries.clone())); env.problem(problem); Declaration::InvalidCycle(entries) } else { let rec_defs = group .iter_ones() .map(|index| mark_def_recursive(take_def!(index))) .collect(); Declaration::DeclareRec(rec_defs, IllegalCycleMark::new(var_store)) }; declarations.push(declaration); } } if !expects.conditions.is_empty() { declarations.push(Declaration::Expects(expects)); } (declarations, output) } fn mark_def_recursive(mut def: Def) -> Def { if let Closure(ClosureData { recursive: recursive @ Recursive::NotRecursive, .. }) = &mut def.loc_expr.value { *recursive = Recursive::Recursive } def } fn make_cycle_entry(symbol: Symbol, def: &Def) -> CycleEntry { CycleEntry { symbol, symbol_region: def.loc_pattern.region, expr_region: def.loc_expr.region, } } fn pattern_to_vars_by_symbol( vars_by_symbol: &mut SendMap, pattern: &Pattern, expr_var: Variable, ) { use Pattern::*; match pattern { Identifier(symbol) | Shadowed(_, _, symbol) => { vars_by_symbol.insert(*symbol, expr_var); } AbilityMemberSpecialization { ident, specializes: _, } => { vars_by_symbol.insert(*ident, expr_var); } AppliedTag { arguments, .. } => { for (var, nested) in arguments { pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var); } } UnwrappedOpaque { argument, opaque, .. } => { let (var, nested) = &**argument; pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var); vars_by_symbol.insert(*opaque, expr_var); } RecordDestructure { destructs, .. } => { for destruct in destructs { vars_by_symbol.insert(destruct.value.symbol, destruct.value.var); } } NumLiteral(..) | IntLiteral(..) | FloatLiteral(..) | StrLiteral(_) | SingleQuote(_) | Underscore | MalformedPattern(_, _) | UnsupportedPattern(_) | OpaqueNotInScope(..) => {} } } fn single_can_def( loc_can_pattern: Loc, loc_can_expr: Loc, expr_var: Variable, opt_loc_annotation: Option>, pattern_vars: SendMap, ) -> Def { let def_annotation = opt_loc_annotation.map(|loc_annotation| Annotation { signature: loc_annotation.value.typ, introduced_variables: loc_annotation.value.introduced_variables, aliases: loc_annotation.value.aliases, region: loc_annotation.region, }); Def { expr_var, loc_pattern: loc_can_pattern, loc_expr: Loc { region: loc_can_expr.region, value: loc_can_expr.value, }, pattern_vars, annotation: def_annotation, } } // Functions' references don't count in defs. // See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its // parent commit for the bug this fixed! enum DefReferences { /// A value may not reference itself Value(References), /// If the def is a function, different rules apply (it can call itself) Function(References), /// An annotation without a body references no other defs AnnotationWithoutBody, } struct DefOutput { output: Output, def: Def, references: DefReferences, } // TODO trim down these arguments! #[allow(clippy::too_many_arguments)] #[allow(clippy::cognitive_complexity)] fn canonicalize_pending_value_def<'a>( env: &mut Env<'a>, pending_def: PendingValueDef<'a>, mut output: Output, scope: &mut Scope, var_store: &mut VarStore, pattern_type: PatternType, aliases: &mut VecMap, ) -> DefOutput { use PendingValueDef::*; // All abilities should be resolved by the time we're canonicalizing value defs. let pending_abilities_in_scope = &[]; let output = match pending_def { AnnotationOnly(_, loc_can_pattern, loc_ann) => { // Make types for the body expr, even if we won't end up having a body. let expr_var = var_store.fresh(); let mut vars_by_symbol = SendMap::default(); // annotation sans body cannot introduce new rigids that are visible in other annotations // but the rigids can show up in type error messages, so still register them let type_annotation = canonicalize_annotation( env, scope, &loc_ann.value, loc_ann.region, var_store, pending_abilities_in_scope, ); // Record all the annotation's references in output.references.lookups type_annotation.add_to( aliases, &mut output.references, &mut output.introduced_variables, ); pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); let arity = type_annotation.typ.arity(); let problem = match &loc_can_pattern.value { Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed { def_symbol: *symbol, }, Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing { original_region: *region, shadow: loc_ident.clone(), kind: ShadowKind::Variable, }, _ => RuntimeError::NoImplementation, }; // Fabricate a body for this annotation, that will error at runtime let value = Expr::RuntimeError(problem); let is_closure = arity > 0; let loc_can_expr = if !is_closure { Loc { value, region: loc_ann.region, } } else { let symbol = scope.gen_unique_symbol(); // generate a fake pattern for each argument. this makes signatures // that are functions only crash when they are applied. let mut underscores = Vec::with_capacity(arity); for _ in 0..arity { let underscore: Loc = Loc { value: Pattern::Underscore, region: Region::zero(), }; underscores.push(( var_store.fresh(), AnnotatedMark::known_exhaustive(), underscore, )); } let body_expr = Loc { value, region: loc_ann.region, }; Loc { value: Closure(ClosureData { function_type: var_store.fresh(), closure_type: var_store.fresh(), return_type: var_store.fresh(), name: symbol, captured_symbols: Vec::new(), recursive: Recursive::NotRecursive, arguments: underscores, loc_body: Box::new(body_expr), }), region: loc_ann.region, } }; let def = single_can_def( loc_can_pattern, loc_can_expr, expr_var, Some(Loc::at(loc_ann.region, type_annotation)), vars_by_symbol.clone(), ); DefOutput { output, references: DefReferences::AnnotationWithoutBody, def, } } TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { let type_annotation = canonicalize_annotation( env, scope, &loc_ann.value, loc_ann.region, var_store, pending_abilities_in_scope, ); // Record all the annotation's references in output.references.lookups type_annotation.add_to( aliases, &mut output.references, &mut output.introduced_variables, ); canonicalize_pending_body( env, output, scope, var_store, loc_can_pattern, loc_expr, Some(Loc::at(loc_ann.region, type_annotation)), ) } Body(_loc_pattern, loc_can_pattern, loc_expr) => { // canonicalize_pending_body( env, output, scope, var_store, loc_can_pattern, loc_expr, None, ) } }; // Disallow ability specializations that aren't on the toplevel (note: we might loosen this // restriction later on). if pattern_type != PatternType::TopLevelDef { if let Loc { value: Pattern::AbilityMemberSpecialization { specializes, .. }, region, } = output.def.loc_pattern { env.problem(Problem::NestedSpecialization(specializes, region)); } } output } // TODO trim down these arguments! #[allow(clippy::too_many_arguments)] #[allow(clippy::cognitive_complexity)] fn canonicalize_pending_body<'a>( env: &mut Env<'a>, mut output: Output, scope: &mut Scope, var_store: &mut VarStore, loc_can_pattern: Loc, loc_expr: &'a Loc, opt_loc_annotation: Option>, ) -> DefOutput { // We treat closure definitions `foo = \a, b -> ...` differently from other body expressions, // because they need more bookkeeping (for tail calls, closure captures, etc.) // // Only defs of the form `foo = ...` can be closure declarations or self tail calls. let (loc_can_expr, def_references) = { match (&loc_can_pattern.value, &loc_expr.value) { ( Pattern::Identifier(defined_symbol) | Pattern::AbilityMemberSpecialization { ident: defined_symbol, .. }, ast::Expr::Closure(arguments, body), ) => { // bookkeeping for tail-call detection. let outer_tailcallable = env.tailcallable_symbol; env.tailcallable_symbol = Some(*defined_symbol); let (mut closure_data, can_output) = crate::expr::canonicalize_closure( env, var_store, scope, arguments, body, Some(*defined_symbol), ); // reset the tailcallable_symbol env.tailcallable_symbol = outer_tailcallable; // The closure is self tail recursive iff it tail calls itself (by defined name). let is_recursive = match can_output.tail_call { Some(tail_symbol) if tail_symbol == *defined_symbol => Recursive::TailRecursive, _ => Recursive::NotRecursive, }; closure_data.recursive = is_recursive; closure_data.name = *defined_symbol; let loc_can_expr = Loc::at(loc_expr.region, Expr::Closure(closure_data)); let def_references = DefReferences::Function(can_output.references.clone()); output.union(can_output); (loc_can_expr, def_references) } _ => { let (loc_can_expr, can_output) = canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); let def_references = DefReferences::Value(can_output.references.clone()); output.union(can_output); (loc_can_expr, def_references) } } }; let expr_var = var_store.fresh(); let mut vars_by_symbol = SendMap::default(); pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); let def = single_can_def( loc_can_pattern, loc_can_expr, expr_var, opt_loc_annotation, vars_by_symbol, ); DefOutput { output, references: def_references, def, } } #[inline(always)] pub fn can_defs_with_return<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: &mut Scope, loc_defs: &'a mut Defs<'a>, loc_ret: &'a Loc>, ) -> (Expr, Output) { let (unsorted, defs_output, symbols_introduced) = canonicalize_defs( env, Output::default(), var_store, scope, loc_defs, PatternType::DefExpr, ); // The def as a whole is a tail call iff its return expression is a tail call. // Use its output as a starting point because its tail_call already has the right answer! let (ret_expr, mut output) = canonicalize_expr(env, var_store, scope, loc_ret.region, &loc_ret.value); output .introduced_variables .union(&defs_output.introduced_variables); output.references.union_mut(&defs_output.references); // Now that we've collected all the references, check to see if any of the new idents // we defined went unused by the return expression. If any were unused, report it. for (symbol, region) in symbols_introduced { if !output.references.has_type_or_value_lookup(symbol) && !scope.abilities_store.is_specialization_name(symbol) { env.problem(Problem::UnusedDef(symbol, region)); } } let (declarations, output) = sort_can_defs(env, var_store, unsorted, output); let mut loc_expr: Loc = ret_expr; for declaration in declarations.into_iter().rev() { loc_expr = decl_to_let(declaration, loc_expr); } (loc_expr.value, output) } fn decl_to_let(decl: Declaration, loc_ret: Loc) -> Loc { match decl { Declaration::Declare(def) => { let region = Region::span_across(&def.loc_pattern.region, &loc_ret.region); let expr = Expr::LetNonRec(Box::new(def), Box::new(loc_ret)); Loc::at(region, expr) } Declaration::DeclareRec(defs, cycle_mark) => { let region = Region::span_across(&defs[0].loc_pattern.region, &loc_ret.region); let expr = Expr::LetRec(defs, Box::new(loc_ret), cycle_mark); Loc::at(region, expr) } Declaration::InvalidCycle(entries) => { Loc::at_zero(Expr::RuntimeError(RuntimeError::CircularDef(entries))) } Declaration::Builtin(_) => { // Builtins should only be added to top-level decls, not to let-exprs! unreachable!() } Declaration::Expects(expects) => { // Expects should only be added to top-level decls, not to let-exprs! unreachable!("{:?}", &expects) } } } fn to_pending_alias_or_opaque<'a>( env: &mut Env<'a>, scope: &mut Scope, name: &'a Loc<&'a str>, vars: &'a [Loc>], ann: &'a Loc>, opt_derived: Option<&'a Loc>>, kind: AliasKind, ) -> PendingTypeDef<'a> { let shadow_kind = match kind { AliasKind::Structural => ShadowKind::Alias, AliasKind::Opaque => ShadowKind::Opaque, }; let region = Region::span_across(&name.region, &ann.region); match scope.introduce_without_shadow_symbol(&Ident::from(name.value), region) { Ok(symbol) => { let mut can_rigids: Vec> = Vec::with_capacity(vars.len()); for loc_var in vars.iter() { match loc_var.value { ast::Pattern::Identifier(name) if name.chars().next().unwrap().is_lowercase() => { let lowercase = Lowercase::from(name); can_rigids.push(Loc { value: lowercase, region: loc_var.region, }); } _ => { // any other pattern in this position is a syntax error. let problem = Problem::InvalidAliasRigid { alias_name: symbol, region: loc_var.region, }; env.problems.push(problem); return PendingTypeDef::InvalidAlias { kind, symbol, region, }; } } } let name = Loc { region: name.region, value: symbol, }; match kind { AliasKind::Structural => PendingTypeDef::Alias { name, vars: can_rigids, ann, }, AliasKind::Opaque => PendingTypeDef::Opaque { name, vars: can_rigids, ann, derived: opt_derived, }, } } Err((original_region, loc_shadowed_symbol)) => { env.problem(Problem::Shadowing { original_region, shadow: loc_shadowed_symbol, kind: shadow_kind, }); PendingTypeDef::ShadowedAlias } } } fn to_pending_type_def<'a>( env: &mut Env<'a>, def: &'a ast::TypeDef<'a>, scope: &mut Scope, pattern_type: PatternType, ) -> PendingTypeDef<'a> { use ast::TypeDef::*; match def { Alias { header: TypeHeader { name, vars }, ann, } => to_pending_alias_or_opaque(env, scope, name, vars, ann, None, AliasKind::Structural), Opaque { header: TypeHeader { name, vars }, typ: ann, derived, } => to_pending_alias_or_opaque( env, scope, name, vars, ann, derived.as_ref(), AliasKind::Opaque, ), Ability { header, members, .. } if pattern_type != PatternType::TopLevelDef => { let header_region = header.region(); let region = Region::span_across( &header_region, &members.last().map(|m| m.region()).unwrap_or(header_region), ); env.problem(Problem::AbilityNotOnToplevel { region }); PendingTypeDef::AbilityNotOnToplevel } Ability { header: TypeHeader { name, vars }, members, loc_has: _, } => { let name = match scope .introduce_without_shadow_symbol(&Ident::from(name.value), name.region) { Ok(symbol) => Loc::at(name.region, symbol), Err((original_region, shadowed_symbol)) => { env.problem(Problem::Shadowing { original_region, shadow: shadowed_symbol, kind: ShadowKind::Ability, }); return PendingTypeDef::AbilityShadows; } }; if !vars.is_empty() { // Disallow ability type arguments, at least for now. let variables_region = Region::across_all(vars.iter().map(|v| &v.region)); env.problem(Problem::AbilityHasTypeVariables { name: name.value, variables_region, }); return PendingTypeDef::InvalidAbility { symbol: name.value, region: name.region, }; } PendingTypeDef::Ability { name, // We'll handle adding the member symbols later on when we do all value defs. members, } } } } enum PendingValue<'a> { Def(PendingValueDef<'a>), Expect(PendingExpect<'a>), SignatureDefMismatch, } struct PendingExpect<'a> { condition: &'a Loc>, preceding_comment: Region, } fn to_pending_value_def<'a>( env: &mut Env<'a>, var_store: &mut VarStore, def: &'a ast::ValueDef<'a>, scope: &mut Scope, output: &mut Output, pattern_type: PatternType, ) -> PendingValue<'a> { use ast::ValueDef::*; match def { Annotation(loc_pattern, loc_ann) => { // This takes care of checking for shadowing and adding idents to scope. let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, output, pattern_type, &loc_pattern.value, loc_pattern.region, ); PendingValue::Def(PendingValueDef::AnnotationOnly( loc_pattern, loc_can_pattern, loc_ann, )) } Body(loc_pattern, loc_expr) => { // This takes care of checking for shadowing and adding idents to scope. let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, output, pattern_type, &loc_pattern.value, loc_pattern.region, ); PendingValue::Def(PendingValueDef::Body( loc_pattern, loc_can_pattern, loc_expr, )) } AnnotatedBody { ann_pattern, ann_type, comment: _, body_pattern, body_expr, } => { if ann_pattern.value.equivalent(&body_pattern.value) { // NOTE: Pick the body pattern, picking the annotation one is // incorrect in the presence of optional record fields! // // { x, y } : { x : Int, y ? Bool }* // { x, y ? False } = rec // // This takes care of checking for shadowing and adding idents to scope. let loc_can_pattern = canonicalize_def_header_pattern( env, var_store, scope, output, pattern_type, &body_pattern.value, body_pattern.region, ); PendingValue::Def(PendingValueDef::TypedBody( body_pattern, loc_can_pattern, ann_type, body_expr, )) } else { // the pattern of the annotation does not match the pattern of the body direc env.problems.push(Problem::SignatureDefMismatch { annotation_pattern: ann_pattern.region, def_pattern: body_pattern.region, }); // TODO: Should we instead build some PendingValueDef::InvalidAnnotatedBody ? This would // remove the `Option` on this function (and be probably more reliable for further // problem/error reporting) PendingValue::SignatureDefMismatch } } Expect(condition) => PendingValue::Expect(PendingExpect { condition, preceding_comment: Region::zero(), }), } } /// Make aliases recursive fn correct_mutual_recursive_type_alias<'a>( env: &mut Env<'a>, original_aliases: VecMap, var_store: &mut VarStore, ) -> VecMap { let capacity = original_aliases.len(); let mut matrix = ReferenceMatrix::new(capacity); let (symbols_introduced, mut aliases) = original_aliases.unzip(); for (index, alias) in aliases.iter().enumerate() { for referenced in alias.typ.symbols() { match symbols_introduced.iter().position(|k| referenced == *k) { None => { /* ignore */ } Some(ref_id) => matrix.set_row_col(index, ref_id, true), } } } let mut solved_aliases = bitvec::vec::BitVec::::repeat(false, capacity); let sccs = matrix.strongly_connected_components_all(); // scratchpad to store aliases that are modified in the current iteration. // Only used when there is are more than one alias in a group. See below why // this is needed. let scratchpad_capacity = sccs .groups() .map(|r| r.count_ones()) .max() .unwrap_or_default(); let mut scratchpad = Vec::with_capacity(scratchpad_capacity); for cycle in sccs.groups() { debug_assert!(cycle.count_ones() > 0); // We need to instantiate the alias with any symbols in the currrent module it // depends on. // // the `strongly_connected_components` returns SCCs in a topologically sorted order: // SCC_0 has those aliases that don't rely on any other, SCC_1 has only those that rely on SCC_1, etc. // // Hence, we only need to worry about symbols in the current SCC or any prior one. // It cannot be using any of the others, and we've already instantiated aliases coming from other modules. let mut to_instantiate = solved_aliases | cycle; // Make sure we report only one error for the cycle, not an error for every // alias in the cycle. let mut can_still_report_error = true; for index in cycle.iter_ones() { // Don't try to instantiate the alias itself in its own definition. to_instantiate.set(index, false); // Within a recursive group, we must instantiate all aliases like how they came to the // loop. e.g. given // // A : [ConsA B, NilA] // B : [ConsB A, NilB] // // Our goal is // // A : [ConsA [ConsB A, NilB], NilA] // B : [ConsB [ConsA B, NilA], NilB] // // But if we would first instantiate B into A, then use the updated A to instantiate B, // we get // // A : [ConsA [ConsB A, NilB], NilA] // B : [ConsB [ConsA [ConsB A, NilB], NilA], NilB] // // Which is incorrect. We do need the instantiated version however. // e.g. if in a next group we have: // // C : A // // Then we must use the instantiated version // // C : [ConsA [ConsB A, NilB], NilA] // // So, we cannot replace the original version of A with its instantiated version // while we process A's group. We have to store the instantiated version until the // current group is done, then move it to the `aliases` array. That is what the scratchpad is for. let alias = if cycle.count_ones() == 1 { // an optimization: we can modify the alias in the `aliases` list directly // because it is the only alias in the group. &mut aliases[index] } else { scratchpad.push((index, aliases[index].clone())); &mut scratchpad.last_mut().unwrap().1 }; // Now, `alias` is possibly a mutable borrow from the `aliases` vector. But we also want // to immutably borrow other elements from that vector to instantiate them into `alias`. // The borrow checker disallows that. // // So we get creative: we swap out the element we want to modify with a dummy. We can // then freely modify the type we moved out, and the `to_instantiate` mask // makes sure that our dummy is not used. let alias_region = alias.region; let mut alias_type = Type::EmptyRec; std::mem::swap(&mut alias_type, &mut alias.typ); let can_instantiate_symbol = |s| match symbols_introduced.iter().position(|i| *i == s) { Some(s_index) if to_instantiate[s_index] => aliases.get(s_index), _ => None, }; let mut new_lambda_sets = ImSet::default(); alias_type.instantiate_aliases( alias_region, &can_instantiate_symbol, var_store, &mut new_lambda_sets, ); let alias = if cycle.count_ones() > 1 { &mut scratchpad.last_mut().unwrap().1 } else { &mut aliases[index] }; // swap the type back std::mem::swap(&mut alias_type, &mut alias.typ); // We can instantiate this alias in future iterations to_instantiate.set(index, true); // add any lambda sets that the instantiation created to the current alias alias.lambda_set_variables.extend( new_lambda_sets .iter() .map(|var| LambdaSet(Type::Variable(*var))), ); // Now mark the alias recursive, if it needs to be. let rec = symbols_introduced[index]; let is_self_recursive = cycle.count_ones() == 1 && matrix.get_row_col(index, index); let is_mutually_recursive = cycle.count_ones() > 1; if is_self_recursive || is_mutually_recursive { let _made_recursive = make_tag_union_of_alias_recursive( env, rec, alias, vec![], var_store, &mut can_still_report_error, ); } } // the current group has instantiated. Now we can move the updated aliases to the `aliases` vector for (index, alias) in scratchpad.drain(..) { aliases[index] = alias; } // The cycle we just instantiated and marked recursive may still be an illegal cycle, if // all the types in the cycle are narrow newtypes. We can't figure this out until now, // because we need all the types to be deeply instantiated. let all_are_narrow = cycle.iter_ones().all(|index| { let typ = &aliases[index].typ; matches!(typ, Type::RecursiveTagUnion(..)) && typ.is_narrow() }); if all_are_narrow { // This cycle is illegal! let mut indices = cycle.iter_ones(); let first_index = indices.next().unwrap(); let rest: Vec = indices.map(|i| symbols_introduced[i]).collect(); let alias_name = symbols_introduced[first_index]; let alias = aliases.get_mut(first_index).unwrap(); mark_cyclic_alias( env, &mut alias.typ, alias_name, alias.region, rest, can_still_report_error, ) } // We've instantiated all we could, so all instantiatable aliases are solved now solved_aliases = to_instantiate; } // Safety: both vectors are equal length and there are no duplicates unsafe { VecMap::zip(symbols_introduced, aliases) } } fn make_tag_union_of_alias_recursive<'a>( env: &mut Env<'a>, alias_name: Symbol, alias: &mut Alias, others: Vec, var_store: &mut VarStore, can_report_cyclic_error: &mut bool, ) -> Result<(), ()> { let alias_args = alias .type_variables .iter() .map(|l| (l.value.name.clone(), Type::Variable(l.value.var))) .collect::>(); let made_recursive = make_tag_union_recursive_help( env, Loc::at( alias.header_region(), (alias_name, alias_args.iter().map(|ta| &ta.1)), ), alias.region, others, &mut alias.typ, var_store, can_report_cyclic_error, ); match made_recursive { MakeTagUnionRecursive::Cyclic => Ok(()), MakeTagUnionRecursive::MadeRecursive { recursion_variable } => { alias.recursion_variables.clear(); alias.recursion_variables.insert(recursion_variable); Ok(()) } MakeTagUnionRecursive::InvalidRecursion => Err(()), } } enum MakeTagUnionRecursive { Cyclic, MadeRecursive { recursion_variable: Variable }, InvalidRecursion, } /// Attempt to make a tag union recursive at the position of `recursive_alias`; for example, /// /// ```roc /// [Cons a (ConsList a), Nil] as ConsList a /// ``` /// /// can be made recursive at the position "ConsList a" with a fresh recursive variable, say r1: /// /// ```roc /// [Cons a r1, Nil] as r1 /// ``` /// /// Returns `Err` if the tag union is recursive, but there is no structure-preserving recursion /// variable for it. This can happen when the type is a nested datatype, for example in either of /// /// ```roc /// Nested a : [Chain a (Nested (List a)), Term] /// DuoList a b : [Cons a (DuoList b a), Nil] /// ``` /// /// When `Err` is returned, a problem will be added to `env`. fn make_tag_union_recursive_help<'a, 'b>( env: &mut Env<'a>, recursive_alias: Loc<(Symbol, impl Iterator)>, region: Region, others: Vec, typ: &'b mut Type, var_store: &mut VarStore, can_report_cyclic_error: &mut bool, ) -> MakeTagUnionRecursive { use MakeTagUnionRecursive::*; let (symbol, args) = recursive_alias.value; let alias_region = recursive_alias.region; match typ { Type::TagUnion(tags, ext) => { let recursion_variable = var_store.fresh(); let type_arguments: Vec<_> = args.into_iter().cloned().collect(); let mut pending_typ = Type::RecursiveTagUnion(recursion_variable, tags.to_vec(), ext.clone()); let substitution_result = pending_typ.substitute_alias( symbol, &type_arguments, &Type::Variable(recursion_variable), ); match substitution_result { Ok(()) => { // We can substitute the alias presence for the variable exactly. *typ = pending_typ; MadeRecursive { recursion_variable } } Err(differing_recursion_region) => { env.problems.push(Problem::NestedDatatype { alias: symbol, def_region: alias_region, differing_recursion_region, }); InvalidRecursion } } } Type::RecursiveTagUnion(recursion_variable, _, _) => MadeRecursive { recursion_variable: *recursion_variable, }, Type::Alias { actual, type_arguments, .. } => { // NB: We need to collect the type arguments to shut off rustc's closure type // instantiator. Otherwise we get unfortunate errors like // reached the recursion limit while instantiating `make_tag_union_recursive_help::<...n/src/def.rs:1879:65: 1879:77]>>` #[allow(clippy::needless_collect)] let type_arguments: Vec<&Type> = type_arguments.iter().map(|ta| &ta.typ).collect(); let recursive_alias = Loc::at_zero((symbol, type_arguments.into_iter())); // try to make `actual` recursive make_tag_union_recursive_help( env, recursive_alias, region, others, actual, var_store, can_report_cyclic_error, ) } _ => { // take care to report a cyclic alias only once (not once for each alias in the cycle) mark_cyclic_alias(env, typ, symbol, region, others, *can_report_cyclic_error); *can_report_cyclic_error = false; Cyclic } } } fn mark_cyclic_alias<'a>( env: &mut Env<'a>, typ: &mut Type, symbol: Symbol, region: Region, others: Vec, report: bool, ) { let problem = roc_types::types::Problem::CyclicAlias(symbol, region, others.clone()); *typ = Type::Erroneous(problem); if report { let problem = Problem::CyclicAlias(symbol, region, others); env.problems.push(problem); } }