mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 05:49:08 +00:00
1353 lines
45 KiB
Rust
1353 lines
45 KiB
Rust
use crate::env::Env;
|
|
use crate::procedure::References;
|
|
use crate::scope::{PendingAbilitiesInScope, Scope};
|
|
use roc_collections::{ImMap, MutSet, SendMap, VecMap, VecSet};
|
|
use roc_module::ident::{Ident, Lowercase, TagName};
|
|
use roc_module::symbol::Symbol;
|
|
use roc_parse::ast::{AssignedField, ExtractSpaces, Pattern, Tag, TypeAnnotation, TypeHeader};
|
|
use roc_problem::can::ShadowKind;
|
|
use roc_region::all::{Loc, Region};
|
|
use roc_types::subs::{VarStore, Variable};
|
|
use roc_types::types::{
|
|
name_type_var, Alias, AliasCommon, AliasKind, AliasVar, LambdaSet, OptAbleType, OptAbleVar,
|
|
Problem, RecordField, Type, TypeExtension,
|
|
};
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct Annotation {
|
|
pub typ: Type,
|
|
pub introduced_variables: IntroducedVariables,
|
|
pub references: VecSet<Symbol>,
|
|
pub aliases: VecMap<Symbol, Alias>,
|
|
}
|
|
|
|
impl Annotation {
|
|
pub fn add_to(
|
|
&self,
|
|
aliases: &mut VecMap<Symbol, Alias>,
|
|
references: &mut References,
|
|
introduced_variables: &mut IntroducedVariables,
|
|
) {
|
|
for symbol in self.references.iter() {
|
|
references.insert_type_lookup(*symbol);
|
|
}
|
|
|
|
introduced_variables.union(&self.introduced_variables);
|
|
|
|
for (name, alias) in self.aliases.iter() {
|
|
if !aliases.contains_key(name) {
|
|
aliases.insert(*name, alias.clone());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub enum NamedOrAbleVariable<'a> {
|
|
Named(&'a NamedVariable),
|
|
Able(&'a AbleVariable),
|
|
}
|
|
|
|
impl<'a> NamedOrAbleVariable<'a> {
|
|
pub fn first_seen(&self) -> Region {
|
|
match self {
|
|
NamedOrAbleVariable::Named(nv) => nv.first_seen,
|
|
NamedOrAbleVariable::Able(av) => av.first_seen,
|
|
}
|
|
}
|
|
|
|
pub fn name(&self) -> &Lowercase {
|
|
match self {
|
|
NamedOrAbleVariable::Named(nv) => &nv.name,
|
|
NamedOrAbleVariable::Able(av) => &av.name,
|
|
}
|
|
}
|
|
|
|
pub fn variable(&self) -> Variable {
|
|
match self {
|
|
NamedOrAbleVariable::Named(nv) => nv.variable,
|
|
NamedOrAbleVariable::Able(av) => av.variable,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub enum OwnedNamedOrAble {
|
|
Named(NamedVariable),
|
|
Able(AbleVariable),
|
|
}
|
|
|
|
impl OwnedNamedOrAble {
|
|
pub fn first_seen(&self) -> Region {
|
|
match self {
|
|
OwnedNamedOrAble::Named(nv) => nv.first_seen,
|
|
OwnedNamedOrAble::Able(av) => av.first_seen,
|
|
}
|
|
}
|
|
|
|
pub fn ref_name(&self) -> &Lowercase {
|
|
match self {
|
|
OwnedNamedOrAble::Named(nv) => &nv.name,
|
|
OwnedNamedOrAble::Able(av) => &av.name,
|
|
}
|
|
}
|
|
|
|
pub fn name(self) -> Lowercase {
|
|
match self {
|
|
OwnedNamedOrAble::Named(nv) => nv.name,
|
|
OwnedNamedOrAble::Able(av) => av.name,
|
|
}
|
|
}
|
|
|
|
pub fn variable(&self) -> Variable {
|
|
match self {
|
|
OwnedNamedOrAble::Named(nv) => nv.variable,
|
|
OwnedNamedOrAble::Able(av) => av.variable,
|
|
}
|
|
}
|
|
|
|
pub fn opt_ability(&self) -> Option<Symbol> {
|
|
match self {
|
|
OwnedNamedOrAble::Named(_) => None,
|
|
OwnedNamedOrAble::Able(av) => Some(av.ability),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// A named type variable, not bound to an ability.
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub struct NamedVariable {
|
|
pub variable: Variable,
|
|
pub name: Lowercase,
|
|
// NB: there may be multiple occurrences of a variable
|
|
pub first_seen: Region,
|
|
}
|
|
|
|
/// A type variable bound to an ability, like "a has Hash".
|
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
pub struct AbleVariable {
|
|
pub variable: Variable,
|
|
pub name: Lowercase,
|
|
pub ability: Symbol,
|
|
// NB: there may be multiple occurrences of a variable
|
|
pub first_seen: Region,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default)]
|
|
pub struct IntroducedVariables {
|
|
pub wildcards: Vec<Loc<Variable>>,
|
|
pub lambda_sets: Vec<Variable>,
|
|
pub inferred: Vec<Loc<Variable>>,
|
|
pub named: VecSet<NamedVariable>,
|
|
pub able: VecSet<AbleVariable>,
|
|
pub host_exposed_aliases: VecMap<Symbol, Variable>,
|
|
}
|
|
|
|
impl IntroducedVariables {
|
|
#[inline(always)]
|
|
fn debug_assert_not_already_present(&self, var: Variable) {
|
|
debug_assert!((self.wildcards.iter().map(|v| &v.value))
|
|
.chain(self.lambda_sets.iter())
|
|
.chain(self.inferred.iter().map(|v| &v.value))
|
|
.chain(self.named.iter().map(|nv| &nv.variable))
|
|
.chain(self.able.iter().map(|av| &av.variable))
|
|
.chain(self.host_exposed_aliases.values())
|
|
.all(|&v| v != var));
|
|
}
|
|
|
|
pub fn insert_named(&mut self, name: Lowercase, var: Loc<Variable>) {
|
|
self.debug_assert_not_already_present(var.value);
|
|
|
|
let named_variable = NamedVariable {
|
|
name,
|
|
variable: var.value,
|
|
first_seen: var.region,
|
|
};
|
|
|
|
self.named.insert(named_variable);
|
|
}
|
|
|
|
pub fn insert_able(&mut self, name: Lowercase, var: Loc<Variable>, ability: Symbol) {
|
|
self.debug_assert_not_already_present(var.value);
|
|
|
|
let able_variable = AbleVariable {
|
|
name,
|
|
ability,
|
|
variable: var.value,
|
|
first_seen: var.region,
|
|
};
|
|
|
|
self.able.insert(able_variable);
|
|
}
|
|
|
|
pub fn insert_wildcard(&mut self, var: Loc<Variable>) {
|
|
self.debug_assert_not_already_present(var.value);
|
|
self.wildcards.push(var);
|
|
}
|
|
|
|
pub fn insert_inferred(&mut self, var: Loc<Variable>) {
|
|
self.debug_assert_not_already_present(var.value);
|
|
self.inferred.push(var);
|
|
}
|
|
|
|
pub fn insert_lambda_set(&mut self, var: Variable) {
|
|
self.debug_assert_not_already_present(var);
|
|
self.lambda_sets.push(var);
|
|
}
|
|
|
|
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
|
|
self.debug_assert_not_already_present(var);
|
|
self.host_exposed_aliases.insert(symbol, var);
|
|
}
|
|
|
|
pub fn union(&mut self, other: &Self) {
|
|
self.wildcards.extend(other.wildcards.iter().copied());
|
|
self.lambda_sets.extend(other.lambda_sets.iter().copied());
|
|
self.inferred.extend(other.inferred.iter().copied());
|
|
self.host_exposed_aliases
|
|
.extend(other.host_exposed_aliases.iter().map(|(k, v)| (*k, *v)));
|
|
|
|
self.named.extend(other.named.iter().cloned());
|
|
self.able.extend(other.able.iter().cloned());
|
|
}
|
|
|
|
pub fn union_owned(&mut self, other: Self) {
|
|
self.wildcards.extend(other.wildcards);
|
|
self.lambda_sets.extend(other.lambda_sets);
|
|
self.inferred.extend(other.inferred);
|
|
self.host_exposed_aliases.extend(other.host_exposed_aliases);
|
|
|
|
self.named.extend(other.named);
|
|
self.able.extend(other.able.iter().cloned());
|
|
}
|
|
|
|
pub fn var_by_name(&self, name: &Lowercase) -> Option<Variable> {
|
|
(self.named.iter().map(|nv| (&nv.name, nv.variable)))
|
|
.chain(self.able.iter().map(|av| (&av.name, av.variable)))
|
|
.find(|(cand, _)| cand == &name)
|
|
.map(|(_, var)| var)
|
|
}
|
|
|
|
pub fn iter_named(&self) -> impl Iterator<Item = NamedOrAbleVariable> {
|
|
(self.named.iter().map(NamedOrAbleVariable::Named))
|
|
.chain(self.able.iter().map(NamedOrAbleVariable::Able))
|
|
}
|
|
|
|
pub fn named_var_by_name(&self, name: &Lowercase) -> Option<NamedOrAbleVariable> {
|
|
self.iter_named().find(|v| v.name() == name)
|
|
}
|
|
|
|
pub fn collect_able(&self) -> Vec<Variable> {
|
|
self.able.iter().map(|av| av.variable).collect()
|
|
}
|
|
|
|
pub fn collect_rigid(&self) -> Vec<Variable> {
|
|
(self.named.iter().map(|nv| nv.variable))
|
|
.chain(self.wildcards.iter().map(|wc| wc.value))
|
|
// For our purposes, lambda set vars are treated like rigids
|
|
.chain(self.lambda_sets.iter().copied())
|
|
.collect()
|
|
}
|
|
|
|
pub fn collect_flex(&self) -> Vec<Variable> {
|
|
self.inferred.iter().map(|iv| iv.value).collect()
|
|
}
|
|
}
|
|
|
|
fn malformed(env: &mut Env, region: Region, name: &str) {
|
|
use roc_problem::can::RuntimeError::*;
|
|
|
|
let problem = MalformedTypeName((*name).into(), region);
|
|
env.problem(roc_problem::can::Problem::RuntimeError(problem));
|
|
}
|
|
|
|
/// Canonicalizes a top-level type annotation.
|
|
pub fn canonicalize_annotation(
|
|
env: &mut Env,
|
|
scope: &mut Scope,
|
|
annotation: &TypeAnnotation,
|
|
region: Region,
|
|
var_store: &mut VarStore,
|
|
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
|
) -> Annotation {
|
|
let mut introduced_variables = IntroducedVariables::default();
|
|
let mut references = VecSet::default();
|
|
let mut aliases = VecMap::default();
|
|
|
|
let (annotation, region) = match annotation {
|
|
TypeAnnotation::Where(annotation, clauses) => {
|
|
// Add each "has" clause. The association of a variable to an ability will be saved on
|
|
// `introduced_variables`, which we'll process later.
|
|
for clause in clauses.iter() {
|
|
let opt_err = canonicalize_has_clause(
|
|
env,
|
|
scope,
|
|
var_store,
|
|
&mut introduced_variables,
|
|
clause,
|
|
pending_abilities_in_scope,
|
|
&mut references,
|
|
);
|
|
if let Err(err_type) = opt_err {
|
|
return Annotation {
|
|
typ: err_type,
|
|
introduced_variables,
|
|
references,
|
|
aliases,
|
|
};
|
|
}
|
|
}
|
|
(&annotation.value, annotation.region)
|
|
}
|
|
annot => (annot, region),
|
|
};
|
|
|
|
let typ = can_annotation_help(
|
|
env,
|
|
annotation,
|
|
region,
|
|
scope,
|
|
var_store,
|
|
&mut introduced_variables,
|
|
&mut aliases,
|
|
&mut references,
|
|
);
|
|
|
|
Annotation {
|
|
typ,
|
|
introduced_variables,
|
|
references,
|
|
aliases,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn make_apply_symbol(
|
|
env: &mut Env,
|
|
region: Region,
|
|
scope: &mut Scope,
|
|
module_name: &str,
|
|
ident: &str,
|
|
) -> Result<Symbol, Type> {
|
|
if module_name.is_empty() {
|
|
// Since module_name was empty, this is an unqualified type.
|
|
// Look it up in scope!
|
|
|
|
match scope.lookup_str(ident, region) {
|
|
Ok(symbol) => Ok(symbol),
|
|
Err(problem) => {
|
|
env.problem(roc_problem::can::Problem::RuntimeError(problem));
|
|
|
|
let ident: Ident = (*ident).into();
|
|
Err(Type::Erroneous(Problem::UnrecognizedIdent(ident)))
|
|
}
|
|
}
|
|
} else {
|
|
match env.qualified_lookup(scope, module_name, ident, region) {
|
|
Ok(symbol) => Ok(symbol),
|
|
Err(problem) => {
|
|
// Either the module wasn't imported, or
|
|
// it was imported but it doesn't expose this ident.
|
|
env.problem(roc_problem::can::Problem::RuntimeError(problem));
|
|
|
|
// A failed import should have already been reported through
|
|
// roc_can::env::Env::qualified_lookup's checks
|
|
Err(Type::Erroneous(Problem::SolvedTypeError))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Retrieves all symbols in an annotations that reference a type definition, that is either an
|
|
/// alias or an opaque type.
|
|
///
|
|
/// For example, in `[A Age U8, B Str {}]`, there are three type definition references - `Age`,
|
|
/// `U8`, and `Str`.
|
|
pub fn find_type_def_symbols(
|
|
scope: &mut Scope,
|
|
initial_annotation: &roc_parse::ast::TypeAnnotation,
|
|
) -> Vec<Symbol> {
|
|
use roc_parse::ast::TypeAnnotation::*;
|
|
|
|
let mut result = Vec::new();
|
|
|
|
let mut stack = vec![initial_annotation];
|
|
|
|
while let Some(annotation) = stack.pop() {
|
|
match annotation {
|
|
Apply(_module_name, ident, arguments) => {
|
|
let ident: Ident = (*ident).into();
|
|
let symbol = scope.scopeless_symbol(&ident, Region::zero());
|
|
|
|
result.push(symbol);
|
|
|
|
for t in arguments.iter() {
|
|
stack.push(&t.value);
|
|
}
|
|
}
|
|
Function(arguments, result) => {
|
|
for t in arguments.iter() {
|
|
stack.push(&t.value);
|
|
}
|
|
|
|
stack.push(&result.value);
|
|
}
|
|
BoundVariable(_) => {}
|
|
As(actual, _, _) => {
|
|
stack.push(&actual.value);
|
|
}
|
|
Record { fields, ext } => {
|
|
let mut inner_stack = Vec::with_capacity(fields.items.len());
|
|
|
|
for field in fields.items.iter() {
|
|
inner_stack.push(&field.value)
|
|
}
|
|
|
|
while let Some(assigned_field) = inner_stack.pop() {
|
|
match assigned_field {
|
|
AssignedField::RequiredValue(_, _, t)
|
|
| AssignedField::OptionalValue(_, _, t) => {
|
|
stack.push(&t.value);
|
|
}
|
|
AssignedField::LabelOnly(_) => {}
|
|
AssignedField::SpaceBefore(inner, _)
|
|
| AssignedField::SpaceAfter(inner, _) => inner_stack.push(inner),
|
|
AssignedField::Malformed(_) => {}
|
|
}
|
|
}
|
|
|
|
for t in ext.iter() {
|
|
stack.push(&t.value);
|
|
}
|
|
}
|
|
TagUnion { ext, tags } => {
|
|
let mut inner_stack = Vec::with_capacity(tags.items.len());
|
|
|
|
for tag in tags.items.iter() {
|
|
inner_stack.push(&tag.value)
|
|
}
|
|
|
|
while let Some(tag) = inner_stack.pop() {
|
|
match tag {
|
|
Tag::Apply { args, .. } => {
|
|
for t in args.iter() {
|
|
stack.push(&t.value);
|
|
}
|
|
}
|
|
Tag::SpaceBefore(inner, _) | Tag::SpaceAfter(inner, _) => {
|
|
inner_stack.push(inner)
|
|
}
|
|
Tag::Malformed(_) => {}
|
|
}
|
|
}
|
|
|
|
for t in ext.iter() {
|
|
stack.push(&t.value);
|
|
}
|
|
}
|
|
SpaceBefore(inner, _) | SpaceAfter(inner, _) => {
|
|
stack.push(inner);
|
|
}
|
|
Where(annotation, clauses) => {
|
|
stack.push(&annotation.value);
|
|
|
|
for has_clause in clauses.iter() {
|
|
stack.push(&has_clause.value.ability.value);
|
|
}
|
|
}
|
|
Inferred | Wildcard | Malformed(_) => {}
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
fn find_fresh_var_name(introduced_variables: &IntroducedVariables) -> Lowercase {
|
|
name_type_var(0, &mut introduced_variables.iter_named(), |var, str| {
|
|
var.name().as_str() == str
|
|
})
|
|
.0
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn can_annotation_help(
|
|
env: &mut Env,
|
|
annotation: &roc_parse::ast::TypeAnnotation,
|
|
region: Region,
|
|
scope: &mut Scope,
|
|
var_store: &mut VarStore,
|
|
introduced_variables: &mut IntroducedVariables,
|
|
local_aliases: &mut VecMap<Symbol, Alias>,
|
|
references: &mut VecSet<Symbol>,
|
|
) -> Type {
|
|
use roc_parse::ast::TypeAnnotation::*;
|
|
|
|
match annotation {
|
|
Function(argument_types, return_type) => {
|
|
let mut args = Vec::new();
|
|
|
|
for arg in *argument_types {
|
|
let arg_ann = can_annotation_help(
|
|
env,
|
|
&arg.value,
|
|
arg.region,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
);
|
|
|
|
args.push(arg_ann);
|
|
}
|
|
|
|
let ret = can_annotation_help(
|
|
env,
|
|
&return_type.value,
|
|
return_type.region,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
);
|
|
|
|
let lambda_set = var_store.fresh();
|
|
introduced_variables.insert_lambda_set(lambda_set);
|
|
let closure = Type::Variable(lambda_set);
|
|
|
|
Type::Function(args, Box::new(closure), Box::new(ret))
|
|
}
|
|
Apply(module_name, ident, type_arguments) => {
|
|
let symbol = match make_apply_symbol(env, region, scope, module_name, ident) {
|
|
Err(problem) => return problem,
|
|
Ok(symbol) => symbol,
|
|
};
|
|
|
|
let mut args = Vec::new();
|
|
|
|
references.insert(symbol);
|
|
|
|
if scope.abilities_store.is_ability(symbol) {
|
|
let fresh_ty_var = find_fresh_var_name(introduced_variables);
|
|
|
|
env.problem(roc_problem::can::Problem::AbilityUsedAsType(
|
|
fresh_ty_var.clone(),
|
|
symbol,
|
|
region,
|
|
));
|
|
|
|
// Generate an variable bound to the ability so we can keep compiling.
|
|
let var = var_store.fresh();
|
|
introduced_variables.insert_able(fresh_ty_var, Loc::at(region, var), symbol);
|
|
return Type::Variable(var);
|
|
}
|
|
|
|
for arg in *type_arguments {
|
|
let arg_ann = can_annotation_help(
|
|
env,
|
|
&arg.value,
|
|
arg.region,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
);
|
|
|
|
args.push(arg_ann);
|
|
}
|
|
|
|
match scope.lookup_alias(symbol) {
|
|
Some(alias) => {
|
|
// use a known alias
|
|
|
|
if alias.type_variables.len() != args.len() {
|
|
let error = Type::Erroneous(Problem::BadTypeArguments {
|
|
symbol,
|
|
region,
|
|
alias_needs: alias.type_variables.len() as u8,
|
|
type_got: args.len() as u8,
|
|
alias_kind: alias.kind,
|
|
});
|
|
return error;
|
|
}
|
|
|
|
let mut type_var_to_arg = Vec::new();
|
|
|
|
for (_, arg_ann) in alias.type_variables.iter().zip(args) {
|
|
type_var_to_arg.push(arg_ann);
|
|
}
|
|
|
|
let mut lambda_set_variables =
|
|
Vec::with_capacity(alias.lambda_set_variables.len());
|
|
|
|
for _ in 0..alias.lambda_set_variables.len() {
|
|
let lvar = var_store.fresh();
|
|
|
|
introduced_variables.insert_lambda_set(lvar);
|
|
|
|
lambda_set_variables.push(LambdaSet(Type::Variable(lvar)));
|
|
}
|
|
|
|
Type::DelayedAlias(AliasCommon {
|
|
symbol,
|
|
type_arguments: type_var_to_arg,
|
|
lambda_set_variables,
|
|
})
|
|
}
|
|
None => Type::Apply(symbol, args, region),
|
|
}
|
|
}
|
|
BoundVariable(v) => {
|
|
let name = Lowercase::from(*v);
|
|
|
|
match introduced_variables.var_by_name(&name) {
|
|
Some(var) => Type::Variable(var),
|
|
None => {
|
|
let var = var_store.fresh();
|
|
|
|
introduced_variables.insert_named(name, Loc::at(region, var));
|
|
|
|
Type::Variable(var)
|
|
}
|
|
}
|
|
}
|
|
As(
|
|
loc_inner,
|
|
_spaces,
|
|
alias_header @ TypeHeader {
|
|
name,
|
|
vars: loc_vars,
|
|
},
|
|
) => {
|
|
let symbol = match scope.introduce(name.value.into(), region) {
|
|
Ok(symbol) => symbol,
|
|
|
|
Err((shadowed_symbol, shadow, _new_symbol)) => {
|
|
let problem = Problem::Shadowed(shadowed_symbol.region, shadow.clone());
|
|
|
|
env.problem(roc_problem::can::Problem::Shadowing {
|
|
original_region: shadowed_symbol.region,
|
|
shadow,
|
|
kind: ShadowKind::Variable,
|
|
});
|
|
|
|
return Type::Erroneous(problem);
|
|
}
|
|
};
|
|
|
|
let inner_type = can_annotation_help(
|
|
env,
|
|
&loc_inner.value,
|
|
region,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
);
|
|
let mut vars = Vec::with_capacity(loc_vars.len());
|
|
let mut lowercase_vars: Vec<Loc<AliasVar>> = Vec::with_capacity(loc_vars.len());
|
|
|
|
references.insert(symbol);
|
|
|
|
for loc_var in *loc_vars {
|
|
let var = match loc_var.value {
|
|
Pattern::Identifier(name) if name.chars().next().unwrap().is_lowercase() => {
|
|
name
|
|
}
|
|
_ => unreachable!("I thought this was validated during parsing"),
|
|
};
|
|
let var_name = Lowercase::from(var);
|
|
|
|
// TODO(abilities): check that there are no abilities bound here.
|
|
if let Some(var) = introduced_variables.var_by_name(&var_name) {
|
|
vars.push(Type::Variable(var));
|
|
lowercase_vars.push(Loc::at(
|
|
loc_var.region,
|
|
AliasVar {
|
|
name: var_name,
|
|
var,
|
|
opt_bound_ability: None,
|
|
},
|
|
));
|
|
} else {
|
|
let var = var_store.fresh();
|
|
|
|
introduced_variables
|
|
.insert_named(var_name.clone(), Loc::at(loc_var.region, var));
|
|
vars.push(Type::Variable(var));
|
|
|
|
lowercase_vars.push(Loc::at(
|
|
loc_var.region,
|
|
AliasVar {
|
|
name: var_name,
|
|
var,
|
|
opt_bound_ability: None,
|
|
},
|
|
));
|
|
}
|
|
}
|
|
|
|
let alias_args = vars.clone();
|
|
|
|
let alias_actual = if let Type::TagUnion(tags, ext) = inner_type {
|
|
let rec_var = var_store.fresh();
|
|
|
|
let mut new_tags = Vec::with_capacity(tags.len());
|
|
let mut is_nested_datatype = false;
|
|
for (tag_name, args) in tags {
|
|
let mut new_args = Vec::with_capacity(args.len());
|
|
for arg in args {
|
|
let mut new_arg = arg.clone();
|
|
let substitution_result =
|
|
new_arg.substitute_alias(symbol, &alias_args, &Type::Variable(rec_var));
|
|
|
|
if let Err(differing_recursion_region) = substitution_result {
|
|
env.problems
|
|
.push(roc_problem::can::Problem::NestedDatatype {
|
|
alias: symbol,
|
|
def_region: alias_header.region(),
|
|
differing_recursion_region,
|
|
});
|
|
is_nested_datatype = true;
|
|
}
|
|
|
|
// Either way, add the argument; not doing so would only result in more
|
|
// confusing error messages later on.
|
|
new_args.push(new_arg);
|
|
}
|
|
new_tags.push((tag_name.clone(), new_args));
|
|
}
|
|
if is_nested_datatype {
|
|
// We don't have a way to represent nested data types; hence, we don't actually
|
|
// use the recursion var in them, and should avoid marking them as such.
|
|
Type::TagUnion(new_tags, ext)
|
|
} else {
|
|
Type::RecursiveTagUnion(rec_var, new_tags, ext)
|
|
}
|
|
} else {
|
|
inner_type
|
|
};
|
|
|
|
let mut hidden_variables = MutSet::default();
|
|
hidden_variables.extend(alias_actual.variables());
|
|
|
|
for loc_var in lowercase_vars.iter() {
|
|
hidden_variables.remove(&loc_var.value.var);
|
|
}
|
|
|
|
scope.add_alias(
|
|
symbol,
|
|
region,
|
|
lowercase_vars,
|
|
alias_actual,
|
|
AliasKind::Structural, // aliases in "as" are never opaque
|
|
);
|
|
|
|
let alias = scope.lookup_alias(symbol).unwrap();
|
|
local_aliases.insert(symbol, alias.clone());
|
|
|
|
if vars.is_empty() && env.home == symbol.module_id() {
|
|
let actual_var = var_store.fresh();
|
|
introduced_variables.insert_host_exposed_alias(symbol, actual_var);
|
|
Type::HostExposedAlias {
|
|
name: symbol,
|
|
type_arguments: vars,
|
|
lambda_set_variables: alias.lambda_set_variables.clone(),
|
|
actual: Box::new(alias.typ.clone()),
|
|
actual_var,
|
|
}
|
|
} else {
|
|
Type::Alias {
|
|
symbol,
|
|
type_arguments: vars
|
|
.into_iter()
|
|
.map(|typ| OptAbleType {
|
|
typ,
|
|
opt_ability: None,
|
|
})
|
|
.collect(),
|
|
lambda_set_variables: alias.lambda_set_variables.clone(),
|
|
actual: Box::new(alias.typ.clone()),
|
|
kind: alias.kind,
|
|
}
|
|
}
|
|
}
|
|
|
|
Record { fields, ext } => {
|
|
let ext_type = can_extension_type(
|
|
env,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
ext,
|
|
roc_problem::can::ExtensionTypeKind::Record,
|
|
);
|
|
|
|
if fields.is_empty() {
|
|
match ext {
|
|
Some(_) => {
|
|
// just `a` does not mean the same as `{}a`, so even
|
|
// if there are no fields, still make this a `Record`,
|
|
// not an EmptyRec
|
|
Type::Record(Default::default(), TypeExtension::from_type(ext_type))
|
|
}
|
|
|
|
None => Type::EmptyRec,
|
|
}
|
|
} else {
|
|
let field_types = can_assigned_fields(
|
|
env,
|
|
&fields.items,
|
|
region,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
);
|
|
|
|
Type::Record(field_types, TypeExtension::from_type(ext_type))
|
|
}
|
|
}
|
|
TagUnion { tags, ext, .. } => {
|
|
let ext_type = can_extension_type(
|
|
env,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
ext,
|
|
roc_problem::can::ExtensionTypeKind::TagUnion,
|
|
);
|
|
|
|
if tags.is_empty() {
|
|
match ext {
|
|
Some(_) => {
|
|
// just `a` does not mean the same as `{}a`, so even
|
|
// if there are no fields, still make this a `Record`,
|
|
// not an EmptyRec
|
|
Type::TagUnion(Default::default(), TypeExtension::from_type(ext_type))
|
|
}
|
|
|
|
None => Type::EmptyTagUnion,
|
|
}
|
|
} else {
|
|
let mut tag_types = can_tags(
|
|
env,
|
|
tags.items,
|
|
region,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
);
|
|
|
|
// sort here; we later instantiate type aliases, so this type might get duplicated
|
|
// many times. Then, when inserting into the subs, the tags are sorted.
|
|
// in theory we save a lot of time by sorting once here
|
|
insertion_sort_by(&mut tag_types, |a, b| a.0.cmp(&b.0));
|
|
|
|
Type::TagUnion(tag_types, TypeExtension::from_type(ext_type))
|
|
}
|
|
}
|
|
SpaceBefore(nested, _) | SpaceAfter(nested, _) => can_annotation_help(
|
|
env,
|
|
nested,
|
|
region,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
),
|
|
Wildcard => {
|
|
let var = var_store.fresh();
|
|
|
|
introduced_variables.insert_wildcard(Loc::at(region, var));
|
|
|
|
Type::Variable(var)
|
|
}
|
|
Inferred => {
|
|
// Inference variables aren't bound to a rigid or a wildcard, so all we have to do is
|
|
// make a fresh unconstrained variable, and let the type solver fill it in for us 🤠
|
|
let var = var_store.fresh();
|
|
|
|
introduced_variables.insert_inferred(Loc::at(region, var));
|
|
|
|
Type::Variable(var)
|
|
}
|
|
Where(_annotation, clauses) => {
|
|
debug_assert!(!clauses.is_empty());
|
|
|
|
// Has clauses are allowed only on the top level of a signature, which we handle elsewhere.
|
|
env.problem(roc_problem::can::Problem::IllegalHasClause {
|
|
region: Region::across_all(clauses.iter().map(|clause| &clause.region)),
|
|
});
|
|
|
|
Type::Erroneous(Problem::CanonicalizationProblem)
|
|
}
|
|
Malformed(string) => {
|
|
malformed(env, region, string);
|
|
|
|
let var = var_store.fresh();
|
|
|
|
introduced_variables.insert_wildcard(Loc::at(region, var));
|
|
|
|
Type::Variable(var)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn canonicalize_has_clause(
|
|
env: &mut Env,
|
|
scope: &mut Scope,
|
|
var_store: &mut VarStore,
|
|
introduced_variables: &mut IntroducedVariables,
|
|
clause: &Loc<roc_parse::ast::HasClause<'_>>,
|
|
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
|
references: &mut VecSet<Symbol>,
|
|
) -> Result<(), Type> {
|
|
let Loc {
|
|
region,
|
|
value: roc_parse::ast::HasClause { var, ability },
|
|
} = clause;
|
|
let region = *region;
|
|
|
|
let var_name = var.extract_spaces().item;
|
|
debug_assert!(
|
|
var_name.starts_with(char::is_lowercase),
|
|
"Vars should have been parsed as lowercase"
|
|
);
|
|
let var_name = Lowercase::from(var_name);
|
|
|
|
let ability = match ability.value {
|
|
TypeAnnotation::Apply(module_name, ident, _type_arguments) => {
|
|
let symbol = make_apply_symbol(env, ability.region, scope, module_name, ident)?;
|
|
|
|
// Ability defined locally, whose members we are constructing right now...
|
|
if !pending_abilities_in_scope.contains_key(&symbol)
|
|
// or an ability that was imported from elsewhere
|
|
&& !scope.abilities_store.is_ability(symbol)
|
|
{
|
|
let region = ability.region;
|
|
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
|
|
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
|
|
}
|
|
symbol
|
|
}
|
|
_ => {
|
|
let region = ability.region;
|
|
env.problem(roc_problem::can::Problem::HasClauseIsNotAbility { region });
|
|
return Err(Type::Erroneous(Problem::HasClauseIsNotAbility(region)));
|
|
}
|
|
};
|
|
|
|
references.insert(ability);
|
|
|
|
if let Some(shadowing) = introduced_variables.named_var_by_name(&var_name) {
|
|
let var_name_ident = var_name.to_string().into();
|
|
let shadow = Loc::at(region, var_name_ident);
|
|
env.problem(roc_problem::can::Problem::Shadowing {
|
|
original_region: shadowing.first_seen(),
|
|
shadow: shadow.clone(),
|
|
kind: ShadowKind::Variable,
|
|
});
|
|
return Err(Type::Erroneous(Problem::Shadowed(
|
|
shadowing.first_seen(),
|
|
shadow,
|
|
)));
|
|
}
|
|
|
|
let var = var_store.fresh();
|
|
|
|
introduced_variables.insert_able(var_name, Loc::at(region, var), ability);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn can_extension_type<'a>(
|
|
env: &mut Env,
|
|
scope: &mut Scope,
|
|
var_store: &mut VarStore,
|
|
introduced_variables: &mut IntroducedVariables,
|
|
local_aliases: &mut VecMap<Symbol, Alias>,
|
|
references: &mut VecSet<Symbol>,
|
|
opt_ext: &Option<&Loc<TypeAnnotation<'a>>>,
|
|
ext_problem_kind: roc_problem::can::ExtensionTypeKind,
|
|
) -> Type {
|
|
fn valid_record_ext_type(typ: &Type) -> bool {
|
|
// Include erroneous types so that we don't overreport errors.
|
|
matches!(
|
|
typ,
|
|
Type::EmptyRec | Type::Record(..) | Type::Variable(..) | Type::Erroneous(..)
|
|
)
|
|
}
|
|
fn valid_tag_ext_type(typ: &Type) -> bool {
|
|
matches!(
|
|
typ,
|
|
Type::EmptyTagUnion | Type::TagUnion(..) | Type::Variable(..) | Type::Erroneous(..)
|
|
)
|
|
}
|
|
|
|
use roc_problem::can::ExtensionTypeKind;
|
|
|
|
let (empty_ext_type, valid_extension_type): (_, fn(&Type) -> bool) = match ext_problem_kind {
|
|
ExtensionTypeKind::Record => (Type::EmptyRec, valid_record_ext_type),
|
|
ExtensionTypeKind::TagUnion => (Type::EmptyTagUnion, valid_tag_ext_type),
|
|
};
|
|
|
|
match opt_ext {
|
|
Some(loc_ann) => {
|
|
let ext_type = can_annotation_help(
|
|
env,
|
|
&loc_ann.value,
|
|
loc_ann.region,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
);
|
|
if valid_extension_type(shallow_dealias_with_scope(scope, &ext_type)) {
|
|
ext_type
|
|
} else {
|
|
// Report an error but mark the extension variable to be inferred
|
|
// so that we're as permissive as possible.
|
|
//
|
|
// THEORY: invalid extension types can appear in this position. Otherwise
|
|
// they would be caught as errors during unification.
|
|
env.problem(roc_problem::can::Problem::InvalidExtensionType {
|
|
region: loc_ann.region,
|
|
kind: ext_problem_kind,
|
|
});
|
|
|
|
let var = var_store.fresh();
|
|
|
|
introduced_variables.insert_inferred(Loc::at_zero(var));
|
|
|
|
Type::Variable(var)
|
|
}
|
|
}
|
|
None => empty_ext_type,
|
|
}
|
|
}
|
|
|
|
/// a shallow dealias, continue until the first constructor is not an alias.
|
|
fn shallow_dealias_with_scope<'a>(scope: &'a mut Scope, typ: &'a Type) -> &'a Type {
|
|
let mut result = typ;
|
|
loop {
|
|
match result {
|
|
Type::Alias { actual, .. } => {
|
|
// another loop
|
|
result = actual;
|
|
}
|
|
Type::DelayedAlias(AliasCommon { symbol, .. }) => match scope.lookup_alias(*symbol) {
|
|
None => unreachable!(),
|
|
Some(alias) => {
|
|
result = &alias.typ;
|
|
}
|
|
},
|
|
|
|
_ => break,
|
|
}
|
|
}
|
|
|
|
result
|
|
}
|
|
|
|
pub fn instantiate_and_freshen_alias_type(
|
|
var_store: &mut VarStore,
|
|
introduced_variables: &mut IntroducedVariables,
|
|
type_variables: &[Loc<AliasVar>],
|
|
type_arguments: Vec<Type>,
|
|
lambda_set_variables: &[LambdaSet],
|
|
mut actual_type: Type,
|
|
) -> (Vec<(Lowercase, Type)>, Vec<LambdaSet>, Type) {
|
|
let mut substitutions = ImMap::default();
|
|
let mut type_var_to_arg = Vec::new();
|
|
|
|
for (loc_var, arg_ann) in type_variables.iter().zip(type_arguments.into_iter()) {
|
|
let name = loc_var.value.name.clone();
|
|
let var = loc_var.value.var;
|
|
|
|
substitutions.insert(var, arg_ann.clone());
|
|
type_var_to_arg.push((name.clone(), arg_ann));
|
|
}
|
|
|
|
// make sure the recursion variable is freshly instantiated
|
|
if let Type::RecursiveTagUnion(rvar, _, _) = &mut actual_type {
|
|
let new = var_store.fresh();
|
|
substitutions.insert(*rvar, Type::Variable(new));
|
|
*rvar = new;
|
|
}
|
|
|
|
// make sure hidden variables are freshly instantiated
|
|
let mut new_lambda_set_variables = Vec::with_capacity(lambda_set_variables.len());
|
|
for typ in lambda_set_variables.iter() {
|
|
if let Type::Variable(var) = typ.0 {
|
|
let fresh = var_store.fresh();
|
|
substitutions.insert(var, Type::Variable(fresh));
|
|
introduced_variables.insert_lambda_set(fresh);
|
|
new_lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
|
|
} else {
|
|
unreachable!("at this point there should be only vars in there");
|
|
}
|
|
}
|
|
|
|
// instantiate variables
|
|
actual_type.substitute(&substitutions);
|
|
|
|
(type_var_to_arg, new_lambda_set_variables, actual_type)
|
|
}
|
|
|
|
pub fn freshen_opaque_def(
|
|
var_store: &mut VarStore,
|
|
opaque: &Alias,
|
|
) -> (Vec<OptAbleVar>, Vec<LambdaSet>, Type) {
|
|
debug_assert!(opaque.kind == AliasKind::Opaque);
|
|
|
|
let fresh_variables: Vec<OptAbleVar> = opaque
|
|
.type_variables
|
|
.iter()
|
|
.map(|alias_var| OptAbleVar {
|
|
var: var_store.fresh(),
|
|
opt_ability: alias_var.value.opt_bound_ability,
|
|
})
|
|
.collect();
|
|
|
|
let fresh_type_arguments = fresh_variables
|
|
.iter()
|
|
.map(|av| Type::Variable(av.var))
|
|
.collect();
|
|
|
|
// NB: We don't introduce the fresh variables here, we introduce them during constraint gen.
|
|
// NB: If there are bugs, check whether this is a problem!
|
|
let mut introduced_variables = IntroducedVariables::default();
|
|
|
|
let (_fresh_type_arguments, fresh_lambda_set, fresh_type) = instantiate_and_freshen_alias_type(
|
|
var_store,
|
|
&mut introduced_variables,
|
|
&opaque.type_variables,
|
|
fresh_type_arguments,
|
|
&opaque.lambda_set_variables,
|
|
opaque.typ.clone(),
|
|
);
|
|
|
|
(fresh_variables, fresh_lambda_set, fresh_type)
|
|
}
|
|
|
|
fn insertion_sort_by<T, F>(arr: &mut [T], mut compare: F)
|
|
where
|
|
F: FnMut(&T, &T) -> std::cmp::Ordering,
|
|
{
|
|
for i in 1..arr.len() {
|
|
let val = &arr[i];
|
|
let mut j = i;
|
|
let pos = arr[..i]
|
|
.binary_search_by(|x| compare(x, val))
|
|
.unwrap_or_else(|pos| pos);
|
|
// Swap all elements until specific position.
|
|
while j > pos {
|
|
arr.swap(j - 1, j);
|
|
j -= 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO trim down these arguments!
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn can_assigned_fields<'a>(
|
|
env: &mut Env,
|
|
fields: &&[Loc<AssignedField<'a, TypeAnnotation<'a>>>],
|
|
region: Region,
|
|
scope: &mut Scope,
|
|
var_store: &mut VarStore,
|
|
introduced_variables: &mut IntroducedVariables,
|
|
local_aliases: &mut VecMap<Symbol, Alias>,
|
|
references: &mut VecSet<Symbol>,
|
|
) -> SendMap<Lowercase, RecordField<Type>> {
|
|
use roc_parse::ast::AssignedField::*;
|
|
use roc_types::types::RecordField::*;
|
|
|
|
// SendMap doesn't have a `with_capacity`
|
|
let mut field_types = SendMap::default();
|
|
|
|
// field names we've seen so far in this record
|
|
let mut seen = std::collections::HashMap::with_capacity(fields.len());
|
|
|
|
'outer: for loc_field in fields.iter() {
|
|
let mut field = &loc_field.value;
|
|
|
|
// use this inner loop to unwrap the SpaceAfter/SpaceBefore
|
|
// when we find the name of this field, break out of the loop
|
|
// with that value, so we can check whether the field name is
|
|
// a duplicate
|
|
let new_name = 'inner: loop {
|
|
match field {
|
|
RequiredValue(field_name, _, annotation) => {
|
|
let field_type = can_annotation_help(
|
|
env,
|
|
&annotation.value,
|
|
annotation.region,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
);
|
|
|
|
let label = Lowercase::from(field_name.value);
|
|
field_types.insert(label.clone(), Required(field_type));
|
|
|
|
break 'inner label;
|
|
}
|
|
OptionalValue(field_name, _, annotation) => {
|
|
let field_type = can_annotation_help(
|
|
env,
|
|
&annotation.value,
|
|
annotation.region,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
);
|
|
|
|
let label = Lowercase::from(field_name.value);
|
|
field_types.insert(label.clone(), RigidOptional(field_type));
|
|
|
|
break 'inner label;
|
|
}
|
|
LabelOnly(loc_field_name) => {
|
|
// Interpret { a, b } as { a : a, b : b }
|
|
let field_name = Lowercase::from(loc_field_name.value);
|
|
let field_type = {
|
|
if let Some(var) = introduced_variables.var_by_name(&field_name) {
|
|
Type::Variable(var)
|
|
} else {
|
|
let field_var = var_store.fresh();
|
|
introduced_variables.insert_named(
|
|
field_name.clone(),
|
|
Loc::at(loc_field_name.region, field_var),
|
|
);
|
|
Type::Variable(field_var)
|
|
}
|
|
};
|
|
|
|
field_types.insert(field_name.clone(), Required(field_type));
|
|
|
|
break 'inner field_name;
|
|
}
|
|
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
|
|
// check the nested field instead
|
|
field = nested;
|
|
continue 'inner;
|
|
}
|
|
Malformed(string) => {
|
|
malformed(env, region, string);
|
|
|
|
// completely skip this element, advance to the next tag
|
|
continue 'outer;
|
|
}
|
|
}
|
|
};
|
|
|
|
// ensure that the new name is not already in this record:
|
|
// note that the right-most tag wins when there are two with the same name
|
|
if let Some(replaced_region) = seen.insert(new_name.clone(), loc_field.region) {
|
|
env.problem(roc_problem::can::Problem::DuplicateRecordFieldType {
|
|
field_name: new_name,
|
|
record_region: region,
|
|
field_region: loc_field.region,
|
|
replaced_region,
|
|
});
|
|
}
|
|
}
|
|
|
|
field_types
|
|
}
|
|
|
|
// TODO trim down these arguments!
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn can_tags<'a>(
|
|
env: &mut Env,
|
|
tags: &'a [Loc<Tag<'a>>],
|
|
region: Region,
|
|
scope: &mut Scope,
|
|
var_store: &mut VarStore,
|
|
introduced_variables: &mut IntroducedVariables,
|
|
local_aliases: &mut VecMap<Symbol, Alias>,
|
|
references: &mut VecSet<Symbol>,
|
|
) -> Vec<(TagName, Vec<Type>)> {
|
|
let mut tag_types = Vec::with_capacity(tags.len());
|
|
|
|
// tag names we've seen so far in this tag union
|
|
let mut seen = std::collections::HashMap::with_capacity(tags.len());
|
|
|
|
'outer: for loc_tag in tags.iter() {
|
|
let mut tag = &loc_tag.value;
|
|
|
|
// use this inner loop to unwrap the SpaceAfter/SpaceBefore
|
|
// when we find the name of this tag, break out of the loop
|
|
// with that value, so we can check whether the tag name is
|
|
// a duplicate
|
|
let new_name = 'inner: loop {
|
|
match tag {
|
|
Tag::Apply { name, args } => {
|
|
let name = name.value.into();
|
|
let mut arg_types = Vec::with_capacity(args.len());
|
|
|
|
for arg in args.iter() {
|
|
let ann = can_annotation_help(
|
|
env,
|
|
&arg.value,
|
|
arg.region,
|
|
scope,
|
|
var_store,
|
|
introduced_variables,
|
|
local_aliases,
|
|
references,
|
|
);
|
|
|
|
arg_types.push(ann);
|
|
}
|
|
|
|
let tag_name = TagName(name);
|
|
tag_types.push((tag_name.clone(), arg_types));
|
|
|
|
break 'inner tag_name;
|
|
}
|
|
Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => {
|
|
// check the nested tag instead
|
|
tag = nested;
|
|
continue 'inner;
|
|
}
|
|
Tag::Malformed(string) => {
|
|
malformed(env, region, string);
|
|
|
|
// completely skip this element, advance to the next tag
|
|
continue 'outer;
|
|
}
|
|
}
|
|
};
|
|
|
|
// ensure that the new name is not already in this tag union:
|
|
// note that the right-most tag wins when there are two with the same name
|
|
if let Some(replaced_region) = seen.insert(new_name.clone(), loc_tag.region) {
|
|
env.problem(roc_problem::can::Problem::DuplicateTag {
|
|
tag_name: new_name,
|
|
tag_region: loc_tag.region,
|
|
tag_union_region: region,
|
|
replaced_region,
|
|
});
|
|
}
|
|
}
|
|
|
|
tag_types
|
|
}
|