mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-02 11:22:19 +00:00
3231 lines
112 KiB
Rust
3231 lines
112 KiB
Rust
use crate::abilities::AbilityMemberData;
|
|
use crate::abilities::ImplKey;
|
|
use crate::abilities::MemberVariables;
|
|
use crate::abilities::PendingMemberType;
|
|
use crate::annotation::canonicalize_annotation;
|
|
use crate::annotation::find_type_def_symbols;
|
|
use crate::annotation::make_apply_symbol;
|
|
use crate::annotation::AnnotationFor;
|
|
use crate::annotation::IntroducedVariables;
|
|
use crate::annotation::OwnedNamedOrAble;
|
|
use crate::derive;
|
|
use crate::env::Env;
|
|
use crate::expr::get_lookup_symbols;
|
|
use crate::expr::AnnotatedMark;
|
|
use crate::expr::ClosureData;
|
|
use crate::expr::Declarations;
|
|
use crate::expr::Expr::{self, *};
|
|
use crate::expr::StructAccessorData;
|
|
use crate::expr::{canonicalize_expr, Output, Recursive};
|
|
use crate::pattern::{canonicalize_def_header_pattern, BindingsFromPattern, Pattern};
|
|
use crate::procedure::References;
|
|
use crate::scope::create_alias;
|
|
use crate::scope::{PendingAbilitiesInScope, Scope};
|
|
use roc_collections::ReferenceMatrix;
|
|
use roc_collections::VecMap;
|
|
use roc_collections::VecSet;
|
|
use roc_collections::{ImSet, MutMap, SendMap};
|
|
use roc_error_macros::internal_error;
|
|
use roc_module::ident::Ident;
|
|
use roc_module::ident::Lowercase;
|
|
use roc_module::symbol::IdentId;
|
|
use roc_module::symbol::ModuleId;
|
|
use roc_module::symbol::Symbol;
|
|
use roc_parse::ast;
|
|
use roc_parse::ast::AssignedField;
|
|
use roc_parse::ast::Defs;
|
|
use roc_parse::ast::ExtractSpaces;
|
|
use roc_parse::ast::TypeHeader;
|
|
use roc_parse::ident::Accessor;
|
|
use roc_parse::pattern::PatternType;
|
|
use roc_problem::can::ShadowKind;
|
|
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
|
|
use roc_region::all::{Loc, Region};
|
|
use roc_types::subs::IllegalCycleMark;
|
|
use roc_types::subs::{VarStore, Variable};
|
|
use roc_types::types::AliasCommon;
|
|
use roc_types::types::AliasKind;
|
|
use roc_types::types::AliasVar;
|
|
use roc_types::types::IndexOrField;
|
|
use roc_types::types::LambdaSet;
|
|
use roc_types::types::MemberImpl;
|
|
use roc_types::types::OptAbleType;
|
|
use roc_types::types::{Alias, Type};
|
|
use std::fmt::Debug;
|
|
|
|
#[derive(Clone, Debug)]
|
|
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>,
|
|
}
|
|
|
|
impl Def {
|
|
pub fn region(&self) -> Region {
|
|
let head_region = match &self.annotation {
|
|
Some(ann) => {
|
|
if ann.region.start() < self.loc_pattern.region.start() {
|
|
ann.region
|
|
} else {
|
|
// Happens with annotation-only bodies like foo : T, since `T` is after the
|
|
// pattern.
|
|
self.loc_pattern.region
|
|
}
|
|
}
|
|
None => self.loc_pattern.region,
|
|
};
|
|
Region::span_across(&head_region, &self.loc_expr.region)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Annotation {
|
|
pub signature: Type,
|
|
pub introduced_variables: IntroducedVariables,
|
|
pub aliases: VecMap<Symbol, Alias>,
|
|
pub region: Region,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct CanDefs {
|
|
defs: Vec<Option<Def>>,
|
|
dbgs: ExpectsOrDbgs,
|
|
expects: ExpectsOrDbgs,
|
|
expects_fx: ExpectsOrDbgs,
|
|
def_ordering: DefOrdering,
|
|
aliases: VecMap<Symbol, Alias>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct ExpectsOrDbgs {
|
|
pub conditions: Vec<Expr>,
|
|
pub regions: Vec<Region>,
|
|
pub preceding_comment: Vec<Region>,
|
|
}
|
|
|
|
impl ExpectsOrDbgs {
|
|
fn with_capacity(capacity: usize) -> Self {
|
|
Self {
|
|
conditions: Vec::with_capacity(capacity),
|
|
regions: Vec::with_capacity(capacity),
|
|
preceding_comment: Vec::with_capacity(capacity),
|
|
}
|
|
}
|
|
|
|
fn push(&mut self, loc_can_condition: Loc<Expr>, preceding_comment: Region) {
|
|
self.conditions.push(loc_can_condition.value);
|
|
self.regions.push(loc_can_condition.region);
|
|
self.preceding_comment.push(preceding_comment);
|
|
}
|
|
}
|
|
|
|
/// 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<ast::Pattern<'a>>,
|
|
Loc<Pattern>,
|
|
&'a Loc<ast::TypeAnnotation<'a>>,
|
|
),
|
|
/// A body with no type annotation
|
|
Body(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>>,
|
|
),
|
|
}
|
|
|
|
impl PendingValueDef<'_> {
|
|
fn loc_pattern(&self) -> &Loc<Pattern> {
|
|
match self {
|
|
PendingValueDef::AnnotationOnly(_, loc_pattern, _) => loc_pattern,
|
|
PendingValueDef::Body(loc_pattern, _) => loc_pattern,
|
|
PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
struct PendingAbilityMember<'a> {
|
|
name: Loc<Symbol>,
|
|
typ: Loc<ast::TypeAnnotation<'a>>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum PendingTypeDef<'a> {
|
|
/// A structural type alias, e.g. `Ints : List Int`
|
|
Alias {
|
|
name: Loc<Symbol>,
|
|
vars: Vec<Loc<Lowercase>>,
|
|
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
|
},
|
|
|
|
/// An opaque type alias, e.g. `Age := U32`.
|
|
Opaque {
|
|
name_str: &'a str,
|
|
name: Loc<Symbol>,
|
|
vars: Vec<Loc<Lowercase>>,
|
|
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
|
derived: Option<&'a Loc<ast::ImplementsAbilities<'a>>>,
|
|
},
|
|
|
|
Ability {
|
|
name: Loc<Symbol>,
|
|
members: Vec<PendingAbilityMember<'a>>,
|
|
},
|
|
|
|
/// An invalid alias, that is ignored in the rest of the pipeline
|
|
/// e.g. a definition like `MyAlias 1 : Int`
|
|
/// with an incorrect pattern
|
|
InvalidAlias {
|
|
#[allow(dead_code)]
|
|
kind: AliasKind,
|
|
symbol: Symbol,
|
|
region: Region,
|
|
},
|
|
|
|
/// An alias with a name that shadows another symbol
|
|
ShadowedAlias,
|
|
|
|
/// An invalid ability, that is ignored in the rest of the pipeline.
|
|
/// E.g. a shadowed ability, or with a bad definition.
|
|
InvalidAbility {
|
|
symbol: Symbol,
|
|
region: Region,
|
|
},
|
|
|
|
AbilityNotOnToplevel,
|
|
AbilityShadows,
|
|
}
|
|
|
|
impl PendingTypeDef<'_> {
|
|
fn introduction(&self) -> Option<(Symbol, Region)> {
|
|
match self {
|
|
PendingTypeDef::Alias { name, ann, .. } => {
|
|
let region = Region::span_across(&name.region, &ann.region);
|
|
|
|
Some((name.value, region))
|
|
}
|
|
PendingTypeDef::Opaque {
|
|
name_str: _,
|
|
name,
|
|
vars: _,
|
|
ann,
|
|
derived,
|
|
} => {
|
|
let end = derived.map(|d| d.region).unwrap_or(ann.region);
|
|
let region = Region::span_across(&name.region, &end);
|
|
|
|
Some((name.value, region))
|
|
}
|
|
PendingTypeDef::Ability { name, .. } => Some((name.value, name.region)),
|
|
PendingTypeDef::InvalidAlias { symbol, region, .. } => Some((*symbol, *region)),
|
|
PendingTypeDef::ShadowedAlias { .. } => None,
|
|
PendingTypeDef::InvalidAbility { symbol, region } => Some((*symbol, *region)),
|
|
PendingTypeDef::AbilityNotOnToplevel => None,
|
|
PendingTypeDef::AbilityShadows => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
// See github.com/roc-lang/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<Def>, IllegalCycleMark),
|
|
Builtin(Def),
|
|
Expects(ExpectsOrDbgs),
|
|
ExpectsFx(ExpectsOrDbgs),
|
|
/// If we know a cycle is illegal during canonicalization.
|
|
/// Otherwise we will try to detect this during solving; see [`IllegalCycleMark`].
|
|
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,
|
|
Expects(_) => 0,
|
|
ExpectsFx(_) => 0,
|
|
}
|
|
}
|
|
|
|
pub fn region(&self) -> Region {
|
|
match self {
|
|
Declaration::Declare(def) => def.region(),
|
|
Declaration::DeclareRec(defs, _) => Region::span_across(
|
|
&defs.first().unwrap().region(),
|
|
&defs.last().unwrap().region(),
|
|
),
|
|
Declaration::Builtin(def) => def.region(),
|
|
Declaration::InvalidCycle(cycles) => Region::span_across(
|
|
&cycles.first().unwrap().expr_region,
|
|
&cycles.last().unwrap().expr_region,
|
|
),
|
|
Declaration::Expects(expects) | Declaration::ExpectsFx(expects) => Region::span_across(
|
|
expects.regions.first().unwrap(),
|
|
expects.regions.last().unwrap(),
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Returns a topologically sorted sequence of alias/opaque names
|
|
fn sort_type_defs_before_introduction(
|
|
referenced_symbols: VecMap<Symbol, Vec<Symbol>>,
|
|
) -> Vec<Symbol> {
|
|
let capacity = referenced_symbols.len();
|
|
let mut matrix = ReferenceMatrix::new(capacity);
|
|
|
|
let (symbols, referenced) = referenced_symbols.unzip();
|
|
|
|
for (index, references) in referenced.iter().enumerate() {
|
|
for referenced in references {
|
|
match symbols.iter().position(|k| k == referenced) {
|
|
None => { /* not defined in this scope */ }
|
|
Some(ref_index) => matrix.set_row_col(index, ref_index, true),
|
|
}
|
|
}
|
|
}
|
|
|
|
// find the strongly connected components and their relations
|
|
matrix
|
|
.strongly_connected_components_all()
|
|
.groups()
|
|
.flat_map(|(group, _)| group.iter_ones())
|
|
.map(|index| symbols[index])
|
|
.collect()
|
|
}
|
|
|
|
#[inline(always)]
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn canonicalize_alias<'a>(
|
|
env: &mut Env<'a>,
|
|
output: &mut Output,
|
|
var_store: &mut VarStore,
|
|
scope: &mut Scope,
|
|
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
|
|
|
name: Loc<Symbol>,
|
|
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
|
vars: &[Loc<Lowercase>],
|
|
kind: AliasKind,
|
|
) -> Result<Alias, ()> {
|
|
let symbol = name.value;
|
|
let annotation_for = match kind {
|
|
AliasKind::Structural => AnnotationFor::Alias,
|
|
AliasKind::Opaque => AnnotationFor::Opaque,
|
|
};
|
|
let can_ann = canonicalize_annotation(
|
|
env,
|
|
scope,
|
|
&ann.value,
|
|
ann.region,
|
|
var_store,
|
|
pending_abilities_in_scope,
|
|
annotation_for,
|
|
);
|
|
|
|
// 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<Loc<AliasVar>> = Vec::with_capacity(vars.len());
|
|
let mut is_phantom = false;
|
|
|
|
let IntroducedVariables {
|
|
named,
|
|
able,
|
|
wildcards,
|
|
inferred,
|
|
infer_ext_in_output,
|
|
..
|
|
} = can_ann.introduced_variables;
|
|
|
|
let mut named: Vec<_> = (named.into_iter().map(OwnedNamedOrAble::Named))
|
|
.chain(able.into_iter().map(OwnedNamedOrAble::Able))
|
|
.collect();
|
|
for loc_lowercase in vars.iter() {
|
|
let opt_index = named
|
|
.iter()
|
|
.position(|nv| nv.ref_name() == &loc_lowercase.value);
|
|
match opt_index {
|
|
Some(index) => {
|
|
// This is a valid lowercase rigid var for the type def.
|
|
let named_variable = named.swap_remove(index);
|
|
let var = named_variable.variable();
|
|
let opt_bound_abilities = named_variable.opt_abilities().map(ToOwned::to_owned);
|
|
let name = named_variable.name();
|
|
|
|
can_vars.push(Loc {
|
|
value: AliasVar {
|
|
name,
|
|
var,
|
|
opt_bound_abilities,
|
|
},
|
|
region: loc_lowercase.region,
|
|
});
|
|
}
|
|
None => match kind {
|
|
AliasKind::Structural => {
|
|
is_phantom = true;
|
|
|
|
env.problems.push(Problem::PhantomTypeArgument {
|
|
typ: symbol,
|
|
variable_region: loc_lowercase.region,
|
|
variable_name: loc_lowercase.value.clone(),
|
|
alias_kind: AliasKind::Structural,
|
|
});
|
|
}
|
|
AliasKind::Opaque => {
|
|
// Opaques can have phantom types.
|
|
can_vars.push(Loc {
|
|
value: AliasVar {
|
|
name: loc_lowercase.value.clone(),
|
|
var: var_store.fresh(),
|
|
opt_bound_abilities: None,
|
|
},
|
|
region: loc_lowercase.region,
|
|
});
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
if is_phantom {
|
|
// Bail out
|
|
return Err(());
|
|
}
|
|
|
|
let num_unbound = named.len() + wildcards.len() + inferred.len();
|
|
if num_unbound > 0 {
|
|
let one_occurrence = named
|
|
.iter()
|
|
.map(|nv| Loc::at(nv.first_seen(), nv.variable()))
|
|
.chain(wildcards)
|
|
.chain(inferred)
|
|
.next()
|
|
.unwrap()
|
|
.region;
|
|
|
|
env.problems.push(Problem::UnboundTypeVariable {
|
|
typ: symbol,
|
|
num_unbound,
|
|
one_occurrence,
|
|
kind,
|
|
});
|
|
|
|
// Bail out
|
|
return Err(());
|
|
}
|
|
|
|
Ok(create_alias(
|
|
symbol,
|
|
name.region,
|
|
can_vars.clone(),
|
|
infer_ext_in_output,
|
|
can_ann.typ,
|
|
kind,
|
|
))
|
|
}
|
|
|
|
/// Canonicalizes a claimed ability implementation like `{ eq }` or `{ eq: myEq }`.
|
|
/// Returns a mapping of the ability member to the implementation symbol.
|
|
/// If there was an error, a problem will be recorded and nothing is returned.
|
|
fn canonicalize_claimed_ability_impl<'a>(
|
|
env: &mut Env<'a>,
|
|
scope: &mut Scope,
|
|
ability: Symbol,
|
|
loc_impl: &Loc<ast::AssignedField<'a, ast::Expr<'a>>>,
|
|
) -> Result<(Symbol, Symbol), ()> {
|
|
let ability_home = ability.module_id();
|
|
|
|
match loc_impl.extract_spaces().item {
|
|
AssignedField::LabelOnly(label) => {
|
|
let label_str = label.value;
|
|
let region = label.region;
|
|
|
|
let member_symbol =
|
|
match env.qualified_lookup_with_module_id(scope, ability_home, label_str, region) {
|
|
Ok(symbol) => symbol,
|
|
Err(_) => {
|
|
env.problem(Problem::NotAnAbilityMember {
|
|
ability,
|
|
name: label_str.to_owned(),
|
|
region,
|
|
});
|
|
|
|
return Err(());
|
|
}
|
|
};
|
|
|
|
// There are two options for how the implementation symbol is defined.
|
|
//
|
|
// OPTION-1: The implementation identifier is the only identifier of that name in the
|
|
// scope. For example,
|
|
//
|
|
// interface F imports [] exposes []
|
|
//
|
|
// Hello := {} has [Encoding.{ toEncoder }]
|
|
//
|
|
// toEncoder = \@Hello {} -> ...
|
|
//
|
|
// In this case, we just do a simple lookup in the scope to find our
|
|
// `toEncoder` impl.
|
|
//
|
|
// OPTION-2: The implementation identifier is a unique shadow of the ability member,
|
|
// which has also been explicitly imported. For example,
|
|
//
|
|
// interface F imports [Encoding.{ toEncoder }] exposes []
|
|
//
|
|
// Hello := {} has [Encoding.{ toEncoder }]
|
|
//
|
|
// toEncoder = \@Hello {} -> ...
|
|
//
|
|
// In this case, we allow the `F` module's `toEncoder` def to shadow
|
|
// `toEncoder` only to define this specialization's `toEncoder`.
|
|
//
|
|
// To handle both cases, try checking for a shadow first, then check for a direct
|
|
// reference. We want to check for a direct reference second so that if there is a
|
|
// shadow, we won't accidentally grab the imported symbol.
|
|
let opt_impl_symbol = (scope.lookup_ability_member_shadow(member_symbol))
|
|
.or_else(|| scope.lookup_str(label_str, region).ok());
|
|
|
|
match opt_impl_symbol {
|
|
// It's possible that even if we find a symbol it is still only the member
|
|
// definition symbol, for example when the ability is defined in the same
|
|
// module as an implementer:
|
|
//
|
|
// Eq has eq : a, a -> U64 | a has Eq
|
|
//
|
|
// A := U8 has [Eq {eq}]
|
|
//
|
|
// So, do a final check that the implementation symbol is not resolved directly
|
|
// to the member.
|
|
Some(impl_symbol) if impl_symbol != member_symbol => {
|
|
Ok((member_symbol, impl_symbol))
|
|
}
|
|
_ => {
|
|
env.problem(Problem::ImplementationNotFound {
|
|
member: member_symbol,
|
|
region: label.region,
|
|
});
|
|
Err(())
|
|
}
|
|
}
|
|
}
|
|
AssignedField::RequiredValue(label, _spaces, value) => {
|
|
let impl_ident = match value.value {
|
|
ast::Expr::Var { module_name, ident } => {
|
|
if module_name.is_empty() {
|
|
ident
|
|
} else {
|
|
env.problem(Problem::QualifiedAbilityImpl {
|
|
region: value.region,
|
|
});
|
|
return Err(());
|
|
}
|
|
}
|
|
_ => {
|
|
env.problem(Problem::AbilityImplNotIdent {
|
|
region: value.region,
|
|
});
|
|
return Err(());
|
|
}
|
|
};
|
|
let impl_region = value.region;
|
|
|
|
let member_symbol = match env.qualified_lookup_with_module_id(
|
|
scope,
|
|
ability_home,
|
|
label.value,
|
|
label.region,
|
|
) {
|
|
Ok(symbol) => symbol,
|
|
Err(_) => {
|
|
env.problem(Problem::NotAnAbilityMember {
|
|
ability,
|
|
name: label.value.to_owned(),
|
|
region: label.region,
|
|
});
|
|
return Err(());
|
|
}
|
|
};
|
|
|
|
let impl_symbol = match scope.lookup(&impl_ident.into(), impl_region) {
|
|
Ok(symbol) => symbol,
|
|
Err(err) => {
|
|
env.problem(Problem::RuntimeError(err));
|
|
return Err(());
|
|
}
|
|
};
|
|
|
|
Ok((member_symbol, impl_symbol))
|
|
}
|
|
AssignedField::OptionalValue(_, _, _) => {
|
|
env.problem(Problem::OptionalAbilityImpl {
|
|
ability,
|
|
region: loc_impl.region,
|
|
});
|
|
Err(())
|
|
}
|
|
AssignedField::Malformed(_) => {
|
|
// An error will already have been reported
|
|
Err(())
|
|
}
|
|
AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => {
|
|
internal_error!("unreachable")
|
|
}
|
|
}
|
|
}
|
|
|
|
struct SeparatedMembers {
|
|
not_required: Vec<Symbol>,
|
|
not_implemented: Vec<Symbol>,
|
|
}
|
|
|
|
/// Partitions ability members in a `has [ Ability {...members} ]` clause into the members the
|
|
/// opaque type claims to implement but are not part of the ability, and the ones it does not
|
|
/// implement.
|
|
fn separate_implemented_and_required_members(
|
|
implemented: VecSet<Symbol>,
|
|
required: VecSet<Symbol>,
|
|
) -> SeparatedMembers {
|
|
use std::cmp::Ordering;
|
|
|
|
let mut implemented = implemented.into_vec();
|
|
let mut required = required.into_vec();
|
|
|
|
implemented.sort();
|
|
required.sort();
|
|
|
|
let mut implemented = implemented.into_iter().peekable();
|
|
let mut required = required.into_iter().peekable();
|
|
|
|
let mut not_required = vec![];
|
|
let mut not_implemented = vec![];
|
|
|
|
loop {
|
|
// Equal => both required and implemented
|
|
// Less => implemented but not required
|
|
// Greater => required but not implemented
|
|
|
|
let ord = match (implemented.peek(), required.peek()) {
|
|
(Some(implemented), Some(required)) => Some(implemented.cmp(required)),
|
|
(Some(_), None) => Some(Ordering::Less),
|
|
(None, Some(_)) => Some(Ordering::Greater),
|
|
(None, None) => None,
|
|
};
|
|
|
|
match ord {
|
|
Some(Ordering::Less) => {
|
|
not_required.push(implemented.next().unwrap());
|
|
}
|
|
Some(Ordering::Greater) => {
|
|
not_implemented.push(required.next().unwrap());
|
|
}
|
|
Some(Ordering::Equal) => {
|
|
_ = implemented.next().unwrap();
|
|
_ = required.next().unwrap();
|
|
}
|
|
None => break,
|
|
}
|
|
}
|
|
|
|
SeparatedMembers {
|
|
not_required,
|
|
not_implemented,
|
|
}
|
|
}
|
|
|
|
type DerivedDef<'a> = Loc<PendingValue<'a>>;
|
|
|
|
struct CanonicalizedOpaque<'a> {
|
|
opaque_def: Alias,
|
|
derived_defs: Vec<DerivedDef<'a>>,
|
|
}
|
|
|
|
#[inline(always)]
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn canonicalize_opaque<'a>(
|
|
env: &mut Env<'a>,
|
|
output: &mut Output,
|
|
var_store: &mut VarStore,
|
|
scope: &mut Scope,
|
|
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
|
|
|
name: Loc<Symbol>,
|
|
name_str: &'a str,
|
|
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
|
vars: &[Loc<Lowercase>],
|
|
has_abilities: Option<&'a Loc<ast::ImplementsAbilities<'a>>>,
|
|
) -> Result<CanonicalizedOpaque<'a>, ()> {
|
|
let alias = canonicalize_alias(
|
|
env,
|
|
output,
|
|
var_store,
|
|
scope,
|
|
pending_abilities_in_scope,
|
|
name,
|
|
ann,
|
|
vars,
|
|
AliasKind::Opaque,
|
|
)?;
|
|
|
|
let mut derived_defs = Vec::new();
|
|
if let Some(has_abilities) = has_abilities {
|
|
let has_abilities = has_abilities.value.collection();
|
|
|
|
let mut derived_abilities = vec![];
|
|
|
|
for has_ability in has_abilities.items {
|
|
let region = has_ability.region;
|
|
let (ability, opt_impls) = match has_ability.value.extract_spaces().item {
|
|
ast::ImplementsAbility::ImplementsAbility { ability, impls } => (ability, impls),
|
|
_ => internal_error!("spaces not extracted"),
|
|
};
|
|
|
|
let ability_region = ability.region;
|
|
|
|
let (ability, members) = match ability.value {
|
|
ast::TypeAnnotation::Apply(module_name, ident, []) => {
|
|
match make_apply_symbol(env, region, scope, module_name, ident) {
|
|
Ok(ability) => {
|
|
let opt_members = scope
|
|
.abilities_store
|
|
.members_of_ability(ability)
|
|
.map(|members| members.iter().copied().collect())
|
|
.or_else(|| pending_abilities_in_scope.get(&ability).cloned());
|
|
|
|
if let Some(members) = opt_members {
|
|
// This is an ability we already imported into the scope,
|
|
// or which is also undergoing canonicalization at the moment.
|
|
(ability, members)
|
|
} else {
|
|
env.problem(Problem::NotAnAbility(ability_region));
|
|
continue;
|
|
}
|
|
}
|
|
Err(_) => {
|
|
// This is bad apply; an error will have been reported for it
|
|
// already.
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
// Register the problem but keep going.
|
|
env.problem(Problem::NotAnAbility(ability_region));
|
|
continue;
|
|
}
|
|
};
|
|
|
|
if let Some(impls) = opt_impls {
|
|
let mut impl_map: VecMap<Symbol, Loc<MemberImpl>> = VecMap::default();
|
|
|
|
// First up canonicalize all the claimed implementations, building a map of ability
|
|
// member -> implementation.
|
|
for loc_impl in impls.extract_spaces().item.items {
|
|
let (member, impl_symbol) =
|
|
match canonicalize_claimed_ability_impl(env, scope, ability, loc_impl) {
|
|
Ok((member, impl_symbol)) => (member, impl_symbol),
|
|
Err(()) => continue,
|
|
};
|
|
|
|
// Did the user claim this implementation for a specialization of a different
|
|
// type? e.g.
|
|
//
|
|
// A has [Hash {hash: myHash}]
|
|
// B has [Hash {hash: myHash}]
|
|
//
|
|
// If so, that's an error and we drop the impl for this opaque type.
|
|
let member_impl = match scope.abilities_store.impl_key(impl_symbol) {
|
|
Some(ImplKey {
|
|
opaque,
|
|
ability_member,
|
|
}) => {
|
|
env.problem(Problem::OverloadedSpecialization {
|
|
overload: loc_impl.region,
|
|
original_opaque: *opaque,
|
|
ability_member: *ability_member,
|
|
});
|
|
MemberImpl::Error
|
|
}
|
|
None => MemberImpl::Impl(impl_symbol),
|
|
};
|
|
|
|
// Did the user already claim an implementation for the ability member for this
|
|
// type previously? (e.g. Hash {hash: hash1, hash: hash2})
|
|
let opt_old_impl_symbol =
|
|
impl_map.insert(member, Loc::at(loc_impl.region, member_impl));
|
|
|
|
if let Some(old_impl_symbol) = opt_old_impl_symbol {
|
|
env.problem(Problem::DuplicateImpl {
|
|
original: old_impl_symbol.region,
|
|
duplicate: loc_impl.region,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check that the members this opaque claims to implement corresponds 1-to-1 with
|
|
// the members the ability offers.
|
|
let SeparatedMembers {
|
|
not_required,
|
|
not_implemented,
|
|
} = separate_implemented_and_required_members(
|
|
impl_map.iter().map(|(member, _)| *member).collect(),
|
|
members,
|
|
);
|
|
|
|
if !not_required.is_empty() {
|
|
// Implementing something that's not required is a recoverable error, we don't
|
|
// need to skip association of the implemented abilities. Just remove the
|
|
// unneeded members.
|
|
for sym in not_required.iter() {
|
|
impl_map.remove(sym);
|
|
}
|
|
|
|
env.problem(Problem::ImplementsNonRequired {
|
|
region,
|
|
ability,
|
|
not_required,
|
|
});
|
|
}
|
|
|
|
if !not_implemented.is_empty() {
|
|
// We'll generate runtime errors for the members that are needed but
|
|
// unspecified.
|
|
for sym in not_implemented.iter() {
|
|
impl_map.insert(*sym, Loc::at_zero(MemberImpl::Error));
|
|
}
|
|
|
|
env.problem(Problem::DoesNotImplementAbility {
|
|
region,
|
|
ability,
|
|
not_implemented,
|
|
});
|
|
}
|
|
|
|
let impls = impl_map
|
|
.into_iter()
|
|
.map(|(member, def)| (member, def.value));
|
|
|
|
scope
|
|
.abilities_store
|
|
.register_declared_implementations(name.value, impls);
|
|
} else if let Some((_, members)) = ability.derivable_ability() {
|
|
let num_members = members.len();
|
|
|
|
derived_defs.reserve(num_members);
|
|
|
|
let mut impls = Vec::with_capacity(num_members);
|
|
for &member in members.iter() {
|
|
let (derived_impl, impl_pat, impl_body) =
|
|
derive::synthesize_member_impl(env, scope, name_str, member);
|
|
|
|
let derived_def = Loc::at(
|
|
derive::DERIVED_REGION,
|
|
PendingValue::Def(PendingValueDef::Body(impl_pat, impl_body)),
|
|
);
|
|
|
|
impls.push((member, MemberImpl::Impl(derived_impl)));
|
|
derived_defs.push(derived_def);
|
|
}
|
|
|
|
scope
|
|
.abilities_store
|
|
.register_declared_implementations(name.value, impls);
|
|
|
|
derived_abilities.push(Loc::at(ability_region, ability));
|
|
} else {
|
|
// There was no record specified of functions to use for
|
|
// members, but also this isn't a builtin ability, so we don't
|
|
// know how to auto-derive it.
|
|
env.problem(Problem::IllegalDerivedAbility(region));
|
|
}
|
|
}
|
|
|
|
if !derived_abilities.is_empty() {
|
|
// Fresh instance of this opaque to be checked for derivability during solving.
|
|
let fresh_inst = Type::DelayedAlias(AliasCommon {
|
|
symbol: name.value,
|
|
type_arguments: alias
|
|
.type_variables
|
|
.iter()
|
|
.map(|alias_var| {
|
|
Loc::at(
|
|
alias_var.region,
|
|
OptAbleType {
|
|
typ: Type::Variable(var_store.fresh()),
|
|
opt_abilities: alias_var.value.opt_bound_abilities.clone(),
|
|
},
|
|
)
|
|
})
|
|
.collect(),
|
|
lambda_set_variables: alias
|
|
.lambda_set_variables
|
|
.iter()
|
|
.map(|_| LambdaSet(Type::Variable(var_store.fresh())))
|
|
.collect(),
|
|
infer_ext_in_output_types: alias
|
|
.infer_ext_in_output_variables
|
|
.iter()
|
|
.map(|_| Type::Variable(var_store.fresh()))
|
|
.collect(),
|
|
});
|
|
|
|
let old = output
|
|
.pending_derives
|
|
.insert(name.value, (fresh_inst, derived_abilities));
|
|
|
|
debug_assert!(old.is_none());
|
|
}
|
|
}
|
|
|
|
Ok(CanonicalizedOpaque {
|
|
opaque_def: alias,
|
|
derived_defs,
|
|
})
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub(crate) fn canonicalize_defs<'a>(
|
|
env: &mut Env<'a>,
|
|
mut output: Output,
|
|
var_store: &mut VarStore,
|
|
scope: &mut Scope,
|
|
loc_defs: &'a mut roc_parse::ast::Defs<'a>,
|
|
pattern_type: PatternType,
|
|
) -> (CanDefs, Output, MutMap<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.
|
|
|
|
let mut pending_type_defs = Vec::with_capacity(loc_defs.type_defs.len());
|
|
let mut pending_value_defs = Vec::with_capacity(loc_defs.value_defs.len());
|
|
let mut pending_abilities_in_scope = PendingAbilitiesInScope::default();
|
|
|
|
// Convert the type defs into pending defs first, then all the value defs.
|
|
// Follow this order because we need all value symbols to fully canonicalize type defs (in case
|
|
// there are opaques that implement an ability using a value symbol). But, value symbols might
|
|
// shadow symbols defined in a local ability def.
|
|
|
|
for (_, either_index) in loc_defs.tags.iter().enumerate() {
|
|
if let Ok(type_index) = either_index.split() {
|
|
let type_def = &loc_defs.type_defs[type_index.index()];
|
|
let pending_type_def = to_pending_type_def(env, type_def, scope, pattern_type);
|
|
if let PendingTypeDef::Ability { name, members } = &pending_type_def {
|
|
pending_abilities_in_scope.insert(
|
|
name.value,
|
|
members.iter().map(|mem| mem.name.value).collect(),
|
|
);
|
|
}
|
|
pending_type_defs.push(pending_type_def);
|
|
}
|
|
}
|
|
|
|
for (index, either_index) in loc_defs.tags.iter().enumerate() {
|
|
if let Err(value_index) = either_index.split() {
|
|
let value_def = &loc_defs.value_defs[value_index.index()];
|
|
let region = loc_defs.regions[index];
|
|
|
|
let pending = to_pending_value_def(
|
|
env,
|
|
var_store,
|
|
value_def,
|
|
scope,
|
|
&pending_abilities_in_scope,
|
|
&mut output,
|
|
pattern_type,
|
|
);
|
|
|
|
pending_value_defs.push(Loc::at(region, pending));
|
|
}
|
|
}
|
|
|
|
if cfg!(debug_assertions) {
|
|
scope.register_debug_idents();
|
|
}
|
|
|
|
let CanonicalizedTypeDefs {
|
|
aliases,
|
|
symbols_introduced,
|
|
derived_defs,
|
|
} = canonicalize_type_defs(
|
|
env,
|
|
&mut output,
|
|
var_store,
|
|
scope,
|
|
&pending_abilities_in_scope,
|
|
pending_type_defs,
|
|
);
|
|
|
|
// Add the derived ASTs, so that we create proper canonicalized defs for them.
|
|
// They can go at the end, and derived defs should never reference anything other than builtin
|
|
// ability members.
|
|
pending_value_defs.extend(derived_defs);
|
|
|
|
// Now that we have the scope completely assembled, and shadowing resolved,
|
|
// we're ready to canonicalize any body exprs.
|
|
canonicalize_value_defs(
|
|
env,
|
|
output,
|
|
var_store,
|
|
scope,
|
|
pending_value_defs,
|
|
pattern_type,
|
|
aliases,
|
|
symbols_introduced,
|
|
)
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn canonicalize_value_defs<'a>(
|
|
env: &mut Env<'a>,
|
|
mut output: Output,
|
|
var_store: &mut VarStore,
|
|
scope: &mut Scope,
|
|
value_defs: Vec<Loc<PendingValue<'a>>>,
|
|
pattern_type: PatternType,
|
|
mut aliases: VecMap<Symbol, Alias>,
|
|
mut symbols_introduced: MutMap<Symbol, Region>,
|
|
) -> (CanDefs, Output, MutMap<Symbol, Region>) {
|
|
// Canonicalize all the patterns, record shadowing problems, and store
|
|
// the ast::Expr values in pending_exprs for further canonicalization
|
|
// once we've finished assembling the entire scope.
|
|
let mut pending_value_defs = Vec::with_capacity(value_defs.len());
|
|
let mut pending_dbgs = Vec::with_capacity(value_defs.len());
|
|
let mut pending_expects = Vec::with_capacity(value_defs.len());
|
|
let mut pending_expect_fx = Vec::with_capacity(value_defs.len());
|
|
|
|
for loc_pending_def in value_defs {
|
|
match loc_pending_def.value {
|
|
PendingValue::Def(pending_def) => {
|
|
// Record the ast::Expr for later. We'll do another pass through these
|
|
// once we have the entire scope assembled. If we were to canonicalize
|
|
// the exprs right now, they wouldn't have symbols in scope from defs
|
|
// that get would have gotten added later in the defs list!
|
|
pending_value_defs.push(pending_def);
|
|
}
|
|
PendingValue::SignatureDefMismatch => { /* skip */ }
|
|
PendingValue::Dbg(pending_dbg) => {
|
|
pending_dbgs.push(pending_dbg);
|
|
}
|
|
PendingValue::Expect(pending_expect) => {
|
|
pending_expects.push(pending_expect);
|
|
}
|
|
PendingValue::ExpectFx(pending_expect) => {
|
|
pending_expect_fx.push(pending_expect);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut symbol_to_index: Vec<(IdentId, u32)> = Vec::with_capacity(pending_value_defs.len());
|
|
|
|
for (def_index, pending_def) in pending_value_defs.iter().enumerate() {
|
|
let mut new_bindings = BindingsFromPattern::new(pending_def.loc_pattern()).peekable();
|
|
|
|
if new_bindings.peek().is_none() {
|
|
env.problem(Problem::NoIdentifiersIntroduced(
|
|
pending_def.loc_pattern().region,
|
|
));
|
|
}
|
|
|
|
for (s, r) in new_bindings {
|
|
// store the top-level defs, used to ensure that closures won't capture them
|
|
if let PatternType::TopLevelDef = pattern_type {
|
|
env.top_level_symbols.insert(s);
|
|
}
|
|
|
|
symbols_introduced.insert(s, r);
|
|
|
|
debug_assert_eq!(env.home, s.module_id());
|
|
debug_assert!(
|
|
!symbol_to_index.iter().any(|(id, _)| *id == s.ident_id()),
|
|
"{:?}",
|
|
s
|
|
);
|
|
|
|
symbol_to_index.push((s.ident_id(), def_index as u32));
|
|
}
|
|
}
|
|
|
|
let capacity = pending_value_defs.len();
|
|
let mut defs = Vec::with_capacity(capacity);
|
|
let mut def_ordering = DefOrdering::from_symbol_to_id(env.home, symbol_to_index, capacity);
|
|
|
|
for (def_id, pending_def) in pending_value_defs.into_iter().enumerate() {
|
|
let temp_output = canonicalize_pending_value_def(
|
|
env,
|
|
pending_def,
|
|
output,
|
|
scope,
|
|
var_store,
|
|
pattern_type,
|
|
&mut aliases,
|
|
);
|
|
|
|
output = temp_output.output;
|
|
|
|
defs.push(Some(temp_output.def));
|
|
|
|
def_ordering.insert_symbol_references(def_id as u32, &temp_output.references)
|
|
}
|
|
|
|
let mut dbgs = ExpectsOrDbgs::with_capacity(pending_dbgs.len());
|
|
let mut expects = ExpectsOrDbgs::with_capacity(pending_expects.len());
|
|
let mut expects_fx = ExpectsOrDbgs::with_capacity(pending_expects.len());
|
|
|
|
for pending in pending_dbgs {
|
|
let (loc_can_condition, can_output) = canonicalize_expr(
|
|
env,
|
|
var_store,
|
|
scope,
|
|
pending.condition.region,
|
|
&pending.condition.value,
|
|
);
|
|
|
|
dbgs.push(loc_can_condition, pending.preceding_comment);
|
|
|
|
output.union(can_output);
|
|
}
|
|
|
|
for pending in pending_expects {
|
|
let (loc_can_condition, can_output) = canonicalize_expr(
|
|
env,
|
|
var_store,
|
|
scope,
|
|
pending.condition.region,
|
|
&pending.condition.value,
|
|
);
|
|
|
|
expects.push(loc_can_condition, pending.preceding_comment);
|
|
|
|
output.union(can_output);
|
|
}
|
|
|
|
for pending in pending_expect_fx {
|
|
let (loc_can_condition, can_output) = canonicalize_expr(
|
|
env,
|
|
var_store,
|
|
scope,
|
|
pending.condition.region,
|
|
&pending.condition.value,
|
|
);
|
|
|
|
expects_fx.push(loc_can_condition, pending.preceding_comment);
|
|
|
|
output.union(can_output);
|
|
}
|
|
|
|
let can_defs = CanDefs {
|
|
defs,
|
|
dbgs,
|
|
expects,
|
|
expects_fx,
|
|
def_ordering,
|
|
aliases,
|
|
};
|
|
|
|
(can_defs, output, symbols_introduced)
|
|
}
|
|
|
|
struct CanonicalizedTypeDefs<'a> {
|
|
aliases: VecMap<Symbol, Alias>,
|
|
symbols_introduced: MutMap<Symbol, Region>,
|
|
derived_defs: Vec<DerivedDef<'a>>,
|
|
}
|
|
|
|
fn canonicalize_type_defs<'a>(
|
|
env: &mut Env<'a>,
|
|
output: &mut Output,
|
|
var_store: &mut VarStore,
|
|
scope: &mut Scope,
|
|
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
|
pending_type_defs: Vec<PendingTypeDef<'a>>,
|
|
) -> CanonicalizedTypeDefs<'a> {
|
|
enum TypeDef<'a> {
|
|
Alias(
|
|
Loc<Symbol>,
|
|
Vec<Loc<Lowercase>>,
|
|
&'a Loc<ast::TypeAnnotation<'a>>,
|
|
),
|
|
Opaque(
|
|
&'a str,
|
|
Loc<Symbol>,
|
|
Vec<Loc<Lowercase>>,
|
|
&'a Loc<ast::TypeAnnotation<'a>>,
|
|
Option<&'a Loc<ast::ImplementsAbilities<'a>>>,
|
|
),
|
|
Ability(Loc<Symbol>, Vec<PendingAbilityMember<'a>>),
|
|
}
|
|
|
|
let mut type_defs = MutMap::default();
|
|
let mut referenced_type_symbols = VecMap::default();
|
|
|
|
// Determine which idents we introduced in the course of this process.
|
|
let mut symbols_introduced = MutMap::default();
|
|
|
|
for pending_def in pending_type_defs.into_iter() {
|
|
if let Some((symbol, region)) = pending_def.introduction() {
|
|
symbols_introduced.insert(symbol, region);
|
|
}
|
|
|
|
match pending_def {
|
|
PendingTypeDef::Alias { name, vars, ann } => {
|
|
let referenced_symbols = find_type_def_symbols(scope, &ann.value);
|
|
|
|
referenced_type_symbols.insert(name.value, referenced_symbols);
|
|
|
|
type_defs.insert(name.value, TypeDef::Alias(name, vars, ann));
|
|
}
|
|
PendingTypeDef::Opaque {
|
|
name_str,
|
|
name,
|
|
vars,
|
|
ann,
|
|
derived,
|
|
} => {
|
|
let referenced_symbols = find_type_def_symbols(scope, &ann.value);
|
|
|
|
referenced_type_symbols.insert(name.value, referenced_symbols);
|
|
// Don't need to insert references for derived types, because these can only contain
|
|
// builtin abilities, and hence do not affect the type def sorting. We'll insert
|
|
// references of usages when canonicalizing the derives.
|
|
|
|
type_defs.insert(
|
|
name.value,
|
|
TypeDef::Opaque(name_str, name, vars, ann, derived),
|
|
);
|
|
}
|
|
PendingTypeDef::Ability { name, members } => {
|
|
let mut referenced_symbols = Vec::with_capacity(2);
|
|
|
|
for member in members.iter() {
|
|
// Add the referenced type symbols of each member function. We need to make
|
|
// sure those are processed first before we resolve the whole ability
|
|
// definition.
|
|
referenced_symbols.extend(find_type_def_symbols(scope, &member.typ.value));
|
|
}
|
|
|
|
referenced_type_symbols.insert(name.value, referenced_symbols);
|
|
type_defs.insert(name.value, TypeDef::Ability(name, members));
|
|
}
|
|
PendingTypeDef::InvalidAlias { .. }
|
|
| PendingTypeDef::InvalidAbility { .. }
|
|
| PendingTypeDef::AbilityShadows
|
|
| PendingTypeDef::ShadowedAlias { .. }
|
|
| PendingTypeDef::AbilityNotOnToplevel => { /* ignore */ }
|
|
}
|
|
}
|
|
|
|
let sorted = sort_type_defs_before_introduction(referenced_type_symbols);
|
|
let mut aliases = VecMap::default();
|
|
let mut abilities = MutMap::default();
|
|
let mut all_derived_defs = Vec::new();
|
|
|
|
for type_name in sorted {
|
|
match type_defs.remove(&type_name).unwrap() {
|
|
TypeDef::Alias(name, vars, ann) => {
|
|
let alias = canonicalize_alias(
|
|
env,
|
|
output,
|
|
var_store,
|
|
scope,
|
|
pending_abilities_in_scope,
|
|
name,
|
|
ann,
|
|
&vars,
|
|
AliasKind::Structural,
|
|
);
|
|
|
|
if let Ok(alias) = alias {
|
|
aliases.insert(name.value, alias);
|
|
}
|
|
}
|
|
|
|
TypeDef::Opaque(name_str, name, vars, ann, derived) => {
|
|
let alias_and_derives = canonicalize_opaque(
|
|
env,
|
|
output,
|
|
var_store,
|
|
scope,
|
|
pending_abilities_in_scope,
|
|
name,
|
|
name_str,
|
|
ann,
|
|
&vars,
|
|
derived,
|
|
);
|
|
|
|
if let Ok(CanonicalizedOpaque {
|
|
opaque_def,
|
|
derived_defs,
|
|
}) = alias_and_derives
|
|
{
|
|
aliases.insert(name.value, opaque_def);
|
|
all_derived_defs.extend(derived_defs);
|
|
}
|
|
}
|
|
|
|
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, members);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now that we know the alias dependency graph, we can try to insert recursion variables
|
|
// where aliases are recursive tag unions, or detect illegal recursions.
|
|
let aliases = correct_mutual_recursive_type_alias(env, aliases, var_store);
|
|
|
|
for (symbol, alias) in aliases.iter() {
|
|
scope.add_alias(
|
|
*symbol,
|
|
alias.region,
|
|
alias.type_variables.clone(),
|
|
alias.infer_ext_in_output_variables.clone(),
|
|
alias.typ.clone(),
|
|
alias.kind,
|
|
);
|
|
}
|
|
|
|
// Resolve all pending abilities, to add them to scope.
|
|
resolve_abilities(
|
|
env,
|
|
output,
|
|
var_store,
|
|
scope,
|
|
abilities,
|
|
pending_abilities_in_scope,
|
|
);
|
|
|
|
CanonicalizedTypeDefs {
|
|
aliases,
|
|
symbols_introduced,
|
|
derived_defs: all_derived_defs,
|
|
}
|
|
}
|
|
|
|
/// Resolve all pending abilities, to add them to scope.
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn resolve_abilities(
|
|
env: &mut Env,
|
|
output: &mut Output,
|
|
var_store: &mut VarStore,
|
|
scope: &mut Scope,
|
|
abilities: MutMap<Symbol, Vec<PendingAbilityMember>>,
|
|
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
|
) {
|
|
for (ability, members) in abilities {
|
|
let mut can_members = Vec::with_capacity(members.len());
|
|
|
|
for PendingAbilityMember {
|
|
name:
|
|
Loc {
|
|
value: member_sym,
|
|
region: member_name_region,
|
|
},
|
|
typ,
|
|
} in members
|
|
{
|
|
let member_annot = canonicalize_annotation(
|
|
env,
|
|
scope,
|
|
&typ.value,
|
|
typ.region,
|
|
var_store,
|
|
pending_abilities_in_scope,
|
|
AnnotationFor::Value,
|
|
);
|
|
|
|
// Record all the annotation's references in output.references.lookups
|
|
for symbol in member_annot.references {
|
|
output.references.insert_type_lookup(symbol);
|
|
}
|
|
|
|
// 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.abilities.contains(&ability));
|
|
|
|
let var_bound_to_ability = match variables_bound_to_ability.as_slice() {
|
|
[one] => one.variable,
|
|
[] => {
|
|
// There are no variables bound to the parent ability - then this member doesn't
|
|
// need to be a part of the ability.
|
|
env.problem(Problem::AbilityMemberMissingHasClause {
|
|
member: member_sym,
|
|
ability,
|
|
region: member_name_region,
|
|
});
|
|
// Pretend the member isn't a part of the ability
|
|
continue;
|
|
}
|
|
[..] => {
|
|
// There is more than one variable bound to the member signature, so something like
|
|
// Eq has eq : a, b -> Bool | a has Eq, b has Eq
|
|
// We have no way of telling what type implements a particular instance of Eq in
|
|
// this case (a or b?), so disallow it.
|
|
let span_has_clauses = Region::across_all(
|
|
variables_bound_to_ability.iter().map(|v| &v.first_seen),
|
|
);
|
|
let bound_var_names = variables_bound_to_ability
|
|
.iter()
|
|
.map(|v| v.name.clone())
|
|
.collect();
|
|
env.problem(Problem::AbilityMemberMultipleBoundVars {
|
|
member: member_sym,
|
|
ability,
|
|
span_has_clauses,
|
|
bound_var_names,
|
|
});
|
|
// Pretend the member isn't a part of the ability
|
|
continue;
|
|
}
|
|
};
|
|
|
|
// The introduced variables are good; add them to the output.
|
|
output
|
|
.introduced_variables
|
|
.union(&member_annot.introduced_variables);
|
|
|
|
let iv = member_annot.introduced_variables;
|
|
|
|
let variables = MemberVariables {
|
|
able_vars: iv.collect_able(),
|
|
rigid_vars: iv.collect_rigid(),
|
|
flex_vars: iv.collect_flex(),
|
|
};
|
|
|
|
let signature = {
|
|
let mut signature = member_annot.typ;
|
|
signature
|
|
.instantiate_lambda_sets_as_unspecialized(var_bound_to_ability, member_sym);
|
|
signature
|
|
};
|
|
|
|
can_members.push((
|
|
member_sym,
|
|
AbilityMemberData {
|
|
parent_ability: ability,
|
|
region: member_name_region,
|
|
typ: PendingMemberType::Local {
|
|
variables,
|
|
signature,
|
|
signature_var: var_store.fresh(),
|
|
},
|
|
},
|
|
));
|
|
}
|
|
|
|
// Store what symbols a type must define implementations for to have this ability.
|
|
scope.abilities_store.register_ability(ability, 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<u32> {
|
|
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<Symbol> {
|
|
for (ident_id, def_id) in self.symbol_to_id.iter() {
|
|
if id as u32 == *def_id {
|
|
return Some(Symbol::new(self.home, *ident_id));
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub(crate) fn sort_can_defs_new(
|
|
env: &mut Env<'_>,
|
|
scope: &mut Scope,
|
|
var_store: &mut VarStore,
|
|
defs: CanDefs,
|
|
mut output: Output,
|
|
exposed_symbols: &VecSet<Symbol>,
|
|
) -> (Declarations, Output) {
|
|
let CanDefs {
|
|
defs,
|
|
dbgs: _,
|
|
expects,
|
|
expects_fx,
|
|
def_ordering,
|
|
aliases,
|
|
} = defs;
|
|
|
|
// TODO: inefficient, but I want to make this what CanDefs contains in the future
|
|
let mut defs: Vec<_> = defs.into_iter().map(|x| x.unwrap()).collect();
|
|
|
|
// symbols are put in declarations in dependency order, from "main" up, so
|
|
//
|
|
// x = 3
|
|
// y = x + 1
|
|
//
|
|
// will get ordering [ y, x ]
|
|
let mut declarations = Declarations::with_capacity(defs.len());
|
|
|
|
// because of the ordering of declarations, expects should come first because they are
|
|
// independent, but can rely on all other top-level symbols in the module
|
|
let it = expects
|
|
.conditions
|
|
.into_iter()
|
|
.zip(expects.regions)
|
|
.zip(expects.preceding_comment);
|
|
|
|
for ((condition, region), preceding_comment) in it {
|
|
// an `expect` does not have a user-defined name, but we'll need a name to call the expectation
|
|
let name = scope.gen_unique_symbol();
|
|
|
|
declarations.push_expect(preceding_comment, name, Loc::at(region, condition));
|
|
}
|
|
|
|
let it = expects_fx
|
|
.conditions
|
|
.into_iter()
|
|
.zip(expects_fx.regions)
|
|
.zip(expects_fx.preceding_comment);
|
|
|
|
for ((condition, region), preceding_comment) in it {
|
|
// an `expect` does not have a user-defined name, but we'll need a name to call the expectation
|
|
let name = scope.gen_unique_symbol();
|
|
|
|
declarations.push_expect_fx(preceding_comment, name, Loc::at(region, condition));
|
|
}
|
|
|
|
for (symbol, alias) in aliases.into_iter() {
|
|
output.aliases.insert(symbol, alias);
|
|
}
|
|
|
|
// We first perform SCC based on any reference, both variable usage and calls
|
|
// considering both value definitions and function bodies. This will spot any
|
|
// recursive relations between any 2 definitions.
|
|
let sccs = def_ordering.references.strongly_connected_components_all();
|
|
|
|
sccs.reorder(&mut defs);
|
|
|
|
for (group, is_initial) in sccs.groups().rev() {
|
|
match group.count_ones() {
|
|
1 => {
|
|
// a group with a single Def, nice and simple
|
|
let def = defs.pop().unwrap();
|
|
let index = group.first_one().unwrap();
|
|
|
|
if def_ordering.references.get_row_col(index, index) {
|
|
// push the "header" for this group of recursive definitions
|
|
let cycle_mark = IllegalCycleMark::new(var_store);
|
|
declarations.push_recursive_group(1, cycle_mark);
|
|
|
|
// then push the definition
|
|
let (symbol, specializes) = match def.loc_pattern.value {
|
|
Pattern::Identifier(symbol) => (symbol, None),
|
|
|
|
Pattern::AbilityMemberSpecialization { ident, specializes } => {
|
|
(ident, Some(specializes))
|
|
}
|
|
|
|
_ => {
|
|
internal_error!("destructures cannot participate in a recursive group; it's always a type error")
|
|
}
|
|
};
|
|
|
|
if is_initial && !exposed_symbols.contains(&symbol) {
|
|
env.problem(Problem::DefsOnlyUsedInRecursion(1, def.region()));
|
|
}
|
|
|
|
match def.loc_expr.value {
|
|
Closure(closure_data) => {
|
|
declarations.push_recursive_def(
|
|
Loc::at(def.loc_pattern.region, symbol),
|
|
Loc::at(def.loc_expr.region, closure_data),
|
|
def.expr_var,
|
|
def.annotation,
|
|
specializes,
|
|
);
|
|
}
|
|
_ => {
|
|
declarations.push_value_def(
|
|
Loc::at(def.loc_pattern.region, symbol),
|
|
def.loc_expr,
|
|
def.expr_var,
|
|
def.annotation,
|
|
specializes,
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
match def.loc_pattern.value {
|
|
Pattern::Identifier(symbol) => match def.loc_expr.value {
|
|
Closure(closure_data) => {
|
|
declarations.push_function_def(
|
|
Loc::at(def.loc_pattern.region, symbol),
|
|
Loc::at(def.loc_expr.region, closure_data),
|
|
def.expr_var,
|
|
def.annotation,
|
|
None,
|
|
);
|
|
}
|
|
_ => {
|
|
declarations.push_value_def(
|
|
Loc::at(def.loc_pattern.region, symbol),
|
|
def.loc_expr,
|
|
def.expr_var,
|
|
def.annotation,
|
|
None,
|
|
);
|
|
}
|
|
},
|
|
Pattern::AbilityMemberSpecialization {
|
|
ident: symbol,
|
|
specializes,
|
|
} => match def.loc_expr.value {
|
|
Closure(closure_data) => {
|
|
declarations.push_function_def(
|
|
Loc::at(def.loc_pattern.region, symbol),
|
|
Loc::at(def.loc_expr.region, closure_data),
|
|
def.expr_var,
|
|
def.annotation,
|
|
Some(specializes),
|
|
);
|
|
}
|
|
_ => {
|
|
declarations.push_value_def(
|
|
Loc::at(def.loc_pattern.region, symbol),
|
|
def.loc_expr,
|
|
def.expr_var,
|
|
def.annotation,
|
|
Some(specializes),
|
|
);
|
|
}
|
|
},
|
|
_ => {
|
|
declarations.push_destructure_def(
|
|
def.loc_pattern,
|
|
def.loc_expr,
|
|
def.expr_var,
|
|
def.annotation,
|
|
def.pattern_vars.into_iter().collect(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
group_length => {
|
|
let group_defs = defs.split_off(defs.len() - group_length);
|
|
|
|
// push the "header" for this group of recursive definitions
|
|
let cycle_mark = IllegalCycleMark::new(var_store);
|
|
declarations.push_recursive_group(group_length as u16, cycle_mark);
|
|
|
|
let mut group_is_initial = is_initial;
|
|
let mut whole_region = None;
|
|
|
|
// then push the definitions of this group
|
|
for def in group_defs {
|
|
let (symbol, specializes) = match def.loc_pattern.value {
|
|
Pattern::Identifier(symbol) => (symbol, None),
|
|
|
|
Pattern::AbilityMemberSpecialization { ident, specializes } => {
|
|
(ident, Some(specializes))
|
|
}
|
|
|
|
_ => {
|
|
internal_error!("destructures cannot participate in a recursive group; it's always a type error")
|
|
}
|
|
};
|
|
|
|
group_is_initial = group_is_initial && !exposed_symbols.contains(&symbol);
|
|
whole_region = match whole_region {
|
|
None => Some(def.region()),
|
|
Some(r) => Some(Region::span_across(&r, &def.region())),
|
|
};
|
|
|
|
match def.loc_expr.value {
|
|
Closure(closure_data) => {
|
|
declarations.push_recursive_def(
|
|
Loc::at(def.loc_pattern.region, symbol),
|
|
Loc::at(def.loc_expr.region, closure_data),
|
|
def.expr_var,
|
|
def.annotation,
|
|
specializes,
|
|
);
|
|
}
|
|
_ => {
|
|
declarations.push_value_def(
|
|
Loc::at(def.loc_pattern.region, symbol),
|
|
def.loc_expr,
|
|
def.expr_var,
|
|
def.annotation,
|
|
specializes,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if group_is_initial {
|
|
env.problem(Problem::DefsOnlyUsedInRecursion(
|
|
group_length,
|
|
whole_region.unwrap(),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
(declarations, output)
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub(crate) fn sort_can_defs(
|
|
env: &mut Env<'_>,
|
|
var_store: &mut VarStore,
|
|
defs: CanDefs,
|
|
mut output: Output,
|
|
) -> (Vec<Declaration>, Output) {
|
|
let CanDefs {
|
|
mut defs,
|
|
dbgs,
|
|
expects,
|
|
expects_fx,
|
|
def_ordering,
|
|
aliases,
|
|
} = defs;
|
|
|
|
for (symbol, alias) in aliases.into_iter() {
|
|
output.aliases.insert(symbol, alias);
|
|
}
|
|
|
|
macro_rules! take_def {
|
|
($index:expr) => {
|
|
match defs[$index].take() {
|
|
Some(def) => def,
|
|
None => {
|
|
// NOTE: a `_ = someDef` can mean we don't have a symbol here
|
|
let symbol = def_ordering.get_symbol($index);
|
|
|
|
roc_error_macros::internal_error!("def not available {:?}", symbol)
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
// We first perform SCC based on any reference, both variable usage and calls
|
|
// considering both value definitions and function bodies. This will spot any
|
|
// recursive relations between any 2 definitions.
|
|
let sccs = def_ordering.references.strongly_connected_components_all();
|
|
|
|
let mut declarations = Vec::with_capacity(defs.len());
|
|
|
|
for (group, is_initial) in sccs.groups() {
|
|
if group.count_ones() == 1 {
|
|
// a group with a single Def, nice and simple
|
|
let index = group.iter_ones().next().unwrap();
|
|
|
|
let def = take_def!(index);
|
|
let is_specialization = matches!(
|
|
def.loc_pattern.value,
|
|
Pattern::AbilityMemberSpecialization { .. }
|
|
);
|
|
|
|
let declaration = if def_ordering.references.get_row_col(index, index) {
|
|
debug_assert!(!is_specialization, "Self-recursive specializations can only be determined during solving - but it was determined for {:?} now, that's a bug!", def);
|
|
|
|
if is_initial
|
|
&& !def
|
|
.pattern_vars
|
|
.keys()
|
|
.any(|sym| output.references.has_value_lookup(*sym))
|
|
{
|
|
// This defs is only used in recursion with itself.
|
|
env.problem(Problem::DefsOnlyUsedInRecursion(1, def.region()));
|
|
}
|
|
|
|
// this function calls itself, and must be typechecked as a recursive def
|
|
Declaration::DeclareRec(vec![mark_def_recursive(def)], IllegalCycleMark::empty())
|
|
} else {
|
|
Declaration::Declare(def)
|
|
};
|
|
|
|
declarations.push(declaration);
|
|
} else {
|
|
// There is something recursive going on between the Defs of this group.
|
|
// Now we use the direct_references to see if it is clearly invalid recursion, e.g.
|
|
//
|
|
// x = y
|
|
// y = x
|
|
//
|
|
// We allow indirect recursion (behind a lambda), e.g.
|
|
//
|
|
// boom = \{} -> boom {}
|
|
//
|
|
// In general we cannot spot faulty recursion (halting problem), so this is our
|
|
// purely-syntactic heuristic. We'll have a second attempt once we know the types in
|
|
// the cycle.
|
|
let direct_sccs = def_ordering
|
|
.direct_references
|
|
.strongly_connected_components_subset(group);
|
|
|
|
debug_assert!(
|
|
!group.iter_ones().any(|index| matches!(defs[index].as_ref().unwrap().loc_pattern.value, Pattern::AbilityMemberSpecialization{..})),
|
|
"A specialization is involved in a recursive cycle - this should not be knowable until solving");
|
|
|
|
let declaration = if direct_sccs.groups().count() == 1 {
|
|
// all defs are part of the same direct cycle, that is invalid!
|
|
let mut entries = Vec::with_capacity(group.count_ones());
|
|
|
|
for index in group.iter_ones() {
|
|
let def = take_def!(index);
|
|
let symbol = def_ordering.get_symbol(index).unwrap();
|
|
|
|
entries.push(make_cycle_entry(symbol, &def))
|
|
}
|
|
|
|
let problem = Problem::RuntimeError(RuntimeError::CircularDef(entries.clone()));
|
|
env.problem(problem);
|
|
|
|
Declaration::InvalidCycle(entries)
|
|
} else {
|
|
let rec_defs: Vec<Def> = group
|
|
.iter_ones()
|
|
.map(|index| mark_def_recursive(take_def!(index)))
|
|
.collect();
|
|
|
|
if is_initial
|
|
&& !rec_defs.iter().any(|def| {
|
|
def.pattern_vars
|
|
.keys()
|
|
.any(|sym| output.references.has_value_lookup(*sym))
|
|
})
|
|
{
|
|
// These defs are only used in mutual recursion with themselves.
|
|
let region = Region::span_across(
|
|
&rec_defs.first().unwrap().region(),
|
|
&rec_defs.last().unwrap().region(),
|
|
);
|
|
env.problem(Problem::DefsOnlyUsedInRecursion(rec_defs.len(), region));
|
|
}
|
|
|
|
Declaration::DeclareRec(rec_defs, IllegalCycleMark::new(var_store))
|
|
};
|
|
|
|
declarations.push(declaration);
|
|
}
|
|
}
|
|
|
|
if !dbgs.conditions.is_empty() {
|
|
declarations.push(Declaration::Expects(dbgs));
|
|
}
|
|
|
|
if !expects.conditions.is_empty() {
|
|
declarations.push(Declaration::Expects(expects));
|
|
}
|
|
|
|
if !expects_fx.conditions.is_empty() {
|
|
declarations.push(Declaration::ExpectsFx(expects_fx));
|
|
}
|
|
|
|
(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<Symbol, Variable>,
|
|
pattern: &Pattern,
|
|
expr_var: Variable,
|
|
) {
|
|
use Pattern::*;
|
|
match pattern {
|
|
Identifier(symbol) | Shadowed(_, _, symbol) => {
|
|
vars_by_symbol.insert(*symbol, expr_var);
|
|
}
|
|
|
|
As(subpattern, symbol) => {
|
|
vars_by_symbol.insert(*symbol, expr_var);
|
|
|
|
pattern_to_vars_by_symbol(vars_by_symbol, &subpattern.value, 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);
|
|
}
|
|
|
|
TupleDestructure { destructs, .. } => {
|
|
for destruct in destructs {
|
|
pattern_to_vars_by_symbol(
|
|
vars_by_symbol,
|
|
&destruct.value.typ.1.value,
|
|
destruct.value.typ.0,
|
|
);
|
|
}
|
|
}
|
|
|
|
RecordDestructure { destructs, .. } => {
|
|
for destruct in destructs {
|
|
vars_by_symbol.insert(destruct.value.symbol, destruct.value.var);
|
|
}
|
|
}
|
|
|
|
List {
|
|
patterns, elem_var, ..
|
|
} => {
|
|
for pat in patterns.patterns.iter() {
|
|
pattern_to_vars_by_symbol(vars_by_symbol, &pat.value, *elem_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,
|
|
}
|
|
}
|
|
|
|
// Functions' references don't count in defs.
|
|
// See 3d5a2560057d7f25813112dfa5309956c0f9e6a9 and its
|
|
// parent commit for the bug this fixed!
|
|
enum DefReferences {
|
|
/// A value may not reference itself
|
|
Value(References),
|
|
|
|
/// If the def is a function, different rules apply (it can call itself)
|
|
Function(References),
|
|
|
|
/// An annotation without a body references no other defs
|
|
AnnotationWithoutBody,
|
|
}
|
|
|
|
struct DefOutput {
|
|
output: Output,
|
|
def: Def,
|
|
references: DefReferences,
|
|
}
|
|
|
|
// TODO trim down these arguments!
|
|
#[allow(clippy::too_many_arguments)]
|
|
#[allow(clippy::cognitive_complexity)]
|
|
fn canonicalize_pending_value_def<'a>(
|
|
env: &mut Env<'a>,
|
|
pending_def: PendingValueDef<'a>,
|
|
mut output: Output,
|
|
scope: &mut Scope,
|
|
var_store: &mut VarStore,
|
|
pattern_type: PatternType,
|
|
aliases: &mut VecMap<Symbol, Alias>,
|
|
) -> DefOutput {
|
|
use PendingValueDef::*;
|
|
|
|
// All abilities should be resolved by the time we're canonicalizing value defs.
|
|
let pending_abilities_in_scope = &Default::default();
|
|
|
|
let output = match pending_def {
|
|
AnnotationOnly(_, loc_can_pattern, loc_ann) => {
|
|
// Make types for the body expr, even if we won't end up having a body.
|
|
let expr_var = var_store.fresh();
|
|
let mut vars_by_symbol = SendMap::default();
|
|
|
|
// annotation sans body cannot introduce new rigids that are visible in other annotations
|
|
// but the rigids can show up in type error messages, so still register them
|
|
let type_annotation = canonicalize_annotation(
|
|
env,
|
|
scope,
|
|
&loc_ann.value,
|
|
loc_ann.region,
|
|
var_store,
|
|
pending_abilities_in_scope,
|
|
AnnotationFor::Value,
|
|
);
|
|
|
|
// Record all the annotation's references in output.references.lookups
|
|
type_annotation.add_to(
|
|
aliases,
|
|
&mut output.references,
|
|
&mut output.introduced_variables,
|
|
);
|
|
|
|
pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var);
|
|
|
|
let arity = type_annotation.typ.arity();
|
|
|
|
let problem = match &loc_can_pattern.value {
|
|
Pattern::Identifier(symbol) => RuntimeError::NoImplementationNamed {
|
|
def_symbol: *symbol,
|
|
},
|
|
Pattern::Shadowed(region, loc_ident, _new_symbol) => RuntimeError::Shadowing {
|
|
original_region: *region,
|
|
shadow: loc_ident.clone(),
|
|
kind: ShadowKind::Variable,
|
|
},
|
|
_ => RuntimeError::NoImplementation,
|
|
};
|
|
|
|
// Fabricate a body for this annotation, that will error at runtime
|
|
let value = Expr::RuntimeError(problem);
|
|
let is_closure = arity > 0;
|
|
let loc_can_expr = if !is_closure {
|
|
Loc {
|
|
value,
|
|
region: loc_ann.region,
|
|
}
|
|
} else {
|
|
let symbol = match &loc_can_pattern.value {
|
|
Pattern::Identifier(symbol) => *symbol,
|
|
_ => scope.gen_unique_symbol(),
|
|
};
|
|
|
|
// generate a fake pattern for each argument. this makes signatures
|
|
// that are functions only crash when they are applied.
|
|
let mut underscores = Vec::with_capacity(arity);
|
|
|
|
for _ in 0..arity {
|
|
let underscore: Loc<Pattern> = Loc {
|
|
value: Pattern::Underscore,
|
|
region: Region::zero(),
|
|
};
|
|
|
|
underscores.push((
|
|
var_store.fresh(),
|
|
AnnotatedMark::known_exhaustive(),
|
|
underscore,
|
|
));
|
|
}
|
|
|
|
let body_expr = Loc {
|
|
value,
|
|
region: loc_ann.region,
|
|
};
|
|
|
|
Loc {
|
|
value: Closure(ClosureData {
|
|
function_type: var_store.fresh(),
|
|
closure_type: var_store.fresh(),
|
|
return_type: var_store.fresh(),
|
|
name: symbol,
|
|
captured_symbols: Vec::new(),
|
|
recursive: Recursive::NotRecursive,
|
|
arguments: underscores,
|
|
loc_body: Box::new(body_expr),
|
|
}),
|
|
region: loc_ann.region,
|
|
}
|
|
};
|
|
|
|
let def = single_can_def(
|
|
loc_can_pattern,
|
|
loc_can_expr,
|
|
expr_var,
|
|
Some(Loc::at(loc_ann.region, type_annotation)),
|
|
vars_by_symbol.clone(),
|
|
);
|
|
|
|
DefOutput {
|
|
output,
|
|
references: DefReferences::AnnotationWithoutBody,
|
|
def,
|
|
}
|
|
}
|
|
|
|
TypedBody(_loc_pattern, loc_can_pattern, loc_ann, loc_expr) => {
|
|
let type_annotation = canonicalize_annotation(
|
|
env,
|
|
scope,
|
|
&loc_ann.value,
|
|
loc_ann.region,
|
|
var_store,
|
|
pending_abilities_in_scope,
|
|
AnnotationFor::Value,
|
|
);
|
|
|
|
// Record all the annotation's references in output.references.lookups
|
|
type_annotation.add_to(
|
|
aliases,
|
|
&mut output.references,
|
|
&mut output.introduced_variables,
|
|
);
|
|
|
|
canonicalize_pending_body(
|
|
env,
|
|
output,
|
|
scope,
|
|
var_store,
|
|
loc_can_pattern,
|
|
loc_expr,
|
|
Some(Loc::at(loc_ann.region, type_annotation)),
|
|
)
|
|
}
|
|
Body(loc_can_pattern, loc_expr) => {
|
|
//
|
|
canonicalize_pending_body(
|
|
env,
|
|
output,
|
|
scope,
|
|
var_store,
|
|
loc_can_pattern,
|
|
loc_expr,
|
|
None,
|
|
)
|
|
}
|
|
};
|
|
|
|
// Disallow ability specializations that aren't on the toplevel (note: we might loosen this
|
|
// restriction later on).
|
|
if pattern_type != PatternType::TopLevelDef {
|
|
if let Loc {
|
|
value: Pattern::AbilityMemberSpecialization { specializes, .. },
|
|
region,
|
|
} = output.def.loc_pattern
|
|
{
|
|
env.problem(Problem::NestedSpecialization(specializes, region));
|
|
}
|
|
}
|
|
|
|
output
|
|
}
|
|
|
|
// TODO trim down these arguments!
|
|
#[allow(clippy::too_many_arguments)]
|
|
#[allow(clippy::cognitive_complexity)]
|
|
fn canonicalize_pending_body<'a>(
|
|
env: &mut Env<'a>,
|
|
mut output: Output,
|
|
scope: &mut Scope,
|
|
var_store: &mut VarStore,
|
|
|
|
loc_can_pattern: Loc<Pattern>,
|
|
loc_expr: &'a Loc<ast::Expr>,
|
|
|
|
opt_loc_annotation: Option<Loc<crate::annotation::Annotation>>,
|
|
) -> DefOutput {
|
|
// We treat closure definitions `foo = \a, b -> ...` differently from other body expressions,
|
|
// because they need more bookkeeping (for tail calls, closure captures, etc.)
|
|
//
|
|
// Only defs of the form `foo = ...` can be closure declarations or self tail calls.
|
|
let (loc_can_expr, def_references) = {
|
|
match (&loc_can_pattern.value, &loc_expr.value) {
|
|
(
|
|
Pattern::Identifier(defined_symbol)
|
|
| Pattern::AbilityMemberSpecialization {
|
|
ident: defined_symbol,
|
|
..
|
|
},
|
|
ast::Expr::Closure(arguments, body),
|
|
) => {
|
|
// bookkeeping for tail-call detection.
|
|
let outer_tailcallable = env.tailcallable_symbol;
|
|
env.tailcallable_symbol = Some(*defined_symbol);
|
|
|
|
let (mut closure_data, can_output) = crate::expr::canonicalize_closure(
|
|
env,
|
|
var_store,
|
|
scope,
|
|
arguments,
|
|
body,
|
|
Some(*defined_symbol),
|
|
);
|
|
|
|
// reset the tailcallable_symbol
|
|
env.tailcallable_symbol = outer_tailcallable;
|
|
|
|
// The closure is self tail recursive iff it tail calls itself (by defined name).
|
|
let is_recursive = match can_output.tail_call {
|
|
Some(tail_symbol) if tail_symbol == *defined_symbol => Recursive::TailRecursive,
|
|
_ => Recursive::NotRecursive,
|
|
};
|
|
|
|
closure_data.recursive = is_recursive;
|
|
closure_data.name = *defined_symbol;
|
|
|
|
let loc_can_expr = Loc::at(loc_expr.region, Expr::Closure(closure_data));
|
|
|
|
let def_references = DefReferences::Function(can_output.references.clone());
|
|
output.union(can_output);
|
|
|
|
(loc_can_expr, def_references)
|
|
}
|
|
|
|
// Turn f = .foo into f = \rcd -[f]-> rcd.foo
|
|
(
|
|
Pattern::Identifier(defined_symbol)
|
|
| Pattern::AbilityMemberSpecialization {
|
|
ident: defined_symbol,
|
|
..
|
|
},
|
|
ast::Expr::AccessorFunction(field),
|
|
) => {
|
|
let field = match field {
|
|
Accessor::RecordField(field) => IndexOrField::Field((*field).into()),
|
|
Accessor::TupleIndex(index) => IndexOrField::Index(index.parse().unwrap()),
|
|
};
|
|
let (loc_can_expr, can_output) = (
|
|
Loc::at(
|
|
loc_expr.region,
|
|
RecordAccessor(StructAccessorData {
|
|
name: *defined_symbol,
|
|
function_var: var_store.fresh(),
|
|
record_var: var_store.fresh(),
|
|
ext_var: var_store.fresh(),
|
|
closure_var: var_store.fresh(),
|
|
field_var: var_store.fresh(),
|
|
field,
|
|
}),
|
|
),
|
|
Output::default(),
|
|
);
|
|
let def_references = DefReferences::Value(can_output.references.clone());
|
|
output.union(can_output);
|
|
|
|
(loc_can_expr, def_references)
|
|
}
|
|
|
|
_ => {
|
|
let (loc_can_expr, can_output) =
|
|
canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value);
|
|
|
|
let def_references = DefReferences::Value(can_output.references.clone());
|
|
output.union(can_output);
|
|
|
|
(loc_can_expr, def_references)
|
|
}
|
|
}
|
|
};
|
|
|
|
let expr_var = var_store.fresh();
|
|
let mut vars_by_symbol = SendMap::default();
|
|
|
|
pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var);
|
|
|
|
let def = single_can_def(
|
|
loc_can_pattern,
|
|
loc_can_expr,
|
|
expr_var,
|
|
opt_loc_annotation,
|
|
vars_by_symbol,
|
|
);
|
|
|
|
DefOutput {
|
|
output,
|
|
references: def_references,
|
|
def,
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn can_defs_with_return<'a>(
|
|
env: &mut Env<'a>,
|
|
var_store: &mut VarStore,
|
|
scope: &mut Scope,
|
|
loc_defs: &'a mut Defs<'a>,
|
|
loc_ret: &'a Loc<ast::Expr<'a>>,
|
|
) -> (Expr, Output) {
|
|
let (unsorted, defs_output, symbols_introduced) = canonicalize_defs(
|
|
env,
|
|
Output::default(),
|
|
var_store,
|
|
scope,
|
|
loc_defs,
|
|
PatternType::DefExpr,
|
|
);
|
|
|
|
// The def as a whole is a tail call iff its return expression is a tail call.
|
|
// Use its output as a starting point because its tail_call already has the right answer!
|
|
let (ret_expr, mut output) =
|
|
canonicalize_expr(env, var_store, scope, loc_ret.region, &loc_ret.value);
|
|
|
|
output
|
|
.introduced_variables
|
|
.union(&defs_output.introduced_variables);
|
|
|
|
// Sort the defs with the output of the return expression - we'll use this to catch unused defs
|
|
// due only to recursion.
|
|
let (declarations, mut output) = sort_can_defs(env, var_store, unsorted, output);
|
|
|
|
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 or any other def.
|
|
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 mut loc_expr: Loc<Expr> = ret_expr;
|
|
|
|
for declaration in declarations.into_iter().rev() {
|
|
loc_expr = decl_to_let(declaration, loc_expr);
|
|
}
|
|
|
|
(loc_expr.value, output)
|
|
}
|
|
|
|
fn decl_to_let(decl: Declaration, loc_ret: Loc<Expr>) -> Loc<Expr> {
|
|
match decl {
|
|
Declaration::Declare(def) => {
|
|
let region = Region::span_across(&def.loc_pattern.region, &loc_ret.region);
|
|
let expr = Expr::LetNonRec(Box::new(def), Box::new(loc_ret));
|
|
Loc::at(region, expr)
|
|
}
|
|
Declaration::DeclareRec(defs, cycle_mark) => {
|
|
let region = Region::span_across(&defs[0].loc_pattern.region, &loc_ret.region);
|
|
let expr = Expr::LetRec(defs, Box::new(loc_ret), cycle_mark);
|
|
Loc::at(region, expr)
|
|
}
|
|
Declaration::InvalidCycle(entries) => {
|
|
Loc::at_zero(Expr::RuntimeError(RuntimeError::CircularDef(entries)))
|
|
}
|
|
Declaration::Builtin(_) => {
|
|
// Builtins should only be added to top-level decls, not to let-exprs!
|
|
unreachable!()
|
|
}
|
|
Declaration::Expects(expects) => {
|
|
let mut loc_ret = loc_ret;
|
|
|
|
let conditions = expects.conditions.into_iter().rev();
|
|
let condition_regions = expects.regions.into_iter().rev();
|
|
let expect_regions = expects.preceding_comment.into_iter().rev();
|
|
|
|
let it = expect_regions.zip(condition_regions).zip(conditions);
|
|
|
|
for ((expect_region, condition_region), condition) in it {
|
|
let region = Region::span_across(&expect_region, &loc_ret.region);
|
|
let lookups_in_cond = get_lookup_symbols(&condition);
|
|
|
|
let expr = Expr::Expect {
|
|
loc_condition: Box::new(Loc::at(condition_region, condition)),
|
|
loc_continuation: Box::new(loc_ret),
|
|
lookups_in_cond,
|
|
};
|
|
|
|
loc_ret = Loc::at(region, expr);
|
|
}
|
|
|
|
loc_ret
|
|
}
|
|
Declaration::ExpectsFx(expects) => {
|
|
// Expects should only be added to top-level decls, not to let-exprs!
|
|
unreachable!("{:?}", &expects)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn to_pending_alias_or_opaque<'a>(
|
|
env: &mut Env<'a>,
|
|
scope: &mut Scope,
|
|
name: &'a Loc<&'a str>,
|
|
vars: &'a [Loc<ast::Pattern<'a>>],
|
|
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
|
opt_derived: Option<&'a Loc<ast::ImplementsAbilities<'a>>>,
|
|
kind: AliasKind,
|
|
) -> PendingTypeDef<'a> {
|
|
let region = Region::span_across(&name.region, &ann.region);
|
|
|
|
match scope.introduce_without_shadow_symbol(&Ident::from(name.value), region) {
|
|
Ok(symbol) => {
|
|
let mut can_rigids: Vec<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 PendingTypeDef::InvalidAlias {
|
|
kind,
|
|
symbol,
|
|
region,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
let name_str = name.value;
|
|
let name = Loc {
|
|
region: name.region,
|
|
value: symbol,
|
|
};
|
|
|
|
match kind {
|
|
AliasKind::Structural => PendingTypeDef::Alias {
|
|
name,
|
|
vars: can_rigids,
|
|
ann,
|
|
},
|
|
AliasKind::Opaque => PendingTypeDef::Opaque {
|
|
name_str,
|
|
name,
|
|
vars: can_rigids,
|
|
ann,
|
|
derived: opt_derived,
|
|
},
|
|
}
|
|
}
|
|
|
|
Err((original_sym, original_region, loc_shadowed_symbol)) => {
|
|
let shadow_kind = match kind {
|
|
AliasKind::Structural => ShadowKind::Alias(original_sym),
|
|
AliasKind::Opaque => ShadowKind::Opaque(original_sym),
|
|
};
|
|
|
|
env.problem(Problem::Shadowing {
|
|
original_region,
|
|
shadow: loc_shadowed_symbol,
|
|
kind: shadow_kind,
|
|
});
|
|
|
|
PendingTypeDef::ShadowedAlias
|
|
}
|
|
}
|
|
}
|
|
|
|
fn to_pending_type_def<'a>(
|
|
env: &mut Env<'a>,
|
|
def: &'a ast::TypeDef<'a>,
|
|
scope: &mut Scope,
|
|
pattern_type: PatternType,
|
|
) -> PendingTypeDef<'a> {
|
|
use ast::TypeDef::*;
|
|
|
|
match def {
|
|
Alias {
|
|
header: TypeHeader { name, vars },
|
|
ann,
|
|
} => to_pending_alias_or_opaque(env, scope, name, vars, ann, None, AliasKind::Structural),
|
|
Opaque {
|
|
header: TypeHeader { name, vars },
|
|
typ: ann,
|
|
derived,
|
|
} => to_pending_alias_or_opaque(
|
|
env,
|
|
scope,
|
|
name,
|
|
vars,
|
|
ann,
|
|
derived.as_ref(),
|
|
AliasKind::Opaque,
|
|
),
|
|
|
|
Ability {
|
|
header, members, ..
|
|
} if pattern_type != PatternType::TopLevelDef => {
|
|
let header_region = header.region();
|
|
let region = Region::span_across(
|
|
&header_region,
|
|
&members.last().map(|m| m.region()).unwrap_or(header_region),
|
|
);
|
|
env.problem(Problem::AbilityNotOnToplevel { region });
|
|
|
|
PendingTypeDef::AbilityNotOnToplevel
|
|
}
|
|
|
|
Ability {
|
|
header: TypeHeader { name, vars },
|
|
members,
|
|
loc_implements: _,
|
|
} => {
|
|
let name = match scope
|
|
.introduce_without_shadow_symbol(&Ident::from(name.value), name.region)
|
|
{
|
|
Ok(symbol) => Loc::at(name.region, symbol),
|
|
Err((original_symbol, original_region, shadowed_symbol)) => {
|
|
env.problem(Problem::Shadowing {
|
|
original_region,
|
|
shadow: shadowed_symbol,
|
|
kind: ShadowKind::Ability(original_symbol),
|
|
});
|
|
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,
|
|
};
|
|
}
|
|
|
|
let mut named_members = Vec::with_capacity(members.len());
|
|
|
|
for member in *members {
|
|
let name_region = member.name.region;
|
|
let member_name = member.name.extract_spaces().item;
|
|
|
|
let member_sym = match scope.introduce(member_name.into(), name_region) {
|
|
Ok(sym) => sym,
|
|
Err((shadowed_symbol, shadow, _new_symbol)) => {
|
|
env.problem(roc_problem::can::Problem::Shadowing {
|
|
original_region: shadowed_symbol.region,
|
|
shadow,
|
|
kind: ShadowKind::Variable,
|
|
});
|
|
// Pretend the member isn't a part of the ability
|
|
continue;
|
|
}
|
|
};
|
|
|
|
named_members.push(PendingAbilityMember {
|
|
name: Loc::at(name_region, member_sym),
|
|
typ: member.typ,
|
|
});
|
|
|
|
if pattern_type == PatternType::TopLevelDef {
|
|
env.top_level_symbols.insert(member_sym);
|
|
}
|
|
}
|
|
|
|
PendingTypeDef::Ability {
|
|
name,
|
|
members: named_members,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
enum PendingValue<'a> {
|
|
Def(PendingValueDef<'a>),
|
|
Dbg(PendingExpectOrDbg<'a>),
|
|
Expect(PendingExpectOrDbg<'a>),
|
|
ExpectFx(PendingExpectOrDbg<'a>),
|
|
SignatureDefMismatch,
|
|
}
|
|
|
|
struct PendingExpectOrDbg<'a> {
|
|
condition: &'a Loc<ast::Expr<'a>>,
|
|
preceding_comment: Region,
|
|
}
|
|
|
|
fn to_pending_value_def<'a>(
|
|
env: &mut Env<'a>,
|
|
var_store: &mut VarStore,
|
|
def: &'a ast::ValueDef<'a>,
|
|
scope: &mut Scope,
|
|
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
|
output: &mut Output,
|
|
pattern_type: PatternType,
|
|
) -> PendingValue<'a> {
|
|
use ast::ValueDef::*;
|
|
|
|
match def {
|
|
Annotation(loc_pattern, loc_ann) => {
|
|
// This takes care of checking for shadowing and adding idents to scope.
|
|
let loc_can_pattern = canonicalize_def_header_pattern(
|
|
env,
|
|
var_store,
|
|
scope,
|
|
pending_abilities_in_scope,
|
|
output,
|
|
pattern_type,
|
|
&loc_pattern.value,
|
|
loc_pattern.region,
|
|
);
|
|
|
|
PendingValue::Def(PendingValueDef::AnnotationOnly(
|
|
loc_pattern,
|
|
loc_can_pattern,
|
|
loc_ann,
|
|
))
|
|
}
|
|
Body(loc_pattern, loc_expr) => {
|
|
// This takes care of checking for shadowing and adding idents to scope.
|
|
let loc_can_pattern = canonicalize_def_header_pattern(
|
|
env,
|
|
var_store,
|
|
scope,
|
|
pending_abilities_in_scope,
|
|
output,
|
|
pattern_type,
|
|
&loc_pattern.value,
|
|
loc_pattern.region,
|
|
);
|
|
|
|
PendingValue::Def(PendingValueDef::Body(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,
|
|
pending_abilities_in_scope,
|
|
output,
|
|
pattern_type,
|
|
&body_pattern.value,
|
|
body_pattern.region,
|
|
);
|
|
|
|
PendingValue::Def(PendingValueDef::TypedBody(
|
|
body_pattern,
|
|
loc_can_pattern,
|
|
ann_type,
|
|
body_expr,
|
|
))
|
|
} else {
|
|
// the pattern of the annotation does not match the pattern of the body direc
|
|
env.problems.push(Problem::SignatureDefMismatch {
|
|
annotation_pattern: ann_pattern.region,
|
|
def_pattern: body_pattern.region,
|
|
});
|
|
|
|
// TODO: Should we instead build some PendingValueDef::InvalidAnnotatedBody ? This would
|
|
// remove the `Option` on this function (and be probably more reliable for further
|
|
// problem/error reporting)
|
|
PendingValue::SignatureDefMismatch
|
|
}
|
|
}
|
|
|
|
Dbg {
|
|
condition,
|
|
preceding_comment,
|
|
} => PendingValue::Dbg(PendingExpectOrDbg {
|
|
condition,
|
|
preceding_comment: *preceding_comment,
|
|
}),
|
|
|
|
Expect {
|
|
condition,
|
|
preceding_comment,
|
|
} => PendingValue::Expect(PendingExpectOrDbg {
|
|
condition,
|
|
preceding_comment: *preceding_comment,
|
|
}),
|
|
|
|
ExpectFx {
|
|
condition,
|
|
preceding_comment,
|
|
} => PendingValue::ExpectFx(PendingExpectOrDbg {
|
|
condition,
|
|
preceding_comment: *preceding_comment,
|
|
}),
|
|
}
|
|
}
|
|
|
|
/// Make aliases recursive
|
|
fn correct_mutual_recursive_type_alias(
|
|
env: &mut Env,
|
|
original_aliases: VecMap<Symbol, Alias>,
|
|
var_store: &mut VarStore,
|
|
) -> VecMap<Symbol, Alias> {
|
|
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::<usize>::repeat(false, capacity);
|
|
|
|
let sccs = matrix.strongly_connected_components_all();
|
|
|
|
// scratchpad to store aliases that are modified in the current iteration.
|
|
// Only used when there is are more than one alias in a group. See below why
|
|
// this is needed.
|
|
let scratchpad_capacity = sccs
|
|
.groups()
|
|
.map(|(r, _)| r.count_ones())
|
|
.max()
|
|
.unwrap_or_default();
|
|
let mut scratchpad = Vec::with_capacity(scratchpad_capacity);
|
|
|
|
for (cycle, _is_initial) 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();
|
|
let mut new_recursion_variables = ImSet::default();
|
|
let mut new_infer_ext_vars = ImSet::default();
|
|
alias_type.instantiate_aliases(
|
|
alias_region,
|
|
&can_instantiate_symbol,
|
|
var_store,
|
|
&mut new_lambda_sets,
|
|
&mut new_recursion_variables,
|
|
&mut new_infer_ext_vars,
|
|
);
|
|
|
|
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))),
|
|
);
|
|
|
|
// add any new recursion variables
|
|
alias.recursion_variables.extend(new_recursion_variables);
|
|
|
|
// add any new infer-in-output extension variables that the instantiation created to the current alias
|
|
alias
|
|
.infer_ext_in_output_variables
|
|
.extend(new_infer_ext_vars);
|
|
|
|
// 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<Symbol> = 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.kind,
|
|
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(
|
|
env: &mut Env,
|
|
alias_name: Symbol,
|
|
alias: &mut Alias,
|
|
others: Vec<Symbol>,
|
|
var_store: &mut VarStore,
|
|
can_report_cyclic_error: &mut bool,
|
|
) -> Result<(), ()> {
|
|
let alias_args = alias
|
|
.type_variables
|
|
.iter()
|
|
.map(|l| Type::Variable(l.value.var));
|
|
|
|
let alias_opt_able_vars = alias.type_variables.iter().map(|l| OptAbleType {
|
|
typ: Type::Variable(l.value.var),
|
|
opt_abilities: l.value.opt_bound_abilities.clone(),
|
|
});
|
|
|
|
let lambda_set_vars = alias.lambda_set_variables.iter();
|
|
let infer_ext_in_output_variables = alias
|
|
.infer_ext_in_output_variables
|
|
.iter()
|
|
.map(|v| Type::Variable(*v));
|
|
|
|
let made_recursive = make_tag_union_recursive_help(
|
|
env,
|
|
Loc::at(alias.header_region(), alias_name),
|
|
alias_args,
|
|
alias_opt_able_vars,
|
|
lambda_set_vars,
|
|
infer_ext_in_output_variables,
|
|
alias.kind,
|
|
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`.
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn make_tag_union_recursive_help<'a, 'b>(
|
|
env: &mut Env<'a>,
|
|
recursive_alias: Loc<Symbol>,
|
|
alias_args: impl Iterator<Item = Type>,
|
|
alias_opt_able_vars: impl Iterator<Item = OptAbleType>,
|
|
lambda_set_variables: impl Iterator<Item = &'b LambdaSet>,
|
|
infer_ext_in_output_variables: impl Iterator<Item = Type>,
|
|
alias_kind: AliasKind,
|
|
region: Region,
|
|
others: Vec<Symbol>,
|
|
typ: &'b mut Type,
|
|
var_store: &mut VarStore,
|
|
can_report_cyclic_error: &mut bool,
|
|
) -> MakeTagUnionRecursive {
|
|
use MakeTagUnionRecursive::*;
|
|
|
|
let symbol = recursive_alias.value;
|
|
let alias_region = recursive_alias.region;
|
|
|
|
match typ {
|
|
Type::TagUnion(tags, ext) => {
|
|
let recursion_variable = var_store.fresh();
|
|
let type_arguments: Vec<_> = alias_args.collect();
|
|
|
|
let mut pending_typ =
|
|
Type::RecursiveTagUnion(recursion_variable, tags.to_vec(), ext.clone());
|
|
|
|
let substitution = match alias_kind {
|
|
// Inline recursion var directly wherever the alias is used.
|
|
AliasKind::Structural => Type::Variable(recursion_variable),
|
|
// Wrap the recursion var in the opaque wherever it's used to avoid leaking the
|
|
// inner type out as structural.
|
|
AliasKind::Opaque => Type::Alias {
|
|
symbol,
|
|
type_arguments: alias_opt_able_vars.collect(),
|
|
lambda_set_variables: lambda_set_variables.cloned().collect(),
|
|
infer_ext_in_output_types: infer_ext_in_output_variables.collect(),
|
|
actual: Box::new(Type::Variable(recursion_variable)),
|
|
kind: AliasKind::Opaque,
|
|
},
|
|
};
|
|
|
|
let substitution_result =
|
|
pending_typ.substitute_alias(symbol, &type_arguments, &substitution);
|
|
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,
|
|
lambda_set_variables,
|
|
infer_ext_in_output_types,
|
|
kind,
|
|
..
|
|
} => {
|
|
// NB: We need to collect the type arguments to shut off rustc's closure type
|
|
// instantiator. Otherwise we get unfortunate errors like
|
|
// reached the recursion limit while instantiating `make_tag_union_recursive_help::<...n/src/def.rs:1879:65: 1879:77]>>`
|
|
#[allow(clippy::needless_collect)]
|
|
let alias_args: Vec<Type> = type_arguments.iter().map(|ta| ta.typ.clone()).collect();
|
|
let recursive_alias = Loc::at_zero(symbol);
|
|
|
|
// try to make `actual` recursive
|
|
make_tag_union_recursive_help(
|
|
env,
|
|
recursive_alias,
|
|
alias_args.into_iter(),
|
|
type_arguments.iter().cloned(),
|
|
lambda_set_variables.iter(),
|
|
infer_ext_in_output_types.iter().cloned(),
|
|
*kind,
|
|
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,
|
|
alias_kind,
|
|
region,
|
|
others,
|
|
*can_report_cyclic_error,
|
|
);
|
|
*can_report_cyclic_error = false;
|
|
|
|
Cyclic
|
|
}
|
|
}
|
|
}
|
|
|
|
fn mark_cyclic_alias(
|
|
env: &mut Env,
|
|
typ: &mut Type,
|
|
symbol: Symbol,
|
|
alias_kind: AliasKind,
|
|
region: Region,
|
|
others: Vec<Symbol>,
|
|
report: bool,
|
|
) {
|
|
*typ = Type::Error;
|
|
|
|
if report {
|
|
let problem = Problem::CyclicAlias(symbol, region, others, alias_kind);
|
|
env.problems.push(problem);
|
|
}
|
|
}
|