roc/compiler/can/src/annotation.rs

687 lines
24 KiB
Rust

use crate::env::Env;
use crate::scope::Scope;
use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_parse::ast::{AssignedField, Tag, TypeAnnotation};
use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Problem, RecordField, Type};
#[derive(Clone, Debug, PartialEq)]
pub struct Annotation {
pub typ: Type,
pub introduced_variables: IntroducedVariables,
pub references: MutSet<Symbol>,
pub aliases: SendMap<Symbol, Alias>,
}
#[derive(Clone, Debug, PartialEq, Default)]
pub struct IntroducedVariables {
// NOTE on rigids
//
// Rigids must be unique within a type annoation.
// E.g. in `identity : a -> a`, there should only be one
// variable (a rigid one, with name "a").
// Hence `rigids : ImMap<Lowercase, Variable>`
//
// But then between annotations, the same name can occur multiple times,
// but a variable can only have one name. Therefore
// `ftv : SendMap<Variable, Lowercase>`.
pub wildcards: Vec<Variable>,
pub var_by_name: SendMap<Lowercase, Variable>,
pub name_by_var: SendMap<Variable, Lowercase>,
pub host_exposed_aliases: MutMap<Symbol, Variable>,
}
impl IntroducedVariables {
pub fn insert_named(&mut self, name: Lowercase, var: Variable) {
self.var_by_name.insert(name.clone(), var);
self.name_by_var.insert(var, name);
}
pub fn insert_wildcard(&mut self, var: Variable) {
self.wildcards.push(var);
}
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
self.host_exposed_aliases.insert(symbol, var);
}
pub fn union(&mut self, other: &Self) {
self.wildcards.extend(other.wildcards.iter().cloned());
self.var_by_name.extend(other.var_by_name.clone());
self.name_by_var.extend(other.name_by_var.clone());
self.host_exposed_aliases
.extend(other.host_exposed_aliases.clone());
}
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> {
self.var_by_name.get(name)
}
pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
self.name_by_var.get(&var)
}
}
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));
}
pub fn canonicalize_annotation(
env: &mut Env,
scope: &mut Scope,
annotation: &roc_parse::ast::TypeAnnotation,
region: Region,
var_store: &mut VarStore,
) -> Annotation {
let mut introduced_variables = IntroducedVariables::default();
let mut references = MutSet::default();
let mut aliases = SendMap::default();
let typ = can_annotation_help(
env,
annotation,
region,
scope,
var_store,
&mut introduced_variables,
&mut aliases,
&mut references,
);
Annotation {
typ,
introduced_variables,
references,
aliases,
}
}
#[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 SendMap<Symbol, Alias>,
references: &mut MutSet<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,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
args.push(arg_ann);
}
let ret = can_annotation_help(
env,
&return_type.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
let closure = Type::Variable(var_store.fresh());
Type::Function(args, Box::new(closure), Box::new(ret))
}
Apply(module_name, ident, type_arguments) => {
let symbol = if module_name.is_empty() {
// Since module_name was empty, this is an unqualified type.
// Look it up in scope!
let ident: Ident = (*ident).into();
match scope.lookup(&ident, region) {
Ok(symbol) => symbol,
Err(problem) => {
env.problem(roc_problem::can::Problem::RuntimeError(problem));
return Type::Erroneous(Problem::UnrecognizedIdent(ident.into()));
}
}
} else {
match env.qualified_lookup(module_name, ident, region) {
Ok(symbol) => 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));
return Type::Erroneous(Problem::UnrecognizedIdent((*ident).into()));
}
}
};
let mut args = Vec::new();
references.insert(symbol);
for arg in *type_arguments {
let arg_ann = can_annotation_help(
env,
&arg.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
args.push(arg_ann);
}
match scope.lookup_alias(symbol) {
Some(alias) => {
// use a known alias
let mut actual = alias.typ.clone();
let mut substitutions = ImMap::default();
let mut vars = Vec::new();
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,
});
return error;
}
for (loc_var, arg_ann) in alias.type_variables.iter().zip(args.into_iter()) {
let name = loc_var.value.0.clone();
let var = loc_var.value.1;
substitutions.insert(var, arg_ann.clone());
vars.push((name.clone(), arg_ann));
}
// make sure the recursion variable is freshly instantiated
if let Type::RecursiveTagUnion(rvar, _, _) = &mut actual {
let new = var_store.fresh();
substitutions.insert(*rvar, Type::Variable(new));
*rvar = new;
}
// make sure hidden variables are freshly instantiated
for var in alias.lambda_set_variables.iter() {
substitutions.insert(var.into_inner(), Type::Variable(var_store.fresh()));
}
// instantiate variables
actual.substitute(&substitutions);
Type::Alias(symbol, vars, Box::new(actual))
}
None => {
let mut args = Vec::new();
references.insert(symbol);
for arg in *type_arguments {
let arg_ann = can_annotation_help(
env,
&arg.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
args.push(arg_ann);
}
Type::Apply(symbol, args)
}
}
}
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, var);
Type::Variable(var)
}
}
}
As(loc_inner, _spaces, loc_as) => match loc_as.value {
TypeAnnotation::Apply(module_name, ident, loc_vars) if module_name.is_empty() => {
let symbol = match scope.introduce(
ident.into(),
&env.exposed_ident_ids,
&mut env.ident_ids,
region,
) {
Ok(symbol) => symbol,
Err((original_region, shadow)) => {
let problem = Problem::Shadowed(original_region, shadow.clone());
env.problem(roc_problem::can::Problem::ShadowingInAnnotation {
original_region,
shadow,
});
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::with_capacity(loc_vars.len());
references.insert(symbol);
for loc_var in loc_vars {
match loc_var.value {
BoundVariable(ident) => {
let var_name = Lowercase::from(ident);
if let Some(var) = introduced_variables.var_by_name(&var_name) {
vars.push((var_name.clone(), Type::Variable(*var)));
lowercase_vars.push(Located::at(loc_var.region, (var_name, *var)));
} else {
let var = var_store.fresh();
introduced_variables.insert_named(var_name.clone(), var);
vars.push((var_name.clone(), Type::Variable(var)));
lowercase_vars.push(Located::at(loc_var.region, (var_name, var)));
}
}
_ => {
// If anything other than a lowercase identifier
// appears here, the whole annotation is invalid.
return Type::Erroneous(Problem::CanonicalizationProblem);
}
}
}
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());
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();
new_arg.substitute_alias(symbol, &Type::Variable(rec_var));
new_args.push(new_arg);
}
new_tags.push((tag_name.clone(), new_args));
}
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.1);
}
scope.add_alias(symbol, region, lowercase_vars, alias_actual);
let alias = scope.lookup_alias(symbol).unwrap();
local_aliases.insert(symbol, alias.clone());
// Type::Alias(symbol, vars, Box::new(alias.typ.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,
arguments: vars,
actual: Box::new(alias.typ.clone()),
actual_var,
}
} else {
Type::Alias(symbol, vars, Box::new(alias.typ.clone()))
}
}
_ => {
// This is a syntactically invalid type alias.
Type::Erroneous(Problem::CanonicalizationProblem)
}
},
Record { fields, ext, .. } => {
let field_types = can_assigned_fields(
env,
fields,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
let ext_type = match ext {
Some(loc_ann) => can_annotation_help(
env,
&loc_ann.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
),
None => Type::EmptyRec,
};
Type::Record(field_types, Box::new(ext_type))
}
TagUnion { tags, ext, .. } => {
let tag_types = can_tags(
env,
tags,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
let ext_type = match ext {
Some(loc_ann) => can_annotation_help(
env,
&loc_ann.value,
loc_ann.region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
),
None => Type::EmptyTagUnion,
};
Type::TagUnion(tag_types, Box::new(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(var);
Type::Variable(var)
}
Malformed(string) => {
malformed(env, region, string);
let var = var_store.fresh();
introduced_variables.insert_wildcard(var);
Type::Variable(var)
}
}
}
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
fn can_assigned_fields<'a>(
env: &mut Env,
fields: &&[Located<AssignedField<'a, TypeAnnotation<'a>>>],
region: Region,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<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(), Optional(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(), 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 [Located<Tag<'a>>],
region: Region,
scope: &mut Scope,
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
local_aliases: &mut SendMap<Symbol, Alias>,
references: &mut MutSet<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::Global { 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::Global(name);
tag_types.push((tag_name.clone(), arg_types));
break 'inner tag_name;
}
Tag::Private { name, args } => {
let ident_id = env.ident_ids.get_or_insert(&name.value.into());
let symbol = Symbol::new(env.home, ident_id);
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::Private(symbol);
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
}