use crate::abilities::MemberVariables; use crate::annotation::canonicalize_annotation; use crate::annotation::IntroducedVariables; use crate::env::Env; use crate::expr::ClosureData; use crate::expr::Expr::{self, *}; use crate::expr::{canonicalize_expr, Output, Recursive}; use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pattern}; use crate::procedure::References; use crate::reference_matrix::ReferenceMatrix; use crate::scope::create_alias; use crate::scope::Scope; use roc_collections::VecMap; use roc_collections::{ImSet, MutMap, SendMap}; 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::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::{VarStore, Variable}; use roc_types::types::AliasKind; 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, } #[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>, def_ordering: DefOrdering, aliases: VecMap, } /// 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 or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively. Alias { name: Loc, vars: Vec>, ann: &'a Loc>, kind: AliasKind, }, Ability { name: Loc, members: &'a [ast::AbilityMember<'a>], }, /// An invalid alias, that is ignored in the rest of the pipeline /// e.g. a shadowed alias, or a definition like `MyAlias 1 : Int` /// with an incorrect pattern InvalidAlias { #[allow(dead_code)] kind: AliasKind, symbol: Symbol, region: Region, }, /// 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::Ability { name, .. } => Some((name.value, name.region)), PendingTypeDef::InvalidAlias { symbol, region, .. } => Some((*symbol, *region)), 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), Builtin(Def), InvalidCycle(Vec), } impl Declaration { pub fn def_count(&self) -> usize { use Declaration::*; match self { Declare(_) => 1, DeclareRec(defs) => defs.len(), InvalidCycle { .. } => 0, Builtin(_) => 0, } } } /// 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 let nodes: Vec<_> = (0..capacity as u32).collect(); let mut output = Vec::with_capacity(capacity); for group in matrix.strongly_connected_components(&nodes).groups() { for index in group.iter_ones() { output.push(symbols[index]) } } output } #[inline(always)] pub(crate) fn canonicalize_defs<'a>( env: &mut Env<'a>, mut output: Output, var_store: &mut VarStore, mut scope: Scope, loc_defs: &'a [&'a Loc>], pattern_type: PatternType, ) -> (CanDefs, Scope, 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 num_defs = loc_defs.len(); let mut pending_type_defs = Vec::with_capacity(num_defs); let mut value_defs = Vec::with_capacity(num_defs); for loc_def in loc_defs { match loc_def.value.unroll_def() { Ok(type_def) => { pending_type_defs.push(to_pending_type_def(env, type_def, &mut scope, pattern_type)) } Err(value_def) => value_defs.push(Loc::at(loc_def.region, value_def)), } } if cfg!(debug_assertions) { env.home.register_debug_idents(&env.ident_ids); } enum TypeDef<'a> { AliasLike( Loc, Vec>, &'a Loc>, AliasKind, ), Ability(Loc, &'a [AbilityMember<'a>]), } let mut type_defs = MutMap::default(); let mut 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, kind, } => { let referenced_symbols = crate::annotation::find_type_def_symbols( env.home, &mut env.ident_ids, &ann.value, ); referenced_type_symbols.insert(name.value, referenced_symbols); type_defs.insert(name.value, TypeDef::AliasLike(name, vars, ann, kind)); } 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(crate::annotation::find_type_def_symbols( env.home, &mut env.ident_ids, &member.typ.value, )); } referenced_type_symbols.insert(name.value, referenced_symbols); type_defs.insert(name.value, TypeDef::Ability(name, members)); abilities_in_scope.push(name.value); } PendingTypeDef::InvalidAlias { .. } | PendingTypeDef::InvalidAbility { .. } | PendingTypeDef::AbilityShadows | 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::AliasLike(name, vars, ann, kind) => { let symbol = name.value; let can_ann = canonicalize_annotation( env, &mut scope, &ann.value, ann.region, var_store, &abilities_in_scope, ); // Does this alias reference any abilities? For now, we don't permit that. let ability_references = can_ann .references .iter() .filter_map(|&ty_ref| abilities_in_scope.iter().find(|&&name| name == ty_ref)) .collect::>(); if let Some(one_ability_ref) = ability_references.first() { env.problem(Problem::AliasUsesAbility { loc_name: name, ability: **one_ability_ref, }); } // 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 mut named = can_ann.introduced_variables.named; for loc_lowercase in vars.iter() { let opt_index = named.iter().position(|nv| nv.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); can_vars.push(Loc { value: (named_variable.name, named_variable.variable), 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 continue; } let IntroducedVariables { wildcards, inferred, .. } = can_ann.introduced_variables; 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 continue; } let alias = create_alias( symbol, name.region, can_vars.clone(), can_ann.typ.clone(), kind, ); aliases.insert(symbol, 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 mut 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, &mut output, var_store, &mut scope, abilities, &abilities_in_scope, pattern_type, ); // Now that we have the scope completely assembled, and shadowing resolved, // we're ready to canonicalize any body exprs. // 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()); for loc_def in value_defs.into_iter() { let mut new_output = Output::default(); match to_pending_value_def( env, var_store, loc_def.value, &mut scope, &mut new_output, pattern_type, ) { None => { /* skip */ } Some(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); } } } 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 bindings_from_patterns(std::iter::once(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())); 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, &mut scope, var_store, &mut aliases, &abilities_in_scope, ); output = temp_output.output; defs.push(Some(temp_output.def)); def_ordering.insert_symbol_references(def_id as u32, &temp_output.references) } // This returns both the defs info as well as the new scope. // // We have to return the new scope because we added defs to it // (and those lookups shouldn't fail later, e.g. when canonicalizing // the return expr), but we didn't want to mutate the original scope // directly because we wanted to keep a clone of it around to diff // when looking for unused idents. ( CanDefs { defs, def_ordering, // The result needs a thread-safe `SendMap` aliases, }, scope, output, 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])>, 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, 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(), &env.exposed_ident_ids, &mut env.ident_ids, 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 mut bad_has_clauses = false; if variables_bound_to_ability.is_empty() { // 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, }); bad_has_clauses = true; } if variables_bound_to_ability.len() > 1 { // 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, }); bad_has_clauses = true; } if !variables_bound_to_other_abilities.is_empty() { // Disallow variables bound to other abilities, for now. for bad_variable in variables_bound_to_other_abilities.iter() { env.problem(Problem::AbilityMemberBindsExternalAbility { member: member_sym, ability: loc_ability_name.value, region: bad_variable.first_seen, }); } bad_has_clauses = true; } if bad_has_clauses { // 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(), }; can_members.push(( member_sym, name_region, var_store.fresh(), member_annot.typ, variables, )); } // 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( env: &mut Env<'_>, defs: CanDefs, mut output: Output, ) -> (Result, RuntimeError>, Output) { let CanDefs { mut defs, 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) } } }; } let nodes: Vec<_> = (0..defs.len() as u32).collect(); // 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(&nodes); let mut declarations = Vec::new(); 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 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) { // this function calls itself, and must be typechecked as a recursive def Declaration::DeclareRec(vec![mark_def_recursive(def)]) } 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 best attempt let nodes: Vec<_> = group.iter_ones().map(|v| v as u32).collect(); let direct_sccs = def_ordering .direct_references .strongly_connected_components(&nodes); 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) }; declarations.push(declaration); } } (Ok(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, } } fn add_annotation_aliases( type_annotation: &crate::annotation::Annotation, aliases: &mut VecMap, ) { for (name, alias) in type_annotation.aliases.iter() { if !aliases.contains(name) { aliases.insert(*name, alias.clone()); } } } // 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, aliases: &mut VecMap, abilities_in_scope: &[Symbol], ) -> DefOutput { use PendingValueDef::*; // 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(); match pending_def { AnnotationOnly(_, loc_can_pattern, loc_ann) => { // 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, abilities_in_scope, ); // Record all the annotation's references in output.references.lookups for symbol in type_annotation.references.iter() { output.references.insert_type_lookup(*symbol); } add_annotation_aliases(&type_annotation, aliases); output .introduced_variables .union(&type_annotation.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 = env.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(), underscore)); } let body_expr = Loc { value, region: loc_ann.region, }; Loc { value: Closure(ClosureData { function_type: var_store.fresh(), closure_type: var_store.fresh(), closure_ext_var: 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, abilities_in_scope, ); // Record all the annotation's references in output.references.lookups for symbol in type_annotation.references.iter() { output.references.insert_type_lookup(*symbol); } add_annotation_aliases(&type_annotation, aliases); output .introduced_variables .union(&type_annotation.introduced_variables); // bookkeeping for tail-call detection. If we're assigning to an // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. let outer_identifier = env.tailcallable_symbol; if let Pattern::Identifier(ref defined_symbol) = &loc_can_pattern.value { env.tailcallable_symbol = Some(*defined_symbol); }; // register the name of this closure, to make sure the closure won't capture it's own name if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = (&loc_can_pattern.value, &loc_expr.value) { env.closure_name_symbol = Some(*defined_symbol); }; pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); let (mut loc_can_expr, can_output) = canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); output.references.union_mut(&can_output.references); // reset the tailcallable_symbol env.tailcallable_symbol = outer_identifier; // First, make sure we are actually assigning an identifier instead of (for example) a tag. // // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, // which also implies it's not a self tail call! // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. match (&loc_can_pattern.value, &loc_can_expr.value) { ( Pattern::Identifier(symbol) | Pattern::AbilityMemberSpecialization { ident: symbol, .. }, Closure(ClosureData { function_type, closure_type, closure_ext_var, return_type, name: closure_name, arguments, loc_body: body, captured_symbols, .. }), ) => { // 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.) let closure_references = env.closures.remove(closure_name).unwrap_or_else(|| { panic!( "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_name, env.closures ) }); // 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 == *symbol => Recursive::TailRecursive, _ => Recursive::NotRecursive, }; loc_can_expr.value = Closure(ClosureData { function_type: *function_type, closure_type: *closure_type, closure_ext_var: *closure_ext_var, return_type: *return_type, name: *symbol, captured_symbols: captured_symbols.clone(), recursive: is_recursive, arguments: arguments.clone(), loc_body: body.clone(), }); 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(), ); output.union(can_output); DefOutput { output, references: DefReferences::Function(closure_references), def, } } _ => { let refs = can_output.references.clone(); 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(), ); output.union(can_output); DefOutput { output, references: DefReferences::Value(refs), def, } } } } // If we have a pattern, then the def has a body (that is, it's not a // standalone annotation), so we need to canonicalize the pattern and expr. Body(loc_pattern, loc_can_pattern, loc_expr) => { // bookkeeping for tail-call detection. If we're assigning to an // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. let outer_identifier = env.tailcallable_symbol; if let (&ast::Pattern::Identifier(_name), &Pattern::Identifier(ref defined_symbol)) = (&loc_pattern.value, &loc_can_pattern.value) { env.tailcallable_symbol = Some(*defined_symbol); // TODO isn't types_by_symbol enough? Do we need vars_by_symbol too? vars_by_symbol.insert(*defined_symbol, expr_var); }; // register the name of this closure, to make sure the closure won't capture it's own name if let (Pattern::Identifier(ref defined_symbol), &ast::Expr::Closure(_, _)) = (&loc_can_pattern.value, &loc_expr.value) { env.closure_name_symbol = Some(*defined_symbol); }; let (mut loc_can_expr, can_output) = canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); // reset the tailcallable_symbol env.tailcallable_symbol = outer_identifier; // First, make sure we are actually assigning an identifier instead of (for example) a tag. // // If we're assigning (UserId userId) = ... then this is certainly not a closure declaration, // which also implies it's not a self tail call! // // Only defs of the form (foo = ...) can be closure declarations or self tail calls. match (&loc_can_pattern.value, &loc_can_expr.value) { ( Pattern::Identifier(symbol), Closure(ClosureData { function_type, closure_type, closure_ext_var, return_type, name: closure_name, arguments, loc_body: body, captured_symbols, .. }), ) => { // 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.) let closure_references = env.closures.remove(closure_name).unwrap_or_else(|| { panic!( "Tried to remove symbol {:?} from procedures, but it was not found: {:?}", closure_name, env.closures ) }); // 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 == *symbol => Recursive::TailRecursive, _ => Recursive::NotRecursive, }; loc_can_expr.value = Closure(ClosureData { function_type: *function_type, closure_type: *closure_type, closure_ext_var: *closure_ext_var, return_type: *return_type, name: *symbol, captured_symbols: captured_symbols.clone(), recursive: is_recursive, arguments: arguments.clone(), loc_body: body.clone(), }); let def = single_can_def( loc_can_pattern, loc_can_expr, expr_var, None, vars_by_symbol.clone(), ); output.union(can_output); DefOutput { output, references: DefReferences::Function(closure_references), def, } } _ => { let refs = can_output.references.clone(); let def = single_can_def( loc_can_pattern, loc_can_expr, expr_var, None, vars_by_symbol.clone(), ); output.union(can_output); DefOutput { output, references: DefReferences::Value(refs), def, } } } } } } #[inline(always)] pub fn can_defs_with_return<'a>( env: &mut Env<'a>, var_store: &mut VarStore, scope: Scope, loc_defs: &'a [&'a Loc>], loc_ret: &'a Loc>, ) -> (Expr, Output) { let (unsorted, mut scope, 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, &mut 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 (can_defs, output) = sort_can_defs(env, unsorted, output); match can_defs { Ok(decls) => { let mut loc_expr: Loc = ret_expr; for declaration in decls.into_iter().rev() { loc_expr = Loc { region: Region::zero(), value: decl_to_let(var_store, declaration, loc_expr), }; } (loc_expr.value, output) } Err(err) => (RuntimeError(err), output), } } fn decl_to_let(var_store: &mut VarStore, decl: Declaration, loc_ret: Loc) -> Expr { match decl { Declaration::Declare(def) => { Expr::LetNonRec(Box::new(def), Box::new(loc_ret), var_store.fresh()) } Declaration::DeclareRec(defs) => Expr::LetRec(defs, Box::new(loc_ret), var_store.fresh()), Declaration::InvalidCycle(entries) => { Expr::RuntimeError(RuntimeError::CircularDef(entries)) } Declaration::Builtin(_) => { // Builtins should only be added to top-level decls, not to let-exprs! unreachable!() } } } 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, } | Opaque { header: TypeHeader { name, vars }, typ: ann, } => { let (kind, shadow_kind) = if matches!(def, Alias { .. }) { (AliasKind::Structural, ShadowKind::Alias) } else { (AliasKind::Opaque, ShadowKind::Opaque) }; let region = Region::span_across(&name.region, &ann.region); match scope.introduce( name.value.into(), &env.exposed_ident_ids, &mut env.ident_ids, 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, }; PendingTypeDef::Alias { name, vars: can_rigids, ann, kind, } } Err((original_region, loc_shadowed_symbol, new_symbol)) => { env.problem(Problem::Shadowing { original_region, shadow: loc_shadowed_symbol, kind: shadow_kind, }); PendingTypeDef::InvalidAlias { kind, symbol: new_symbol, region, } } } } 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( name.value.into(), &env.exposed_ident_ids, &mut env.ident_ids, 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, } } } } 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, ) -> Option> { 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, ); Some(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, ); Some(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, ); Some(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) None } } Expect(_condition) => todo!(), } } /// 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 group: Vec<_> = (0u32..capacity as u32).collect(); let sccs = matrix.strongly_connected_components(&group); // 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.0.clone(), Type::Variable(l.value.1))) .collect::>(); let made_recursive = make_tag_union_recursive_help( env, Loc::at(alias.header_region(), (alias_name, &alias_args)), 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>( env: &mut Env<'a>, recursive_alias: Loc<(Symbol, &[(Lowercase, Type)])>, region: Region, others: Vec, typ: &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 = args.iter().map(|(_, t)| t.clone()).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, .. } => { // try to make `actual` recursive make_tag_union_recursive_help( env, Loc::at_zero((symbol, type_arguments)), 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); } }