roc/compiler/can/src/def.rs

2197 lines
81 KiB
Rust

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, local_successors_with_duplicates, Output, Recursive};
use crate::pattern::{bindings_from_patterns, canonicalize_def_header_pattern, Pattern};
use crate::procedure::References;
use crate::scope::create_alias;
use crate::scope::Scope;
use roc_collections::all::ImSet;
use roc_collections::all::{default_hasher, ImEntry, ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::Lowercase;
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::collections::HashMap;
use std::fmt::Debug;
use ven_graph::{strongly_connected_components, topological_sort};
#[derive(Clone, Debug, PartialEq)]
pub struct Def {
pub loc_pattern: Loc<Pattern>,
pub loc_expr: Loc<Expr>,
pub expr_var: Variable,
pub pattern_vars: SendMap<Symbol, Variable>,
pub annotation: Option<Annotation>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Annotation {
pub signature: Type,
pub introduced_variables: IntroducedVariables,
pub aliases: SendMap<Symbol, Alias>,
pub region: Region,
}
#[derive(Debug)]
pub struct CanDefs {
pub refs_by_symbol: MutMap<Symbol, (Region, References)>,
pub can_defs_by_symbol: MutMap<Symbol, Def>,
pub aliases: SendMap<Symbol, Alias>,
}
/// 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, PartialEq)]
enum PendingValueDef<'a> {
/// A standalone annotation with no body
AnnotationOnly(
&'a Loc<ast::Pattern<'a>>,
Loc<Pattern>,
&'a Loc<ast::TypeAnnotation<'a>>,
),
/// A body with no type annotation
Body(
&'a Loc<ast::Pattern<'a>>,
Loc<Pattern>,
&'a Loc<ast::Expr<'a>>,
),
/// A body with a type annotation
TypedBody(
&'a Loc<ast::Pattern<'a>>,
Loc<Pattern>,
&'a Loc<ast::TypeAnnotation<'a>>,
&'a Loc<ast::Expr<'a>>,
),
}
#[derive(Debug, Clone, PartialEq)]
enum PendingTypeDef<'a> {
/// A structural or opaque type alias, e.g. `Ints : List Int` or `Age := U32` respectively.
Alias {
name: Loc<Symbol>,
vars: Vec<Loc<Lowercase>>,
ann: &'a Loc<ast::TypeAnnotation<'a>>,
kind: AliasKind,
},
Ability {
name: Loc<Symbol>,
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 { kind: AliasKind },
/// An invalid ability, that is ignored in the rest of the pipeline.
/// E.g. a shadowed ability, or with a bad definition.
InvalidAbility,
}
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum Declaration {
Declare(Def),
DeclareRec(Vec<Def>),
Builtin(Def),
InvalidCycle(Vec<CycleEntry>),
}
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(
mut referenced_symbols: MutMap<Symbol, Vec<Symbol>>,
) -> Vec<Symbol> {
let defined_symbols: Vec<Symbol> = referenced_symbols.keys().copied().collect();
// find the strongly connected components and their relations
let sccs = {
// only retain symbols from the current set of defined symbols; the rest come from other modules
for v in referenced_symbols.iter_mut() {
v.1.retain(|x| defined_symbols.iter().any(|s| s == x));
}
let all_successors_with_self = |symbol: &Symbol| referenced_symbols[symbol].iter().copied();
strongly_connected_components(&defined_symbols, all_successors_with_self)
};
// then sort the strongly connected components
let groups: Vec<_> = (0..sccs.len()).collect();
let mut group_symbols: Vec<Vec<Symbol>> = vec![Vec::new(); groups.len()];
let mut symbol_to_group_index = MutMap::default();
let mut group_to_groups = vec![Vec::new(); groups.len()];
for (index, group) in sccs.iter().enumerate() {
for s in group {
symbol_to_group_index.insert(*s, index);
}
}
for (index, group) in sccs.iter().enumerate() {
for s in group {
let reachable = &referenced_symbols[s];
for r in reachable {
let new_index = symbol_to_group_index[r];
if new_index != index {
group_to_groups[index].push(new_index);
}
}
}
}
for v in group_symbols.iter_mut() {
v.sort();
v.dedup();
}
let all_successors_with_self = |group: &usize| group_to_groups[*group].iter().copied();
// split into self-recursive and mutually recursive
match topological_sort(&groups, all_successors_with_self) {
Ok(result) => result
.iter()
.rev()
.flat_map(|group_index| sccs[*group_index].iter())
.copied()
.collect(),
Err(_loop_detected) => unreachable!("the groups cannot recurse"),
}
}
#[inline(always)]
pub fn canonicalize_defs<'a>(
env: &mut Env<'a>,
mut output: Output,
var_store: &mut VarStore,
original_scope: &Scope,
loc_defs: &'a [&'a Loc<ast::Def<'a>>],
pattern_type: PatternType,
) -> (CanDefs, Scope, Output, MutMap<Symbol, Region>) {
// 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.
// Record both the original and final idents from the scope,
// so we can diff them while detecting unused defs.
let mut scope = original_scope.clone();
let num_defs = loc_defs.len();
let mut refs_by_symbol = MutMap::default();
let mut can_defs_by_symbol = HashMap::with_capacity_and_hasher(num_defs, default_hasher());
let mut 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) => type_defs.push(Loc::at(loc_def.region, type_def)),
Err(value_def) => value_defs.push(Loc::at(loc_def.region, value_def)),
}
}
// We need to canonicalize all the type defs first.
// Clippy is wrong - we do need the collect, otherwise "env" and "scope" are captured for
// longer than we'd like.
#[allow(clippy::needless_collect)]
let pending_type_defs = type_defs
.into_iter()
.filter_map(|loc_def| {
to_pending_type_def(env, loc_def.value, &mut scope, pattern_type).map(
|(new_output, pending_def)| {
output.union(new_output);
pending_def
},
)
})
.collect::<Vec<_>>();
if cfg!(debug_assertions) {
env.home.register_debug_idents(&env.ident_ids);
}
enum TypeDef<'a> {
AliasLike(
Loc<Symbol>,
Vec<Loc<Lowercase>>,
&'a Loc<ast::TypeAnnotation<'a>>,
AliasKind,
),
Ability(Loc<Symbol>, &'a [AbilityMember<'a>]),
}
let mut type_defs = MutMap::default();
let mut abilities_in_scope = Vec::new();
let mut referenced_type_symbols = MutMap::default();
for pending_def in pending_type_defs.into_iter() {
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 { .. } => { /* ignore */
}
}
}
let sorted = sort_type_defs_before_introduction(referenced_type_symbols);
let mut aliases = SendMap::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::<Vec<_>>();
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.type_lookups.insert(symbol);
output.references.referenced_type_defs.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = 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.clone());
}
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,
);
}
// Now we can go through and resolve all pending abilities, to add them to scope.
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,
&mut 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.type_lookups.insert(symbol);
output.references.referenced_type_defs.insert(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);
}
// 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() {
match to_pending_value_def(env, var_store, loc_def.value, &mut scope, pattern_type) {
None => { /* skip */ }
Some((new_output, pending_def)) => {
// store the top-level defs, used to ensure that closures won't capture them
if let PatternType::TopLevelDef = pattern_type {
match &pending_def {
PendingValueDef::AnnotationOnly(_, loc_can_pattern, _)
| PendingValueDef::Body(_, loc_can_pattern, _)
| PendingValueDef::TypedBody(_, loc_can_pattern, _, _) => {
env.top_level_symbols.extend(
bindings_from_patterns(std::iter::once(loc_can_pattern))
.iter()
.map(|t| t.0),
)
}
}
}
// 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);
}
}
}
for pending_def in pending_value_defs.into_iter() {
output = canonicalize_pending_value_def(
env,
pending_def,
output,
&mut scope,
&mut can_defs_by_symbol,
var_store,
&mut refs_by_symbol,
&mut aliases,
&abilities_in_scope,
);
// TODO we should do something with these references; they include
// things like type annotations.
}
// Determine which idents we introduced in the course of this process.
let mut symbols_introduced = MutMap::default();
for (symbol, region) in scope.symbols() {
if !original_scope.contains_symbol(*symbol) {
symbols_introduced.insert(*symbol, *region);
}
}
// 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.
//
// We have to return the scope separately from the defs, because the
// defs need to get moved later.
(
CanDefs {
refs_by_symbol,
can_defs_by_symbol,
// The result needs a thread-safe `SendMap`
aliases: aliases.into_iter().collect(),
},
scope,
output,
symbols_introduced,
)
}
#[inline(always)]
pub fn sort_can_defs(
env: &mut Env<'_>,
defs: CanDefs,
mut output: Output,
) -> (Result<Vec<Declaration>, RuntimeError>, Output) {
let CanDefs {
refs_by_symbol,
mut can_defs_by_symbol,
aliases,
} = defs;
for (symbol, alias) in aliases.into_iter() {
output.aliases.insert(symbol, alias);
}
let mut defined_symbols: Vec<Symbol> = Vec::new();
for symbol in can_defs_by_symbol.keys() {
defined_symbols.push(*symbol);
}
// Use topological sort to reorder the defs based on their dependencies to one another.
// This way, during code gen, no def will refer to a value that hasn't been initialized yet.
// As a bonus, the topological sort also reveals any cycles between the defs, allowing
// us to give a CircularAssignment error for invalid (mutual) recursion, and a `DeclareRec` for mutually
// recursive definitions.
// All successors that occur in the body of a symbol.
let all_successors_without_self = |symbol: &Symbol| -> Vec<Symbol> {
// This may not be in refs_by_symbol. For example, the `f` in `f x` here:
//
// f = \z -> z
//
// (\x ->
// a = f x
// x
// )
//
// It's not part of the current defs (the one with `a = f x`); rather,
// it's in the enclosing scope. It's still referenced though, so successors
// will receive it as an argument!
match refs_by_symbol.get(symbol) {
Some((_, references)) => {
// We can only sort the symbols at the current level. That is safe because
// symbols defined at higher levels cannot refer to symbols at lower levels.
// Therefore they can never form a cycle!
//
// In the above example, `f` cannot reference `a`, and in the closure
// a call to `f` cannot cycle back to `a`.
let mut loc_succ = local_successors_with_duplicates(references, &env.closures);
// if the current symbol is a closure, peek into its body
if let Some(References { value_lookups, .. }) = env.closures.get(symbol) {
let home = env.home;
for lookup in value_lookups.iter() {
if lookup != symbol && lookup.module_id() == home {
// DO NOT register a self-call behind a lambda!
//
// We allow `boom = \_ -> boom {}`, but not `x = x`
loc_succ.push(*lookup);
}
}
}
// remove anything that is not defined in the current block
loc_succ.retain(|key| defined_symbols.contains(key));
loc_succ.sort();
loc_succ.dedup();
loc_succ
}
None => vec![],
}
};
// All successors that occur in the body of a symbol, including the symbol itself
// This is required to determine whether a symbol is recursive. Recursive symbols
// (that are not faulty) always need a DeclareRec, even if there is just one symbol in the
// group
let mut all_successors_with_self = |symbol: &Symbol| -> Vec<Symbol> {
// This may not be in refs_by_symbol. For example, the `f` in `f x` here:
//
// f = \z -> z
//
// (\x ->
// a = f x
// x
// )
//
// It's not part of the current defs (the one with `a = f x`); rather,
// it's in the enclosing scope. It's still referenced though, so successors
// will receive it as an argument!
match refs_by_symbol.get(symbol) {
Some((_, references)) => {
// We can only sort the symbols at the current level. That is safe because
// symbols defined at higher levels cannot refer to symbols at lower levels.
// Therefore they can never form a cycle!
//
// In the above example, `f` cannot reference `a`, and in the closure
// a call to `f` cannot cycle back to `a`.
let mut loc_succ = local_successors_with_duplicates(references, &env.closures);
// if the current symbol is a closure, peek into its body
if let Some(References { value_lookups, .. }) = env.closures.get(symbol) {
for lookup in value_lookups.iter() {
loc_succ.push(*lookup);
}
}
// remove anything that is not defined in the current block
loc_succ.retain(|key| defined_symbols.contains(key));
loc_succ.sort();
loc_succ.dedup();
loc_succ
}
None => vec![],
}
};
// If a symbol is a direct successor of itself, there is an invalid cycle.
// The difference with the function above is that this one does not look behind lambdas,
// but does consider direct self-recursion.
let direct_successors = |symbol: &Symbol| -> Vec<Symbol> {
match refs_by_symbol.get(symbol) {
Some((_, references)) => {
let mut loc_succ = local_successors_with_duplicates(references, &env.closures);
// NOTE: if the symbol is a closure we DONT look into its body
// remove anything that is not defined in the current block
loc_succ.retain(|key| defined_symbols.contains(key));
// NOTE: direct recursion does matter here: `x = x` is invalid recursion!
loc_succ.sort();
loc_succ.dedup();
loc_succ
}
None => vec![],
}
};
// TODO also do the same `addDirects` check elm/compiler does, so we can
// report an error if a recursive definition can't possibly terminate!
match ven_graph::topological_sort_into_groups(
defined_symbols.as_slice(),
all_successors_without_self,
) {
Ok(groups) => {
let mut declarations = Vec::new();
// groups are in reversed order
for group in groups.into_iter().rev() {
group_to_declaration(
&group,
&env.closures,
&mut all_successors_with_self,
&mut can_defs_by_symbol,
&mut declarations,
);
}
(Ok(declarations), output)
}
Err((mut groups, nodes_in_cycle)) => {
let mut declarations = Vec::new();
let mut problems = Vec::new();
// nodes_in_cycle are symbols that form a syntactic cycle. That isn't always a problem,
// and in general it's impossible to decide whether it is. So we use a crude heuristic:
//
// Definitions where the cycle occurs behind a lambda are OK
//
// boom = \_ -> boom {}
//
// But otherwise we report an error, e.g.
//
// foo = if b then foo else bar
for cycle in strongly_connected_components(&nodes_in_cycle, all_successors_without_self)
{
// check whether the cycle is faulty, which is when it has
// a direct successor in the current cycle. This catches things like:
//
// x = x
//
// or
//
// p = q
// q = p
let is_invalid_cycle = match cycle.get(0) {
Some(symbol) => {
let mut succs = direct_successors(symbol);
succs.retain(|key| cycle.contains(key));
!succs.is_empty()
}
None => false,
};
if is_invalid_cycle {
// We want to show the entire cycle in the error message, so expand it out.
let mut entries = Vec::new();
for symbol in &cycle {
match refs_by_symbol.get(symbol) {
None => unreachable!(
r#"Symbol `{:?}` not found in refs_by_symbol! refs_by_symbol was: {:?}"#,
symbol, refs_by_symbol
),
Some((region, _)) => {
let expr_region =
can_defs_by_symbol.get(symbol).unwrap().loc_expr.region;
let entry = CycleEntry {
symbol: *symbol,
symbol_region: *region,
expr_region,
};
entries.push(entry);
}
}
}
// Sort them by line number to make the report more helpful.
entries.sort_by_key(|entry| entry.symbol_region);
problems.push(Problem::RuntimeError(RuntimeError::CircularDef(
entries.clone(),
)));
declarations.push(Declaration::InvalidCycle(entries));
}
// if it's an invalid cycle, other groups may depend on the
// symbols defined here, so also push this cycle onto the groups
//
// if it's not an invalid cycle, this is slightly inefficient,
// because we know this becomes exactly one DeclareRec already
groups.push(cycle);
}
// now we have a collection of groups whose dependencies are not cyclic.
// They are however not yet topologically sorted. Here we have to get a bit
// creative to get all the definitions in the correct sorted order.
let mut group_ids = Vec::with_capacity(groups.len());
let mut symbol_to_group_index = MutMap::default();
for (i, group) in groups.iter().enumerate() {
for symbol in group {
symbol_to_group_index.insert(*symbol, i);
}
group_ids.push(i);
}
let successors_of_group = |group_id: &usize| {
let mut result = MutSet::default();
// for each symbol in this group
for symbol in &groups[*group_id] {
// find its successors
for succ in all_successors_without_self(symbol) {
// and add its group to the result
match symbol_to_group_index.get(&succ) {
Some(index) => {
result.insert(*index);
}
None => unreachable!("no index for symbol {:?}", succ),
}
}
}
// don't introduce any cycles to self
result.remove(group_id);
result
};
match ven_graph::topological_sort_into_groups(&group_ids, successors_of_group) {
Ok(sorted_group_ids) => {
for sorted_group in sorted_group_ids.iter().rev() {
for group_id in sorted_group.iter().rev() {
let group = &groups[*group_id];
group_to_declaration(
group,
&env.closures,
&mut all_successors_with_self,
&mut can_defs_by_symbol,
&mut declarations,
);
}
}
}
Err(_) => unreachable!("there should be no cycles now!"),
}
for problem in problems {
env.problem(problem);
}
(Ok(declarations), output)
}
}
}
fn group_to_declaration(
group: &[Symbol],
closures: &MutMap<Symbol, References>,
successors: &mut dyn FnMut(&Symbol) -> Vec<Symbol>,
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
declarations: &mut Vec<Declaration>,
) {
use Declaration::*;
// We want only successors in the current group, otherwise definitions get duplicated
let filtered_successors = |symbol: &Symbol| -> Vec<Symbol> {
let mut result = successors(symbol);
result.retain(|key| group.contains(key));
result
};
// Patterns like
//
// { x, y } = someDef
//
// Can bind multiple symbols. When not incorrectly recursive (which is guaranteed in this function),
// normally `someDef` would be inserted twice. We use the region of the pattern as a unique key
// for a definition, so every definition is only inserted (thus typechecked and emitted) once
let mut seen_pattern_regions: Vec<Region> = Vec::with_capacity(2);
for cycle in strongly_connected_components(group, filtered_successors) {
if cycle.len() == 1 {
let symbol = &cycle[0];
match can_defs_by_symbol.remove(symbol) {
Some(mut new_def) => {
// Determine recursivity of closures that are not tail-recursive
if let Closure(ClosureData {
recursive: recursive @ Recursive::NotRecursive,
..
}) = &mut new_def.loc_expr.value
{
*recursive = closure_recursivity(*symbol, closures);
}
let is_recursive = successors(symbol).contains(symbol);
if !seen_pattern_regions.contains(&new_def.loc_pattern.region) {
seen_pattern_regions.push(new_def.loc_pattern.region);
if is_recursive {
declarations.push(DeclareRec(vec![new_def]));
} else {
declarations.push(Declare(new_def));
}
}
}
None => roc_error_macros::internal_error!("def not available {:?}", symbol),
}
} else {
let mut can_defs = Vec::new();
// Topological sort gives us the reverse of the sorting we want!
for symbol in cycle.into_iter().rev() {
match can_defs_by_symbol.remove(&symbol) {
Some(mut new_def) => {
// Determine recursivity of closures that are not tail-recursive
if let Closure(ClosureData {
recursive: recursive @ Recursive::NotRecursive,
..
}) = &mut new_def.loc_expr.value
{
*recursive = closure_recursivity(symbol, closures);
}
if !seen_pattern_regions.contains(&new_def.loc_pattern.region) {
seen_pattern_regions.push(new_def.loc_pattern.region);
can_defs.push(new_def);
}
}
None => roc_error_macros::internal_error!("def not available {:?}", symbol),
}
}
declarations.push(DeclareRec(can_defs));
}
}
}
fn pattern_to_vars_by_symbol(
vars_by_symbol: &mut SendMap<Symbol, Variable>,
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<Pattern>,
loc_can_expr: Loc<Expr>,
expr_var: Variable,
opt_loc_annotation: Option<Loc<crate::annotation::Annotation>>,
pattern_vars: SendMap<Symbol, Variable>,
) -> 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 ImMap<Symbol, Alias>,
) {
for (name, alias) in type_annotation.aliases.iter() {
match aliases.entry(*name) {
ImEntry::Occupied(_) => {
// do nothing
}
ImEntry::Vacant(vacant) => {
vacant.insert(alias.clone());
}
}
}
}
// 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,
can_defs_by_symbol: &mut MutMap<Symbol, Def>,
var_store: &mut VarStore,
refs_by_symbol: &mut MutMap<Symbol, (Region, References)>,
aliases: &mut ImMap<Symbol, Alias>,
abilities_in_scope: &[Symbol],
) -> Output {
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.type_lookups.insert(*symbol);
output.references.referenced_type_defs.insert(*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<Pattern> = 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,
}
};
if let Pattern::Identifier(symbol)
| Pattern::AbilityMemberSpecialization { ident: symbol, .. } = loc_can_pattern.value
{
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(),
);
can_defs_by_symbol.insert(symbol, def);
} else {
for (_, (symbol, _)) in scope.idents() {
if !vars_by_symbol.contains_key(symbol) {
continue;
}
// We could potentially avoid some clones here by using Rc strategically,
// but the total amount of cloning going on here should typically be minimal.
can_defs_by_symbol.insert(
*symbol,
Def {
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Loc {
region: loc_can_expr.region,
// TODO try to remove this .clone()!
value: loc_can_expr.value.clone(),
},
pattern_vars: vars_by_symbol.clone(),
annotation: Some(Annotation {
signature: type_annotation.typ.clone(),
introduced_variables: output.introduced_variables.clone(),
aliases: type_annotation.aliases.clone(),
region: loc_ann.region,
}),
},
);
}
}
}
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.type_lookups.insert(*symbol);
output.references.referenced_type_defs.insert(*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.
if let Pattern::Identifier(symbol)
| Pattern::AbilityMemberSpecialization { ident: symbol, .. } = loc_can_pattern.value
{
if let Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: ref closure_name,
ref arguments,
loc_body: ref body,
ref captured_symbols,
..
}) = loc_can_expr.value
{
// Since everywhere in the code it'll be referred to by its defined name,
// remove its generated name from the closure map. (We'll re-insert it later.)
let 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
)
});
// Re-insert the closure into the map, under its defined name.
// closures don't have a name, and therefore pick a fresh symbol. But in this
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
// and we want to reference it by that name.
env.closures.insert(symbol, references);
// 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,
};
// Recursion doesn't count as referencing. (If it did, all recursive functions
// would result in circular def errors!)
refs_by_symbol.entry(symbol).and_modify(|(_, refs)| {
refs.value_lookups.remove(&symbol);
});
// renamed_closure_def = Some(&symbol);
loc_can_expr.value = Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: symbol,
captured_symbols: captured_symbols.clone(),
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
});
// Functions' references don't count in defs.
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
// parent commit for the bug this fixed!
let refs = References::new();
refs_by_symbol.insert(symbol, (loc_can_pattern.region, refs));
} else {
let refs = can_output.references;
refs_by_symbol.insert(symbol, (loc_ann.region, refs));
}
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(),
);
can_defs_by_symbol.insert(symbol, def);
} else {
for (_, (symbol, region)) in scope.idents() {
if !vars_by_symbol.contains_key(symbol) {
continue;
}
let refs = can_output.references.clone();
refs_by_symbol.insert(*symbol, (*region, refs));
can_defs_by_symbol.insert(
*symbol,
Def {
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Loc {
region: loc_can_expr.region,
// TODO try to remove this .clone()!
value: loc_can_expr.value.clone(),
},
pattern_vars: vars_by_symbol.clone(),
annotation: Some(Annotation {
signature: type_annotation.typ.clone(),
introduced_variables: type_annotation.introduced_variables.clone(),
aliases: type_annotation.aliases.clone(),
region: loc_ann.region,
}),
},
);
}
}
}
// 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.
if let Pattern::Identifier(symbol) = loc_can_pattern.value {
if let Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: ref closure_name,
ref arguments,
loc_body: ref body,
ref captured_symbols,
..
}) = loc_can_expr.value
{
// Since everywhere in the code it'll be referred to by its defined name,
// remove its generated name from the closure map. (We'll re-insert it later.)
let 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
)
});
// Re-insert the closure into the map, under its defined name.
// closures don't have a name, and therefore pick a fresh symbol. But in this
// case, the closure has a proper name (e.g. `foo` in `foo = \x y -> ...`
// and we want to reference it by that name.
env.closures.insert(symbol, references);
// 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,
};
// Recursion doesn't count as referencing. (If it did, all recursive functions
// would result in circular def errors!)
refs_by_symbol.entry(symbol).and_modify(|(_, refs)| {
refs.value_lookups.remove(&symbol);
});
loc_can_expr.value = Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
return_type,
name: symbol,
captured_symbols: captured_symbols.clone(),
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
});
// Functions' references don't count in defs.
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
// parent commit for the bug this fixed!
let refs = References::new();
refs_by_symbol.insert(symbol, (loc_pattern.region, refs));
} else {
let refs = can_output.references.clone();
refs_by_symbol.insert(symbol, (loc_pattern.region, refs));
}
let def = single_can_def(
loc_can_pattern,
loc_can_expr,
expr_var,
None,
vars_by_symbol.clone(),
);
can_defs_by_symbol.insert(symbol, def);
} else {
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
// which defined names reference each other.
for (symbol, region) in bindings_from_patterns(std::iter::once(&loc_can_pattern)) {
let refs = can_output.references.clone();
refs_by_symbol.insert(symbol, (region, refs));
can_defs_by_symbol.insert(
symbol,
Def {
expr_var,
// TODO try to remove this .clone()!
loc_pattern: loc_can_pattern.clone(),
loc_expr: Loc {
// TODO try to remove this .clone()!
region: loc_can_expr.region,
value: loc_can_expr.value.clone(),
},
pattern_vars: vars_by_symbol.clone(),
annotation: None,
},
);
}
}
output.union(can_output);
}
};
output
}
#[inline(always)]
pub fn can_defs_with_return<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
scope: Scope,
loc_defs: &'a [&'a Loc<ast::Def<'a>>],
loc_ret: &'a Loc<ast::Expr<'a>>,
) -> (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_value_lookup(symbol)
&& !output.references.has_type_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<Expr> = 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>) -> 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 closure_recursivity(symbol: Symbol, closures: &MutMap<Symbol, References>) -> Recursive {
let mut visited = MutSet::default();
let mut stack = Vec::new();
if let Some(references) = closures.get(&symbol) {
for v in references.calls.iter() {
stack.push(*v);
}
// while there are symbols left to visit
while let Some(nested_symbol) = stack.pop() {
if nested_symbol == symbol {
return Recursive::Recursive;
}
// if the called symbol not yet in the graph
if !visited.contains(&nested_symbol) {
// add it to the visited set
// if it calls any functions
if let Some(nested_references) = closures.get(&nested_symbol) {
// add its called to the stack
for v in nested_references.calls.iter() {
stack.push(*v);
}
}
visited.insert(nested_symbol);
}
}
}
Recursive::NotRecursive
}
fn to_pending_type_def<'a>(
env: &mut Env<'a>,
def: &'a ast::TypeDef<'a>,
scope: &mut Scope,
pattern_type: PatternType,
) -> Option<(Output, 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<Loc<Lowercase>> = 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 Some((
Output::default(),
PendingTypeDef::InvalidAlias { kind },
));
}
}
}
let name = Loc {
region: name.region,
value: symbol,
};
let pending_def = PendingTypeDef::Alias {
name,
vars: can_rigids,
ann,
kind,
};
Some((Output::default(), pending_def))
}
Err((original_region, loc_shadowed_symbol, _new_symbol)) => {
env.problem(Problem::Shadowing {
original_region,
shadow: loc_shadowed_symbol,
kind: shadow_kind,
});
Some((Output::default(), PendingTypeDef::InvalidAlias { kind }))
}
}
}
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 });
Some((Output::default(), PendingTypeDef::InvalidAbility))
}
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 Some((Output::default(), PendingTypeDef::InvalidAbility));
}
};
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 Some((Output::default(), PendingTypeDef::InvalidAbility));
}
let pending_ability = PendingTypeDef::Ability {
name,
// We'll handle adding the member symbols later on when we do all value defs.
members,
};
Some((Output::default(), pending_ability))
}
}
}
fn to_pending_value_def<'a>(
env: &mut Env<'a>,
var_store: &mut VarStore,
def: &'a ast::ValueDef<'a>,
scope: &mut Scope,
pattern_type: PatternType,
) -> Option<(Output, PendingValueDef<'a>)> {
use ast::ValueDef::*;
match def {
Annotation(loc_pattern, loc_ann) => {
// This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = canonicalize_def_header_pattern(
env,
var_store,
scope,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
);
Some((
output,
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 (output, loc_can_pattern) = canonicalize_def_header_pattern(
env,
var_store,
scope,
pattern_type,
&loc_pattern.value,
loc_pattern.region,
);
Some((
output,
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 (output, loc_can_pattern) = canonicalize_def_header_pattern(
env,
var_store,
scope,
pattern_type,
&body_pattern.value,
body_pattern.region,
);
Some((
output,
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>,
mut original_aliases: SendMap<Symbol, Alias>,
var_store: &mut VarStore,
) -> ImMap<Symbol, Alias> {
let symbols_introduced: Vec<Symbol> = original_aliases.keys().copied().collect();
let all_successors_with_self = |symbol: &Symbol| -> Vec<Symbol> {
match original_aliases.get(symbol) {
Some(alias) => {
let mut loc_succ = alias.typ.symbols();
// remove anything that is not defined in the current block
loc_succ.retain(|key| symbols_introduced.contains(key));
loc_succ
}
None => vec![],
}
};
// TODO investigate should this be in a loop?
let defined_symbols: Vec<Symbol> = original_aliases.keys().copied().collect();
let cycles = strongly_connected_components(&defined_symbols, all_successors_with_self);
let mut solved_aliases = ImMap::default();
for cycle in cycles {
debug_assert!(!cycle.is_empty());
let mut pending_aliases: ImMap<_, _> = cycle
.iter()
.map(|&sym| (sym, original_aliases.remove(&sym).unwrap()))
.collect();
// 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;
// We need to instantiate the alias with any symbols in the currrent module it
// depends on.
// We only need to worry about symbols in this SCC or any prior one, since the SCCs
// were sorted topologically, and we've already instantiated aliases coming from other
// modules.
// NB: ImMap::clone is O(1): https://docs.rs/im/latest/src/im/hash/map.rs.html#1527-1544
let mut to_instantiate = solved_aliases.clone().union(pending_aliases.clone());
for &rec in cycle.iter() {
let alias = pending_aliases.get_mut(&rec).unwrap();
// Don't try to instantiate the alias itself in its definition.
let original_alias_def = to_instantiate.remove(&rec).unwrap();
let mut new_lambda_sets = ImSet::default();
alias.typ.instantiate_aliases(
alias.region,
&to_instantiate,
var_store,
&mut new_lambda_sets,
);
for lambda_set_var in new_lambda_sets {
alias
.lambda_set_variables
.push(LambdaSet(Type::Variable(lambda_set_var)));
}
to_instantiate.insert(rec, original_alias_def);
// Now mark the alias recursive, if it needs to be.
let is_self_recursive = alias.typ.contains_symbol(rec);
let is_mutually_recursive = cycle.len() > 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 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().all(|sym| {
let typ = &pending_aliases.get(sym).unwrap().typ;
matches!(typ, Type::RecursiveTagUnion(..)) && typ.is_narrow()
});
if all_are_narrow {
// This cycle is illegal!
let mut rest = cycle;
let alias_name = rest.pop().unwrap();
let alias = pending_aliases.get_mut(&alias_name).unwrap();
mark_cyclic_alias(
env,
&mut alias.typ,
alias_name,
alias.region,
rest,
can_still_report_error,
)
}
// Now, promote all resolved aliases in this cycle as solved.
solved_aliases.extend(pending_aliases);
}
solved_aliases
}
fn make_tag_union_of_alias_recursive<'a>(
env: &mut Env<'a>,
alias_name: Symbol,
alias: &mut Alias,
others: Vec<Symbol>,
var_store: &mut VarStore,
can_report_error: &mut bool,
) -> Result<(), ()> {
let alias_args = alias
.type_variables
.iter()
.map(|l| (l.value.0.clone(), Type::Variable(l.value.1)))
.collect::<Vec<_>>();
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_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<Symbol>,
typ: &mut Type,
var_store: &mut VarStore,
can_report_error: &mut bool,
) -> MakeTagUnionRecursive {
use MakeTagUnionRecursive::*;
let Loc {
value: (symbol, args),
region: alias_region,
} = recursive_alias;
let vars = args.iter().map(|(_, t)| t.clone()).collect::<Vec<_>>();
match typ {
Type::TagUnion(tags, ext) => {
let recursion_variable = var_store.fresh();
let mut pending_typ =
Type::RecursiveTagUnion(recursion_variable, tags.to_vec(), ext.clone());
let substitution_result =
pending_typ.substitute_alias(symbol, &vars, &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,
..
} => make_tag_union_recursive_help(
env,
Loc::at_zero((symbol, type_arguments)),
region,
others,
actual,
var_store,
can_report_error,
),
_ => {
mark_cyclic_alias(env, typ, symbol, region, others, *can_report_error);
*can_report_error = false;
Cyclic
}
}
}
fn mark_cyclic_alias<'a>(
env: &mut Env<'a>,
typ: &mut Type,
symbol: Symbol,
region: Region,
others: Vec<Symbol>,
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);
}
}