mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-22 08:17:40 +03:00
Merge trunk into emit-records
This commit is contained in:
commit
ae30a99689
722
Cargo.lock
generated
722
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -45,7 +45,7 @@ Each Roc platform gets its own separate package repository, with packages built
|
||||
|
||||
## Project Goals
|
||||
|
||||
Roc is in the extremely early stages of development. It barely does anything yet. With any luck, it will support doing something useful in 2020.
|
||||
Roc is in the extremely early stages of development. It barely does anything yet. With any luck, it will support doing something useful by the end of 2020.
|
||||
|
||||
Besides the above language design, a separate goal is for Roc to ship with an ambitiously boundary-pushing graphical editor. Not like "an IDE," but rather something that makes people say "I have never seen anything remotely like this outside of Bret Victor demos."
|
||||
|
||||
|
18
diff1.txt
Normal file
18
diff1.txt
Normal file
@ -0,0 +1,18 @@
|
||||
Def {
|
||||
loc_pattern: |L 0-0, C 0-1| Identifier('Test.Blah$q'),
|
||||
loc_expr: |L 0-3, C 4-26| Closure(30, 'Test.Blah$2', Recursive, [(18, |L 0-0, C 5-6| Identifier('Test.Blah$x'))], (|L 1-3, C 8-26| When { cond_var: 19, expr_var: 29, loc_cond: |L 1-3, C 8-26| Var { module: ModuleName(""), symbol_for_lookup: 'Test.Blah$x', resolved_symbol: 'Test.Blah$x' }, branches: [(|L 2-2, C 12-13| IntLiteral(0), |L 1-3, C 8-26| Int(20, 0)), (|L 3-3, C 12-13| Underscore, |L 1-3, C 8-26| Call((27, |L 3-3, C 17-18| Var { module: ModuleName(""), symbol_for_lookup: 'Test.Blah$p', resolved_symbol: 'Test.Blah$p' }, 28), [(26, |L 3-3, C 20-25| Call((24, |L 3-3, C 22-23| Var { module: ModuleName("Num"), symbol_for_lookup: 'Num.sub', resolved_symbol: 'Num.sub' }, 25), [(21, |L 3-3, C 20-21| Var { module: ModuleName(""), symbol_for_lookup: 'Test.Blah$x', resolved_symbol: 'Test.Blah$x' }), (23, |L 3-3, C 24-25| Int(22, 1))], BinOp(Minus)))], Space))] }, 31)),
|
||||
expr_var: 17,
|
||||
pattern_vars: {
|
||||
'Test.Blah$q': 17,
|
||||
},
|
||||
annotation: None,
|
||||
},
|
||||
Def {
|
||||
loc_pattern: |L 5-5, C 0-1| Identifier('Test.Blah$p'),
|
||||
loc_expr: |L 5-8, C 4-26| Closure(15, 'Test.Blah$1', Recursive, [(3, |L 5-5, C 5-6| Identifier('Test.Blah$x'))], (|L 6-8, C 8-26| When { cond_var: 4, expr_var: 14, loc_cond: |L 6-8, C 8-26| Var { module: ModuleName(""), symbol_for_lookup: 'Test.Blah$x', resolved_symbol: 'Test.Blah$x' }, branches: [(|L 7-7, C 12-13| IntLiteral(0), |L 6-8, C 8-26| Int(5, 0)), (|L 8-8, C 12-13| Underscore, |L 6-8, C 8-26| Call((12, |L 8-8, C 17-18| Var { module: ModuleName(""), symbol_for_lookup: 'Test.Blah$q', resolved_symbol: 'Test.Blah$q' }, 13), [(11, |L 8-8, C 20-25| Call((9, |L 8-8, C 22-23| Var { module: ModuleName("Num"), symbol_for_lookup: 'Num.sub', resolved_symbol: 'Num.sub' }, 10), [(6, |L 8-8, C 20-21| Var { module: ModuleName(""), symbol_for_lookup: 'Test.Blah$x', resolved_symbol: 'Test.Blah$x' }), (8, |L 8-8, C 24-25| Int(7, 1))], BinOp(Minus)))], Space))] }, 16)),
|
||||
expr_var: 2,
|
||||
pattern_vars: {
|
||||
'Test.Blah$p': 2,
|
||||
},
|
||||
annotation: None,
|
||||
},
|
9
diff2.txt
Normal file
9
diff2.txt
Normal file
@ -0,0 +1,9 @@
|
||||
Def {
|
||||
loc_pattern: |L 0-0, C 0-1| Identifier(`Test.q`),
|
||||
loc_expr: |L 0-3, C 4-26| Closure(30, `Test.1`, Recursive, [(18, |L 0-0, C 5-6| Identifier(`Test.x`))], (|L 1-3, C 8-26| When { cond_var: 19, expr_var: 29, loc_cond: |L 1-3, C 8-26| Var(`Test.x`), branches: [(WhenPattern { pattern: |L 2-2, C 12-13| IntLiteral(0), guard: None }, |L 1-3, C 8-26| Int(20, 0)), (WhenPattern { pattern: |L 3-3, C 12-13| Underscore, guard: None }, |L 1-3, C 8-26| Call((27, |L 3-3, C 17-18| Var(`Test.p`), 28), [(26, |L 3-3, C 20-25| Call((24, |L 3-3, C 22-23| Var(`Num.sub`), 25), [(21, |L 3-3, C 20-21| Var(`Test.x`)), (23, |L 3-3, C 24-25| Int(22, 1))], BinOp(Minus)))], Space))] }, 31)),
|
||||
expr_var: 17,
|
||||
pattern_vars: {
|
||||
`Test.q`: 17,
|
||||
},
|
||||
annotation: None,
|
||||
},
|
@ -4,7 +4,7 @@ Roc is a direct descendant of the [Elm programming language](https://elm-lang.or
|
||||
|
||||
This is a guide to help Elm programmers learn what's different between Elm and Roc.
|
||||
|
||||
> NOTE: As of 2019, only a subset of what's in this document has been implemented.
|
||||
> NOTE: As of 2020, only a subset of what's in this document has been implemented.
|
||||
|
||||
## Comments
|
||||
|
||||
|
@ -1,17 +1,33 @@
|
||||
use crate::can;
|
||||
use crate::can::env::Env;
|
||||
use crate::can::ident::{Lowercase, ModuleName, TagName, Uppercase};
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::{ImMap, SendMap};
|
||||
use crate::can::ident::Ident;
|
||||
use crate::can::ident::{Lowercase, TagName};
|
||||
use crate::can::scope::Scope;
|
||||
use crate::collections::{default_hasher, ImMap, MutMap, MutSet, SendMap};
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::parse::ast::{AssignedField, Tag, TypeAnnotation};
|
||||
use crate::region::Located;
|
||||
use crate::region::Region;
|
||||
use crate::subs::{VarStore, Variable};
|
||||
use crate::types::RecordFieldLabel;
|
||||
use crate::types::{Problem, Type};
|
||||
use crate::types::{Alias, Problem, Type};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Annotation {
|
||||
pub typ: Type,
|
||||
pub ftv: MutMap<Variable, Lowercase>,
|
||||
pub rigids: ImMap<Lowercase, Variable>,
|
||||
pub references: MutSet<Symbol>,
|
||||
pub aliases: SendMap<Symbol, Alias>,
|
||||
}
|
||||
|
||||
pub fn canonicalize_annotation(
|
||||
env: &Env,
|
||||
env: &mut Env,
|
||||
scope: &mut Scope,
|
||||
annotation: &crate::parse::ast::TypeAnnotation,
|
||||
region: Region,
|
||||
var_store: &VarStore,
|
||||
) -> (SendMap<Variable, Lowercase>, crate::types::Type) {
|
||||
) -> Annotation {
|
||||
// NOTE on rigids
|
||||
//
|
||||
// Rigids must be unique within a type annoation.
|
||||
@ -23,192 +39,358 @@ pub fn canonicalize_annotation(
|
||||
// but a variable can only have one name. Therefore
|
||||
// `ftv : SendMap<Variable, Lowercase>`.
|
||||
let mut rigids = ImMap::default();
|
||||
let mut local_aliases = Vec::new();
|
||||
let mut result =
|
||||
can_annotation_help(env, annotation, var_store, &mut rigids, &mut local_aliases);
|
||||
let mut aliases = SendMap::default();
|
||||
let (typ, references) = can_annotation_help(
|
||||
env,
|
||||
annotation,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
&mut rigids,
|
||||
&mut aliases,
|
||||
);
|
||||
|
||||
for (module_name, name, tipe) in local_aliases {
|
||||
result.substitute_alias(&module_name, &name, &tipe);
|
||||
}
|
||||
let mut ftv = MutMap::default();
|
||||
|
||||
let mut ftv = SendMap::default();
|
||||
|
||||
for (k, v) in rigids {
|
||||
for (k, v) in rigids.clone() {
|
||||
ftv.insert(v, k);
|
||||
}
|
||||
|
||||
(ftv, result)
|
||||
Annotation {
|
||||
typ,
|
||||
ftv,
|
||||
references,
|
||||
rigids,
|
||||
aliases,
|
||||
}
|
||||
}
|
||||
|
||||
fn can_annotation_help(
|
||||
env: &Env,
|
||||
env: &mut Env,
|
||||
annotation: &crate::parse::ast::TypeAnnotation,
|
||||
region: Region,
|
||||
scope: &mut Scope,
|
||||
var_store: &VarStore,
|
||||
rigids: &mut ImMap<Lowercase, Variable>,
|
||||
local_aliases: &mut Vec<(ModuleName, Uppercase, crate::types::Type)>,
|
||||
) -> crate::types::Type {
|
||||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
) -> (Type, MutSet<Symbol>) {
|
||||
use crate::parse::ast::TypeAnnotation::*;
|
||||
|
||||
match annotation {
|
||||
Function(argument_types, return_type) => {
|
||||
let mut args = Vec::new();
|
||||
let mut references = MutSet::default();
|
||||
|
||||
for arg in *argument_types {
|
||||
args.push(can_annotation_help(
|
||||
let (arg_ann, arg_refs) = can_annotation_help(
|
||||
env,
|
||||
&arg.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
));
|
||||
);
|
||||
|
||||
references.extend(arg_refs);
|
||||
|
||||
args.push(arg_ann);
|
||||
}
|
||||
|
||||
let ret =
|
||||
can_annotation_help(env, &return_type.value, var_store, rigids, local_aliases);
|
||||
Type::Function(args, Box::new(ret))
|
||||
let (ret, ret_refs) = can_annotation_help(
|
||||
env,
|
||||
&return_type.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
);
|
||||
|
||||
references.extend(ret_refs);
|
||||
|
||||
(Type::Function(args, Box::new(ret)), references)
|
||||
}
|
||||
Apply(module_name, name, type_arguments) => {
|
||||
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(crate::can::problem::Problem::RuntimeError(problem));
|
||||
|
||||
return (
|
||||
Type::Erroneous(Problem::UnrecognizedIdent(ident.into())),
|
||||
MutSet::default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
} 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(crate::can::problem::Problem::RuntimeError(problem));
|
||||
|
||||
return (
|
||||
Type::Erroneous(Problem::UnrecognizedIdent((*ident).into())),
|
||||
MutSet::default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let mut args = Vec::new();
|
||||
let mut references = HashSet::with_capacity_and_hasher(1, default_hasher());
|
||||
|
||||
references.insert(symbol);
|
||||
|
||||
for arg in *type_arguments {
|
||||
args.push(can_annotation_help(
|
||||
let (arg_ann, arg_refs) = can_annotation_help(
|
||||
env,
|
||||
&arg.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
));
|
||||
);
|
||||
|
||||
references.extend(arg_refs);
|
||||
|
||||
args.push(arg_ann);
|
||||
}
|
||||
|
||||
Type::Apply {
|
||||
module_name: module_name.join(".").into(),
|
||||
name: (*name).into(),
|
||||
args,
|
||||
}
|
||||
(Type::Apply(symbol, args), references)
|
||||
}
|
||||
BoundVariable(v) => {
|
||||
let name = Lowercase::from(*v);
|
||||
let typ = match rigids.get(&name) {
|
||||
Some(var) => Type::Variable(*var),
|
||||
None => {
|
||||
let var = var_store.fresh();
|
||||
|
||||
if let Some(var) = rigids.get(&name) {
|
||||
Type::Variable(*var)
|
||||
} else {
|
||||
let var = var_store.fresh();
|
||||
rigids.insert(name, var);
|
||||
Type::Variable(var)
|
||||
}
|
||||
rigids.insert(name, var);
|
||||
|
||||
Type::Variable(var)
|
||||
}
|
||||
};
|
||||
|
||||
(typ, MutSet::default())
|
||||
}
|
||||
As(loc_inner, _spaces, loc_as) => match loc_as.value {
|
||||
TypeAnnotation::Apply(module_name, name, loc_vars) if module_name.is_empty() => {
|
||||
let inner_type =
|
||||
can_annotation_help(env, &loc_inner.value, var_store, rigids, local_aliases);
|
||||
let name = Uppercase::from(name);
|
||||
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);
|
||||
env.problem(can::problem::Problem::ErroneousAnnotation(problem.clone()));
|
||||
|
||||
return (Type::Erroneous(problem), MutSet::default());
|
||||
}
|
||||
};
|
||||
|
||||
let (inner_type, mut references) = can_annotation_help(
|
||||
env,
|
||||
&loc_inner.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
);
|
||||
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) = rigids.get(&var_name) {
|
||||
vars.push((var_name, *var));
|
||||
vars.push((var_name, Type::Variable(*var)));
|
||||
} else {
|
||||
let var = var_store.fresh();
|
||||
|
||||
rigids.insert(var_name.clone(), var);
|
||||
vars.push((var_name, 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);
|
||||
return (
|
||||
Type::Erroneous(Problem::CanonicalizationProblem),
|
||||
references,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let alias = Type::Alias(env.home.clone(), name.clone(), vars, Box::new(inner_type));
|
||||
let alias_actual = if let Type::TagUnion(tags, ext) = inner_type {
|
||||
let rec_var = var_store.fresh();
|
||||
|
||||
local_aliases.push((env.home.clone(), name, alias.clone()));
|
||||
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
|
||||
};
|
||||
|
||||
alias
|
||||
let alias = Alias {
|
||||
region,
|
||||
vars: lowercase_vars,
|
||||
typ: alias_actual.clone(),
|
||||
};
|
||||
local_aliases.insert(symbol, alias);
|
||||
|
||||
let type_alias = Type::Alias(symbol, vars, Box::new(alias_actual));
|
||||
(type_alias, references)
|
||||
}
|
||||
_ => {
|
||||
// This is a syntactically invalid type alias.
|
||||
Type::Erroneous(Problem::CanonicalizationProblem)
|
||||
(
|
||||
Type::Erroneous(Problem::CanonicalizationProblem),
|
||||
MutSet::default(),
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
Record { fields, ext } => {
|
||||
let mut field_types = SendMap::default();
|
||||
let mut references = MutSet::default();
|
||||
|
||||
for field in fields.iter() {
|
||||
can_assigned_field(
|
||||
let field_refs = can_assigned_field(
|
||||
env,
|
||||
&field.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
&mut field_types,
|
||||
);
|
||||
|
||||
references.extend(field_refs);
|
||||
}
|
||||
|
||||
let ext_type = match ext {
|
||||
Some(loc_ann) => {
|
||||
can_annotation_help(env, &loc_ann.value, var_store, rigids, local_aliases)
|
||||
}
|
||||
None => Type::EmptyRec,
|
||||
let (ext_type, ext_refs) = match ext {
|
||||
Some(loc_ann) => can_annotation_help(
|
||||
env,
|
||||
&loc_ann.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
),
|
||||
None => (Type::EmptyRec, MutSet::default()),
|
||||
};
|
||||
|
||||
Type::Record(field_types, Box::new(ext_type))
|
||||
references.extend(ext_refs);
|
||||
|
||||
(Type::Record(field_types, Box::new(ext_type)), references)
|
||||
}
|
||||
TagUnion { tags, ext } => {
|
||||
let mut tag_types = Vec::with_capacity(tags.len());
|
||||
let mut references = MutSet::default();
|
||||
|
||||
for tag in tags.iter() {
|
||||
can_tag(
|
||||
let tag_refs = can_tag(
|
||||
env,
|
||||
&tag.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
&mut tag_types,
|
||||
);
|
||||
|
||||
references.extend(tag_refs);
|
||||
}
|
||||
|
||||
let ext_type = match ext {
|
||||
Some(loc_ann) => {
|
||||
can_annotation_help(env, &loc_ann.value, var_store, rigids, local_aliases)
|
||||
}
|
||||
None => Type::EmptyTagUnion,
|
||||
let (ext_type, ext_refs) = match ext {
|
||||
Some(loc_ann) => can_annotation_help(
|
||||
env,
|
||||
&loc_ann.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
),
|
||||
None => (Type::EmptyTagUnion, MutSet::default()),
|
||||
};
|
||||
|
||||
Type::TagUnion(tag_types, Box::new(ext_type))
|
||||
references.extend(ext_refs);
|
||||
|
||||
(Type::TagUnion(tag_types, Box::new(ext_type)), references)
|
||||
}
|
||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
|
||||
can_annotation_help(env, nested, var_store, rigids, local_aliases)
|
||||
can_annotation_help(env, nested, region, scope, var_store, rigids, local_aliases)
|
||||
}
|
||||
Wildcard | Malformed(_) => {
|
||||
let var = var_store.fresh();
|
||||
Type::Variable(var)
|
||||
|
||||
(Type::Variable(var), MutSet::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO trim down these arguments!
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn can_assigned_field<'a>(
|
||||
env: &Env,
|
||||
env: &mut Env,
|
||||
field: &AssignedField<'a, TypeAnnotation<'a>>,
|
||||
region: Region,
|
||||
scope: &mut Scope,
|
||||
var_store: &VarStore,
|
||||
rigids: &mut ImMap<Lowercase, Variable>,
|
||||
|
||||
local_aliases: &mut Vec<(ModuleName, Uppercase, crate::types::Type)>,
|
||||
field_types: &mut SendMap<RecordFieldLabel, Type>,
|
||||
) {
|
||||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
field_types: &mut SendMap<Lowercase, Type>,
|
||||
) -> MutSet<Symbol> {
|
||||
use crate::parse::ast::AssignedField::*;
|
||||
|
||||
match field {
|
||||
LabeledValue(field_name, _, annotation) => {
|
||||
let field_type =
|
||||
can_annotation_help(env, &annotation.value, var_store, rigids, local_aliases);
|
||||
let (field_type, refs) = can_annotation_help(
|
||||
env,
|
||||
&annotation.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
);
|
||||
let label = Lowercase::from(field_name.value);
|
||||
|
||||
field_types.insert(label, field_type);
|
||||
|
||||
refs
|
||||
}
|
||||
LabelOnly(loc_field_name) => {
|
||||
// Interpret { a, b } as { a : a, b : b }
|
||||
@ -222,47 +404,97 @@ fn can_assigned_field<'a>(
|
||||
Type::Variable(field_var)
|
||||
}
|
||||
};
|
||||
|
||||
field_types.insert(field_name, field_type);
|
||||
|
||||
MutSet::default()
|
||||
}
|
||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => {
|
||||
can_assigned_field(env, nested, var_store, rigids, local_aliases, field_types)
|
||||
}
|
||||
Malformed(_) => {}
|
||||
SpaceBefore(nested, _) | SpaceAfter(nested, _) => can_assigned_field(
|
||||
env,
|
||||
nested,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
field_types,
|
||||
),
|
||||
Malformed(_) => MutSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO trim down these arguments!
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn can_tag<'a>(
|
||||
env: &Env,
|
||||
env: &mut Env,
|
||||
tag: &Tag<'a>,
|
||||
region: Region,
|
||||
scope: &mut Scope,
|
||||
var_store: &VarStore,
|
||||
rigids: &mut ImMap<Lowercase, Variable>,
|
||||
local_aliases: &mut Vec<(ModuleName, Uppercase, crate::types::Type)>,
|
||||
local_aliases: &mut SendMap<Symbol, Alias>,
|
||||
tag_types: &mut Vec<(TagName, Vec<Type>)>,
|
||||
) {
|
||||
) -> MutSet<Symbol> {
|
||||
match tag {
|
||||
Tag::Global { name, args } => {
|
||||
let name = name.value.into();
|
||||
let mut references = MutSet::default();
|
||||
let mut arg_types = Vec::with_capacity(args.len());
|
||||
|
||||
let arg_types = args
|
||||
.iter()
|
||||
.map(|arg| can_annotation_help(env, &arg.value, var_store, rigids, local_aliases))
|
||||
.collect();
|
||||
for arg in args.iter() {
|
||||
let (ann, refs) = can_annotation_help(
|
||||
env,
|
||||
&arg.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
);
|
||||
|
||||
arg_types.push(ann);
|
||||
references.extend(refs);
|
||||
}
|
||||
|
||||
tag_types.push((TagName::Global(name), arg_types));
|
||||
|
||||
references
|
||||
}
|
||||
Tag::Private { name, args } => {
|
||||
let symbol = Symbol::from_private_tag(env.home.as_str(), name.value);
|
||||
let ident_id = env.ident_ids.get_or_insert(&name.value.into());
|
||||
let symbol = Symbol::new(env.home, ident_id);
|
||||
let mut references = MutSet::default();
|
||||
let mut arg_types = Vec::with_capacity(args.len());
|
||||
|
||||
let arg_types = args
|
||||
.iter()
|
||||
.map(|arg| can_annotation_help(env, &arg.value, var_store, rigids, local_aliases))
|
||||
.collect();
|
||||
for arg in args.iter() {
|
||||
let (ann, refs) = can_annotation_help(
|
||||
env,
|
||||
&arg.value,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
);
|
||||
|
||||
arg_types.push(ann);
|
||||
references.extend(refs);
|
||||
}
|
||||
|
||||
tag_types.push((TagName::Private(symbol), arg_types));
|
||||
|
||||
references
|
||||
}
|
||||
Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => {
|
||||
can_tag(env, nested, var_store, rigids, local_aliases, tag_types)
|
||||
}
|
||||
Tag::Malformed(_) => {}
|
||||
Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => can_tag(
|
||||
env,
|
||||
nested,
|
||||
region,
|
||||
scope,
|
||||
var_store,
|
||||
rigids,
|
||||
local_aliases,
|
||||
tag_types,
|
||||
),
|
||||
Tag::Malformed(_) => MutSet::default(),
|
||||
}
|
||||
}
|
||||
|
801
src/can/def.rs
801
src/can/def.rs
File diff suppressed because it is too large
Load Diff
@ -1,14 +1,19 @@
|
||||
use crate::can::ident::ModuleName;
|
||||
use crate::can::problem::Problem;
|
||||
use crate::can::problem::{Problem, RuntimeError};
|
||||
use crate::can::procedure::References;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::MutMap;
|
||||
use crate::collections::{MutMap, MutSet};
|
||||
use crate::module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
|
||||
use crate::region::Region;
|
||||
use inlinable_string::InlinableString;
|
||||
|
||||
/// The canonicalization environment for a particular module.
|
||||
pub struct Env {
|
||||
pub struct Env<'a> {
|
||||
/// The module's path. Private tags and unqualified references to identifiers
|
||||
/// are assumed to be relative to this path.
|
||||
pub home: ModuleName,
|
||||
pub home: ModuleId,
|
||||
|
||||
pub dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
|
||||
pub module_ids: &'a ModuleIds,
|
||||
|
||||
/// Problems we've encountered along the way, which will be reported to the user at the end.
|
||||
pub problems: Vec<Problem>,
|
||||
@ -18,18 +23,96 @@ pub struct Env {
|
||||
|
||||
/// current tail-callable symbol
|
||||
pub tailcallable_symbol: Option<Symbol>,
|
||||
|
||||
/// Modules which were referenced by lookups.
|
||||
/// Any modules which were imported but not used
|
||||
/// are unused imports.
|
||||
pub referenced_modules: MutSet<ModuleId>,
|
||||
pub referenced_symbols: MutSet<Symbol>,
|
||||
|
||||
pub ident_ids: IdentIds,
|
||||
pub exposed_ident_ids: IdentIds,
|
||||
}
|
||||
|
||||
impl Env {
|
||||
pub fn new(home: ModuleName) -> Env {
|
||||
impl<'a> Env<'a> {
|
||||
pub fn new(
|
||||
home: ModuleId,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
module_ids: &'a ModuleIds,
|
||||
exposed_ident_ids: IdentIds,
|
||||
) -> Env<'a> {
|
||||
Env {
|
||||
home,
|
||||
dep_idents,
|
||||
module_ids,
|
||||
ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later
|
||||
exposed_ident_ids,
|
||||
problems: Vec::new(),
|
||||
closures: MutMap::default(),
|
||||
referenced_modules: MutSet::default(),
|
||||
referenced_symbols: MutSet::default(),
|
||||
tailcallable_symbol: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns Err if the symbol resolved, but it was not exposed by the given module
|
||||
pub fn qualified_lookup(
|
||||
&mut self,
|
||||
module_name: &str,
|
||||
ident: &str,
|
||||
region: Region,
|
||||
) -> Result<Symbol, RuntimeError> {
|
||||
debug_assert!(
|
||||
!module_name.is_empty(),
|
||||
"Called env.qualified_lookup with an unqualified ident: {:?}",
|
||||
ident
|
||||
);
|
||||
|
||||
let module_name: InlinableString = module_name.into();
|
||||
|
||||
match self.module_ids.get_id(&module_name) {
|
||||
Some(&module_id) => {
|
||||
let ident: InlinableString = ident.into();
|
||||
|
||||
match self
|
||||
.dep_idents
|
||||
.get(&module_id)
|
||||
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
|
||||
{
|
||||
Some(ident_id) => {
|
||||
let symbol = Symbol::new(module_id, *ident_id);
|
||||
|
||||
self.referenced_modules.insert(module_id);
|
||||
self.referenced_symbols.insert(symbol);
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
None => Err(RuntimeError::ValueNotExposed {
|
||||
module_name,
|
||||
ident,
|
||||
region,
|
||||
}),
|
||||
}
|
||||
}
|
||||
None => Err(RuntimeError::ModuleNotImported {
|
||||
module_name,
|
||||
ident: ident.into(),
|
||||
region,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates a unique, new symbol like "$1" or "$5",
|
||||
/// using the home module as the module_id.
|
||||
///
|
||||
/// This is used, for example, during canonicalization of an Expr::Closure
|
||||
/// to generate a unique symbol to refer to that closure.
|
||||
pub fn gen_unique_symbol(&mut self) -> Symbol {
|
||||
let ident_id = self.ident_ids.gen_unique();
|
||||
|
||||
Symbol::new(self.home, ident_id)
|
||||
}
|
||||
|
||||
pub fn problem(&mut self, problem: Problem) {
|
||||
self.problems.push(problem)
|
||||
}
|
||||
|
400
src/can/expr.rs
400
src/can/expr.rs
@ -1,26 +1,22 @@
|
||||
use crate::can::def::{can_defs_with_return, Def};
|
||||
use crate::can::env::Env;
|
||||
use crate::can::ident::{Lowercase, ModuleName, TagName};
|
||||
use crate::can::ident::{Lowercase, TagName};
|
||||
use crate::can::num::{
|
||||
finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result,
|
||||
int_expr_from_result,
|
||||
};
|
||||
use crate::can::pattern::idents_from_patterns;
|
||||
use crate::can::pattern::PatternType::*;
|
||||
use crate::can::pattern::{canonicalize_pattern, remove_idents, Pattern};
|
||||
use crate::can::problem::Problem;
|
||||
use crate::can::problem::RuntimeError;
|
||||
use crate::can::problem::RuntimeError::*;
|
||||
use crate::can::pattern::{canonicalize_pattern, Pattern};
|
||||
use crate::can::problem::{Problem, RuntimeError};
|
||||
use crate::can::procedure::References;
|
||||
use crate::can::scope::Scope;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::{ImMap, ImSet, MutMap, MutSet, SendMap};
|
||||
use crate::ident::Ident;
|
||||
use crate::collections::{ImSet, MutMap, MutSet, SendMap};
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::operator::CalledVia;
|
||||
use crate::parse::ast;
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::{VarStore, Variable};
|
||||
use im_rc::Vector;
|
||||
use crate::types::Alias;
|
||||
use std::fmt::Debug;
|
||||
use std::i64;
|
||||
use std::ops::Neg;
|
||||
@ -29,7 +25,8 @@ use std::ops::Neg;
|
||||
pub struct Output {
|
||||
pub references: References,
|
||||
pub tail_call: Option<Symbol>,
|
||||
pub rigids: SendMap<Variable, Lowercase>,
|
||||
pub rigids: SendMap<Lowercase, Variable>,
|
||||
pub aliases: SendMap<Symbol, Alias>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
@ -45,11 +42,7 @@ pub enum Expr {
|
||||
},
|
||||
|
||||
// Lookups
|
||||
Var {
|
||||
module: ModuleName,
|
||||
symbol_for_lookup: Symbol,
|
||||
resolved_symbol: Symbol,
|
||||
},
|
||||
Var(Symbol),
|
||||
// Branching
|
||||
When {
|
||||
cond_var: Variable,
|
||||
@ -60,14 +53,13 @@ pub enum Expr {
|
||||
If {
|
||||
cond_var: Variable,
|
||||
branch_var: Variable,
|
||||
loc_cond: Box<Located<Expr>>,
|
||||
loc_then: Box<Located<Expr>>,
|
||||
loc_else: Box<Located<Expr>>,
|
||||
branches: Vec<(Located<Expr>, Located<Expr>)>,
|
||||
final_else: Box<Located<Expr>>,
|
||||
},
|
||||
|
||||
// Let
|
||||
LetRec(Vec<Def>, Box<Located<Expr>>, Variable),
|
||||
LetNonRec(Box<Def>, Box<Located<Expr>>, Variable),
|
||||
LetRec(Vec<Def>, Box<Located<Expr>>, Variable, Aliases),
|
||||
LetNonRec(Box<Def>, Box<Located<Expr>>, Variable, Aliases),
|
||||
|
||||
/// This is *only* for calling functions, not for tag application.
|
||||
/// The Tag variant contains any applied values inside it.
|
||||
@ -108,9 +100,7 @@ pub enum Expr {
|
||||
Update {
|
||||
record_var: Variable,
|
||||
ext_var: Variable,
|
||||
module: ModuleName,
|
||||
symbol: Symbol,
|
||||
ident: Ident,
|
||||
updates: SendMap<Lowercase, Field>,
|
||||
},
|
||||
|
||||
@ -126,6 +116,8 @@ pub enum Expr {
|
||||
RuntimeError(RuntimeError),
|
||||
}
|
||||
|
||||
type Aliases = SendMap<Symbol, Alias>;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Field {
|
||||
pub var: Variable,
|
||||
@ -141,18 +133,18 @@ pub enum Recursive {
|
||||
NotRecursive,
|
||||
}
|
||||
|
||||
pub fn canonicalize_expr(
|
||||
env: &mut Env,
|
||||
pub fn canonicalize_expr<'a>(
|
||||
env: &mut Env<'a>,
|
||||
var_store: &VarStore,
|
||||
scope: &mut Scope,
|
||||
region: Region,
|
||||
expr: &ast::Expr,
|
||||
expr: &'a ast::Expr<'a>,
|
||||
) -> (Located<Expr>, Output) {
|
||||
use self::Expr::*;
|
||||
use Expr::*;
|
||||
|
||||
let (expr, output) = match expr {
|
||||
ast::Expr::Int(string) => {
|
||||
let answer = int_expr_from_result(var_store, finish_parsing_int(string), env);
|
||||
let answer = int_expr_from_result(var_store, finish_parsing_int(*string), env);
|
||||
|
||||
(answer, Output::default())
|
||||
}
|
||||
@ -165,33 +157,17 @@ pub fn canonicalize_expr(
|
||||
fields,
|
||||
update: Some(loc_update),
|
||||
} => {
|
||||
let ident = if let ast::Expr::Var(module_parts, name) = &loc_update.value {
|
||||
Ident::new(module_parts, name)
|
||||
} else {
|
||||
panic!(
|
||||
"TODO canonicalize invalid record update (non-Var in update position)\n{:?}",
|
||||
&loc_update.value
|
||||
);
|
||||
};
|
||||
|
||||
let (can_update, update_out) =
|
||||
canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value);
|
||||
if let Var {
|
||||
resolved_symbol,
|
||||
module,
|
||||
..
|
||||
} = can_update.value
|
||||
{
|
||||
if let Var(symbol) = &can_update.value {
|
||||
let (can_fields, mut output) = canonicalize_fields(env, var_store, scope, fields);
|
||||
|
||||
output.references = output.references.union(update_out.references);
|
||||
|
||||
let answer = Update {
|
||||
module,
|
||||
record_var: var_store.fresh(),
|
||||
ext_var: var_store.fresh(),
|
||||
symbol: resolved_symbol,
|
||||
ident,
|
||||
symbol: *symbol,
|
||||
updates: can_fields,
|
||||
};
|
||||
|
||||
@ -284,15 +260,12 @@ pub fn canonicalize_expr(
|
||||
output.tail_call = None;
|
||||
|
||||
let expr = match fn_expr.value {
|
||||
Var {
|
||||
ref resolved_symbol,
|
||||
..
|
||||
} => {
|
||||
output.references.calls.insert(resolved_symbol.clone());
|
||||
Var(symbol) => {
|
||||
output.references.calls.insert(symbol);
|
||||
|
||||
// we're tail-calling a symbol by name, check if it's the tail-callable symbol
|
||||
output.tail_call = match &env.tailcallable_symbol {
|
||||
Some(tc_sym) if tc_sym == resolved_symbol => Some(resolved_symbol.clone()),
|
||||
Some(tc_sym) if *tc_sym == symbol => Some(symbol),
|
||||
Some(_) | None => None,
|
||||
};
|
||||
|
||||
@ -333,16 +306,8 @@ pub fn canonicalize_expr(
|
||||
|
||||
(expr, output)
|
||||
}
|
||||
ast::Expr::Var(module_parts, name) => {
|
||||
let symbol = if module_parts.is_empty() {
|
||||
scope.symbol(name)
|
||||
} else {
|
||||
Symbol::from_parts(module_parts, name)
|
||||
};
|
||||
|
||||
let ident = Ident::new(module_parts, name);
|
||||
|
||||
canonicalize_lookup(env, scope, ident, symbol, region)
|
||||
ast::Expr::Var { module_name, ident } => {
|
||||
canonicalize_lookup(env, scope, module_name, ident, region)
|
||||
} //ast::Expr::InterpolatedStr(pairs, suffix) => {
|
||||
// let mut output = Output::new();
|
||||
// let can_pairs: Vec<(String, Located<Expr>)> = pairs
|
||||
@ -365,9 +330,9 @@ pub fn canonicalize_expr(
|
||||
// value: ident,
|
||||
// };
|
||||
|
||||
// env.problem(Problem::UnrecognizedConstant(loc_ident.clone()));
|
||||
// env.problem(Problem::LookupNotInScope(loc_ident.clone()));
|
||||
|
||||
// RuntimeError(UnrecognizedConstant(loc_ident))
|
||||
// RuntimeError(LookupNotInScope(loc_ident))
|
||||
// }
|
||||
// };
|
||||
|
||||
@ -398,34 +363,17 @@ pub fn canonicalize_expr(
|
||||
// The globally unique symbol that will refer to this closure once it gets converted
|
||||
// into a top-level procedure for code gen.
|
||||
//
|
||||
// The symbol includes the module name, the top-level declaration name, and the
|
||||
// index (0-based) of the closure within that declaration.
|
||||
//
|
||||
// Example: "MyModule$main$3" if this is the 4th closure in MyModule.main.
|
||||
//
|
||||
// In the case of `foo = \x y -> ...`, the symbol is later changed to `foo`.
|
||||
let symbol = scope.gen_unique_symbol();
|
||||
// In the Foo module, this will look something like Foo.$1 or Foo.$2.
|
||||
let symbol = env.gen_unique_symbol();
|
||||
|
||||
// The body expression gets a new scope for canonicalization.
|
||||
// Shadow `scope` to make sure we don't accidentally use the original one for the
|
||||
// rest of this block.
|
||||
let mut scope = scope.clone();
|
||||
|
||||
let arg_idents: Vector<(Ident, (Symbol, Region))> =
|
||||
idents_from_patterns(loc_arg_patterns.iter(), &scope);
|
||||
|
||||
// Add the arguments' idents to scope.idents. If there's a collision,
|
||||
// it means there was shadowing, which will be handled later.
|
||||
scope.idents = union_pairs(scope.idents, arg_idents.iter());
|
||||
|
||||
// rest of this block, but keep the original around for later diffing.
|
||||
let original_scope = scope;
|
||||
let mut scope = original_scope.clone();
|
||||
let mut can_args = Vec::with_capacity(loc_arg_patterns.len());
|
||||
|
||||
for loc_pattern in loc_arg_patterns.into_iter() {
|
||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
||||
// (However, still include it in scope, because you *can* recursively refer to yourself.)
|
||||
let mut shadowable_idents = scope.idents.clone();
|
||||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
||||
|
||||
let can_arg = canonicalize_pattern(
|
||||
env,
|
||||
var_store,
|
||||
@ -433,7 +381,6 @@ pub fn canonicalize_expr(
|
||||
FunctionArg,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
&mut shadowable_idents,
|
||||
);
|
||||
|
||||
can_args.push((var_store.fresh(), can_arg));
|
||||
@ -449,19 +396,18 @@ pub fn canonicalize_expr(
|
||||
|
||||
// Now that we've collected all the references, check to see if any of the args we defined
|
||||
// went unreferenced. If any did, report them as unused arguments.
|
||||
for (ident, (arg_symbol, region)) in arg_idents {
|
||||
if !output.references.has_local(&arg_symbol) {
|
||||
// The body never referenced this argument we declared. It's an unused argument!
|
||||
env.problem(Problem::UnusedArgument(Located {
|
||||
region,
|
||||
value: ident,
|
||||
}));
|
||||
}
|
||||
for (symbol, region) in scope.symbols() {
|
||||
if !original_scope.contains_symbol(*symbol) {
|
||||
if !output.references.has_lookup(*symbol) {
|
||||
// The body never referenced this argument we declared. It's an unused argument!
|
||||
env.problem(Problem::UnusedArgument(*symbol, *region));
|
||||
}
|
||||
|
||||
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
|
||||
// we end up with weird conclusions like the expression (\x -> x + 1)
|
||||
// references the (nonexistant) local variable x!
|
||||
output.references.locals.remove(&arg_symbol);
|
||||
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
|
||||
// we end up with weird conclusions like the expression (\x -> x + 1)
|
||||
// references the (nonexistant) local variable x!
|
||||
output.references.lookups.remove(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
env.register_closure(symbol.clone(), output.references.clone());
|
||||
@ -489,12 +435,6 @@ pub fn canonicalize_expr(
|
||||
let mut can_branches = Vec::with_capacity(branches.len());
|
||||
|
||||
for branch in branches {
|
||||
let mut shadowable_idents = scope.idents.clone();
|
||||
|
||||
let loc_first_pattern = &branch.patterns.first().unwrap();
|
||||
|
||||
remove_idents(&loc_first_pattern.value, &mut shadowable_idents);
|
||||
|
||||
let (can_when_pattern, loc_can_expr, branch_references) = canonicalize_when_branch(
|
||||
env,
|
||||
var_store,
|
||||
@ -571,10 +511,12 @@ pub fn canonicalize_expr(
|
||||
ast::Expr::PrivateTag(tag) => {
|
||||
let variant_var = var_store.fresh();
|
||||
let ext_var = var_store.fresh();
|
||||
let tag_ident = env.ident_ids.get_or_insert(&(*tag).into());
|
||||
let symbol = Symbol::new(env.home, tag_ident);
|
||||
|
||||
(
|
||||
Tag {
|
||||
name: TagName::Private(Symbol::from_private_tag(env.home.as_str(), tag)),
|
||||
name: TagName::Private(symbol),
|
||||
arguments: vec![],
|
||||
variant_var,
|
||||
ext_var,
|
||||
@ -582,7 +524,7 @@ pub fn canonicalize_expr(
|
||||
Output::default(),
|
||||
)
|
||||
}
|
||||
ast::Expr::If((cond, then_branch, else_branch)) => {
|
||||
ast::Expr::If(cond, then_branch, else_branch) => {
|
||||
let (loc_cond, mut output) =
|
||||
canonicalize_expr(env, var_store, scope, cond.region, &cond.value);
|
||||
let (loc_then, then_output) = canonicalize_expr(
|
||||
@ -607,9 +549,8 @@ pub fn canonicalize_expr(
|
||||
If {
|
||||
cond_var: var_store.fresh(),
|
||||
branch_var: var_store.fresh(),
|
||||
loc_cond: Box::new(loc_cond),
|
||||
loc_then: Box::new(loc_then),
|
||||
loc_else: Box::new(loc_else),
|
||||
branches: vec![(loc_cond, loc_then)],
|
||||
final_else: Box::new(loc_else),
|
||||
},
|
||||
output,
|
||||
)
|
||||
@ -678,6 +619,10 @@ pub fn canonicalize_expr(
|
||||
}
|
||||
};
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
env.home.register_debug_idents(&env.ident_ids);
|
||||
}
|
||||
|
||||
// At the end, diff used_idents and defined_idents to see which were unused.
|
||||
// Add warnings for those!
|
||||
|
||||
@ -694,65 +639,30 @@ pub fn canonicalize_expr(
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn canonicalize_lookup(
|
||||
env: &mut Env,
|
||||
scope: &Scope,
|
||||
ident: Ident,
|
||||
symbol_for_lookup: Symbol,
|
||||
region: Region,
|
||||
) -> (Expr, Output) {
|
||||
use self::Expr::*;
|
||||
|
||||
let mut output = Output::default();
|
||||
let can_expr = match resolve_ident(&env, &scope, ident, &mut output.references) {
|
||||
Ok((module, resolved_symbol)) => Var {
|
||||
module,
|
||||
symbol_for_lookup,
|
||||
resolved_symbol,
|
||||
},
|
||||
Err(ident) => {
|
||||
let loc_ident = Located {
|
||||
region,
|
||||
value: ident,
|
||||
};
|
||||
|
||||
env.problem(Problem::UnrecognizedConstant(loc_ident.clone()));
|
||||
|
||||
RuntimeError(UnrecognizedConstant(loc_ident))
|
||||
}
|
||||
};
|
||||
|
||||
(can_expr, output)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn canonicalize_when_branch<'a>(
|
||||
env: &mut Env,
|
||||
env: &mut Env<'a>,
|
||||
var_store: &VarStore,
|
||||
scope: &Scope,
|
||||
region: Region,
|
||||
loc_pattern: &Located<ast::Pattern<'a>>,
|
||||
loc_expr: &Located<ast::Expr<'a>>,
|
||||
loc_expr: &'a Located<ast::Expr<'a>>,
|
||||
output: &mut Output,
|
||||
) -> (Located<Pattern>, Located<Expr>, References) {
|
||||
// Each case branch gets a new scope for canonicalization.
|
||||
// Shadow `scope` to make sure we don't accidentally use the original one for the
|
||||
// rest of this block.
|
||||
let mut scope = scope.clone();
|
||||
// rest of this block, but keep the original around for later diffing.
|
||||
let original_scope = scope;
|
||||
let mut scope = original_scope.clone();
|
||||
|
||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
||||
// (However, still include it in scope, because you *can* recursively refer to yourself.)
|
||||
let mut shadowable_idents = scope.idents.clone();
|
||||
remove_idents(&loc_pattern.value, &mut shadowable_idents);
|
||||
|
||||
// Patterns introduce new idents to the scope!
|
||||
// Add the defined identifiers to scope. If there's a collision, it means there
|
||||
// was shadowing, which will be handled later.
|
||||
let defined_idents: Vector<(Ident, (Symbol, Region))> =
|
||||
idents_from_patterns(std::iter::once(loc_pattern), &scope);
|
||||
|
||||
scope.idents = union_pairs(scope.idents, defined_idents.iter());
|
||||
let loc_can_pattern = canonicalize_pattern(
|
||||
env,
|
||||
var_store,
|
||||
&mut scope,
|
||||
WhenBranch,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
);
|
||||
|
||||
let (can_expr, branch_output) =
|
||||
canonicalize_expr(env, var_store, &mut scope, region, &loc_expr.value);
|
||||
@ -765,64 +675,34 @@ fn canonicalize_when_branch<'a>(
|
||||
|
||||
// Now that we've collected all the references for this branch, check to see if
|
||||
// any of the new idents it defined were unused. If any were, report it.
|
||||
for (ident, (symbol, region)) in defined_idents {
|
||||
if !output.references.has_local(&symbol) {
|
||||
let loc_ident = Located {
|
||||
region,
|
||||
value: ident.clone(),
|
||||
};
|
||||
|
||||
env.problem(Problem::UnusedAssignment(loc_ident));
|
||||
for (symbol, region) in scope.symbols() {
|
||||
if !output.references.has_lookup(*symbol) && !original_scope.contains_symbol(*symbol) {
|
||||
env.problem(Problem::UnusedDef(*symbol, *region));
|
||||
}
|
||||
}
|
||||
|
||||
let loc_can_pattern = canonicalize_pattern(
|
||||
env,
|
||||
var_store,
|
||||
&mut scope,
|
||||
WhenBranch,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
&mut shadowable_idents,
|
||||
);
|
||||
|
||||
(loc_can_pattern, can_expr, branch_output.references)
|
||||
}
|
||||
|
||||
pub fn union_pairs<'a, K, V, I>(mut map: ImMap<K, V>, pairs: I) -> ImMap<K, V>
|
||||
where
|
||||
I: Iterator<Item = &'a (K, V)>,
|
||||
K: std::hash::Hash + std::cmp::Eq + Clone,
|
||||
K: 'a,
|
||||
V: Clone,
|
||||
V: 'a,
|
||||
{
|
||||
for (ref k, ref v) in pairs {
|
||||
map.insert(k.clone(), v.clone());
|
||||
}
|
||||
|
||||
map
|
||||
}
|
||||
|
||||
pub fn local_successors<'a>(
|
||||
references: &'a References,
|
||||
closures: &'a MutMap<Symbol, References>,
|
||||
) -> ImSet<Symbol> {
|
||||
let mut answer = references.locals.clone();
|
||||
let mut answer = im_rc::hashset::HashSet::clone(&references.lookups);
|
||||
|
||||
for call_symbol in references.calls.iter() {
|
||||
answer = answer.union(call_successors(call_symbol, closures));
|
||||
answer = answer.union(call_successors(*call_symbol, closures));
|
||||
}
|
||||
|
||||
answer
|
||||
}
|
||||
|
||||
fn call_successors<'a>(
|
||||
call_symbol: &'a Symbol,
|
||||
call_symbol: Symbol,
|
||||
closures: &'a MutMap<Symbol, References>,
|
||||
) -> ImSet<Symbol> {
|
||||
// TODO (this comment should be moved to a GH issue) this may cause an infinite loop if 2 definitions reference each other; may need to track visited definitions!
|
||||
match closures.get(call_symbol) {
|
||||
match closures.get(&call_symbol) {
|
||||
Some(references) => {
|
||||
let mut answer = local_successors(&references, closures);
|
||||
|
||||
@ -849,21 +729,20 @@ where
|
||||
Some((_, refs)) => {
|
||||
visited.insert(defined_symbol);
|
||||
|
||||
for local in refs.locals.iter() {
|
||||
for local in refs.lookups.iter() {
|
||||
if !visited.contains(&local) {
|
||||
let other_refs: References =
|
||||
references_from_local(local.clone(), visited, refs_by_def, closures);
|
||||
references_from_local(*local, visited, refs_by_def, closures);
|
||||
|
||||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.locals.insert(local.clone());
|
||||
answer.lookups.insert(local.clone());
|
||||
}
|
||||
|
||||
for call in refs.calls.iter() {
|
||||
if !visited.contains(&call) {
|
||||
let other_refs =
|
||||
references_from_call(call.clone(), visited, refs_by_def, closures);
|
||||
let other_refs = references_from_call(*call, visited, refs_by_def, closures);
|
||||
|
||||
answer = answer.union(other_refs);
|
||||
}
|
||||
@ -892,30 +771,25 @@ where
|
||||
|
||||
visited.insert(call_symbol);
|
||||
|
||||
for closed_over_local in references.locals.iter() {
|
||||
for closed_over_local in references.lookups.iter() {
|
||||
if !visited.contains(&closed_over_local) {
|
||||
let other_refs = references_from_local(
|
||||
closed_over_local.clone(),
|
||||
visited,
|
||||
refs_by_def,
|
||||
closures,
|
||||
);
|
||||
let other_refs =
|
||||
references_from_local(*closed_over_local, visited, refs_by_def, closures);
|
||||
|
||||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.locals.insert(closed_over_local.clone());
|
||||
answer.lookups.insert(closed_over_local.clone());
|
||||
}
|
||||
|
||||
for call in references.calls.iter() {
|
||||
if !visited.contains(&call) {
|
||||
let other_refs =
|
||||
references_from_call(call.clone(), visited, refs_by_def, closures);
|
||||
let other_refs = references_from_call(*call, visited, refs_by_def, closures);
|
||||
|
||||
answer = answer.union(other_refs);
|
||||
}
|
||||
|
||||
answer.calls.insert(call.clone());
|
||||
answer.calls.insert(*call);
|
||||
}
|
||||
|
||||
answer
|
||||
@ -928,63 +802,8 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// If it could not be found, return it unchanged as an Err.
|
||||
#[inline(always)] // This is shared code between Var and InterpolatedStr; it was inlined when handwritten
|
||||
fn resolve_ident<'a>(
|
||||
env: &'a Env,
|
||||
scope: &Scope,
|
||||
ident: Ident,
|
||||
references: &mut References,
|
||||
) -> Result<(ModuleName, Symbol), Ident> {
|
||||
if scope.idents.contains_key(&ident) {
|
||||
let recognized = match ident {
|
||||
Ident::Unqualified(name) => {
|
||||
let symbol = scope.symbol(&name);
|
||||
|
||||
references.locals.insert(symbol.clone());
|
||||
|
||||
("".into(), symbol)
|
||||
}
|
||||
Ident::Qualified(path, name) => {
|
||||
let symbol = Symbol::new(&path, &name);
|
||||
|
||||
references.globals.insert(symbol.clone());
|
||||
|
||||
(ModuleName::from(path), symbol)
|
||||
}
|
||||
};
|
||||
|
||||
Ok(recognized)
|
||||
} else {
|
||||
match ident {
|
||||
Ident::Unqualified(name) => {
|
||||
// Try again, this time using the current module as the path.
|
||||
let qualified = Ident::Qualified(env.home.as_str().into(), name.clone());
|
||||
|
||||
if scope.idents.contains_key(&qualified) {
|
||||
let symbol = Symbol::new(env.home.as_str(), &name);
|
||||
|
||||
references.globals.insert(symbol.clone());
|
||||
|
||||
Ok(("".into(), symbol))
|
||||
} else {
|
||||
// We couldn't find the unqualified ident in scope. NAMING PROBLEM!
|
||||
Err(Ident::Unqualified(name))
|
||||
}
|
||||
}
|
||||
Ident::Qualified(module_name, name) => {
|
||||
let symbol = Symbol::from_qualified_ident(module_name.clone(), name);
|
||||
|
||||
references.globals.insert(symbol.clone());
|
||||
|
||||
Ok((ModuleName::from(module_name), symbol))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn canonicalize_fields<'a>(
|
||||
env: &mut Env,
|
||||
env: &mut Env<'a>,
|
||||
var_store: &VarStore,
|
||||
scope: &mut Scope,
|
||||
fields: &'a [Located<ast::AssignedField<'a, ast::Expr<'a>>>],
|
||||
@ -1011,7 +830,7 @@ fn canonicalize_fields<'a>(
|
||||
}
|
||||
|
||||
fn canonicalize_field<'a>(
|
||||
env: &mut Env,
|
||||
env: &mut Env<'a>,
|
||||
var_store: &VarStore,
|
||||
scope: &mut Scope,
|
||||
field: &'a ast::AssignedField<'a, ast::Expr<'a>>,
|
||||
@ -1048,3 +867,48 @@ fn canonicalize_field<'a>(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn canonicalize_lookup(
|
||||
env: &mut Env<'_>,
|
||||
scope: &mut Scope,
|
||||
module_name: &str,
|
||||
ident: &str,
|
||||
region: Region,
|
||||
) -> (Expr, Output) {
|
||||
use Expr::*;
|
||||
|
||||
let mut output = Output::default();
|
||||
let can_expr = if module_name.is_empty() {
|
||||
// Since module_name was empty, this is an unqualified var.
|
||||
// Look it up in scope!
|
||||
match scope.lookup(&(*ident).into(), region) {
|
||||
Ok(symbol) => {
|
||||
output.references.lookups.insert(symbol);
|
||||
|
||||
Var(symbol)
|
||||
}
|
||||
Err(problem) => {
|
||||
env.problem(Problem::RuntimeError(problem.clone()));
|
||||
|
||||
RuntimeError(problem)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Since module_name was nonempty, this is a qualified var.
|
||||
// Look it up in the env!
|
||||
match env.qualified_lookup(module_name, ident, region) {
|
||||
Ok(symbol) => Var(symbol),
|
||||
Err(problem) => {
|
||||
// Either the module wasn't imported, or
|
||||
// it was imported but it doesn't expose this ident.
|
||||
env.problem(Problem::RuntimeError(problem.clone()));
|
||||
|
||||
RuntimeError(problem)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// If it's valid, this ident should be in scope already.
|
||||
|
||||
(can_expr, output)
|
||||
}
|
||||
|
@ -1,7 +1,17 @@
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::module::symbol::{Interns, ModuleId, Symbol};
|
||||
use inlinable_string::InlinableString;
|
||||
use std::fmt;
|
||||
|
||||
/// This could be uppercase or lowercase, qualified or unqualified.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Ident(InlinableString);
|
||||
|
||||
impl Ident {
|
||||
pub fn as_inline_str(&self) -> &InlinableString {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ModuleName(InlinableString);
|
||||
|
||||
@ -13,7 +23,7 @@ pub struct Lowercase(InlinableString);
|
||||
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Uppercase(InlinableString);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum TagName {
|
||||
/// Global tags have no module, but tend to be short strings (since they're
|
||||
/// never qualified), so we store them as inlinable strings.
|
||||
@ -30,10 +40,10 @@ pub enum TagName {
|
||||
}
|
||||
|
||||
impl TagName {
|
||||
pub fn as_str(&self) -> &str {
|
||||
pub fn into_string(self, interns: &Interns, home: ModuleId) -> InlinableString {
|
||||
match self {
|
||||
Self::Global(uppercase) => uppercase.as_str(),
|
||||
Self::Private(symbol) => symbol.as_str(),
|
||||
TagName::Global(uppercase) => uppercase.as_inline_str().clone(),
|
||||
TagName::Private(symbol) => symbol.fully_qualified(interns, home),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,6 +126,10 @@ impl Uppercase {
|
||||
pub fn as_str(&self) -> &str {
|
||||
&*self.0
|
||||
}
|
||||
|
||||
pub fn as_inline_str(&self) -> &InlinableString {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Uppercase {
|
||||
@ -148,6 +162,61 @@ impl<'a> From<String> for Lowercase {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<InlinableString> for Lowercase {
|
||||
fn into(self) -> InlinableString {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for Ident {
|
||||
#[inline(always)]
|
||||
fn as_ref(&self) -> &str {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Ident {
|
||||
fn from(string: &'a str) -> Self {
|
||||
Self(string.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<str>> for Ident {
|
||||
fn from(string: Box<str>) -> Self {
|
||||
Self((string.as_ref()).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for Ident {
|
||||
fn from(string: String) -> Self {
|
||||
Self(string.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InlinableString> for Ident {
|
||||
fn from(string: InlinableString) -> Self {
|
||||
Self(string)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<InlinableString> for Ident {
|
||||
fn into(self) -> InlinableString {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<&'a InlinableString> for &'a Ident {
|
||||
fn into(self) -> &'a InlinableString {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Into<Box<str>> for Ident {
|
||||
fn into(self) -> Box<str> {
|
||||
self.0.to_string().into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Rather than displaying as this:
|
||||
///
|
||||
/// Lowercase("foo")
|
||||
|
@ -11,4 +11,3 @@ pub mod problem;
|
||||
pub mod procedure;
|
||||
pub mod scope;
|
||||
pub mod string;
|
||||
pub mod symbol;
|
||||
|
@ -1,41 +1,48 @@
|
||||
use crate::can::def::{canonicalize_defs, sort_can_defs, Declaration};
|
||||
use crate::can::env::Env;
|
||||
use crate::can::expr::Output;
|
||||
use crate::can::ident::ModuleName;
|
||||
use crate::can::ident::Ident;
|
||||
use crate::can::ident::Lowercase;
|
||||
use crate::can::operator::desugar_def;
|
||||
use crate::can::pattern::PatternType;
|
||||
use crate::can::problem::RuntimeError;
|
||||
use crate::can::scope::Scope;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::SendMap;
|
||||
use crate::module::symbol::ModuleId;
|
||||
use crate::collections::{MutMap, MutSet};
|
||||
use crate::module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
|
||||
use crate::parse::ast;
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::{VarStore, Variable};
|
||||
use crate::types::Alias;
|
||||
use bumpalo::Bump;
|
||||
use inlinable_string::InlinableString;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Module {
|
||||
pub module_id: ModuleId,
|
||||
pub declarations: Vec<Declaration>,
|
||||
pub exposed_imports: SendMap<Symbol, Variable>,
|
||||
}
|
||||
|
||||
pub struct ModuleOutput {
|
||||
pub aliases: MutMap<Symbol, Alias>,
|
||||
pub rigid_variables: MutMap<Lowercase, Variable>,
|
||||
pub declarations: Vec<Declaration>,
|
||||
pub exposed_imports: SendMap<Symbol, Variable>,
|
||||
pub exposed_imports: MutMap<Symbol, Variable>,
|
||||
pub lookups: Vec<(Symbol, Variable, Region)>,
|
||||
pub ident_ids: IdentIds,
|
||||
pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>,
|
||||
pub references: MutSet<Symbol>,
|
||||
}
|
||||
|
||||
// TODO trim these down
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn canonicalize_module_defs<'a>(
|
||||
arena: &Bump,
|
||||
loc_defs: bumpalo::collections::Vec<'a, Located<ast::Def<'a>>>,
|
||||
home: ModuleName,
|
||||
_exposes: Vec<InlinableString>,
|
||||
scope: &mut Scope,
|
||||
home: ModuleId,
|
||||
module_ids: &ModuleIds,
|
||||
exposed_ident_ids: IdentIds,
|
||||
dep_idents: MutMap<ModuleId, IdentIds>,
|
||||
exposed_imports: MutMap<Ident, (Symbol, Region)>,
|
||||
mut exposed_symbols: MutSet<Symbol>,
|
||||
var_store: &VarStore,
|
||||
) -> Result<ModuleOutput, RuntimeError> {
|
||||
let mut exposed_imports = SendMap::default();
|
||||
let mut can_exposed_imports = MutMap::default();
|
||||
let mut scope = Scope::new(home);
|
||||
let num_deps = dep_idents.len();
|
||||
|
||||
// Desugar operators (convert them to Apply calls, taking into account
|
||||
// operator precedence and associativity rules), before doing other canonicalization.
|
||||
@ -45,7 +52,7 @@ pub fn canonicalize_module_defs<'a>(
|
||||
// operators, and then again on *their* nested operators, ultimately applying the
|
||||
// rules multiple times unnecessarily.
|
||||
let mut desugared =
|
||||
bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + scope.idents.len(), arena);
|
||||
bumpalo::collections::Vec::with_capacity_in(loc_defs.len() + num_deps, arena);
|
||||
|
||||
for loc_def in loc_defs {
|
||||
desugared.push(&*arena.alloc(Located {
|
||||
@ -54,8 +61,9 @@ pub fn canonicalize_module_defs<'a>(
|
||||
}));
|
||||
}
|
||||
|
||||
let mut env = Env::new(home);
|
||||
let mut lookups = Vec::with_capacity(scope.idents.len());
|
||||
let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids);
|
||||
let mut lookups = Vec::with_capacity(num_deps);
|
||||
let mut rigid_variables = MutMap::default();
|
||||
|
||||
// Exposed values are treated like defs that appear before any others, e.g.
|
||||
//
|
||||
@ -68,40 +76,150 @@ pub fn canonicalize_module_defs<'a>(
|
||||
//
|
||||
// Here we essentially add those "defs" to "the beginning of the module"
|
||||
// by canonicalizing them right before we canonicalize the actual ast::Def nodes.
|
||||
for (ident, (symbol, region)) in scope.idents.iter() {
|
||||
if ident.first_char().is_lowercase() {
|
||||
for (ident, (symbol, region)) in exposed_imports {
|
||||
let first_char = ident.as_inline_str().chars().next().unwrap();
|
||||
|
||||
if first_char.is_lowercase() {
|
||||
let expr_var = var_store.fresh();
|
||||
|
||||
// Add an entry to exposed_imports using the current module's name
|
||||
// as the key; e.g. if this is the Foo module and we have
|
||||
// exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when
|
||||
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
|
||||
exposed_imports.insert(scope.symbol(&*ident.clone().name()), expr_var);
|
||||
match scope.import(ident, symbol, region) {
|
||||
Ok(symbol) => {
|
||||
// Add an entry to exposed_imports using the current module's name
|
||||
// as the key; e.g. if this is the Foo module and we have
|
||||
// exposes [ Bar.{ baz } ] then insert Foo.baz as the key, so when
|
||||
// anything references `baz` in this Foo module, it will resolve to Bar.baz.
|
||||
can_exposed_imports.insert(symbol, expr_var);
|
||||
|
||||
// This will be used during constraint generation,
|
||||
// to add the usual Lookup constraint as if this were a normal def.
|
||||
lookups.push((symbol.clone(), expr_var, *region));
|
||||
// This will be used during constraint generation,
|
||||
// to add the usual Lookup constraint as if this were a normal def.
|
||||
lookups.push((symbol, expr_var, region));
|
||||
}
|
||||
Err((_shadowed_symbol, _region)) => {
|
||||
panic!("TODO gracefully handle shadowing in imports.")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// TODO add type aliases to type alias dictionary, based on exposed types
|
||||
panic!("TODO add type aliases to type alias dictionary, based on exposed types");
|
||||
}
|
||||
}
|
||||
|
||||
let mut output = Output::default();
|
||||
let defs = canonicalize_defs(&mut env, &mut output.rigids, var_store, scope, &desugared);
|
||||
let (defs, scope, output) = canonicalize_defs(
|
||||
&mut env,
|
||||
Output::default(),
|
||||
var_store,
|
||||
&scope,
|
||||
&desugared,
|
||||
PatternType::TopLevelDef,
|
||||
);
|
||||
|
||||
for (var, lowercase) in output.rigids.clone() {
|
||||
rigid_variables.insert(var, lowercase);
|
||||
}
|
||||
|
||||
let mut references = MutSet::default();
|
||||
|
||||
// Gather up all the symbols that were referenced across all the defs.
|
||||
for symbol in output.references.lookups.iter() {
|
||||
references.insert(*symbol);
|
||||
}
|
||||
|
||||
// Gather up all the symbols that were referenced from other modules.
|
||||
for symbol in env.referenced_symbols.iter() {
|
||||
references.insert(*symbol);
|
||||
}
|
||||
|
||||
match sort_can_defs(&mut env, defs, Output::default()) {
|
||||
(Ok(declarations), _) => {
|
||||
// TODO examine the patterns, extract toplevel identifiers from them,
|
||||
// and verify that everything in the `exposes` list is actually present in
|
||||
// that set of identifiers. You can't expose it if it wasn't defined!
|
||||
(Ok(declarations), output) => {
|
||||
use crate::can::def::Declaration::*;
|
||||
|
||||
let mut exposed_vars_by_symbol = Vec::with_capacity(exposed_symbols.len());
|
||||
|
||||
for decl in declarations.iter() {
|
||||
match decl {
|
||||
Declare(def) => {
|
||||
// TODO if this doesn't work, try def.expr_var
|
||||
for (symbol, variable) in def.pattern_vars.iter() {
|
||||
if exposed_symbols.contains(symbol) {
|
||||
// This is one of our exposed symbols;
|
||||
// record the corresponding variable!
|
||||
exposed_vars_by_symbol.push((*symbol, *variable));
|
||||
|
||||
// Remove this from exposed_symbols,
|
||||
// so that at the end of the process,
|
||||
// we can see if there were any
|
||||
// exposed symbols which did not have
|
||||
// corresponding defs.
|
||||
exposed_symbols.remove(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
DeclareRec(defs) => {
|
||||
for def in defs {
|
||||
for (symbol, variable) in def.pattern_vars.iter() {
|
||||
if exposed_symbols.contains(symbol) {
|
||||
// This is one of our exposed symbols;
|
||||
// record the corresponding variable!
|
||||
exposed_vars_by_symbol.push((*symbol, *variable));
|
||||
|
||||
// Remove this from exposed_symbols,
|
||||
// so that at the end of the process,
|
||||
// we can see if there were any
|
||||
// exposed symbols which did not have
|
||||
// corresponding defs.
|
||||
exposed_symbols.remove(symbol);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InvalidCycle(identifiers, _) => {
|
||||
panic!("TODO gracefully handle potentially attempting to expose invalid cyclic defs {:?}" , identifiers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut aliases = MutMap::default();
|
||||
|
||||
for (symbol, alias) in scope.into_aliases() {
|
||||
// Remove this from exposed_symbols,
|
||||
// so that at the end of the process,
|
||||
// we can see if there were any
|
||||
// exposed symbols which did not have
|
||||
// corresponding defs.
|
||||
exposed_symbols.remove(&symbol);
|
||||
|
||||
// TODO store aliases as a MutMap inside Scope
|
||||
// (and manually remove them when exiting a scope)
|
||||
// and we can use it directly instead of rebuilding it
|
||||
// piece by piece like this.
|
||||
aliases.insert(symbol, alias);
|
||||
}
|
||||
|
||||
// By this point, all exposed symbols should have been removed from
|
||||
// exposed_symbols and added to exposed_vars_by_symbol. If any were
|
||||
// not, that means they were declared as exposed but there was
|
||||
// no actual declaration with that name!
|
||||
if !exposed_symbols.is_empty() {
|
||||
panic!("TODO gracefully handle invalid `exposes` entry (or entries) which had no corresponding definition: {:?}", exposed_symbols);
|
||||
}
|
||||
|
||||
// TODO incorporate rigids into here (possibly by making this be a Let instead
|
||||
// of an And)
|
||||
|
||||
// Incorporate any remaining output.lookups entries into references.
|
||||
for symbol in output.references.lookups {
|
||||
references.insert(symbol);
|
||||
}
|
||||
|
||||
Ok(ModuleOutput {
|
||||
aliases,
|
||||
rigid_variables,
|
||||
declarations,
|
||||
exposed_imports,
|
||||
references,
|
||||
exposed_imports: can_exposed_imports,
|
||||
lookups,
|
||||
exposed_vars_by_symbol,
|
||||
ident_ids: env.ident_ids,
|
||||
})
|
||||
}
|
||||
(Err(runtime_error), _) => Err(runtime_error),
|
||||
|
@ -38,8 +38,8 @@ pub fn desugar_def<'a>(arena: &'a Bump, def: &'a Def<'a>) -> Def<'a> {
|
||||
Body(loc_pattern, loc_expr) | Nested(Body(loc_pattern, loc_expr)) => {
|
||||
Body(loc_pattern, desugar_expr(arena, loc_expr))
|
||||
}
|
||||
TypedDef(loc_pattern, loc_annotation, loc_expr)
|
||||
| Nested(TypedDef(loc_pattern, loc_annotation, loc_expr)) => TypedDef(
|
||||
TypedBody(loc_pattern, loc_annotation, loc_expr)
|
||||
| Nested(TypedBody(loc_pattern, loc_annotation, loc_expr)) => TypedBody(
|
||||
loc_pattern,
|
||||
loc_annotation.clone(),
|
||||
desugar_expr(arena, loc_expr),
|
||||
@ -72,8 +72,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||
| Nested(BlockStr(_))
|
||||
| AccessorFunction(_)
|
||||
| Nested(AccessorFunction(_))
|
||||
| Var(_, _)
|
||||
| Nested(Var(_, _))
|
||||
| Var { .. }
|
||||
| Nested(Var { .. })
|
||||
| MalformedIdent(_)
|
||||
| Nested(MalformedIdent(_))
|
||||
| MalformedClosure
|
||||
@ -199,15 +199,18 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||
|
||||
let region = loc_op.region;
|
||||
let op = loc_op.value;
|
||||
// TODO desugar this in canonicalization instead, so we can work
|
||||
// in terms of integers exclusively and not need to create strings
|
||||
// which canonicalization then needs to look up, check if they're exposed, etc
|
||||
let value = match op {
|
||||
Negate => Var(
|
||||
bumpalo::vec![in arena; ModuleName::NUM].into_bump_slice(),
|
||||
"negate",
|
||||
),
|
||||
Not => Var(
|
||||
bumpalo::vec![in arena; ModuleName::BOOL].into_bump_slice(),
|
||||
"not",
|
||||
),
|
||||
Negate => Var {
|
||||
module_name: ModuleName::NUM,
|
||||
ident: "negate",
|
||||
},
|
||||
Not => Var {
|
||||
module_name: ModuleName::BOOL,
|
||||
ident: "not",
|
||||
},
|
||||
};
|
||||
let loc_fn_var = arena.alloc(Located { region, value });
|
||||
let desugared_args = bumpalo::vec![in arena; desugar_expr(arena, loc_arg)];
|
||||
@ -234,61 +237,18 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'a>>) -> &'a
|
||||
}),
|
||||
)
|
||||
}
|
||||
If((condition, then_branch, else_branch))
|
||||
| Nested(If((condition, then_branch, else_branch))) => {
|
||||
// desugar if into case, meaning that
|
||||
//
|
||||
// if b then x else y
|
||||
//
|
||||
// becomes
|
||||
//
|
||||
// when b is
|
||||
// False -> y
|
||||
// _ -> x
|
||||
//
|
||||
// False compiles to 0, and the number zero is special;
|
||||
// processors often have special-cased instructions that work on 0
|
||||
// rather than having to load a nonzero value into another register.
|
||||
// Case in point: the jz ("jump if zero") instruction.
|
||||
// So by making our two comparisons be "0 and else",
|
||||
// LLVM will compile this to a jz instruction,
|
||||
// whereas if we made it be "1 and else" it couldn't do that.
|
||||
let mut branches = Vec::with_capacity_in(2, arena);
|
||||
If(condition, then_branch, else_branch)
|
||||
| Nested(If(condition, then_branch, else_branch)) => {
|
||||
// If does not get desugared yet so we can give more targetted error messages during
|
||||
// type checking.
|
||||
let desugared_cond = &*arena.alloc(desugar_expr(arena, &condition));
|
||||
let desugared_then = &*arena.alloc(desugar_expr(arena, &then_branch));
|
||||
let desugared_else = &*arena.alloc(desugar_expr(arena, &else_branch));
|
||||
|
||||
// no type errors will occur here so using this region should be fine
|
||||
let pattern_region = condition.region;
|
||||
|
||||
branches.push(&*arena.alloc(WhenBranch {
|
||||
patterns: bumpalo::vec![in arena; Located {
|
||||
value: Pattern::GlobalTag("False"),
|
||||
region: pattern_region,
|
||||
}],
|
||||
value: Located {
|
||||
value: Nested(&else_branch.value),
|
||||
region: else_branch.region,
|
||||
},
|
||||
guard: None,
|
||||
}));
|
||||
|
||||
branches.push(&*arena.alloc(WhenBranch {
|
||||
patterns: bumpalo::vec![in arena; Located {
|
||||
value: Pattern::Underscore,
|
||||
region: pattern_region,
|
||||
}],
|
||||
value: Located {
|
||||
value: Nested(&then_branch.value),
|
||||
region: then_branch.region,
|
||||
},
|
||||
guard: None,
|
||||
}));
|
||||
|
||||
desugar_expr(
|
||||
arena,
|
||||
arena.alloc(Located {
|
||||
value: When(condition, branches),
|
||||
region: loc_expr.region,
|
||||
}),
|
||||
)
|
||||
arena.alloc(Located {
|
||||
value: If(desugared_cond, desugared_then, desugared_else),
|
||||
region: loc_expr.region,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -311,7 +271,10 @@ fn desugar_field<'a>(
|
||||
LabelOnly(loc_str) => {
|
||||
// Desugar { x } into { x: x }
|
||||
let loc_expr = Located {
|
||||
value: Var(&[], loc_str.value),
|
||||
value: Var {
|
||||
module_name: "",
|
||||
ident: loc_str.value,
|
||||
},
|
||||
region: loc_str.region,
|
||||
};
|
||||
|
||||
@ -331,76 +294,29 @@ fn desugar_field<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
// TODO move this desugaring to canonicalization, to avoid dealing with strings as much
|
||||
#[inline(always)]
|
||||
fn binop_to_function(binop: BinOp, arena: &Bump) -> (&[&str], &str) {
|
||||
fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) {
|
||||
use self::BinOp::*;
|
||||
|
||||
match binop {
|
||||
Caret => (
|
||||
bumpalo::vec![ in arena; ModuleName::NUM ].into_bump_slice(),
|
||||
"pow",
|
||||
),
|
||||
Star => (
|
||||
bumpalo::vec![ in arena; ModuleName::NUM ].into_bump_slice(),
|
||||
"mul",
|
||||
),
|
||||
Slash => (
|
||||
bumpalo::vec![ in arena; ModuleName::FLOAT ].into_bump_slice(),
|
||||
"div",
|
||||
),
|
||||
DoubleSlash => (
|
||||
bumpalo::vec![ in arena; ModuleName::INT ].into_bump_slice(),
|
||||
"divFloor",
|
||||
),
|
||||
Percent => (
|
||||
bumpalo::vec![ in arena; ModuleName::NUM ].into_bump_slice(),
|
||||
"rem",
|
||||
),
|
||||
DoublePercent => (
|
||||
bumpalo::vec![ in arena; ModuleName::NUM ].into_bump_slice(),
|
||||
"mod",
|
||||
),
|
||||
Plus => (
|
||||
bumpalo::vec![ in arena; ModuleName::NUM ].into_bump_slice(),
|
||||
"plus",
|
||||
),
|
||||
Minus => (
|
||||
bumpalo::vec![ in arena; ModuleName::NUM ].into_bump_slice(),
|
||||
"sub",
|
||||
),
|
||||
Equals => (
|
||||
bumpalo::vec![ in arena; ModuleName::BOOL ].into_bump_slice(),
|
||||
"isEq",
|
||||
),
|
||||
NotEquals => (
|
||||
bumpalo::vec![ in arena; ModuleName::BOOL ].into_bump_slice(),
|
||||
"isNotEq",
|
||||
),
|
||||
LessThan => (
|
||||
bumpalo::vec![ in arena; ModuleName::NUM ].into_bump_slice(),
|
||||
"isLt",
|
||||
),
|
||||
GreaterThan => (
|
||||
bumpalo::vec![ in arena; ModuleName::NUM ].into_bump_slice(),
|
||||
"isGt",
|
||||
),
|
||||
LessThanOrEq => (
|
||||
bumpalo::vec![ in arena; ModuleName::NUM ].into_bump_slice(),
|
||||
"isLte",
|
||||
),
|
||||
GreaterThanOrEq => (
|
||||
bumpalo::vec![ in arena; ModuleName::NUM ].into_bump_slice(),
|
||||
"isGte",
|
||||
),
|
||||
And => (
|
||||
bumpalo::vec![ in arena; ModuleName::BOOL ].into_bump_slice(),
|
||||
"and",
|
||||
),
|
||||
Or => (
|
||||
bumpalo::vec![ in arena; ModuleName::BOOL ].into_bump_slice(),
|
||||
"or",
|
||||
),
|
||||
Pizza => panic!("Cannot desugar the |> operator"),
|
||||
Caret => (ModuleName::NUM, "pow"),
|
||||
Star => (ModuleName::NUM, "mul"),
|
||||
Slash => (ModuleName::FLOAT, "div"),
|
||||
DoubleSlash => (ModuleName::INT, "div"),
|
||||
Percent => (ModuleName::NUM, "rem"),
|
||||
DoublePercent => (ModuleName::NUM, "mod"),
|
||||
Plus => (ModuleName::NUM, "add"),
|
||||
Minus => (ModuleName::NUM, "sub"),
|
||||
Equals => (ModuleName::BOOL, "isEq"),
|
||||
NotEquals => (ModuleName::BOOL, "isNotEq"),
|
||||
LessThan => (ModuleName::NUM, "isLt"),
|
||||
GreaterThan => (ModuleName::NUM, "isGt"),
|
||||
LessThanOrEq => (ModuleName::NUM, "isLte"),
|
||||
GreaterThanOrEq => (ModuleName::NUM, "isGte"),
|
||||
And => (ModuleName::BOOL, "and"),
|
||||
Or => (ModuleName::BOOL, "or"),
|
||||
Pizza => unreachable!("Cannot desugar the |> operator"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -564,14 +480,14 @@ fn desugar_bin_op<'a>(arena: &'a Bump, loc_expr: &'a Located<Expr<'_>>) -> &'a L
|
||||
binop => {
|
||||
// This is a normal binary operator like (+), so desugar it
|
||||
// into the appropriate function call.
|
||||
let (module_parts, name) = binop_to_function(binop, arena);
|
||||
let (module_name, ident) = binop_to_function(binop);
|
||||
let mut args = Vec::with_capacity_in(2, arena);
|
||||
|
||||
args.push(left);
|
||||
args.push(right);
|
||||
|
||||
let loc_expr = arena.alloc(Located {
|
||||
value: Expr::Var(module_parts, name),
|
||||
value: Expr::Var { module_name, ident },
|
||||
region: loc_op.region,
|
||||
});
|
||||
|
||||
|
@ -1,16 +1,13 @@
|
||||
use crate::can::env::Env;
|
||||
use crate::can::ident::{Lowercase, TagName};
|
||||
use crate::can::ident::{Ident, Lowercase, TagName};
|
||||
use crate::can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
|
||||
use crate::can::problem::Problem;
|
||||
use crate::can::problem::{Problem, RuntimeError};
|
||||
use crate::can::scope::Scope;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::ImMap;
|
||||
use crate::ident::Ident;
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::parse::ast;
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::VarStore;
|
||||
use crate::subs::Variable;
|
||||
use im_rc::Vector;
|
||||
|
||||
/// A pattern, including possible problems (e.g. shadowing) so that
|
||||
/// codegen can generate a runtime error if this pattern is reached.
|
||||
@ -21,11 +18,11 @@ pub enum Pattern {
|
||||
IntLiteral(i64),
|
||||
FloatLiteral(f64),
|
||||
StrLiteral(Box<str>),
|
||||
RecordDestructure(Variable, Vec<RecordDestruct>),
|
||||
RecordDestructure(Variable, Vec<Located<RecordDestruct>>),
|
||||
Underscore,
|
||||
|
||||
// Runtime Exceptions
|
||||
Shadowed(Located<Ident>),
|
||||
Shadowed(Region, Located<Ident>),
|
||||
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
UnsupportedPattern(Region),
|
||||
}
|
||||
@ -46,8 +43,27 @@ pub fn symbols_from_pattern(pattern: &Pattern) -> Vec<Symbol> {
|
||||
}
|
||||
|
||||
pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
||||
if let Pattern::Identifier(symbol) = pattern {
|
||||
symbols.push(symbol.clone());
|
||||
use Pattern::*;
|
||||
|
||||
match pattern {
|
||||
Identifier(symbol) => {
|
||||
symbols.push(symbol.clone());
|
||||
}
|
||||
|
||||
AppliedTag(_, _, arguments) => {
|
||||
for (_, nested) in arguments {
|
||||
symbols_from_pattern_help(&nested.value, symbols);
|
||||
}
|
||||
}
|
||||
RecordDestructure(_, destructs) => {
|
||||
for destruct in destructs {
|
||||
symbols.push(destruct.value.symbol.clone());
|
||||
}
|
||||
}
|
||||
|
||||
IntLiteral(_) | FloatLiteral(_) | StrLiteral(_) | Underscore | UnsupportedPattern(_) => {}
|
||||
|
||||
Shadowed(_, _) => {}
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,43 +74,64 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec<Symbol>) {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub enum PatternType {
|
||||
TopLevelDef,
|
||||
Assignment,
|
||||
DefExpr,
|
||||
FunctionArg,
|
||||
WhenBranch,
|
||||
}
|
||||
|
||||
pub fn canonicalize_pattern<'a>(
|
||||
env: &'a mut Env,
|
||||
env: &mut Env<'a>,
|
||||
var_store: &VarStore,
|
||||
scope: &mut Scope,
|
||||
pattern_type: PatternType,
|
||||
pattern: &'a ast::Pattern<'a>,
|
||||
pattern: &ast::Pattern<'a>,
|
||||
region: Region,
|
||||
shadowable_idents: &'a mut ImMap<Ident, (Symbol, Region)>,
|
||||
) -> Located<Pattern> {
|
||||
use self::PatternType::*;
|
||||
use crate::parse::ast::Pattern::*;
|
||||
use PatternType::*;
|
||||
|
||||
let can_pattern = match &pattern {
|
||||
&Identifier(ref name) => {
|
||||
match canonicalize_pattern_identifier(name, env, scope, region, shadowable_idents) {
|
||||
Ok(symbol) => Pattern::Identifier(symbol),
|
||||
Err(loc_shadowed_ident) => Pattern::Shadowed(loc_shadowed_ident),
|
||||
let can_pattern = match pattern {
|
||||
Identifier(name) => match scope.introduce(
|
||||
(*name).into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
region,
|
||||
) {
|
||||
Ok(symbol) => Pattern::Identifier(symbol),
|
||||
Err((original_region, shadow)) => {
|
||||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
}));
|
||||
|
||||
Pattern::Shadowed(original_region, shadow)
|
||||
}
|
||||
}
|
||||
&GlobalTag(name) => {
|
||||
},
|
||||
GlobalTag(name) => {
|
||||
// Canonicalize the tag's name.
|
||||
Pattern::AppliedTag(var_store.fresh(), TagName::Global((*name).into()), vec![])
|
||||
}
|
||||
&PrivateTag(name) => {
|
||||
PrivateTag(name) => {
|
||||
let ident_id = env.ident_ids.get_or_insert(&(*name).into());
|
||||
|
||||
// Canonicalize the tag's name.
|
||||
Pattern::AppliedTag(
|
||||
var_store.fresh(),
|
||||
TagName::Private(Symbol::from_private_tag(env.home.as_str(), name)),
|
||||
TagName::Private(Symbol::new(env.home, ident_id)),
|
||||
vec![],
|
||||
)
|
||||
}
|
||||
&Apply(tag, patterns) => {
|
||||
Apply(tag, patterns) => {
|
||||
let tag_name = match tag.value {
|
||||
GlobalTag(name) => TagName::Global(name.into()),
|
||||
PrivateTag(name) => {
|
||||
let ident_id = env.ident_ids.get_or_insert(&name.into());
|
||||
|
||||
TagName::Private(Symbol::new(env.home, ident_id))
|
||||
}
|
||||
_ => unreachable!("Other patterns cannot be applied"),
|
||||
};
|
||||
|
||||
let mut can_patterns = Vec::with_capacity(patterns.len());
|
||||
for loc_pattern in *patterns {
|
||||
can_patterns.push((
|
||||
@ -106,52 +143,43 @@ pub fn canonicalize_pattern<'a>(
|
||||
pattern_type,
|
||||
&loc_pattern.value,
|
||||
loc_pattern.region,
|
||||
shadowable_idents,
|
||||
),
|
||||
));
|
||||
}
|
||||
|
||||
let tag_name = match tag.value {
|
||||
GlobalTag(name) => TagName::Global(name.into()),
|
||||
PrivateTag(name) => {
|
||||
TagName::Private(Symbol::from_private_tag(env.home.as_str(), name))
|
||||
}
|
||||
_ => unreachable!("Other patterns cannot be applied"),
|
||||
};
|
||||
|
||||
Pattern::AppliedTag(var_store.fresh(), tag_name, can_patterns)
|
||||
}
|
||||
|
||||
&FloatLiteral(ref string) => match pattern_type {
|
||||
FloatLiteral(ref string) => match pattern_type {
|
||||
WhenBranch => {
|
||||
let float = finish_parsing_float(string)
|
||||
.unwrap_or_else(|_| panic!("TODO handle malformed float pattern"));
|
||||
|
||||
Pattern::FloatLiteral(float)
|
||||
}
|
||||
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||
unsupported_pattern(env, ptype, region)
|
||||
}
|
||||
},
|
||||
|
||||
&Underscore => match pattern_type {
|
||||
Underscore => match pattern_type {
|
||||
WhenBranch | FunctionArg => Pattern::Underscore,
|
||||
ptype @ Assignment | ptype @ TopLevelDef => unsupported_pattern(env, ptype, region),
|
||||
ptype @ DefExpr | ptype @ TopLevelDef => unsupported_pattern(env, ptype, region),
|
||||
},
|
||||
|
||||
&IntLiteral(string) => match pattern_type {
|
||||
IntLiteral(string) => match pattern_type {
|
||||
WhenBranch => {
|
||||
let int = finish_parsing_int(string)
|
||||
.unwrap_or_else(|_| panic!("TODO handle malformed int pattern"));
|
||||
|
||||
Pattern::IntLiteral(int)
|
||||
}
|
||||
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||
unsupported_pattern(env, ptype, region)
|
||||
}
|
||||
},
|
||||
|
||||
&NonBase10Literal {
|
||||
NonBase10Literal {
|
||||
string,
|
||||
base,
|
||||
is_negative,
|
||||
@ -166,110 +194,114 @@ pub fn canonicalize_pattern<'a>(
|
||||
Pattern::IntLiteral(int)
|
||||
}
|
||||
}
|
||||
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||
unsupported_pattern(env, ptype, region)
|
||||
}
|
||||
},
|
||||
|
||||
&StrLiteral(_string) => match pattern_type {
|
||||
StrLiteral(_string) => match pattern_type {
|
||||
WhenBranch => {
|
||||
panic!("TODO check whether string pattern is malformed.");
|
||||
// Pattern::StrLiteral((*string).into())
|
||||
}
|
||||
ptype @ Assignment | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||
ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => {
|
||||
unsupported_pattern(env, ptype, region)
|
||||
}
|
||||
},
|
||||
|
||||
&SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => {
|
||||
return canonicalize_pattern(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
pattern_type,
|
||||
sub_pattern,
|
||||
region,
|
||||
shadowable_idents,
|
||||
)
|
||||
SpaceBefore(sub_pattern, _) | SpaceAfter(sub_pattern, _) | Nested(sub_pattern) => {
|
||||
return canonicalize_pattern(env, var_store, scope, pattern_type, sub_pattern, region)
|
||||
}
|
||||
&RecordDestructure(patterns) => {
|
||||
RecordDestructure(patterns) => {
|
||||
let ext_var = var_store.fresh();
|
||||
let mut fields = Vec::with_capacity(patterns.len());
|
||||
let mut opt_erroneous = None;
|
||||
|
||||
for loc_pattern in *patterns {
|
||||
match loc_pattern.value {
|
||||
Identifier(label) => {
|
||||
let symbol = match canonicalize_pattern_identifier(
|
||||
&label,
|
||||
env,
|
||||
scope,
|
||||
match scope.introduce(
|
||||
label.into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
region,
|
||||
shadowable_idents,
|
||||
) {
|
||||
Ok(symbol) => symbol,
|
||||
Err(loc_shadowed_ident) => {
|
||||
// If any idents are shadowed, consider the entire
|
||||
// destructure pattern shadowed!
|
||||
let _loc_pattern = Located {
|
||||
region,
|
||||
value: Pattern::Shadowed(loc_shadowed_ident),
|
||||
};
|
||||
panic!("TODO gather all the shadowing errors, not just the first one, and report them in Problems.");
|
||||
Ok(symbol) => {
|
||||
fields.push(Located {
|
||||
region: loc_pattern.region,
|
||||
value: RecordDestruct {
|
||||
var: var_store.fresh(),
|
||||
label: Lowercase::from(label),
|
||||
symbol,
|
||||
guard: None,
|
||||
},
|
||||
});
|
||||
}
|
||||
Err((original_region, shadow)) => {
|
||||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
}));
|
||||
|
||||
// No matter what the other patterns
|
||||
// are, we're definitely shadowed and will
|
||||
// get a runtime exception as soon as we
|
||||
// encounter the first bad pattern.
|
||||
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow));
|
||||
}
|
||||
};
|
||||
|
||||
fields.push(RecordDestruct {
|
||||
var: var_store.fresh(),
|
||||
label: Lowercase::from(label),
|
||||
symbol,
|
||||
guard: None,
|
||||
});
|
||||
}
|
||||
RecordField(label, loc_guard) => {
|
||||
let symbol = match canonicalize_pattern_identifier(
|
||||
&label,
|
||||
env,
|
||||
scope,
|
||||
match scope.introduce(
|
||||
label.into(),
|
||||
&env.exposed_ident_ids,
|
||||
&mut env.ident_ids,
|
||||
region,
|
||||
shadowable_idents,
|
||||
) {
|
||||
Ok(symbol) => symbol,
|
||||
Err(loc_shadowed_ident) => {
|
||||
// If any idents are shadowed, consider the entire
|
||||
// destructure pattern shadowed!
|
||||
let _loc_pattern = Located {
|
||||
region,
|
||||
value: Pattern::Shadowed(loc_shadowed_ident),
|
||||
};
|
||||
panic!("TODO gather all the shadowing errors, not just the first one, and report them in Problems.");
|
||||
Ok(symbol) => {
|
||||
let can_guard = canonicalize_pattern(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
pattern_type,
|
||||
&loc_guard.value,
|
||||
loc_guard.region,
|
||||
);
|
||||
|
||||
fields.push(Located {
|
||||
region: loc_pattern.region,
|
||||
value: RecordDestruct {
|
||||
var: var_store.fresh(),
|
||||
label: Lowercase::from(label),
|
||||
symbol,
|
||||
guard: Some((var_store.fresh(), can_guard)),
|
||||
},
|
||||
});
|
||||
}
|
||||
Err((original_region, shadow)) => {
|
||||
env.problem(Problem::RuntimeError(RuntimeError::Shadowing {
|
||||
original_region,
|
||||
shadow: shadow.clone(),
|
||||
}));
|
||||
|
||||
// No matter what the other patterns
|
||||
// are, we're definitely shadowed and will
|
||||
// get a runtime exception as soon as we
|
||||
// encounter the first bad pattern.
|
||||
opt_erroneous = Some(Pattern::Shadowed(original_region, shadow));
|
||||
}
|
||||
};
|
||||
|
||||
let can_guard = canonicalize_pattern(
|
||||
env,
|
||||
var_store,
|
||||
scope,
|
||||
pattern_type,
|
||||
&loc_guard.value,
|
||||
loc_guard.region,
|
||||
shadowable_idents,
|
||||
);
|
||||
|
||||
fields.push(RecordDestruct {
|
||||
var: var_store.fresh(),
|
||||
label: Lowercase::from(label),
|
||||
symbol,
|
||||
guard: Some((var_store.fresh(), can_guard)),
|
||||
});
|
||||
}
|
||||
_ => panic!("invalid pattern in record"),
|
||||
}
|
||||
}
|
||||
|
||||
Pattern::RecordDestructure(ext_var, fields)
|
||||
// If we encountered an erroneous pattern (e.g. one with shadowing),
|
||||
// use the resulting RuntimeError. Otherwise, return a successful record destructure.
|
||||
opt_erroneous.unwrap_or_else(|| Pattern::RecordDestructure(ext_var, fields))
|
||||
}
|
||||
&RecordField(_name, _loc_pattern) => {
|
||||
unreachable!("should be handled in RecordDestructure");
|
||||
RecordField(_name, _loc_pattern) => {
|
||||
unreachable!("should have been handled in RecordDestructure");
|
||||
}
|
||||
|
||||
_ => panic!("TODO finish restoring can_pattern branch for {:?}", pattern),
|
||||
@ -281,191 +313,63 @@ pub fn canonicalize_pattern<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn canonicalize_pattern_identifier<'a>(
|
||||
name: &'a &str,
|
||||
env: &'a mut Env,
|
||||
scope: &mut Scope,
|
||||
region: Region,
|
||||
shadowable_idents: &'a mut ImMap<Ident, (Symbol, Region)>,
|
||||
) -> Result<Symbol, Located<Ident>> {
|
||||
let lowercase_ident = Ident::Unqualified((*name).into());
|
||||
|
||||
// We use shadowable_idents for this, and not scope, because for assignments
|
||||
// they are different. When canonicalizing a particular assignment, that new
|
||||
// ident is in scope (for recursion) but not shadowable.
|
||||
//
|
||||
// For example, when canonicalizing (fibonacci = ...), `fibonacci` should be in scope
|
||||
// so that it can refer to itself without getting a naming problem, but it should not
|
||||
// be in the collection of shadowable idents because you can't shadow yourself!
|
||||
match shadowable_idents.get(&lowercase_ident) {
|
||||
Some((_, region)) => {
|
||||
let loc_shadowed_ident = Located {
|
||||
region: *region,
|
||||
value: lowercase_ident,
|
||||
};
|
||||
|
||||
// This is already in scope, meaning it's about to be shadowed.
|
||||
// Shadowing is not allowed!
|
||||
env.problem(Problem::Shadowing(loc_shadowed_ident.clone()));
|
||||
|
||||
// Change this Pattern to a Shadowed variant, so that
|
||||
// codegen knows to generate a runtime exception here.
|
||||
Err(loc_shadowed_ident)
|
||||
}
|
||||
None => {
|
||||
// Make sure we aren't shadowing something in the home module's scope.
|
||||
let qualified_ident =
|
||||
Ident::Qualified(env.home.as_str().into(), lowercase_ident.name());
|
||||
|
||||
match scope.idents.get(&qualified_ident) {
|
||||
Some((_, region)) => {
|
||||
let loc_shadowed_ident = Located {
|
||||
region: *region,
|
||||
value: qualified_ident,
|
||||
};
|
||||
|
||||
// This is already in scope, meaning it's about to be shadowed.
|
||||
// Shadowing is not allowed!
|
||||
env.problem(Problem::Shadowing(loc_shadowed_ident.clone()));
|
||||
|
||||
// Change this Pattern to a Shadowed variant, so that
|
||||
// codegen knows to generate a runtime exception here.
|
||||
Err(loc_shadowed_ident)
|
||||
}
|
||||
None => {
|
||||
let new_ident = qualified_ident.clone();
|
||||
let new_name = qualified_ident.name();
|
||||
let symbol = scope.symbol(&new_name);
|
||||
|
||||
// This is a fresh identifier that wasn't already in scope.
|
||||
// Add it to scope!
|
||||
let symbol_and_region = (symbol.clone(), region);
|
||||
|
||||
// Add this to both scope.idents *and* shadowable_idents.
|
||||
// The latter is relevant when recursively canonicalizing
|
||||
// tag application patterns, which can bring multiple
|
||||
// new idents into scope. For example, it's important that
|
||||
// we catch (Blah foo foo) -> … as being an example of shadowing.
|
||||
shadowable_idents.insert(new_ident.clone(), symbol_and_region.clone());
|
||||
scope.idents.insert(new_ident, symbol_and_region);
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't
|
||||
/// assign to Int patterns), report it to Env and return an UnsupportedPattern runtime error pattern.
|
||||
fn unsupported_pattern(env: &mut Env, pattern_type: PatternType, region: Region) -> Pattern {
|
||||
fn unsupported_pattern<'a>(
|
||||
env: &mut Env<'a>,
|
||||
pattern_type: PatternType,
|
||||
region: Region,
|
||||
) -> Pattern {
|
||||
env.problem(Problem::UnsupportedPattern(pattern_type, region));
|
||||
|
||||
Pattern::UnsupportedPattern(region)
|
||||
}
|
||||
|
||||
pub fn remove_idents(pattern: &ast::Pattern, idents: &mut ImMap<Ident, (Symbol, Region)>) {
|
||||
use crate::parse::ast::Pattern::*;
|
||||
|
||||
match &pattern {
|
||||
Identifier(name) => {
|
||||
idents.remove(&(Ident::Unqualified((*name).into())));
|
||||
}
|
||||
QualifiedIdentifier(_name) => {
|
||||
panic!("TODO implement QualifiedIdentifier pattern in remove_idents.");
|
||||
}
|
||||
Apply(_, patterns) => {
|
||||
for loc_pattern in *patterns {
|
||||
remove_idents(&loc_pattern.value, idents);
|
||||
}
|
||||
}
|
||||
RecordDestructure(patterns) => {
|
||||
for loc_pattern in *patterns {
|
||||
remove_idents(&loc_pattern.value, idents);
|
||||
}
|
||||
}
|
||||
RecordField(_, loc_pattern) => {
|
||||
remove_idents(&loc_pattern.value, idents);
|
||||
}
|
||||
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => {
|
||||
// Ignore the newline/comment info; it doesn't matter in canonicalization.
|
||||
remove_idents(pattern, idents)
|
||||
}
|
||||
GlobalTag(_)
|
||||
| PrivateTag(_)
|
||||
| IntLiteral(_)
|
||||
| NonBase10Literal { .. }
|
||||
| FloatLiteral(_)
|
||||
| StrLiteral(_)
|
||||
| BlockStrLiteral(_)
|
||||
| Malformed(_)
|
||||
| Underscore => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn idents_from_patterns<'a, I>(
|
||||
loc_patterns: I,
|
||||
scope: &Scope,
|
||||
) -> Vector<(Ident, (Symbol, Region))>
|
||||
pub fn bindings_from_patterns<'a, I>(loc_patterns: I, scope: &Scope) -> Vec<(Symbol, Region)>
|
||||
where
|
||||
I: Iterator<Item = &'a Located<ast::Pattern<'a>>>,
|
||||
I: Iterator<Item = &'a Located<Pattern>>,
|
||||
{
|
||||
let mut answer = Vector::new();
|
||||
let mut answer = Vec::new();
|
||||
|
||||
for loc_pattern in loc_patterns {
|
||||
add_idents_from_pattern(&loc_pattern.region, &loc_pattern.value, scope, &mut answer);
|
||||
add_bindings_from_patterns(&loc_pattern.region, &loc_pattern.value, scope, &mut answer);
|
||||
}
|
||||
|
||||
answer
|
||||
}
|
||||
|
||||
/// helper function for idents_from_patterns
|
||||
fn add_idents_from_pattern<'a>(
|
||||
region: &'a Region,
|
||||
pattern: &'a ast::Pattern<'a>,
|
||||
scope: &'a Scope,
|
||||
answer: &'a mut Vector<(Ident, (Symbol, Region))>,
|
||||
fn add_bindings_from_patterns(
|
||||
region: &Region,
|
||||
pattern: &Pattern,
|
||||
scope: &Scope,
|
||||
answer: &mut Vec<(Symbol, Region)>,
|
||||
) {
|
||||
use crate::parse::ast::Pattern::*;
|
||||
use Pattern::*;
|
||||
|
||||
match pattern {
|
||||
Identifier(name) => {
|
||||
let symbol = scope.symbol(&name);
|
||||
|
||||
answer.push_back((Ident::Unqualified((*name).into()), (symbol, *region)));
|
||||
Identifier(symbol) => {
|
||||
answer.push((*symbol, *region));
|
||||
}
|
||||
QualifiedIdentifier(_name) => {
|
||||
panic!("TODO implement QualifiedIdentifier pattern.");
|
||||
}
|
||||
Apply(_tag, patterns) => {
|
||||
for loc_pattern in *patterns {
|
||||
add_idents_from_pattern(&loc_pattern.region, &loc_pattern.value, scope, answer);
|
||||
AppliedTag(_, _, loc_args) => {
|
||||
for (_, loc_arg) in loc_args {
|
||||
add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, scope, answer);
|
||||
}
|
||||
}
|
||||
|
||||
RecordDestructure(patterns) => {
|
||||
for loc_pattern in *patterns {
|
||||
add_idents_from_pattern(&loc_pattern.region, &loc_pattern.value, scope, answer);
|
||||
RecordDestructure(_, destructs) => {
|
||||
for Located {
|
||||
region,
|
||||
value: RecordDestruct { symbol, .. },
|
||||
} in destructs
|
||||
{
|
||||
answer.push((*symbol, *region));
|
||||
}
|
||||
}
|
||||
RecordField(name, loc_pattern) => {
|
||||
let symbol = scope.symbol(&name);
|
||||
|
||||
answer.push_back((Ident::Unqualified((*name).into()), (symbol, *region)));
|
||||
add_idents_from_pattern(&loc_pattern.region, &loc_pattern.value, scope, answer);
|
||||
}
|
||||
SpaceBefore(pattern, _) | SpaceAfter(pattern, _) | Nested(pattern) => {
|
||||
// Ignore the newline/comment info; it doesn't matter in canonicalization.
|
||||
add_idents_from_pattern(region, pattern, scope, answer)
|
||||
}
|
||||
GlobalTag(_)
|
||||
| PrivateTag(_)
|
||||
| IntLiteral(_)
|
||||
| NonBase10Literal { .. }
|
||||
IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
| StrLiteral(_)
|
||||
| BlockStrLiteral(_)
|
||||
| Malformed(_)
|
||||
| Underscore => (),
|
||||
| Underscore
|
||||
| Shadowed(_, _)
|
||||
| UnsupportedPattern(_) => (),
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,21 @@
|
||||
use crate::can::ident::Ident;
|
||||
use crate::can::pattern::PatternType;
|
||||
use crate::ident::Ident;
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::operator::BinOp;
|
||||
use crate::region::{Located, Region};
|
||||
use crate::types;
|
||||
use inlinable_string::InlinableString;
|
||||
|
||||
/// Problems that can occur in the course of canonicalization.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum Problem {
|
||||
Shadowing(Located<Ident>),
|
||||
UnrecognizedFunctionName(Located<Ident>),
|
||||
UnrecognizedConstant(Located<Ident>),
|
||||
UnusedAssignment(Located<Ident>),
|
||||
UnusedArgument(Located<Ident>),
|
||||
// TODO use Symbol over Ident with these
|
||||
UnusedDef(Symbol, Region),
|
||||
UnusedArgument(Symbol, Region),
|
||||
PrecedenceProblem(PrecedenceProblem),
|
||||
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
UnsupportedPattern(PatternType, Region),
|
||||
CircularAssignment(Vec<Located<Ident>>),
|
||||
ErroneousAnnotation(types::Problem),
|
||||
RuntimeError(RuntimeError),
|
||||
}
|
||||
|
||||
@ -25,14 +26,29 @@ pub enum PrecedenceProblem {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum RuntimeError {
|
||||
Shadowing {
|
||||
original_region: Region,
|
||||
shadow: Located<Ident>,
|
||||
},
|
||||
UnrecognizedFunctionName(Located<InlinableString>),
|
||||
LookupNotInScope(Located<InlinableString>),
|
||||
ValueNotExposed {
|
||||
module_name: InlinableString,
|
||||
ident: InlinableString,
|
||||
region: Region,
|
||||
},
|
||||
ModuleNotImported {
|
||||
module_name: InlinableString,
|
||||
ident: InlinableString,
|
||||
region: Region,
|
||||
},
|
||||
InvalidPrecedence(PrecedenceProblem, Region),
|
||||
UnrecognizedFunctionName(Located<Ident>),
|
||||
UnrecognizedConstant(Located<Ident>),
|
||||
FloatOutsideRange(Box<str>),
|
||||
IntOutsideRange(Box<str>),
|
||||
InvalidHex(std::num::ParseIntError, Box<str>),
|
||||
InvalidOctal(std::num::ParseIntError, Box<str>),
|
||||
InvalidBinary(std::num::ParseIntError, Box<str>),
|
||||
QualifiedPatternIdent(InlinableString),
|
||||
CircularDef(
|
||||
Vec<Located<Ident>>,
|
||||
Vec<(Region /* pattern */, Region /* expr */)>,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::can::expr::Expr;
|
||||
use crate::can::pattern::Pattern;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::ImSet;
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::Variable;
|
||||
|
||||
@ -40,33 +40,30 @@ impl Procedure {
|
||||
}
|
||||
|
||||
/// These are all ordered sets because they end up getting traversed in a graph search
|
||||
/// to determine how assignments shuold be ordered. We want builds to be reproducible,
|
||||
/// to determine how defs shuold be ordered. We want builds to be reproducible,
|
||||
/// so it's important that building the same code gives the same order every time!
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct References {
|
||||
pub locals: ImSet<Symbol>,
|
||||
pub globals: ImSet<Symbol>,
|
||||
pub lookups: ImSet<Symbol>,
|
||||
pub calls: ImSet<Symbol>,
|
||||
}
|
||||
|
||||
impl References {
|
||||
pub fn new() -> References {
|
||||
References {
|
||||
locals: ImSet::default(),
|
||||
globals: ImSet::default(),
|
||||
lookups: ImSet::default(),
|
||||
calls: ImSet::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn union(mut self, other: References) -> Self {
|
||||
self.locals = self.locals.union(other.locals);
|
||||
self.globals = self.globals.union(other.globals);
|
||||
self.lookups = self.lookups.union(other.lookups);
|
||||
self.calls = self.calls.union(other.calls);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn has_local(&self, symbol: &Symbol) -> bool {
|
||||
self.locals.contains(symbol)
|
||||
pub fn has_lookup(&self, symbol: Symbol) -> bool {
|
||||
self.lookups.contains(&symbol)
|
||||
}
|
||||
}
|
||||
|
150
src/can/scope.rs
150
src/can/scope.rs
@ -1,56 +1,144 @@
|
||||
use crate::can::ident::ModuleName;
|
||||
use crate::can::ident::{Lowercase, Uppercase};
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::can::ident::{Ident, Lowercase};
|
||||
use crate::can::problem::RuntimeError;
|
||||
use crate::collections::ImMap;
|
||||
use crate::ident::Ident;
|
||||
use crate::module::symbol::{IdentIds, ModuleId, Symbol};
|
||||
use crate::region::{Located, Region};
|
||||
use crate::types::Type;
|
||||
use crate::subs::Variable;
|
||||
use crate::types::{Alias, Type};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Scope {
|
||||
pub idents: ImMap<Ident, (Symbol, Region)>,
|
||||
pub aliases: ImMap<Uppercase, (Region, Vec<Located<Lowercase>>, Type)>,
|
||||
pub module_name: ModuleName,
|
||||
symbol_prefix: Box<str>,
|
||||
next_unique_id: u64,
|
||||
/// All the identifiers in scope, mapped to were they were defined and
|
||||
/// the Symbol they resolve to.
|
||||
idents: ImMap<Ident, (Symbol, Region)>,
|
||||
|
||||
/// A cache of all the symbols in scope. This makes lookups much
|
||||
/// faster when checking for unused defs and unused arguments.
|
||||
symbols: ImMap<Symbol, Region>,
|
||||
|
||||
/// The type aliases currently in scope
|
||||
aliases: ImMap<Symbol, Alias>,
|
||||
|
||||
/// The current module being processed. This will be used to turn
|
||||
/// unqualified idents into Symbols.
|
||||
home: ModuleId,
|
||||
}
|
||||
|
||||
impl Scope {
|
||||
pub fn new(
|
||||
module_name: ModuleName,
|
||||
symbol_prefix: Box<str>,
|
||||
declared_idents: ImMap<Ident, (Symbol, Region)>,
|
||||
) -> Scope {
|
||||
pub fn new(home: ModuleId) -> Scope {
|
||||
Scope {
|
||||
symbol_prefix,
|
||||
module_name,
|
||||
|
||||
// This is used to generate unique names for anonymous closures.
|
||||
// It always begins at 0.
|
||||
next_unique_id: 0,
|
||||
|
||||
home,
|
||||
idents: ImMap::default(),
|
||||
symbols: ImMap::default(),
|
||||
aliases: ImMap::default(),
|
||||
idents: declared_idents,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn symbol(&self, name: &str) -> Symbol {
|
||||
Symbol::new(&self.symbol_prefix, name)
|
||||
pub fn idents(&self) -> impl Iterator<Item = &(Ident, (Symbol, Region))> {
|
||||
self.idents.iter()
|
||||
}
|
||||
|
||||
pub fn gen_unique_symbol(&mut self) -> Symbol {
|
||||
self.next_unique_id += 1;
|
||||
pub fn symbols(&self) -> impl Iterator<Item = &(Symbol, Region)> {
|
||||
self.symbols.iter()
|
||||
}
|
||||
|
||||
Symbol::new(&self.symbol_prefix, &self.next_unique_id.to_string())
|
||||
pub fn into_aliases(self) -> ImMap<Symbol, Alias> {
|
||||
self.aliases
|
||||
}
|
||||
|
||||
pub fn contains_ident(&self, ident: &Ident) -> bool {
|
||||
self.idents.contains_key(ident)
|
||||
}
|
||||
|
||||
pub fn contains_symbol(&self, symbol: Symbol) -> bool {
|
||||
self.symbols.contains_key(&symbol)
|
||||
}
|
||||
|
||||
pub fn num_idents(&self) -> usize {
|
||||
self.idents.len()
|
||||
}
|
||||
|
||||
pub fn lookup(&mut self, ident: &Ident, region: Region) -> Result<Symbol, RuntimeError> {
|
||||
match self.idents.get(ident) {
|
||||
Some((symbol, _)) => Ok(*symbol),
|
||||
None => Err(RuntimeError::LookupNotInScope(Located {
|
||||
region,
|
||||
value: ident.clone().into(),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lookup_alias(&self, symbol: Symbol) -> Option<&Alias> {
|
||||
self.aliases.get(&symbol)
|
||||
}
|
||||
|
||||
/// Introduce a new ident to scope.
|
||||
///
|
||||
/// Returns Err if this would shadow an existing ident, including the
|
||||
/// Symbol and Region of the ident we already had in scope under that name.
|
||||
pub fn introduce(
|
||||
&mut self,
|
||||
ident: Ident,
|
||||
exposed_ident_ids: &IdentIds,
|
||||
all_ident_ids: &mut IdentIds,
|
||||
region: Region,
|
||||
) -> Result<Symbol, (Region, Located<Ident>)> {
|
||||
match self.idents.get(&ident) {
|
||||
Some((_, original_region)) => {
|
||||
let shadow = Located {
|
||||
value: ident,
|
||||
region,
|
||||
};
|
||||
|
||||
Err((*original_region, shadow))
|
||||
}
|
||||
None => {
|
||||
// If this IdentId was already added previously
|
||||
// when the value was exposed in the module header,
|
||||
// use that existing IdentId. Otherwise, create a fresh one.
|
||||
let ident_id = match exposed_ident_ids.get_id(&ident.as_inline_str()) {
|
||||
Some(ident_id) => *ident_id,
|
||||
None => all_ident_ids.add(ident.clone().into()),
|
||||
};
|
||||
|
||||
let symbol = Symbol::new(self.home, ident_id);
|
||||
|
||||
self.symbols.insert(symbol, region);
|
||||
self.idents.insert(ident, (symbol, region));
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Import a Symbol from another module into this module's top-level scope.
|
||||
///
|
||||
/// Returns Err if this would shadow an existing ident, including the
|
||||
/// Symbol and Region of the ident we already had in scope under that name.
|
||||
pub fn import(
|
||||
&mut self,
|
||||
ident: Ident,
|
||||
symbol: Symbol,
|
||||
region: Region,
|
||||
) -> Result<Symbol, (Symbol, Region)> {
|
||||
match self.idents.get(&ident) {
|
||||
Some(shadowed) => Err(*shadowed),
|
||||
None => {
|
||||
self.symbols.insert(symbol, region);
|
||||
self.idents.insert(ident, (symbol, region));
|
||||
|
||||
Ok(symbol)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_alias(
|
||||
&mut self,
|
||||
name: Uppercase,
|
||||
name: Symbol,
|
||||
region: Region,
|
||||
vars: Vec<Located<Lowercase>>,
|
||||
vars: Vec<Located<(Lowercase, Variable)>>,
|
||||
typ: Type,
|
||||
) {
|
||||
self.aliases.insert(name, (region, vars, typ));
|
||||
self.aliases.insert(name, Alias { region, vars, typ });
|
||||
}
|
||||
}
|
||||
|
@ -1,69 +0,0 @@
|
||||
use crate::module::header::ModuleName;
|
||||
use inlinable_string::InlinableString;
|
||||
use std::fmt;
|
||||
|
||||
/// A globally unique identifier, used for both vars and tags.
|
||||
/// It will be used directly in code gen.
|
||||
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Symbol(InlinableString);
|
||||
|
||||
/// Rather than displaying as this:
|
||||
///
|
||||
/// Symbol("Foo.bar")
|
||||
///
|
||||
/// ...instead display as this:
|
||||
///
|
||||
/// 'Foo.bar'
|
||||
impl fmt::Debug for Symbol {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "'{}'", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<InlinableString> for Symbol {
|
||||
fn into(self) -> InlinableString {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<InlinableString> for Symbol {
|
||||
fn from(string: InlinableString) -> Self {
|
||||
Symbol(string)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Symbol {
|
||||
fn from(string: &str) -> Self {
|
||||
Symbol(string.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Symbol {
|
||||
pub fn new(prefix: &str, name: &str) -> Symbol {
|
||||
Symbol(format!("{}{}", prefix, name).into())
|
||||
}
|
||||
|
||||
pub fn from_parts(module_parts: &[&str], name: &str) -> Symbol {
|
||||
Symbol(if module_parts.is_empty() {
|
||||
name.into()
|
||||
} else {
|
||||
format!("{}.{}", module_parts.join("."), name).into()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_private_tag(home: &str, tag_name: &str) -> Symbol {
|
||||
Symbol(format!("{}.{}", home, tag_name).into())
|
||||
}
|
||||
|
||||
pub fn from_module<'a>(module_name: &'a ModuleName<'a>, ident: &'a &'a str) -> Symbol {
|
||||
Symbol(format!("{}.{}", module_name.as_str(), ident).into())
|
||||
}
|
||||
|
||||
pub fn from_qualified_ident(module_name: Box<str>, ident: Box<str>) -> Symbol {
|
||||
Symbol(format!("{}.{}", module_name, ident).into())
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
@ -1,15 +1,15 @@
|
||||
use crate::can::ident::ModuleName;
|
||||
use crate::collections::SendMap;
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::region::Region;
|
||||
use crate::subs::Variable;
|
||||
use crate::types::Constraint::{self, *};
|
||||
use crate::types::Expected::{self, *};
|
||||
use crate::types::Type::{self, *};
|
||||
use crate::types::{self, LetConstraint, Reason};
|
||||
use crate::types::{LetConstraint, Reason};
|
||||
|
||||
#[inline(always)]
|
||||
pub fn int_literal(var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
|
||||
let typ = number_literal_type("Int", "Integer");
|
||||
let typ = number_literal_type(Symbol::INT_INTEGER);
|
||||
let reason = Reason::IntLiteral;
|
||||
|
||||
num_literal(var, typ, reason, expected, region)
|
||||
@ -17,7 +17,7 @@ pub fn int_literal(var: Variable, expected: Expected<Type>, region: Region) -> C
|
||||
|
||||
#[inline(always)]
|
||||
pub fn float_literal(var: Variable, expected: Expected<Type>, region: Region) -> Constraint {
|
||||
let typ = number_literal_type("Float", "FloatingPoint");
|
||||
let typ = number_literal_type(Symbol::FLOAT_FLOATINGPOINT);
|
||||
let reason = Reason::FloatLiteral;
|
||||
|
||||
num_literal(var, typ, reason, expected, region)
|
||||
@ -29,6 +29,7 @@ pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars,
|
||||
def_types: SendMap::default(),
|
||||
def_aliases: SendMap::default(),
|
||||
defs_constraint: constraint,
|
||||
ret_constraint: Constraint::True,
|
||||
}))
|
||||
@ -55,21 +56,13 @@ fn num_literal(
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn number_literal_type(module_name: &str, type_name: &str) -> Type {
|
||||
builtin_type(
|
||||
ModuleName::NUM,
|
||||
types::TYPE_NUM,
|
||||
vec![builtin_type(module_name, type_name, Vec::new())],
|
||||
)
|
||||
fn number_literal_type(symbol: Symbol) -> Type {
|
||||
builtin_type(Symbol::NUM_NUM, vec![builtin_type(symbol, Vec::new())])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn builtin_type(module_name: &str, type_name: &str, args: Vec<Type>) -> Type {
|
||||
Type::Apply {
|
||||
module_name: module_name.into(),
|
||||
name: type_name.into(),
|
||||
args,
|
||||
}
|
||||
pub fn builtin_type(symbol: Symbol, args: Vec<Type>) -> Type {
|
||||
Type::Apply(symbol, args)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -79,10 +72,10 @@ pub fn empty_list_type(var: Variable) -> Type {
|
||||
|
||||
#[inline(always)]
|
||||
pub fn list_type(typ: Type) -> Type {
|
||||
builtin_type("List", "List", vec![typ])
|
||||
builtin_type(Symbol::LIST_LIST, vec![typ])
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn str_type() -> Type {
|
||||
builtin_type("Str", "Str", Vec::new())
|
||||
builtin_type(Symbol::STR_STR, Vec::new())
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
use crate::can::def::Declaration;
|
||||
use crate::can::def::Def;
|
||||
use crate::can::def::{Declaration, Def};
|
||||
use crate::can::expr::Expr::{self, *};
|
||||
use crate::can::expr::Field;
|
||||
use crate::can::ident::{Lowercase, ModuleName, TagName};
|
||||
use crate::can::ident::{Lowercase, TagName};
|
||||
use crate::can::pattern::Pattern;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::{ImMap, SendMap};
|
||||
use crate::constrain::builtins::{
|
||||
empty_list_type, float_literal, int_literal, list_type, str_type,
|
||||
};
|
||||
use crate::constrain::pattern::{constrain_pattern, PatternState};
|
||||
use crate::module::symbol::{ModuleId, Symbol};
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::Variable;
|
||||
use crate::types::Alias;
|
||||
use crate::types::AnnotationSource::{self, *};
|
||||
use crate::types::Constraint::{self, *};
|
||||
use crate::types::Expected::{self, *};
|
||||
@ -43,6 +43,23 @@ pub fn exists(flex_vars: Vec<Variable>, constraint: Constraint) -> Constraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars,
|
||||
def_types: SendMap::default(),
|
||||
def_aliases: SendMap::default(),
|
||||
defs_constraint: constraint,
|
||||
ret_constraint: Constraint::True,
|
||||
}))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn exists_with_aliases(
|
||||
aliases: SendMap<Symbol, Alias>,
|
||||
flex_vars: Vec<Variable>,
|
||||
constraint: Constraint,
|
||||
) -> Constraint {
|
||||
Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars,
|
||||
def_types: SendMap::default(),
|
||||
def_aliases: aliases,
|
||||
defs_constraint: constraint,
|
||||
ret_constraint: Constraint::True,
|
||||
}))
|
||||
@ -52,8 +69,8 @@ pub struct Env {
|
||||
/// Whenever we encounter a user-defined type variable (a "rigid" var for short),
|
||||
/// for example `a` in the annotation `identity : a -> a`, we add it to this
|
||||
/// map so that expressions within that annotation can share these vars.
|
||||
pub rigids: ImMap<Lowercase, Type>,
|
||||
pub module_name: ModuleName,
|
||||
pub rigids: ImMap<Lowercase, Variable>,
|
||||
pub home: ModuleId,
|
||||
}
|
||||
|
||||
pub fn constrain_expr(
|
||||
@ -111,9 +128,7 @@ pub fn constrain_expr(
|
||||
}
|
||||
Update {
|
||||
record_var,
|
||||
module,
|
||||
ext_var,
|
||||
ident,
|
||||
symbol,
|
||||
updates,
|
||||
} => {
|
||||
@ -139,10 +154,9 @@ pub fn constrain_expr(
|
||||
vars.push(*ext_var);
|
||||
|
||||
let con = Lookup(
|
||||
module.clone(),
|
||||
symbol.clone(),
|
||||
*symbol,
|
||||
ForReason(
|
||||
Reason::RecordUpdateKeys(ident.clone(), fields),
|
||||
Reason::RecordUpdateKeys(*symbol, fields),
|
||||
record_type,
|
||||
region,
|
||||
),
|
||||
@ -241,11 +255,7 @@ pub fn constrain_expr(
|
||||
]),
|
||||
)
|
||||
}
|
||||
Var {
|
||||
symbol_for_lookup,
|
||||
module,
|
||||
..
|
||||
} => Lookup(module.clone(), symbol_for_lookup.clone(), expected, region),
|
||||
Var(symbol) => Lookup(*symbol, expected, region),
|
||||
Closure(fn_var, _symbol, _recursive, args, boxed) => {
|
||||
let (loc_body_expr, ret_var) = &**boxed;
|
||||
let mut state = PatternState {
|
||||
@ -291,6 +301,7 @@ pub fn constrain_expr(
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: state.vars,
|
||||
def_types: state.headers,
|
||||
def_aliases: SendMap::default(),
|
||||
defs_constraint,
|
||||
ret_constraint,
|
||||
})),
|
||||
@ -305,9 +316,8 @@ pub fn constrain_expr(
|
||||
If {
|
||||
cond_var,
|
||||
branch_var,
|
||||
loc_cond,
|
||||
loc_then,
|
||||
loc_else,
|
||||
branches,
|
||||
final_else,
|
||||
} => {
|
||||
// TODO use Bool alias here, so we don't allocate this type every time
|
||||
let bool_type = Type::TagUnion(
|
||||
@ -318,72 +328,88 @@ pub fn constrain_expr(
|
||||
Box::new(Type::EmptyTagUnion),
|
||||
);
|
||||
let expect_bool = Expected::ForReason(Reason::IfCondition, bool_type, region);
|
||||
|
||||
let cond_con = Eq(Type::Variable(*cond_var), expect_bool, loc_cond.region);
|
||||
let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2);
|
||||
|
||||
match expected {
|
||||
FromAnnotation(name, arity, _, tipe) => {
|
||||
let then_con = constrain_expr(
|
||||
env,
|
||||
loc_then.region,
|
||||
&loc_then.value,
|
||||
FromAnnotation(
|
||||
name.clone(),
|
||||
arity,
|
||||
AnnotationSource::TypedIfBranch(0),
|
||||
tipe.clone(),
|
||||
),
|
||||
);
|
||||
for (index, (loc_cond, loc_body)) in branches.iter().enumerate() {
|
||||
let cond_con = Eq(
|
||||
Type::Variable(*cond_var),
|
||||
expect_bool.clone(),
|
||||
loc_cond.region,
|
||||
);
|
||||
let then_con = constrain_expr(
|
||||
env,
|
||||
loc_body.region,
|
||||
&loc_body.value,
|
||||
FromAnnotation(
|
||||
name.clone(),
|
||||
arity,
|
||||
AnnotationSource::TypedIfBranch(index + 1),
|
||||
tipe.clone(),
|
||||
),
|
||||
);
|
||||
|
||||
branch_cons.push(cond_con);
|
||||
branch_cons.push(then_con);
|
||||
}
|
||||
let else_con = constrain_expr(
|
||||
env,
|
||||
loc_else.region,
|
||||
&loc_else.value,
|
||||
final_else.region,
|
||||
&final_else.value,
|
||||
FromAnnotation(
|
||||
name,
|
||||
arity,
|
||||
AnnotationSource::TypedIfBranch(1),
|
||||
AnnotationSource::TypedIfBranch(branches.len() + 1),
|
||||
tipe.clone(),
|
||||
),
|
||||
);
|
||||
|
||||
let ast_con = Eq(Type::Variable(*branch_var), NoExpectation(tipe), region);
|
||||
|
||||
exists(
|
||||
vec![*cond_var, *branch_var],
|
||||
And(vec![cond_con, then_con, else_con, ast_con]),
|
||||
)
|
||||
branch_cons.push(ast_con);
|
||||
branch_cons.push(else_con);
|
||||
|
||||
exists(vec![*cond_var, *branch_var], And(branch_cons))
|
||||
}
|
||||
_ => {
|
||||
let then_con = constrain_expr(
|
||||
env,
|
||||
loc_then.region,
|
||||
&loc_then.value,
|
||||
ForReason(
|
||||
Reason::IfBranch { index: 0 },
|
||||
Type::Variable(*branch_var),
|
||||
region,
|
||||
),
|
||||
);
|
||||
for (index, (loc_cond, loc_body)) in branches.iter().enumerate() {
|
||||
let cond_con = Eq(
|
||||
Type::Variable(*cond_var),
|
||||
expect_bool.clone(),
|
||||
loc_cond.region,
|
||||
);
|
||||
let then_con = constrain_expr(
|
||||
env,
|
||||
loc_body.region,
|
||||
&loc_body.value,
|
||||
ForReason(
|
||||
Reason::IfBranch { index: index + 1 },
|
||||
Type::Variable(*branch_var),
|
||||
region,
|
||||
),
|
||||
);
|
||||
|
||||
branch_cons.push(cond_con);
|
||||
branch_cons.push(then_con);
|
||||
}
|
||||
let else_con = constrain_expr(
|
||||
env,
|
||||
loc_else.region,
|
||||
&loc_else.value,
|
||||
final_else.region,
|
||||
&final_else.value,
|
||||
ForReason(
|
||||
Reason::IfBranch { index: 1 },
|
||||
Reason::IfBranch {
|
||||
index: branches.len() + 1,
|
||||
},
|
||||
Type::Variable(*branch_var),
|
||||
region,
|
||||
),
|
||||
);
|
||||
|
||||
exists(
|
||||
vec![*cond_var, *branch_var],
|
||||
And(vec![
|
||||
cond_con,
|
||||
then_con,
|
||||
else_con,
|
||||
Eq(Type::Variable(*branch_var), expected, region),
|
||||
]),
|
||||
)
|
||||
branch_cons.push(Eq(Type::Variable(*branch_var), expected, region));
|
||||
branch_cons.push(else_con);
|
||||
|
||||
exists(vec![*cond_var, *branch_var], And(branch_cons))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -496,7 +522,7 @@ pub fn constrain_expr(
|
||||
|
||||
let constraint = constrain_expr(
|
||||
&Env {
|
||||
module_name: env.module_name.clone(),
|
||||
home: env.home,
|
||||
rigids: ImMap::default(),
|
||||
},
|
||||
region,
|
||||
@ -533,10 +559,11 @@ pub fn constrain_expr(
|
||||
),
|
||||
)
|
||||
}
|
||||
LetRec(defs, loc_ret, var) => {
|
||||
LetRec(defs, loc_ret, var, aliases) => {
|
||||
let body_con = constrain_expr(env, loc_ret.region, &loc_ret.value, expected.clone());
|
||||
|
||||
exists(
|
||||
exists_with_aliases(
|
||||
aliases.clone(),
|
||||
vec![*var],
|
||||
And(vec![
|
||||
constrain_recursive_defs(env, defs, body_con),
|
||||
@ -546,10 +573,11 @@ pub fn constrain_expr(
|
||||
]),
|
||||
)
|
||||
}
|
||||
LetNonRec(def, loc_ret, var) => {
|
||||
LetNonRec(def, loc_ret, var, aliases) => {
|
||||
let body_con = constrain_expr(env, loc_ret.region, &loc_ret.value, expected.clone());
|
||||
|
||||
exists(
|
||||
exists_with_aliases(
|
||||
aliases.clone(),
|
||||
vec![*var],
|
||||
And(vec![
|
||||
constrain_def(env, def, body_con),
|
||||
@ -631,6 +659,7 @@ fn constrain_when_branch(
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: state.vars,
|
||||
def_types: state.headers,
|
||||
def_aliases: SendMap::default(),
|
||||
defs_constraint: Constraint::And(state.constraints),
|
||||
ret_constraint,
|
||||
}))
|
||||
@ -650,7 +679,7 @@ fn constrain_empty_record(region: Region, expected: Expected<Type>) -> Constrain
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn constrain_decls(module_name: ModuleName, decls: &[Declaration]) -> Constraint {
|
||||
pub fn constrain_decls(home: ModuleId, decls: &[Declaration]) -> Constraint {
|
||||
let mut constraint = Constraint::SaveTheEnvironment;
|
||||
for decl in decls.iter().rev() {
|
||||
// NOTE: rigids are empty because they are not shared between top-level definitions
|
||||
@ -658,7 +687,7 @@ pub fn constrain_decls(module_name: ModuleName, decls: &[Declaration]) -> Constr
|
||||
Declaration::Declare(def) => {
|
||||
constraint = constrain_def(
|
||||
&Env {
|
||||
module_name: module_name.clone(),
|
||||
home,
|
||||
rigids: ImMap::default(),
|
||||
},
|
||||
def,
|
||||
@ -668,7 +697,7 @@ pub fn constrain_decls(module_name: ModuleName, decls: &[Declaration]) -> Constr
|
||||
Declaration::DeclareRec(defs) => {
|
||||
constraint = constrain_recursive_defs(
|
||||
&Env {
|
||||
module_name: module_name.clone(),
|
||||
home,
|
||||
rigids: ImMap::default(),
|
||||
},
|
||||
defs,
|
||||
@ -683,8 +712,6 @@ pub fn constrain_decls(module_name: ModuleName, decls: &[Declaration]) -> Constr
|
||||
}
|
||||
|
||||
fn constrain_def_pattern(loc_pattern: &Located<Pattern>, expr_type: Type) -> PatternState {
|
||||
// Exclude the current ident from shadowable_idents; you can't shadow yourself!
|
||||
// (However, still include it in scope, because you *can* recursively refer to yourself.)
|
||||
let pattern_expected = PExpected::NoExpectation(expr_type);
|
||||
|
||||
let mut state = PatternState {
|
||||
@ -703,7 +730,7 @@ fn constrain_def_pattern(loc_pattern: &Located<Pattern>, expr_type: Type) -> Pat
|
||||
state
|
||||
}
|
||||
|
||||
pub fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||
fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||
let expr_var = def.expr_var;
|
||||
let expr_type = Type::Variable(expr_var);
|
||||
|
||||
@ -711,40 +738,42 @@ pub fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||
|
||||
pattern_state.vars.push(expr_var);
|
||||
|
||||
let mut def_aliases = SendMap::default();
|
||||
let mut new_rigids = Vec::new();
|
||||
|
||||
let expr_con = match &def.annotation {
|
||||
Some((annotation, free_vars)) => {
|
||||
Some((annotation, free_vars, ann_def_aliases)) => {
|
||||
def_aliases = ann_def_aliases.clone();
|
||||
let arity = annotation.arity();
|
||||
let rigids = &env.rigids;
|
||||
let mut ftv: ImMap<Lowercase, Type> = rigids.clone();
|
||||
let mut ftv = rigids.clone();
|
||||
|
||||
for (var, name) in free_vars {
|
||||
// if the rigid is known already, nothing needs to happen
|
||||
// otherwise register it.
|
||||
if !rigids.contains_key(name) {
|
||||
// possible use this rigid in nested def's
|
||||
ftv.insert(name.clone(), Type::Variable(*var));
|
||||
|
||||
new_rigids.push(*var);
|
||||
}
|
||||
}
|
||||
let annotation = instantiate_rigids(
|
||||
&annotation,
|
||||
&free_vars,
|
||||
&mut new_rigids,
|
||||
&mut ftv,
|
||||
&def.loc_pattern,
|
||||
&mut pattern_state.headers,
|
||||
);
|
||||
|
||||
let annotation_expected = FromAnnotation(
|
||||
def.loc_pattern.clone(),
|
||||
annotation.arity(),
|
||||
arity,
|
||||
AnnotationSource::TypedBody,
|
||||
annotation.clone(),
|
||||
annotation,
|
||||
);
|
||||
|
||||
pattern_state.constraints.push(Eq(
|
||||
expr_type,
|
||||
annotation_expected.clone(),
|
||||
// TODO proper region
|
||||
Region::zero(),
|
||||
));
|
||||
|
||||
constrain_expr(
|
||||
&Env {
|
||||
module_name: env.module_name.clone(),
|
||||
home: env.home,
|
||||
rigids: ftv,
|
||||
},
|
||||
def.loc_expr.region,
|
||||
@ -764,10 +793,12 @@ pub fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||
rigid_vars: new_rigids,
|
||||
flex_vars: pattern_state.vars,
|
||||
def_types: pattern_state.headers,
|
||||
def_aliases,
|
||||
defs_constraint: Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(), // always empty
|
||||
flex_vars: Vec::new(), // empty, because our functions have no arguments
|
||||
def_types: SendMap::default(), // empty, because our functions have no arguments!
|
||||
def_aliases: SendMap::default(),
|
||||
defs_constraint: And(pattern_state.constraints),
|
||||
ret_constraint: expr_con,
|
||||
})),
|
||||
@ -775,6 +806,45 @@ pub fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
|
||||
}))
|
||||
}
|
||||
|
||||
fn instantiate_rigids(
|
||||
annotation: &Type,
|
||||
free_vars: &SendMap<Lowercase, Variable>,
|
||||
new_rigids: &mut Vec<Variable>,
|
||||
ftv: &mut ImMap<Lowercase, Variable>,
|
||||
loc_pattern: &Located<Pattern>,
|
||||
headers: &mut SendMap<Symbol, Located<Type>>,
|
||||
) -> Type {
|
||||
let mut annotation = annotation.clone();
|
||||
let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default();
|
||||
|
||||
for (name, var) in free_vars {
|
||||
if let Some(existing_rigid) = ftv.get(name) {
|
||||
rigid_substitution.insert(*var, Type::Variable(*existing_rigid));
|
||||
} else {
|
||||
// It's possible to use this rigid in nested defs
|
||||
ftv.insert(name.clone(), *var);
|
||||
|
||||
new_rigids.push(*var);
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate rigid variables
|
||||
if !rigid_substitution.is_empty() {
|
||||
annotation.substitute(&rigid_substitution);
|
||||
}
|
||||
|
||||
if let Some(new_headers) = crate::constrain::pattern::headers_from_annotation(
|
||||
&loc_pattern.value,
|
||||
&Located::at(loc_pattern.region, annotation.clone()),
|
||||
) {
|
||||
for (k, v) in new_headers {
|
||||
headers.insert(k, v);
|
||||
}
|
||||
}
|
||||
|
||||
annotation
|
||||
}
|
||||
|
||||
fn constrain_recursive_defs(env: &Env, defs: &[Def], body_con: Constraint) -> Constraint {
|
||||
rec_defs_help(
|
||||
env,
|
||||
@ -792,6 +862,7 @@ pub fn rec_defs_help(
|
||||
mut rigid_info: Info,
|
||||
mut flex_info: Info,
|
||||
) -> Constraint {
|
||||
let mut def_aliases = SendMap::default();
|
||||
for def in defs {
|
||||
let expr_var = def.expr_var;
|
||||
let expr_type = Type::Variable(expr_var);
|
||||
@ -828,6 +899,7 @@ pub fn rec_defs_help(
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: Vec::new(), // empty because Roc function defs have no args
|
||||
def_types: SendMap::default(), // empty because Roc function defs have no args
|
||||
def_aliases: SendMap::default(),
|
||||
defs_constraint: True, // I think this is correct, once again because there are no args
|
||||
ret_constraint: expr_con,
|
||||
}));
|
||||
@ -837,31 +909,32 @@ pub fn rec_defs_help(
|
||||
flex_info.def_types.extend(pattern_state.headers);
|
||||
}
|
||||
|
||||
Some((annotation, seen_rigids)) => {
|
||||
let rigids = &env.rigids;
|
||||
let mut ftv: ImMap<Lowercase, Type> = rigids.clone();
|
||||
|
||||
for (var, name) in seen_rigids {
|
||||
// if the rigid is known already, nothing needs to happen
|
||||
// otherwise register it.
|
||||
if !rigids.contains_key(name) {
|
||||
// possible use this rigid in nested def's
|
||||
ftv.insert(name.clone(), Type::Variable(*var));
|
||||
|
||||
new_rigids.push(*var);
|
||||
}
|
||||
Some((annotation, free_vars, ann_def_aliases)) => {
|
||||
for (symbol, alias) in ann_def_aliases.clone() {
|
||||
def_aliases.insert(symbol, alias);
|
||||
}
|
||||
let arity = annotation.arity();
|
||||
let mut ftv = env.rigids.clone();
|
||||
|
||||
let annotation = instantiate_rigids(
|
||||
annotation,
|
||||
&free_vars,
|
||||
&mut new_rigids,
|
||||
&mut ftv,
|
||||
&def.loc_pattern,
|
||||
&mut pattern_state.headers,
|
||||
);
|
||||
|
||||
let annotation_expected = FromAnnotation(
|
||||
def.loc_pattern.clone(),
|
||||
annotation.arity(),
|
||||
arity,
|
||||
AnnotationSource::TypedBody,
|
||||
annotation.clone(),
|
||||
);
|
||||
let expr_con = constrain_expr(
|
||||
&Env {
|
||||
rigids: ftv,
|
||||
module_name: env.module_name.clone(),
|
||||
home: env.home,
|
||||
},
|
||||
def.loc_expr.region,
|
||||
&def.loc_expr.value,
|
||||
@ -880,6 +953,7 @@ pub fn rec_defs_help(
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: Vec::new(), // empty because Roc function defs have no args
|
||||
def_types: SendMap::default(), // empty because Roc function defs have no args
|
||||
def_aliases: SendMap::default(),
|
||||
defs_constraint: True, // I think this is correct, once again because there are no args
|
||||
ret_constraint: expr_con,
|
||||
}));
|
||||
@ -891,6 +965,7 @@ pub fn rec_defs_help(
|
||||
rigid_vars: new_rigids,
|
||||
flex_vars: Vec::new(), // no flex vars introduced
|
||||
def_types: SendMap::default(), // no headers introduced (at this level)
|
||||
def_aliases: SendMap::default(),
|
||||
defs_constraint: def_con,
|
||||
ret_constraint: True,
|
||||
})));
|
||||
@ -903,15 +978,18 @@ pub fn rec_defs_help(
|
||||
rigid_vars: rigid_info.vars,
|
||||
flex_vars: Vec::new(),
|
||||
def_types: rigid_info.def_types,
|
||||
def_aliases,
|
||||
defs_constraint: True,
|
||||
ret_constraint: Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: flex_info.vars,
|
||||
def_types: flex_info.def_types.clone(),
|
||||
def_aliases: SendMap::default(),
|
||||
defs_constraint: Let(Box::new(LetConstraint {
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: Vec::new(),
|
||||
def_types: flex_info.def_types,
|
||||
def_aliases: SendMap::default(),
|
||||
defs_constraint: True,
|
||||
ret_constraint: And(flex_info.constraints),
|
||||
})),
|
||||
|
@ -1,17 +1,266 @@
|
||||
use crate::can::def::Declaration;
|
||||
use crate::can::ident::ModuleName;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::can::ident::Lowercase;
|
||||
use crate::collections::{ImMap, MutMap, SendMap};
|
||||
use crate::constrain::expr::constrain_decls;
|
||||
use crate::region::Region;
|
||||
use crate::subs::Variable;
|
||||
use crate::types::Constraint;
|
||||
use crate::module::symbol::{ModuleId, Symbol};
|
||||
use crate::region::{Located, Region};
|
||||
use crate::solve::SolvedType;
|
||||
use crate::subs::{VarId, VarStore, Variable};
|
||||
use crate::types::{Alias, Constraint, LetConstraint, Type};
|
||||
|
||||
#[inline(always)]
|
||||
pub fn constrain_module(
|
||||
module_name: ModuleName,
|
||||
home: ModuleId,
|
||||
decls: &[Declaration],
|
||||
_lookups: Vec<(Symbol, Variable, Region)>,
|
||||
) -> Constraint {
|
||||
// NOTE lookups are now not included!
|
||||
constrain_decls(module_name, &decls)
|
||||
constrain_decls(home, &decls)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Import<'a> {
|
||||
pub loc_symbol: Located<Symbol>,
|
||||
pub solved_type: &'a SolvedType,
|
||||
}
|
||||
|
||||
pub fn constrain_imported_values(
|
||||
imports: Vec<Import<'_>>,
|
||||
aliases: MutMap<Symbol, Alias>,
|
||||
mut body_con: Constraint,
|
||||
var_store: &VarStore,
|
||||
) -> Constraint {
|
||||
// TODO try out combining all the def_types and free_vars, so that we
|
||||
// don't need to make this big linked list of nested constraints.
|
||||
// Theoretically that should be equivalent to doing it this way!
|
||||
for import in imports {
|
||||
body_con = constrain_imported_value(
|
||||
import.loc_symbol,
|
||||
import.solved_type,
|
||||
body_con,
|
||||
&aliases,
|
||||
var_store,
|
||||
);
|
||||
}
|
||||
|
||||
body_con
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct FreeVars {
|
||||
rigid_vars: ImMap<Lowercase, Variable>,
|
||||
flex_vars: ImMap<VarId, Variable>,
|
||||
}
|
||||
|
||||
fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &VarStore) -> Type {
|
||||
use crate::solve::SolvedType::*;
|
||||
|
||||
match solved_type {
|
||||
Func(args, ret) => {
|
||||
let mut new_args = Vec::with_capacity(args.len());
|
||||
|
||||
for arg in args {
|
||||
new_args.push(to_type(&arg, free_vars, var_store));
|
||||
}
|
||||
|
||||
let new_ret = to_type(&ret, free_vars, var_store);
|
||||
|
||||
Type::Function(new_args, Box::new(new_ret))
|
||||
}
|
||||
Apply(symbol, args) => {
|
||||
let mut new_args = Vec::with_capacity(args.len());
|
||||
|
||||
for arg in args {
|
||||
new_args.push(to_type(&arg, free_vars, var_store));
|
||||
}
|
||||
|
||||
Type::Apply(*symbol, new_args)
|
||||
}
|
||||
Rigid(lowercase) => {
|
||||
if let Some(var) = free_vars.rigid_vars.get(&lowercase) {
|
||||
Type::Variable(*var)
|
||||
} else {
|
||||
let var = var_store.fresh();
|
||||
free_vars.rigid_vars.insert(lowercase.clone(), var);
|
||||
Type::Variable(var)
|
||||
}
|
||||
}
|
||||
Flex(var_id) => {
|
||||
if let Some(var) = free_vars.flex_vars.get(&var_id) {
|
||||
Type::Variable(*var)
|
||||
} else {
|
||||
let var = var_store.fresh();
|
||||
free_vars.flex_vars.insert(*var_id, var);
|
||||
Type::Variable(var)
|
||||
}
|
||||
}
|
||||
Record { fields, ext } => {
|
||||
let mut new_fields = SendMap::default();
|
||||
|
||||
for (label, typ) in fields {
|
||||
new_fields.insert(label.clone(), to_type(&typ, free_vars, var_store));
|
||||
}
|
||||
|
||||
Type::Record(new_fields, Box::new(to_type(ext, free_vars, var_store)))
|
||||
}
|
||||
EmptyRecord => Type::EmptyRec,
|
||||
EmptyTagUnion => Type::EmptyTagUnion,
|
||||
TagUnion(tags, ext) => {
|
||||
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.iter() {
|
||||
new_args.push(to_type(arg, free_vars, var_store));
|
||||
}
|
||||
|
||||
new_tags.push((tag_name.clone(), new_args));
|
||||
}
|
||||
|
||||
Type::TagUnion(new_tags, Box::new(to_type(ext, free_vars, var_store)))
|
||||
}
|
||||
RecursiveTagUnion(rec_var_id, tags, ext) => {
|
||||
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.iter() {
|
||||
new_args.push(to_type(arg, free_vars, var_store));
|
||||
}
|
||||
|
||||
new_tags.push((tag_name.clone(), new_args));
|
||||
}
|
||||
|
||||
let rec_var = free_vars
|
||||
.flex_vars
|
||||
.get(rec_var_id)
|
||||
.expect("rec var not in flex vars");
|
||||
|
||||
Type::RecursiveTagUnion(
|
||||
*rec_var,
|
||||
new_tags,
|
||||
Box::new(to_type(ext, free_vars, var_store)),
|
||||
)
|
||||
}
|
||||
Boolean(val) => Type::Boolean(val.clone()),
|
||||
Alias(symbol, solved_type_variables, solved_actual) => {
|
||||
let mut type_variables = Vec::with_capacity(solved_type_variables.len());
|
||||
|
||||
for (lowercase, solved_type) in solved_type_variables {
|
||||
type_variables.push((
|
||||
lowercase.clone(),
|
||||
to_type(solved_type, free_vars, var_store),
|
||||
));
|
||||
}
|
||||
|
||||
let actual = to_type(solved_actual, free_vars, var_store);
|
||||
|
||||
Type::Alias(*symbol, type_variables, Box::new(actual))
|
||||
}
|
||||
Error => {
|
||||
panic!("TODO convert from SolvedType::Error to Type somehow");
|
||||
}
|
||||
Erroneous(problem) => Type::Erroneous(problem.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
fn constrain_imported_value(
|
||||
loc_symbol: Located<Symbol>,
|
||||
solved_type: &SolvedType,
|
||||
body_con: Constraint,
|
||||
aliases: &MutMap<Symbol, Alias>,
|
||||
var_store: &VarStore,
|
||||
) -> Constraint {
|
||||
use Constraint::*;
|
||||
let mut free_vars = FreeVars::default();
|
||||
|
||||
// an imported symbol can be both an alias and a value
|
||||
match solved_type {
|
||||
SolvedType::Alias(symbol, _, _) if symbol == &loc_symbol.value => {
|
||||
if let Some(alias) = aliases.get(symbol) {
|
||||
constrain_imported_alias(loc_symbol, alias, body_con, var_store)
|
||||
} else {
|
||||
panic!("Alias {:?} is not available", symbol)
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let mut def_types = SendMap::default();
|
||||
let typ = to_type(solved_type, &mut free_vars, var_store);
|
||||
|
||||
def_types.insert(
|
||||
loc_symbol.value,
|
||||
Located {
|
||||
region: loc_symbol.region,
|
||||
value: typ,
|
||||
},
|
||||
);
|
||||
|
||||
let mut rigid_vars = Vec::new();
|
||||
|
||||
for (_, var) in free_vars.rigid_vars {
|
||||
rigid_vars.push(var);
|
||||
}
|
||||
|
||||
Let(Box::new(LetConstraint {
|
||||
// rigids from other modules should not be treated as rigid
|
||||
// within this module; rather, they should be treated as flex
|
||||
rigid_vars,
|
||||
flex_vars: Vec::new(),
|
||||
// Importing a value doesn't constrain this module at all.
|
||||
// All it does is introduce variables and provide def_types for lookups
|
||||
def_types,
|
||||
def_aliases: SendMap::default(),
|
||||
defs_constraint: True,
|
||||
ret_constraint: body_con,
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn constrain_imported_alias(
|
||||
loc_symbol: Located<Symbol>,
|
||||
imported_alias: &Alias,
|
||||
body_con: Constraint,
|
||||
var_store: &VarStore,
|
||||
) -> Constraint {
|
||||
use Constraint::*;
|
||||
let mut def_aliases = SendMap::default();
|
||||
|
||||
let mut vars = Vec::with_capacity(imported_alias.vars.len());
|
||||
let mut substitution = ImMap::default();
|
||||
for Located {
|
||||
region,
|
||||
value: (lowercase, old_var),
|
||||
} in &imported_alias.vars
|
||||
{
|
||||
let new_var = var_store.fresh();
|
||||
vars.push(Located::at(*region, (lowercase.clone(), new_var)));
|
||||
substitution.insert(*old_var, Type::Variable(new_var));
|
||||
}
|
||||
|
||||
let mut actual = imported_alias.typ.clone();
|
||||
actual.substitute(&substitution);
|
||||
|
||||
let alias = Alias {
|
||||
vars,
|
||||
region: loc_symbol.region,
|
||||
typ: actual,
|
||||
};
|
||||
|
||||
def_aliases.insert(loc_symbol.value, alias);
|
||||
|
||||
Let(Box::new(LetConstraint {
|
||||
// rigids from other modules should not be treated as rigid
|
||||
// within this module; rather, they should be treated as flex
|
||||
rigid_vars: Vec::new(),
|
||||
flex_vars: Vec::new(),
|
||||
// Importing a value doesn't constrain this module at all.
|
||||
// All it does is introduce variables and provide def_types for lookups
|
||||
def_types: SendMap::default(),
|
||||
def_aliases,
|
||||
defs_constraint: True,
|
||||
ret_constraint: body_con,
|
||||
}))
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
use crate::can::ident::Lowercase;
|
||||
use crate::can::pattern::Pattern::{self, *};
|
||||
use crate::can::pattern::RecordDestruct;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::SendMap;
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::region::{Located, Region};
|
||||
use crate::subs::Variable;
|
||||
use crate::types::{Constraint, Expected, PExpected, PatternCategory, RecordFieldLabel, Type};
|
||||
use crate::types::{Constraint, Expected, PExpected, PatternCategory, Type};
|
||||
|
||||
pub struct PatternState {
|
||||
pub headers: SendMap<Symbol, Located<Type>>,
|
||||
@ -12,6 +13,92 @@ pub struct PatternState {
|
||||
pub constraints: Vec<Constraint>,
|
||||
}
|
||||
|
||||
/// If there is a type annotation, the pattern state headers can be optimized by putting the
|
||||
/// annotation in the headers. Normally
|
||||
///
|
||||
/// x = 4
|
||||
///
|
||||
/// Would add `x => <42>` to the headers (i.e., symbol points to a type variable). If the
|
||||
/// definition has an annotation, we instead now add `x => Int`.
|
||||
pub fn headers_from_annotation(
|
||||
pattern: &Pattern,
|
||||
annotation: &Located<Type>,
|
||||
) -> Option<SendMap<Symbol, Located<Type>>> {
|
||||
let mut headers = SendMap::default();
|
||||
// Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int`
|
||||
// in such incorrect cases we don't put the full annotation in headers, just a variable, and let
|
||||
// inference generate a proper error.
|
||||
let is_structurally_valid = headers_from_annotation_help(pattern, annotation, &mut headers);
|
||||
|
||||
if is_structurally_valid {
|
||||
Some(headers)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn headers_from_annotation_help(
|
||||
pattern: &Pattern,
|
||||
annotation: &Located<Type>,
|
||||
headers: &mut SendMap<Symbol, Located<Type>>,
|
||||
) -> bool {
|
||||
match pattern {
|
||||
Identifier(symbol) => {
|
||||
headers.insert(symbol.clone(), annotation.clone());
|
||||
true
|
||||
}
|
||||
Underscore
|
||||
| Shadowed(_, _)
|
||||
| UnsupportedPattern(_)
|
||||
| IntLiteral(_)
|
||||
| FloatLiteral(_)
|
||||
| StrLiteral(_) => true,
|
||||
|
||||
RecordDestructure(_, destructs) => match annotation.value.shallow_dealias() {
|
||||
Type::Record(fields, _) => {
|
||||
for destruct in destructs {
|
||||
// NOTE ignores the .guard field.
|
||||
if let Some(field_type) = fields.get(&destruct.value.label) {
|
||||
headers.insert(
|
||||
destruct.value.symbol.clone(),
|
||||
Located::at(annotation.region, field_type.clone()),
|
||||
);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
Type::EmptyRec => destructs.is_empty(),
|
||||
_ => false,
|
||||
},
|
||||
|
||||
AppliedTag(_, tag_name, arguments) => match annotation.value.shallow_dealias() {
|
||||
Type::TagUnion(tags, _) => {
|
||||
if let Some((_, arg_types)) = tags.iter().find(|(name, _)| name == tag_name) {
|
||||
if !arguments.len() == arg_types.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
arguments
|
||||
.iter()
|
||||
.zip(arg_types.iter())
|
||||
.all(|(arg_pattern, arg_type)| {
|
||||
headers_from_annotation_help(
|
||||
&arg_pattern.1.value,
|
||||
&Located::at(annotation.region, arg_type.clone()),
|
||||
headers,
|
||||
)
|
||||
})
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// This accepts PatternState (rather than returning it) so that the caller can
|
||||
/// intiialize the Vecs in PatternState using with_capacity
|
||||
/// based on its knowledge of their lengths.
|
||||
@ -65,13 +152,17 @@ pub fn constrain_pattern(
|
||||
state.vars.push(*ext_var);
|
||||
let ext_type = Type::Variable(*ext_var);
|
||||
|
||||
let mut field_types: SendMap<RecordFieldLabel, Type> = SendMap::default();
|
||||
let mut field_types: SendMap<Lowercase, Type> = SendMap::default();
|
||||
|
||||
for RecordDestruct {
|
||||
var,
|
||||
label,
|
||||
symbol,
|
||||
guard,
|
||||
for Located {
|
||||
value:
|
||||
RecordDestruct {
|
||||
var,
|
||||
label,
|
||||
symbol,
|
||||
guard,
|
||||
},
|
||||
..
|
||||
} in patterns
|
||||
{
|
||||
let pat_type = Type::Variable(*var);
|
||||
@ -130,7 +221,7 @@ pub fn constrain_pattern(
|
||||
state.vars.push(*ext_var);
|
||||
state.constraints.push(tag_con);
|
||||
}
|
||||
Shadowed(_) => {
|
||||
Shadowed(_, _) => {
|
||||
panic!("TODO constrain Shadowed pattern");
|
||||
}
|
||||
}
|
||||
|
@ -239,13 +239,13 @@ pub fn build_expr<'a, B: Backend>(
|
||||
let ir_type = type_from_layout(cfg, layout, subs);
|
||||
builder.ins().stack_addr(ir_type, slot, Offset32::new(0))
|
||||
}
|
||||
Access {
|
||||
label,
|
||||
field_layout,
|
||||
struct_layout,
|
||||
} => {
|
||||
panic!("I don't yet know how to crane build {:?}", expr);
|
||||
}
|
||||
// Access {
|
||||
// label,
|
||||
// field_layout,
|
||||
// struct_layout,
|
||||
// } => {
|
||||
// panic!("I don't yet know how to crane build {:?}", expr);
|
||||
// }
|
||||
_ => {
|
||||
panic!("I don't yet know how to crane build {:?}", expr);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ use cranelift::prelude::AbiParam;
|
||||
use cranelift_codegen::ir::{types, Signature, Type};
|
||||
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||
|
||||
use crate::can::ident::ModuleName;
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::mono::layout::Layout;
|
||||
use crate::subs::FlatType::*;
|
||||
use crate::subs::{Content, Subs, Variable};
|
||||
@ -17,23 +17,16 @@ pub fn type_from_var(var: Variable, subs: &Subs, cfg: TargetFrontendConfig) -> T
|
||||
pub fn type_from_content(content: &Content, subs: &Subs, cfg: TargetFrontendConfig) -> Type {
|
||||
match content {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
} => {
|
||||
let module_name = module_name.as_str();
|
||||
let name = name.as_str();
|
||||
|
||||
if module_name == ModuleName::NUM && name == crate::types::TYPE_NUM {
|
||||
Apply(symbol, args) => {
|
||||
if *symbol == Symbol::NUM_NUM {
|
||||
let arg = *args.iter().next().unwrap();
|
||||
let arg_content = subs.get_without_compacting(arg).content;
|
||||
|
||||
num_to_crane_type(arg_content)
|
||||
} else {
|
||||
panic!(
|
||||
"TODO handle type_from_content for FlatType::Apply of {}.{} with args {:?}",
|
||||
module_name, name, args
|
||||
"TODO handle content_to_basic_type for FlatType::Apply of {:?} with args {:?}",
|
||||
symbol, args
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -47,33 +40,20 @@ pub fn type_from_content(content: &Content, subs: &Subs, cfg: TargetFrontendConf
|
||||
fn num_to_crane_type(content: Content) -> Type {
|
||||
match content {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
} => {
|
||||
let module_name = module_name.as_str();
|
||||
let name = name.as_str();
|
||||
|
||||
if module_name == ModuleName::FLOAT
|
||||
&& name == crate::types::TYPE_FLOATINGPOINT
|
||||
&& args.is_empty()
|
||||
{
|
||||
Apply(symbol, args) => match symbol {
|
||||
Symbol::FLOAT_FLOATINGPOINT => {
|
||||
debug_assert!(args.is_empty());
|
||||
types::F64
|
||||
} else if module_name == ModuleName::INT
|
||||
&& name == crate::types::TYPE_INTEGER
|
||||
&& args.is_empty()
|
||||
{
|
||||
}
|
||||
Symbol::INT_INTEGER => {
|
||||
debug_assert!(args.is_empty());
|
||||
types::I64
|
||||
} else {
|
||||
panic!(
|
||||
"Unrecognized numeric type: {}.{} with args {:?}",
|
||||
module_name, name, args
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => panic!(
|
||||
"Unrecognized numeric type: {:?} with args {:?}",
|
||||
symbol, args
|
||||
),
|
||||
},
|
||||
other => panic!(
|
||||
"TODO handle num_to_crane_type (branch 0) for {:?} which is NESTED inside Num.Num",
|
||||
other
|
||||
|
@ -55,9 +55,9 @@ pub use self::backing_vec::Persistent;
|
||||
pub trait UnifyKey: Copy + Clone + Debug + PartialEq {
|
||||
type Value: Clone + Debug;
|
||||
|
||||
fn index(&self) -> usize;
|
||||
fn index(&self) -> u32;
|
||||
|
||||
fn from_index(u: usize) -> Self;
|
||||
fn from_index(u: u32) -> Self;
|
||||
|
||||
fn tag() -> &'static str;
|
||||
|
||||
@ -237,7 +237,7 @@ impl<S: UnificationStore> UnificationTable<S> {
|
||||
|
||||
/// Creates a fresh key with the given value.
|
||||
pub fn new_key(&mut self, value: S::Value) -> S::Key {
|
||||
let len = self.values.len();
|
||||
let len = self.values.len() as u32;
|
||||
let key: S::Key = UnifyKey::from_index(len);
|
||||
self.values.push(VarValue::new_var(key, value));
|
||||
debug!("{}: created new key: {:?}", S::tag(), key);
|
||||
@ -255,7 +255,7 @@ impl<S: UnificationStore> UnificationTable<S> {
|
||||
/// the closure.
|
||||
pub fn reset_unifications(&mut self, mut value: impl FnMut(S::Key) -> S::Value) {
|
||||
self.values.reset_unifications(|i| {
|
||||
let key = UnifyKey::from_index(i);
|
||||
let key = UnifyKey::from_index(i as u32);
|
||||
let value = value(key);
|
||||
VarValue::new_var(key, value)
|
||||
});
|
||||
@ -274,7 +274,7 @@ impl<S: UnificationStore> UnificationTable<S> {
|
||||
/// Returns the keys of all variables created since the `snapshot`.
|
||||
pub fn vars_since_snapshot(&self, snapshot: &Snapshot<S>) -> Range<S::Key> {
|
||||
let range = self.values.values_since_snapshot(&snapshot.snapshot);
|
||||
S::Key::from_index(range.start)..S::Key::from_index(range.end)
|
||||
S::Key::from_index(range.start as u32)..S::Key::from_index(range.end as u32)
|
||||
}
|
||||
|
||||
/// Obtains the current value for a particular key.
|
||||
@ -311,7 +311,7 @@ impl<S: UnificationStore> UnificationTable<S> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_redirect(&mut self, vid: S::Key) -> bool {
|
||||
pub fn is_redirect(&self, vid: S::Key) -> bool {
|
||||
self.value(vid).raw_parent() != vid
|
||||
}
|
||||
|
||||
|
@ -29,8 +29,8 @@ pub fn fmt_def<'a>(buf: &mut String<'a>, def: &'a Def<'a>, indent: u16) {
|
||||
fmt_expr(buf, &loc_expr.value, indent, false, true);
|
||||
}
|
||||
}
|
||||
TypedDef(_loc_pattern, _loc_annotation, _loc_expr) => {
|
||||
panic!("TODO support Annotation in TypedDef");
|
||||
TypedBody(_loc_pattern, _loc_annotation, _loc_expr) => {
|
||||
panic!("TODO support Annotation in TypedBody");
|
||||
}
|
||||
SpaceBefore(sub_def, spaces) => {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
|
134
src/fmt/expr.rs
134
src/fmt/expr.rs
@ -3,6 +3,8 @@ use crate::fmt::pattern::fmt_pattern;
|
||||
use crate::fmt::spaces::{
|
||||
add_spaces, fmt_comments_only, fmt_if_spaces, fmt_spaces, is_comment, newline, INDENT,
|
||||
};
|
||||
use crate::operator;
|
||||
use crate::operator::BinOp;
|
||||
use crate::parse::ast::{AssignedField, Base, CommentOrNewline, Expr, Pattern};
|
||||
use crate::region::Located;
|
||||
use bumpalo::collections::{String, Vec};
|
||||
@ -43,13 +45,13 @@ pub fn fmt_expr<'a>(
|
||||
buf.push_str(string);
|
||||
buf.push('"');
|
||||
}
|
||||
Var(module_parts, name) => {
|
||||
for part in module_parts.iter() {
|
||||
buf.push_str(part);
|
||||
Var { module_name, ident } => {
|
||||
if !module_name.is_empty() {
|
||||
buf.push_str(module_name);
|
||||
buf.push('.');
|
||||
}
|
||||
|
||||
buf.push_str(name);
|
||||
buf.push_str(ident);
|
||||
}
|
||||
Apply(loc_expr, loc_args, _) => {
|
||||
if apply_needs_parens {
|
||||
@ -132,7 +134,7 @@ pub fn fmt_expr<'a>(
|
||||
// still print the return value.
|
||||
fmt_expr(buf, &ret.value, indent, false, true);
|
||||
}
|
||||
If((loc_condition, loc_then, loc_else)) => {
|
||||
If(loc_condition, loc_then, loc_else) => {
|
||||
fmt_if(buf, loc_condition, loc_then, loc_else, indent);
|
||||
}
|
||||
When(loc_condition, branches) => {
|
||||
@ -190,10 +192,117 @@ pub fn fmt_expr<'a>(
|
||||
List(loc_items) => {
|
||||
fmt_list(buf, &loc_items, indent);
|
||||
}
|
||||
BinOp((loc_left_side, bin_op, loc_right_side)) => fmt_bin_op(
|
||||
buf,
|
||||
loc_left_side,
|
||||
bin_op,
|
||||
loc_right_side,
|
||||
false,
|
||||
apply_needs_parens,
|
||||
indent,
|
||||
),
|
||||
UnaryOp(sub_expr, unary_op) => {
|
||||
match &unary_op.value {
|
||||
operator::UnaryOp::Negate => {
|
||||
buf.push('-');
|
||||
}
|
||||
operator::UnaryOp::Not => {
|
||||
buf.push('!');
|
||||
}
|
||||
}
|
||||
|
||||
fmt_expr(
|
||||
buf,
|
||||
&sub_expr.value,
|
||||
indent,
|
||||
apply_needs_parens,
|
||||
format_newlines,
|
||||
);
|
||||
}
|
||||
Nested(nested_expr) => {
|
||||
fmt_expr(
|
||||
buf,
|
||||
nested_expr,
|
||||
indent,
|
||||
apply_needs_parens,
|
||||
format_newlines,
|
||||
);
|
||||
}
|
||||
AccessorFunction(key) => {
|
||||
buf.push('.');
|
||||
buf.push_str(key);
|
||||
}
|
||||
Access(expr, key) => {
|
||||
fmt_expr(buf, expr, indent, apply_needs_parens, true);
|
||||
buf.push('.');
|
||||
buf.push_str(key);
|
||||
}
|
||||
other => panic!("TODO implement Display for AST variant {:?}", other),
|
||||
}
|
||||
}
|
||||
|
||||
fn fmt_bin_op<'a>(
|
||||
buf: &mut String<'a>,
|
||||
loc_left_side: &'a Located<Expr<'a>>,
|
||||
loc_bin_op: &'a Located<BinOp>,
|
||||
loc_right_side: &'a Located<Expr<'a>>,
|
||||
part_of_multi_line_bin_ops: bool,
|
||||
apply_needs_parens: bool,
|
||||
indent: u16,
|
||||
) {
|
||||
fmt_expr(buf, &loc_left_side.value, indent, apply_needs_parens, false);
|
||||
|
||||
let is_multiline = is_multiline_expr(&loc_right_side.value)
|
||||
|| is_multiline_expr(&loc_left_side.value)
|
||||
|| part_of_multi_line_bin_ops;
|
||||
|
||||
if is_multiline {
|
||||
newline(buf, indent + INDENT)
|
||||
} else {
|
||||
buf.push(' ');
|
||||
}
|
||||
|
||||
match &loc_bin_op.value {
|
||||
operator::BinOp::Caret => buf.push('^'),
|
||||
operator::BinOp::Star => buf.push('*'),
|
||||
operator::BinOp::Slash => buf.push('/'),
|
||||
operator::BinOp::DoubleSlash => buf.push_str("//"),
|
||||
operator::BinOp::Percent => buf.push('%'),
|
||||
operator::BinOp::DoublePercent => buf.push_str("%%"),
|
||||
operator::BinOp::Plus => buf.push('+'),
|
||||
operator::BinOp::Minus => buf.push('-'),
|
||||
operator::BinOp::Equals => buf.push_str("=="),
|
||||
operator::BinOp::NotEquals => buf.push_str("!="),
|
||||
operator::BinOp::LessThan => buf.push('<'),
|
||||
operator::BinOp::GreaterThan => buf.push('>'),
|
||||
operator::BinOp::LessThanOrEq => buf.push_str("<="),
|
||||
operator::BinOp::GreaterThanOrEq => buf.push_str(">="),
|
||||
operator::BinOp::And => buf.push_str("&&"),
|
||||
operator::BinOp::Or => buf.push_str("||"),
|
||||
operator::BinOp::Pizza => buf.push_str("|>"),
|
||||
}
|
||||
|
||||
buf.push(' ');
|
||||
|
||||
match &loc_right_side.value {
|
||||
Expr::BinOp((nested_left_side, nested_bin_op, nested_right_side)) => {
|
||||
fmt_bin_op(
|
||||
buf,
|
||||
nested_left_side,
|
||||
nested_bin_op,
|
||||
nested_right_side,
|
||||
is_multiline,
|
||||
apply_needs_parens,
|
||||
indent,
|
||||
);
|
||||
}
|
||||
|
||||
_ => {
|
||||
fmt_expr(buf, &loc_right_side.value, indent, apply_needs_parens, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fmt_list<'a>(
|
||||
buf: &mut String<'a>,
|
||||
loc_items: &'a Vec<'a, &'a Located<Expr<'a>>>,
|
||||
@ -369,7 +478,7 @@ pub fn is_multiline_pattern<'a>(pattern: &'a Pattern<'a>) -> bool {
|
||||
| Pattern::BlockStrLiteral(_)
|
||||
| Pattern::Underscore
|
||||
| Pattern::Malformed(_)
|
||||
| Pattern::QualifiedIdentifier(_) => false,
|
||||
| Pattern::QualifiedIdentifier { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -391,7 +500,7 @@ pub fn is_multiline_expr<'a>(expr: &'a Expr<'a>) -> bool {
|
||||
| Str(_)
|
||||
| Access(_, _)
|
||||
| AccessorFunction(_)
|
||||
| Var(_, _)
|
||||
| Var { .. }
|
||||
| MalformedIdent(_)
|
||||
| MalformedClosure
|
||||
| GlobalTag(_)
|
||||
@ -410,14 +519,21 @@ pub fn is_multiline_expr<'a>(expr: &'a Expr<'a>) -> bool {
|
||||
|| args.iter().any(|loc_arg| is_multiline_expr(&loc_arg.value))
|
||||
}
|
||||
|
||||
If((loc_cond, loc_if_true, loc_if_false)) => {
|
||||
If(loc_cond, loc_if_true, loc_if_false) => {
|
||||
is_multiline_expr(&loc_cond.value)
|
||||
|| is_multiline_expr(&loc_if_true.value)
|
||||
|| is_multiline_expr(&loc_if_false.value)
|
||||
}
|
||||
|
||||
BinOp((loc_left, _, loc_right)) => {
|
||||
is_multiline_expr(&loc_left.value) || is_multiline_expr(&loc_right.value)
|
||||
let next_is_multiline_bin_op: bool = match &loc_right.value {
|
||||
Expr::BinOp((_, _, nested_loc_right)) => is_multiline_expr(&nested_loc_right.value),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
is_multiline_expr(&loc_left.value)
|
||||
|| is_multiline_expr(&loc_right.value)
|
||||
|| next_is_multiline_bin_op
|
||||
}
|
||||
|
||||
UnaryOp(loc_subexpr, _) | PrecedenceConflict(_, _, loc_subexpr) => {
|
||||
|
@ -109,13 +109,13 @@ pub fn fmt_pattern<'a>(
|
||||
|
||||
// Malformed
|
||||
Malformed(string) => buf.push_str(string),
|
||||
QualifiedIdentifier(maybe_qualified) => {
|
||||
for part in maybe_qualified.module_parts.iter() {
|
||||
buf.push_str(part);
|
||||
QualifiedIdentifier { module_name, ident } => {
|
||||
if !module_name.is_empty() {
|
||||
buf.push_str(module_name);
|
||||
buf.push('.');
|
||||
}
|
||||
|
||||
buf.push_str(maybe_qualified.value);
|
||||
buf.push_str(ident);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
55
src/ident.rs
55
src/ident.rs
@ -1,55 +0,0 @@
|
||||
use std::fmt::{self, Display, Formatter};
|
||||
|
||||
/// An identifier, possibly fully-qualified with a module name
|
||||
/// e.g. (Http.Request from http)
|
||||
/// Parameterized on a phantom marker for whether it has been canonicalized
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum Ident {
|
||||
Unqualified(Box<str>),
|
||||
Qualified(Box<str>, Box<str>),
|
||||
}
|
||||
|
||||
impl Ident {
|
||||
pub fn new(module_parts: &[&str], name: &str) -> Self {
|
||||
debug_assert!(!name.is_empty());
|
||||
|
||||
if module_parts.is_empty() {
|
||||
Ident::Unqualified(name.into())
|
||||
} else {
|
||||
Ident::Qualified(module_parts.to_vec().join(".").into(), name.into())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_qualified(&self) -> bool {
|
||||
match self {
|
||||
Ident::Unqualified(_) => false,
|
||||
Ident::Qualified(_, _) => true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(self) -> Box<str> {
|
||||
match self {
|
||||
Ident::Unqualified(name) => name,
|
||||
Ident::Qualified(_, name) => name,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn first_char(&self) -> char {
|
||||
let opt_first = match self {
|
||||
Ident::Unqualified(name) => name.chars().next(),
|
||||
Ident::Qualified(_, name) => name.chars().next(),
|
||||
};
|
||||
|
||||
opt_first
|
||||
.unwrap_or_else(|| panic!("Attempted to get the first character of an empty Ident"))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ident {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Ident::Unqualified(name) => write!(f, "{}", name),
|
||||
Ident::Qualified(path, name) => write!(f, "{}.{}", path, name),
|
||||
}
|
||||
}
|
||||
}
|
14
src/infer.rs
14
src/infer.rs
@ -1,4 +1,4 @@
|
||||
use crate::collections::{MutMap, SendMap};
|
||||
use crate::collections::SendMap;
|
||||
use crate::solve::{self, Solved};
|
||||
use crate::subs::{Content, Subs, Variable};
|
||||
use crate::types::{Constraint, Problem};
|
||||
@ -9,13 +9,11 @@ pub fn infer_expr(
|
||||
constraint: &Constraint,
|
||||
expr_var: Variable,
|
||||
) -> (Content, Solved<Subs>) {
|
||||
let (solved, _) = solve::run(
|
||||
&SendMap::default(),
|
||||
MutMap::default(),
|
||||
problems,
|
||||
subs,
|
||||
constraint,
|
||||
);
|
||||
let env = solve::Env {
|
||||
aliases: SendMap::default(),
|
||||
vars_by_symbol: SendMap::default(),
|
||||
};
|
||||
let (solved, _) = solve::run(&env, problems, subs, constraint);
|
||||
|
||||
let content = solved.inner().get_without_compacting(expr_var).content;
|
||||
|
||||
|
@ -14,7 +14,6 @@
|
||||
pub mod can;
|
||||
pub mod collections;
|
||||
pub mod graph;
|
||||
pub mod ident;
|
||||
pub mod operator;
|
||||
pub mod parse;
|
||||
pub mod region;
|
||||
|
@ -3,11 +3,10 @@ use inkwell::types::BasicTypeEnum::{self, *};
|
||||
use inkwell::types::{BasicType, FunctionType};
|
||||
use inkwell::AddressSpace;
|
||||
|
||||
use crate::can::ident::ModuleName;
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::mono::layout::Layout;
|
||||
use crate::subs::FlatType::*;
|
||||
use crate::subs::{Content, Subs, Variable};
|
||||
use crate::types;
|
||||
|
||||
pub fn type_from_var<'ctx>(
|
||||
var: Variable,
|
||||
@ -31,23 +30,16 @@ pub fn content_to_basic_type<'ctx>(
|
||||
) -> Result<BasicTypeEnum<'ctx>, String> {
|
||||
match content {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
} => {
|
||||
let module_name = module_name.as_str();
|
||||
let name = name.as_str();
|
||||
|
||||
if module_name == ModuleName::NUM && name == types::TYPE_NUM {
|
||||
Apply(symbol, args) => {
|
||||
if *symbol == Symbol::NUM_NUM {
|
||||
let arg = *args.iter().next().unwrap();
|
||||
let arg_content = subs.get_without_compacting(arg).content;
|
||||
|
||||
num_to_basic_type(arg_content, context)
|
||||
} else {
|
||||
panic!(
|
||||
"TODO handle content_to_basic_type for FlatType::Apply of {}.{} with args {:?}",
|
||||
module_name, name, args
|
||||
"TODO handle content_to_basic_type for FlatType::Apply of {:?} with args {:?}",
|
||||
symbol, args
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -75,33 +67,20 @@ pub fn content_to_basic_type<'ctx>(
|
||||
fn num_to_basic_type(content: Content, context: &Context) -> Result<BasicTypeEnum<'_>, String> {
|
||||
match content {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
} => {
|
||||
let module_name = module_name.as_str();
|
||||
let name = name.as_str();
|
||||
|
||||
if module_name == ModuleName::FLOAT
|
||||
&& name == types::TYPE_FLOATINGPOINT
|
||||
&& args.is_empty()
|
||||
{
|
||||
Apply(symbol, args) => match symbol {
|
||||
Symbol::FLOAT_FLOATINGPOINT => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(BasicTypeEnum::FloatType(context.f64_type()))
|
||||
} else if module_name == ModuleName::INT
|
||||
&& name == types::TYPE_INTEGER
|
||||
&& args.is_empty()
|
||||
{
|
||||
}
|
||||
Symbol::INT_INTEGER => {
|
||||
debug_assert!(args.is_empty());
|
||||
Ok(BasicTypeEnum::IntType(context.i64_type()))
|
||||
} else {
|
||||
Err(format!(
|
||||
"Unrecognized numeric type: {}.{} with args {:?}",
|
||||
module_name, name, args
|
||||
))
|
||||
}
|
||||
}
|
||||
_ => Err(format!(
|
||||
"Unrecognized numeric type: {:?} with args {:?}",
|
||||
symbol, args
|
||||
)),
|
||||
},
|
||||
other => panic!(
|
||||
"TODO handle num_to_basic_type (branch 0) for {:?} which is NESTED inside Num.Num",
|
||||
other
|
||||
|
837
src/load/mod.rs
837
src/load/mod.rs
File diff suppressed because it is too large
Load Diff
@ -28,6 +28,7 @@ impl<'a> ModuleName<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO is this all duplicated from parse::ast?
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct InterfaceHeader<'a> {
|
||||
pub name: Loc<ModuleName<'a>>,
|
||||
|
@ -1,39 +1,94 @@
|
||||
use crate::can::ident::ModuleName;
|
||||
use crate::collections::{default_hasher, MutMap};
|
||||
use inlinable_string::InlinableString;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::{fmt, u32};
|
||||
|
||||
pub const NUM_BUILTIN_MODULES: usize = 12;
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
// TODO: benchmark this as { ident_id: u32, module_id: u32 } and see if perf stays the same
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Symbol(u64);
|
||||
|
||||
/// In Debug builds only, Symbol has a name() method that lets
|
||||
/// you look up its name in a global intern table. This table is
|
||||
/// behind a mutex, so it is neither populated nor available in release builds.
|
||||
impl Symbol {
|
||||
// Num
|
||||
pub const NUM_ABS: Symbol = Symbol::new(ModuleId::NUM, IdentId::NUM_ABS);
|
||||
pub const NUM_NUM: Symbol = Symbol::new(ModuleId::NUM, IdentId::NUM_NUM);
|
||||
pub const NUM_INT: Symbol = Symbol::new(ModuleId::NUM, IdentId::NUM_INT);
|
||||
pub const NUM_INTEGER: Symbol = Symbol::new(ModuleId::NUM, IdentId::NUM_INTEGER);
|
||||
pub const NUM_FLOAT: Symbol = Symbol::new(ModuleId::NUM, IdentId::NUM_FLOAT);
|
||||
pub const NUM_FLOATINGPOINT: Symbol = Symbol::new(ModuleId::NUM, IdentId::NUM_FLOATINGPOINT);
|
||||
|
||||
// Bool
|
||||
pub const BOOL_NOT: Symbol = Symbol::new(ModuleId::BOOL, IdentId::BOOL_NOT);
|
||||
pub const BOOL_BOOL: Symbol = Symbol::new(ModuleId::BOOL, IdentId::BOOL_BOOL);
|
||||
// NOTE: the define_builtins! macro adds a bunch of constants to this impl,
|
||||
//
|
||||
// e.g. pub const NUM_NUM: Symbol = …
|
||||
|
||||
pub const fn new(module_id: ModuleId, ident_id: IdentId) -> Symbol {
|
||||
// The bit layout of the u64 inside a Symbol is:
|
||||
//
|
||||
// |------ 32 bits -----|------ 32 bits -----|
|
||||
// | module_id | ident_id |
|
||||
let bits = ((module_id.0 as u64) << 32) | (ident_id.0 as u64);
|
||||
// | ident_id | module_id |
|
||||
// |--------------------|--------------------|
|
||||
//
|
||||
// module_id comes second because we need to query it more often,
|
||||
// and this way we can get it by truncating the u64 to u32,
|
||||
// whereas accessing the first slot requires a bit shift first.
|
||||
let bits = ((ident_id.0 as u64) << 32) | (module_id.0 as u64);
|
||||
|
||||
Symbol(bits)
|
||||
}
|
||||
|
||||
pub fn module_id(self) -> ModuleId {
|
||||
ModuleId(self.0 as u32)
|
||||
}
|
||||
|
||||
pub fn ident_id(self) -> IdentId {
|
||||
IdentId((self.0 >> 32) as u32)
|
||||
}
|
||||
|
||||
pub fn module_string<'a>(&self, interns: &'a Interns) -> &'a InlinableString {
|
||||
interns
|
||||
.module_ids
|
||||
.get_name(self.module_id())
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"module_string could not find IdentIds for {:?}",
|
||||
self.module_id()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn ident_string(self, interns: &Interns) -> &InlinableString {
|
||||
let ident_ids = interns
|
||||
.all_ident_ids
|
||||
.get(&self.module_id())
|
||||
.unwrap_or_else(|| {
|
||||
panic!(
|
||||
"ident_string could not find IdentIds for {:?}",
|
||||
self.module_id()
|
||||
)
|
||||
});
|
||||
|
||||
ident_ids.get_name(self.ident_id()).unwrap_or_else(|| {
|
||||
panic!(
|
||||
"ident_string's IdentIds did not contain an entry for {} in module {:?}",
|
||||
self.ident_id().0,
|
||||
self.module_id()
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fully_qualified(self, interns: &Interns, home: ModuleId) -> InlinableString {
|
||||
let module_id = self.module_id();
|
||||
|
||||
if module_id == home {
|
||||
self.ident_string(interns).clone()
|
||||
} else {
|
||||
// TODO do this without format! to avoid allocation for short strings
|
||||
format!(
|
||||
"{}.{}",
|
||||
self.module_string(interns),
|
||||
self.ident_string(interns)
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn emit(self) -> InlinableString {
|
||||
format!("${}", self.0).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Rather than displaying as this:
|
||||
@ -42,11 +97,44 @@ impl Symbol {
|
||||
///
|
||||
/// ...instead display as this:
|
||||
///
|
||||
/// 'Foo.bar'
|
||||
/// `Foo.bar`
|
||||
impl fmt::Debug for Symbol {
|
||||
#[cfg(debug_assertions)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "'{}'", self.0)
|
||||
let module_id = self.module_id();
|
||||
let ident_id = self.ident_id();
|
||||
|
||||
match DEBUG_IDENT_IDS_BY_MODULE_ID.lock() {
|
||||
Ok(names) => match &names.get(&module_id.0) {
|
||||
Some(ident_ids) => match ident_ids.get_name(ident_id) {
|
||||
Some(ident_str) => write!(f, "`{:?}.{}`", module_id, ident_str),
|
||||
None => fallback_debug_fmt(*self, f),
|
||||
},
|
||||
None => fallback_debug_fmt(*self, f),
|
||||
},
|
||||
Err(err) => {
|
||||
// Print and return Err rather than panicking, because this
|
||||
// might be used in a panic error message, and if we panick
|
||||
// while we're already panicking it'll kill the process
|
||||
// without printing any of the errors!
|
||||
println!("DEBUG INFO: Failed to acquire lock for Debug reading from DEBUG_IDENT_IDS_BY_MODULE_ID, presumably because a thread panicked: {:?}", err);
|
||||
|
||||
fallback_debug_fmt(*self, f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
fallback_debug_fmt(*self, f)
|
||||
}
|
||||
}
|
||||
|
||||
fn fallback_debug_fmt(symbol: Symbol, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let module_id = symbol.module_id();
|
||||
let ident_id = symbol.ident_id();
|
||||
|
||||
write!(f, "`{:?}.{:?}`", module_id, ident_id)
|
||||
}
|
||||
|
||||
// TODO this is only here to prevent clippy from complaining about an unused
|
||||
@ -65,7 +153,49 @@ lazy_static! {
|
||||
/// which displays not only the Module ID, but also the Module Name which
|
||||
/// corresponds to that ID.
|
||||
///
|
||||
pub static ref DEBUG_MODULE_ID_NAMES: std::sync::Mutex<crate::collections::MutMap<u32, Box<str>>> =
|
||||
static ref DEBUG_MODULE_ID_NAMES: std::sync::Mutex<crate::collections::MutMap<u32, Box<str>>> =
|
||||
// This stores a u32 key instead of a ModuleId key so that if there's
|
||||
// a problem with ModuleId's Debug implementation, logging this for diagnostic
|
||||
// purposes won't recursively trigger ModuleId's Debug instance in the course of printing
|
||||
// this out.
|
||||
std::sync::Mutex::new(crate::collections::MutMap::default());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Interns {
|
||||
pub module_ids: ModuleIds,
|
||||
pub all_ident_ids: MutMap<ModuleId, IdentIds>,
|
||||
}
|
||||
|
||||
impl Interns {
|
||||
pub fn symbol(&self, module_id: ModuleId, ident: InlinableString) -> Symbol {
|
||||
match self.all_ident_ids.get(&module_id) {
|
||||
Some(ident_ids) => match ident_ids.get_id(&ident) {
|
||||
Some(ident_id) => Symbol::new(module_id, *ident_id),
|
||||
None => {
|
||||
panic!("Interns::symbol could not find ident entry for {:?} for module {:?} in Interns {:?}", ident, module_id, self);
|
||||
}
|
||||
},
|
||||
None => {
|
||||
panic!(
|
||||
"Interns::symbol could not find entry for module {:?} in Interns {:?}",
|
||||
module_id, self
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_index(module_id: ModuleId, ident_id: u32) -> Symbol {
|
||||
Symbol::new(module_id, IdentId(ident_id))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
lazy_static! {
|
||||
/// This is used in Debug builds only, to let us have a Debug instance
|
||||
/// which displays not only the Module ID, but also the Module Name which
|
||||
/// corresponds to that ID.
|
||||
static ref DEBUG_IDENT_IDS_BY_MODULE_ID: std::sync::Mutex<crate::collections::MutMap<u32, IdentIds>> =
|
||||
// This stores a u32 key instead of a ModuleId key so that if there's
|
||||
// a problem with ModuleId's Debug implementation, logging this for diagnostic
|
||||
// purposes won't recursively trigger ModuleId's Debug instance in the course of printing
|
||||
@ -81,17 +211,9 @@ pub struct ModuleId(u32);
|
||||
/// you look up its name in a global intern table. This table is
|
||||
/// behind a mutex, so it is neither populated nor available in release builds.
|
||||
impl ModuleId {
|
||||
// NOTE: Always add constants to the *end* of this list (with a unique integer),
|
||||
// and then also go to `impl Default for ModuleIds` and incorporate the new constant
|
||||
// into the *end* of its default module insertions. Insertion order matters for those!
|
||||
pub const STR: ModuleId = ModuleId(0);
|
||||
pub const BOOL: ModuleId = ModuleId(1);
|
||||
pub const INT: ModuleId = ModuleId(2);
|
||||
pub const FLOAT: ModuleId = ModuleId(3);
|
||||
pub const LIST: ModuleId = ModuleId(4);
|
||||
pub const MAP: ModuleId = ModuleId(5);
|
||||
pub const SET: ModuleId = ModuleId(6);
|
||||
pub const NUM: ModuleId = ModuleId(7);
|
||||
// NOTE: the define_builtins! macro adds a bunch of constants to this impl,
|
||||
//
|
||||
// e.g. pub const NUM: ModuleId = …
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn name(self) -> Box<str> {
|
||||
@ -110,6 +232,25 @@ impl ModuleId {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn register_debug_idents(self, ident_ids: &IdentIds) {
|
||||
let mut all = DEBUG_IDENT_IDS_BY_MODULE_ID.lock().expect("Failed to acquire lock for Debug interning into DEBUG_MODULE_ID_NAMES, presumably because a thread panicked.");
|
||||
|
||||
all.insert(self.0, ident_ids.clone());
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub fn register_debug_idents(self, _ident_ids: &IdentIds) {
|
||||
// This is a no-op that should get DCE'd
|
||||
}
|
||||
|
||||
pub fn to_string<'a>(&self, interns: &'a Interns) -> &'a InlinableString {
|
||||
interns
|
||||
.module_ids
|
||||
.get_name(*self)
|
||||
.unwrap_or_else(|| panic!("Could not find ModuleIds for {:?}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ModuleId {
|
||||
@ -137,65 +278,15 @@ impl fmt::Debug for ModuleId {
|
||||
///
|
||||
/// Each module name is stored twice, for faster lookups.
|
||||
/// Since these are interned strings, this shouldn't result in many total allocations in practice.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModuleIds {
|
||||
by_name: MutMap<InlinableString, ModuleId>,
|
||||
/// Each ModuleId is an index into this Vec
|
||||
by_id: Vec<InlinableString>,
|
||||
}
|
||||
|
||||
impl Default for ModuleIds {
|
||||
fn default() -> Self {
|
||||
// +1 because the user will be compiling at least 1 non-builtin module!
|
||||
let capacity = NUM_BUILTIN_MODULES + 1;
|
||||
|
||||
let mut by_name = HashMap::with_capacity_and_hasher(capacity, default_hasher());
|
||||
let mut by_id = Vec::with_capacity(capacity);
|
||||
|
||||
let mut insert_both = |id: ModuleId, name_str: &'static str| {
|
||||
let name: InlinableString = name_str.into();
|
||||
|
||||
// It's very important that these are inserted in the correct order!
|
||||
debug_assert!(id.0 as usize == by_id.len(), "When setting up default ModuleIds, `{:?}` was inserted in the wrong order. It has a hardcoded ID of {:?} but was inserted at index {:?}", name_str, id.0, by_id.len());
|
||||
|
||||
// Make sure we haven't already inserted an entry for this module name.
|
||||
debug_assert!(!by_name.contains_key(&name), "Duplicate default module! We already have an ID for module `{:?}` (namely {:?}), but we tried to insert it again with ID {:?}", name, by_name.get(&name).unwrap(), id.0);
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
Self::insert_debug_name(id, &name);
|
||||
}
|
||||
|
||||
by_name.insert(name.clone(), id);
|
||||
by_id.push(name);
|
||||
};
|
||||
|
||||
// These MUST be inserted in the correct order:
|
||||
//
|
||||
// * The first ModuleId pased in must be 0
|
||||
// * Each subsequent ModuleId must be 1 greater than the previous one
|
||||
//
|
||||
// This is because these will be translated into indices into a Vec,
|
||||
// and each time this gets called, the name gets pushed onto the Vec.
|
||||
// So for these IDs to correspond to the correct names, they must be
|
||||
// inserted in this order!
|
||||
//
|
||||
// Everywherere else this invariant is enforced by the API,
|
||||
// but for these hardcoded modules we have to enforce it manually.
|
||||
insert_both(ModuleId::STR, ModuleName::STR);
|
||||
insert_both(ModuleId::BOOL, ModuleName::BOOL);
|
||||
insert_both(ModuleId::INT, ModuleName::INT);
|
||||
insert_both(ModuleId::FLOAT, ModuleName::FLOAT);
|
||||
insert_both(ModuleId::LIST, ModuleName::LIST);
|
||||
insert_both(ModuleId::MAP, ModuleName::MAP);
|
||||
insert_both(ModuleId::SET, ModuleName::SET);
|
||||
insert_both(ModuleId::NUM, ModuleName::NUM);
|
||||
|
||||
ModuleIds { by_name, by_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleIds {
|
||||
pub fn get_or_insert_id(&mut self, module_name: &InlinableString) -> ModuleId {
|
||||
pub fn get_or_insert(&mut self, module_name: &InlinableString) -> ModuleId {
|
||||
match self.by_name.get(module_name) {
|
||||
Some(id) => *id,
|
||||
None => {
|
||||
@ -236,130 +327,228 @@ impl ModuleIds {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
lazy_static! {
|
||||
/// This is used in Debug builds only, to let us have a Debug instance
|
||||
/// which displays not only the Ident ID, but also the name string which
|
||||
/// corresponds to that ID.
|
||||
pub static ref DEBUG_IDENT_ID_NAMES: std::sync::Mutex<crate::collections::MutMap<u32, Box<str>>> =
|
||||
// This stores a u32 key instead of a ModuleId key so that if there's
|
||||
// a problem with ModuleId's Debug implementation, logging this for diagnostic
|
||||
// purposes won't recursively trigger ModuleId's Debug instance in the course of printing
|
||||
// this out.
|
||||
std::sync::Mutex::new(crate::collections::MutMap::default());
|
||||
}
|
||||
|
||||
/// An ID that is assigned to interned string identifiers within a module.
|
||||
/// By turning these strings into numbers, post-canonicalization processes
|
||||
/// like unification and optimization can run a lot faster.
|
||||
///
|
||||
/// This ID is unique within a given module, not globally - so to turn this back into
|
||||
/// a string, you would need a ModuleId, an IdentId, and a Map<ModuleId, Map<IdentId, String>>.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct IdentId(u32);
|
||||
|
||||
impl fmt::Debug for IdentId {
|
||||
/// In debug builds, whenever we create a new IdentId, we record is name in
|
||||
/// a global interning table so that Debug can look it up later. That table
|
||||
/// needs a global mutex, so we don't do this in release builds. This means
|
||||
/// the Debug impl in release builds only shows the number, not the name (which
|
||||
/// it does not have available, due to having never stored it in the mutexed intern table.)
|
||||
#[cfg(debug_assertions)]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "({:?}, {})", self.name(), self.0)
|
||||
}
|
||||
|
||||
/// In relese builds, all we have access to is the number, so only display that.
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// In Debug builds only, IdentId has a name() method that lets
|
||||
/// you look up its name in a global intern table. This table is
|
||||
/// behind a mutex, so it is neither populated nor available in release builds.
|
||||
impl IdentId {
|
||||
// Num
|
||||
pub const NUM_ABS: IdentId = IdentId(0);
|
||||
pub const NUM_NUM: IdentId = IdentId(1);
|
||||
pub const NUM_INT: IdentId = IdentId(2);
|
||||
pub const NUM_INTEGER: IdentId = IdentId(3);
|
||||
pub const NUM_FLOAT: IdentId = IdentId(4);
|
||||
pub const NUM_FLOATINGPOINT: IdentId = IdentId(5);
|
||||
|
||||
// Bool
|
||||
pub const BOOL_BOOL: IdentId = IdentId(0);
|
||||
pub const BOOL_NOT: IdentId = IdentId(1);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn name(self) -> Box<str> {
|
||||
let names =
|
||||
DEBUG_IDENT_ID_NAMES
|
||||
.lock()
|
||||
.expect("Failed to acquire lock for Debug reading from DEBUG_IDENT_ID_NAMES, presumably because a thread panicked.");
|
||||
|
||||
match names.get(&self.0) {
|
||||
Some(str_ref) => str_ref.clone(),
|
||||
None => {
|
||||
panic!(
|
||||
"Could not find a Debug name for ident ID {} in {:?}",
|
||||
self.0, names,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores a mapping between IdentId and InlinableString.
|
||||
///
|
||||
/// Each module name is stored twice, for faster lookups.
|
||||
/// Since these are interned strings, this shouldn't result in many total allocations in practice.
|
||||
#[derive(Debug, Default)]
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct IdentIds {
|
||||
by_name: MutMap<InlinableString, IdentId>,
|
||||
/// Each ModuleId is an index into this Vec
|
||||
by_ident: MutMap<InlinableString, IdentId>,
|
||||
|
||||
/// Each IdentId is an index into this Vec
|
||||
by_id: Vec<InlinableString>,
|
||||
|
||||
next_generated_name: u32,
|
||||
}
|
||||
|
||||
impl IdentIds {
|
||||
pub fn get_or_insert_id(&mut self, ident_name: &InlinableString) -> IdentId {
|
||||
match self.by_name.get(ident_name) {
|
||||
pub fn idents(&self) -> impl Iterator<Item = (IdentId, &InlinableString)> {
|
||||
self.by_id
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, ident)| (IdentId(index as u32), ident))
|
||||
}
|
||||
|
||||
pub fn add(&mut self, ident_name: InlinableString) -> IdentId {
|
||||
let by_id = &mut self.by_id;
|
||||
let ident_id = IdentId(by_id.len() as u32);
|
||||
|
||||
self.by_ident.insert(ident_name.clone(), ident_id);
|
||||
by_id.push(ident_name);
|
||||
|
||||
ident_id
|
||||
}
|
||||
|
||||
pub fn get_or_insert(&mut self, name: &InlinableString) -> IdentId {
|
||||
match self.by_ident.get(name) {
|
||||
Some(id) => *id,
|
||||
None => {
|
||||
let by_id = &mut self.by_id;
|
||||
let ident_id = IdentId(by_id.len() as u32);
|
||||
|
||||
by_id.push(ident_name.clone());
|
||||
by_id.push(name.clone());
|
||||
|
||||
self.by_name.insert(ident_name.clone(), ident_id);
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
Self::insert_debug_name(ident_id, &ident_name);
|
||||
}
|
||||
self.by_ident.insert(name.clone(), ident_id);
|
||||
|
||||
ident_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
fn insert_debug_name(ident_id: IdentId, ident_name: &InlinableString) {
|
||||
let mut names = DEBUG_IDENT_ID_NAMES.lock().expect("Failed to acquire lock for Debug interning into DEBUG_IDENT_ID_NAMES, presumably because a thread panicked.");
|
||||
/// Generates a unique, new name that's just a strigified integer
|
||||
/// (e.g. "1" or "5"), using an internal counter. Since valid Roc variable
|
||||
/// names cannot begin with a number, this has no chance of colliding
|
||||
/// with actual user-defined variables.
|
||||
///
|
||||
/// This is used, for example, during canonicalization of an Expr::Closure
|
||||
/// to generate a unique symbol to refer to that closure.
|
||||
pub fn gen_unique(&mut self) -> IdentId {
|
||||
// TODO convert this directly from u32 into InlinableString,
|
||||
// without allocating an extra string along the way like this.
|
||||
let ident = self.next_generated_name.to_string().into();
|
||||
|
||||
names.insert(ident_id.0, ident_name.to_string().into());
|
||||
}
|
||||
self.next_generated_name += 1;
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
fn insert_debug_name(_ident_id: IdentId, _ident_name: &InlinableString) {
|
||||
// By design, this is a no-op in release builds!
|
||||
self.add(ident)
|
||||
}
|
||||
|
||||
pub fn get_id(&self, ident_name: &InlinableString) -> Option<&IdentId> {
|
||||
self.by_name.get(ident_name)
|
||||
self.by_ident.get(ident_name)
|
||||
}
|
||||
|
||||
pub fn get_name(&self, id: IdentId) -> Option<&InlinableString> {
|
||||
self.by_id.get(id.0 as usize)
|
||||
}
|
||||
}
|
||||
|
||||
// BUILTINS
|
||||
|
||||
macro_rules! define_builtins {
|
||||
{
|
||||
$(
|
||||
$module_id:literal $module_const:ident: $module_name:literal => {
|
||||
$(
|
||||
$ident_id:literal $ident_const:ident: $ident_name:literal
|
||||
)+
|
||||
}
|
||||
)+
|
||||
num_modules: $total:literal
|
||||
} => {
|
||||
impl IdentIds {
|
||||
pub fn exposed_builtins() -> MutMap<ModuleId, IdentIds> {
|
||||
let mut exposed_idents_by_module = MutMap::default();
|
||||
|
||||
$(
|
||||
debug_assert!(!exposed_idents_by_module.contains_key(&ModuleId($module_id)), "Error setting up Builtins: when setting up module {} {:?} - the module ID {} is already present in the map. Check the map for duplicate module IDs!", $module_id, $module_name, $module_id);
|
||||
|
||||
let ident_ids = {
|
||||
let by_id = vec! [
|
||||
$(
|
||||
$ident_name.into(),
|
||||
)+
|
||||
];
|
||||
let mut by_ident = MutMap::default();
|
||||
|
||||
$(
|
||||
debug_assert!(!by_ident.contains_key($ident_name.clone().into()), "Error setting up Builtins: when inserting {} …: {:?} into module {} …: {:?} - the Ident name {:?} is already present in the map. Check the map for duplicate ident names within the {:?} module!", $ident_id, $ident_name, $module_id, $module_name, $ident_name, $module_name);
|
||||
debug_assert!(by_ident.len() == $ident_id, "Error setting up Builtins: when inserting {} …: {:?} into module {} …: {:?} - this entry was assigned an ID of {}, but based on insertion order, it should have had an ID of {} instead! To fix this, change it from {} …: {:?} to {} …: {:?} instead.", $ident_id, $ident_name, $module_id, $module_name, $ident_id, by_ident.len(), $ident_id, $ident_name, by_ident.len(), $ident_name);
|
||||
|
||||
by_ident.insert($ident_name.into(), IdentId($ident_id));
|
||||
)+
|
||||
|
||||
IdentIds {
|
||||
by_ident,
|
||||
by_id,
|
||||
next_generated_name: 0,
|
||||
}
|
||||
};
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
let module_id = ModuleId($module_id);
|
||||
|
||||
ModuleIds::insert_debug_name(module_id, &$module_name.into());
|
||||
module_id.register_debug_idents(&ident_ids);
|
||||
}
|
||||
|
||||
exposed_idents_by_module.insert(
|
||||
ModuleId($module_id),
|
||||
ident_ids
|
||||
);
|
||||
)+
|
||||
|
||||
debug_assert!(exposed_idents_by_module.len() == $total, "Error setting up Builtins: `total:` is set to the wrong amount. It was set to {} but {} modules were set up.", $total, exposed_idents_by_module.len());
|
||||
|
||||
exposed_idents_by_module
|
||||
}
|
||||
}
|
||||
|
||||
impl ModuleId {
|
||||
$(
|
||||
pub const $module_const: ModuleId = ModuleId($module_id);
|
||||
)+
|
||||
}
|
||||
|
||||
impl Default for ModuleIds {
|
||||
fn default() -> Self {
|
||||
// +1 because the user will be compiling at least 1 non-builtin module!
|
||||
let capacity = $total + 1;
|
||||
|
||||
let mut by_name = HashMap::with_capacity_and_hasher(capacity, default_hasher());
|
||||
let mut by_id = Vec::with_capacity(capacity);
|
||||
|
||||
let mut insert_both = |id: ModuleId, name_str: &'static str| {
|
||||
let name: InlinableString = name_str.into();
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
Self::insert_debug_name(id, &name);
|
||||
}
|
||||
|
||||
by_name.insert(name.clone(), id);
|
||||
by_id.push(name);
|
||||
};
|
||||
|
||||
$(
|
||||
insert_both(ModuleId($module_id), $module_name);
|
||||
)+
|
||||
|
||||
ModuleIds { by_name, by_id }
|
||||
}
|
||||
}
|
||||
|
||||
impl Symbol {
|
||||
$(
|
||||
$(
|
||||
pub const $ident_const: Symbol = Symbol::new(ModuleId($module_id), IdentId($ident_id));
|
||||
)+
|
||||
)+
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_builtins! {
|
||||
0 ATTR: "Attr" => {
|
||||
0 ATTR_ATTR: "Attr" // the Attr.Attr type alias, used in uniqueness types
|
||||
}
|
||||
1 NUM: "Num" => {
|
||||
0 NUM_NUM: "Num" // the Num.Num type alias
|
||||
1 NUM_ABS: "abs"
|
||||
2 NUM_ADD: "add"
|
||||
3 NUM_SUB: "sub"
|
||||
4 NUM_MUL: "mul"
|
||||
}
|
||||
2 INT: "Int" => {
|
||||
0 INT_INT: "Int" // the Int.Int type alias
|
||||
1 INT_INTEGER: "Integer" // Int : Num Integer
|
||||
2 INT_DIV: "div"
|
||||
}
|
||||
3 FLOAT: "Float" => {
|
||||
0 FLOAT_FLOAT: "Float" // the Float.Float type alias
|
||||
1 FLOAT_FLOATINGPOINT: "FloatingPoint" // Float : Num FloatingPoint
|
||||
2 FLOAT_DIV: "div"
|
||||
}
|
||||
4 BOOL: "Bool" => {
|
||||
0 BOOL_BOOL: "Bool" // the Bool.Bool type alias
|
||||
1 BOOL_AND: "and"
|
||||
2 BOOL_OR: "or"
|
||||
}
|
||||
5 STR: "Str" => {
|
||||
0 STR_STR: "Str" // the Str.Str type alias
|
||||
1 STR_ISEMPTY: "isEmpty"
|
||||
}
|
||||
6 LIST: "List" => {
|
||||
0 LIST_LIST: "List" // the List.List type alias
|
||||
1 LIST_ISEMPTY: "isEmpty"
|
||||
}
|
||||
|
||||
num_modules: 7 // Keep this count up to date by hand! (Rust macros can't do arithmetic.)
|
||||
}
|
||||
|
@ -1,9 +1,7 @@
|
||||
use crate::can::pattern::Pattern;
|
||||
use crate::can::{
|
||||
self,
|
||||
ident::{Lowercase, ModuleName},
|
||||
};
|
||||
use crate::can::{self, ident::Lowercase};
|
||||
use crate::collections::MutMap;
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::mono::layout::{Builtin, Layout};
|
||||
use crate::region::Located;
|
||||
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||
@ -132,10 +130,8 @@ fn from_can<'a>(
|
||||
Int(_, val) => Expr::Int(val),
|
||||
Float(_, val) => Expr::Float(val),
|
||||
Str(string) | BlockStr(string) => Expr::Str(env.arena.alloc(string)),
|
||||
Var {
|
||||
resolved_symbol, ..
|
||||
} => Expr::Load(resolved_symbol.into()),
|
||||
LetNonRec(def, ret_expr, _) => {
|
||||
Var(symbol) => Expr::Load(symbol.emit()),
|
||||
LetNonRec(def, ret_expr, _, _) => {
|
||||
let arena = env.arena;
|
||||
let loc_pattern = def.loc_pattern;
|
||||
let loc_expr = def.loc_expr;
|
||||
@ -154,11 +150,11 @@ fn from_can<'a>(
|
||||
//
|
||||
// identity 5
|
||||
//
|
||||
if let Identifier(name) = &loc_pattern.value {
|
||||
if let Identifier(symbol) = &loc_pattern.value {
|
||||
if let Closure(_, _, _, _, _) = &loc_expr.value {
|
||||
// Extract Procs, but discard the resulting Expr::Load.
|
||||
// That Load looks up the pointer, which we won't use here!
|
||||
from_can(env, loc_expr.value, procs, Some(name.clone().into()));
|
||||
from_can(env, loc_expr.value, procs, Some(symbol.emit()));
|
||||
|
||||
// Discard this LetNonRec by replacing it with its ret_expr.
|
||||
return from_can(env, ret_expr.value, procs, None);
|
||||
@ -309,7 +305,7 @@ fn add_closure<'a>(
|
||||
};
|
||||
|
||||
let arg_name: InlinableString = match &loc_arg.value {
|
||||
Pattern::Identifier(name) => name.as_str().into(),
|
||||
Pattern::Identifier(symbol) => symbol.emit(),
|
||||
_ => {
|
||||
panic!("TODO determine arg_name for pattern {:?}", loc_arg.value);
|
||||
}
|
||||
@ -354,7 +350,9 @@ fn store_pattern<'a>(
|
||||
// identity 5
|
||||
//
|
||||
match can_pat {
|
||||
Identifier(name) => stored.push((name.into(), var, from_can(env, can_expr, procs, None))),
|
||||
Identifier(symbol) => {
|
||||
stored.push((symbol.emit(), var, from_can(env, can_expr, procs, None)))
|
||||
}
|
||||
Underscore => {
|
||||
// Since _ is never read, it's safe to reassign it.
|
||||
stored.push(("_".into(), var, from_can(env, can_expr, procs, None)))
|
||||
@ -463,28 +461,13 @@ fn from_can_when<'a>(
|
||||
// TODO we can also Switch on record fields if we're pattern matching
|
||||
// on a record field that's also Switchable.
|
||||
let is_switchable = match &content {
|
||||
Content::Structure(FlatType::Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
}) if module_name.as_str() == ModuleName::NUM
|
||||
&& name.as_str() == crate::types::TYPE_NUM =>
|
||||
{
|
||||
Content::Structure(FlatType::Apply(Symbol::NUM_NUM, args)) => {
|
||||
debug_assert!(args.len() == 1);
|
||||
|
||||
let arg = args.iter().next().unwrap();
|
||||
|
||||
match subs.get_without_compacting(*arg).content {
|
||||
Content::Structure(FlatType::Apply {
|
||||
module_name, name, ..
|
||||
}) if module_name.as_str() == ModuleName::INT => {
|
||||
// This check shouldn't be necessary; the only
|
||||
// type that fits the pattern of Num.Num Int._____
|
||||
// is an Int!
|
||||
debug_assert!(name.as_str() == crate::types::TYPE_INTEGER);
|
||||
|
||||
true
|
||||
}
|
||||
Content::Structure(FlatType::Apply(Symbol::INT_INTEGER, _)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -529,7 +512,7 @@ fn from_can_when<'a>(
|
||||
|
||||
opt_default_branch = Some(arena.alloc(mono_expr));
|
||||
}
|
||||
Shadowed(_loc_ident) => {
|
||||
Shadowed(_, _) => {
|
||||
panic!("TODO runtime error for shadowing in a pattern");
|
||||
}
|
||||
// Example: (5 = 1 + 2) is an unsupported pattern in an assignment; Int patterns aren't allowed in assignments!
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::can::ident::{Lowercase, ModuleName};
|
||||
use crate::can::ident::Lowercase;
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||
use crate::types;
|
||||
use bumpalo::collections::Vec;
|
||||
use bumpalo::Bump;
|
||||
use cranelift_codegen::isa::TargetFrontendConfig;
|
||||
@ -41,7 +41,7 @@ impl<'a> Layout<'a> {
|
||||
panic!("Layout::from_content encountered an unresolved {:?}", var);
|
||||
}
|
||||
Structure(flat_type) => layout_from_flat_type(arena, flat_type, subs),
|
||||
Alias(_, _, _, _) => {
|
||||
Alias(_, _, _) => {
|
||||
panic!("TODO recursively resolve type aliases in Layout::from_content");
|
||||
}
|
||||
Error => Err(()),
|
||||
@ -101,13 +101,8 @@ fn layout_from_flat_type<'a>(
|
||||
use crate::subs::FlatType::*;
|
||||
|
||||
match flat_type {
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
} => {
|
||||
// TODO use SIMD string comparisons to speed these up
|
||||
if module_name.as_str() == ModuleName::NUM && name.as_str() == types::TYPE_NUM {
|
||||
Apply(symbol, args) => {
|
||||
if symbol == Symbol::NUM_NUM {
|
||||
// Num.Num should only ever have 1 argument, e.g. Num.Num Int.Integer
|
||||
debug_assert!(args.len() == 1);
|
||||
|
||||
@ -116,14 +111,7 @@ fn layout_from_flat_type<'a>(
|
||||
|
||||
layout_from_num_content(content)
|
||||
} else {
|
||||
panic!(
|
||||
"TODO layout_from_flat_type for {:?}",
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
}
|
||||
);
|
||||
panic!("TODO layout_from_flat_type for {:?}", Apply(symbol, args));
|
||||
}
|
||||
}
|
||||
Func(args, ret_var) => {
|
||||
@ -189,6 +177,9 @@ fn layout_from_flat_type<'a>(
|
||||
TagUnion(_, _) => {
|
||||
panic!("TODO make Layout for non-empty Tag Union");
|
||||
}
|
||||
RecursiveTagUnion(_, _, _) => {
|
||||
panic!("TODO make Layout for non-empty Tag Union");
|
||||
}
|
||||
EmptyTagUnion => {
|
||||
panic!("TODO make Layout for empty Tag Union");
|
||||
}
|
||||
@ -208,33 +199,20 @@ fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, ()> {
|
||||
var @ FlexVar(_) | var @ RigidVar(_) => {
|
||||
panic!("Layout::from_content encountered an unresolved {:?}", var);
|
||||
}
|
||||
Structure(Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
}) => {
|
||||
// TODO use SIMD string comparisons to speed these up
|
||||
if module_name.as_str() == ModuleName::INT && name.as_str() == types::TYPE_INTEGER {
|
||||
Ok(Layout::Builtin(Builtin::Int64))
|
||||
} else if module_name.as_str() == ModuleName::FLOAT
|
||||
&& name.as_str() == types::TYPE_FLOATINGPOINT
|
||||
{
|
||||
Ok(Layout::Builtin(Builtin::Float64))
|
||||
} else {
|
||||
Structure(Apply(symbol, args)) => match symbol {
|
||||
Symbol::INT_INTEGER => Ok(Layout::Builtin(Builtin::Int64)),
|
||||
Symbol::FLOAT_FLOATINGPOINT => Ok(Layout::Builtin(Builtin::Float64)),
|
||||
_ => {
|
||||
panic!(
|
||||
"Invalid Num.Num type application: {:?}",
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args
|
||||
}
|
||||
Apply(symbol, args)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
Structure(_) => {
|
||||
panic!("Invalid Num.Num type application: {:?}", content);
|
||||
}
|
||||
Alias(_, _, _, _) => {
|
||||
Alias(_, _, _) => {
|
||||
panic!("TODO recursively resolve type aliases in num_from_content");
|
||||
}
|
||||
Error => Err(()),
|
||||
|
114
src/parse/ast.rs
114
src/parse/ast.rs
@ -74,48 +74,10 @@ impl<'a> ExposesEntry<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// An optional qualifier (the `Foo.Bar` in `Foo.Bar.baz`).
|
||||
/// If module_parts is empty, this is unqualified.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct MaybeQualified<'a, Val> {
|
||||
pub module_parts: &'a [&'a str],
|
||||
pub value: Val,
|
||||
}
|
||||
|
||||
impl<'a> MaybeQualified<'a, &'a str> {
|
||||
pub fn len(&self) -> usize {
|
||||
let mut answer = self.value.len();
|
||||
|
||||
for part in self.module_parts {
|
||||
answer += part.len();
|
||||
}
|
||||
|
||||
answer
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> MaybeQualified<'a, &'a [&'a str]> {
|
||||
pub fn len(&self) -> usize {
|
||||
let mut answer = 0;
|
||||
|
||||
for module_part in self.module_parts {
|
||||
answer += module_part.len();
|
||||
}
|
||||
|
||||
for value_part in self.module_parts {
|
||||
answer += value_part.len();
|
||||
}
|
||||
|
||||
answer
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct WhenPattern<'a> {
|
||||
pub pattern: Loc<Pattern<'a>>,
|
||||
pub guard: Option<Loc<Expr<'a>>>,
|
||||
}
|
||||
|
||||
/// A parsed expression. This uses lifetimes extensively for two reasons:
|
||||
@ -154,7 +116,10 @@ pub enum Expr<'a> {
|
||||
},
|
||||
|
||||
// Lookups
|
||||
Var(&'a [&'a str], &'a str),
|
||||
Var {
|
||||
module_name: &'a str,
|
||||
ident: &'a str,
|
||||
},
|
||||
|
||||
// Tags
|
||||
GlobalTag(&'a str),
|
||||
@ -173,7 +138,7 @@ pub enum Expr<'a> {
|
||||
UnaryOp(&'a Loc<Expr<'a>>, Loc<UnaryOp>),
|
||||
|
||||
// Conditionals
|
||||
If(&'a (Loc<Expr<'a>>, Loc<Expr<'a>>, Loc<Expr<'a>>)),
|
||||
If(&'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>, &'a Loc<Expr<'a>>),
|
||||
When(
|
||||
/// The condition
|
||||
&'a Loc<Expr<'a>>,
|
||||
@ -225,7 +190,7 @@ pub enum Def<'a> {
|
||||
// No need to track that relationship in any data structure.
|
||||
Body(&'a Loc<Pattern<'a>>, &'a Loc<Expr<'a>>),
|
||||
|
||||
TypedDef(
|
||||
TypedBody(
|
||||
&'a Loc<Pattern<'a>>,
|
||||
Loc<TypeAnnotation<'a>>,
|
||||
&'a Loc<Expr<'a>>,
|
||||
@ -247,7 +212,7 @@ pub enum TypeAnnotation<'a> {
|
||||
Function(&'a [Loc<TypeAnnotation<'a>>], &'a Loc<TypeAnnotation<'a>>),
|
||||
|
||||
/// Applying a type to some arguments (e.g. Map.Map String Int)
|
||||
Apply(&'a [&'a str], &'a str, &'a [Loc<TypeAnnotation<'a>>]),
|
||||
Apply(&'a str, &'a str, &'a [Loc<TypeAnnotation<'a>>]),
|
||||
|
||||
/// A bound type variable, e.g. `a` in `(a -> a)`
|
||||
BoundVariable(&'a str),
|
||||
@ -376,7 +341,10 @@ pub enum Pattern<'a> {
|
||||
|
||||
// Malformed
|
||||
Malformed(&'a str),
|
||||
QualifiedIdentifier(MaybeQualified<'a, &'a str>),
|
||||
QualifiedIdentifier {
|
||||
module_name: &'a str,
|
||||
ident: &'a str,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
@ -391,29 +359,36 @@ impl<'a> Pattern<'a> {
|
||||
match ident {
|
||||
Ident::GlobalTag(string) => Pattern::GlobalTag(string),
|
||||
Ident::PrivateTag(string) => Pattern::PrivateTag(string),
|
||||
Ident::Access(maybe_qualified) => {
|
||||
if maybe_qualified.value.len() == 1 {
|
||||
Pattern::Identifier(maybe_qualified.value.iter().next().unwrap())
|
||||
} else {
|
||||
let mut buf = String::with_capacity_in(
|
||||
maybe_qualified.module_parts.len() + maybe_qualified.value.len(),
|
||||
arena,
|
||||
);
|
||||
Ident::Access { module_name, parts } => {
|
||||
if parts.len() == 1 {
|
||||
// This is valid iff there is no module.
|
||||
let ident = parts.iter().next().unwrap();
|
||||
|
||||
for part in maybe_qualified.module_parts.iter() {
|
||||
buf.push_str(part);
|
||||
buf.push('.');
|
||||
if module_name.is_empty() {
|
||||
Pattern::Identifier(ident)
|
||||
} else {
|
||||
Pattern::QualifiedIdentifier { module_name, ident }
|
||||
}
|
||||
} else {
|
||||
// This is definitely malformed.
|
||||
let mut buf =
|
||||
String::with_capacity_in(module_name.len() + (2 * parts.len()), arena);
|
||||
let mut any_parts_printed = if module_name.is_empty() {
|
||||
false
|
||||
} else {
|
||||
buf.push_str(module_name);
|
||||
|
||||
let mut iter = maybe_qualified.value.iter().peekable();
|
||||
true
|
||||
};
|
||||
|
||||
while let Some(part) = iter.next() {
|
||||
buf.push_str(part);
|
||||
|
||||
// If there are more fields to come, add a "."
|
||||
if iter.peek().is_some() {
|
||||
for part in parts.iter() {
|
||||
if any_parts_printed {
|
||||
buf.push('.');
|
||||
} else {
|
||||
any_parts_printed = true;
|
||||
}
|
||||
|
||||
buf.push_str(part);
|
||||
}
|
||||
|
||||
Pattern::Malformed(buf.into_bump_str())
|
||||
@ -474,7 +449,16 @@ impl<'a> Pattern<'a> {
|
||||
|
||||
// Malformed
|
||||
(Malformed(x), Malformed(y)) => x == y,
|
||||
(QualifiedIdentifier(x), QualifiedIdentifier(y)) => x == y,
|
||||
(
|
||||
QualifiedIdentifier {
|
||||
module_name: a,
|
||||
ident: x,
|
||||
},
|
||||
QualifiedIdentifier {
|
||||
module_name: b,
|
||||
ident: y,
|
||||
},
|
||||
) => (a == b) && (x == y),
|
||||
|
||||
// Different constructors
|
||||
_ => false,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::collections::arena_join;
|
||||
use crate::parse::ast::{Attempting, MaybeQualified};
|
||||
use crate::parse::ast::Attempting;
|
||||
use crate::parse::parser::{unexpected, unexpected_eof, ParseResult, Parser, State};
|
||||
use bumpalo::collections::string::String;
|
||||
use bumpalo::collections::vec::Vec;
|
||||
@ -16,7 +16,10 @@ pub enum Ident<'a> {
|
||||
/// @Foo or @Bar
|
||||
PrivateTag(&'a str),
|
||||
/// foo or foo.bar or Foo.Bar.baz.qux
|
||||
Access(MaybeQualified<'a, &'a [&'a str]>),
|
||||
Access {
|
||||
module_name: &'a str,
|
||||
parts: &'a [&'a str],
|
||||
},
|
||||
/// .foo
|
||||
AccessorFunction(&'a str),
|
||||
/// .Foo or foo. or something like foo.Bar
|
||||
@ -29,7 +32,20 @@ impl<'a> Ident<'a> {
|
||||
|
||||
match self {
|
||||
GlobalTag(string) | PrivateTag(string) => string.len(),
|
||||
Access(string) => string.len(),
|
||||
Access { module_name, parts } => {
|
||||
let mut len = if module_name.is_empty() {
|
||||
0
|
||||
} else {
|
||||
module_name.len() + 1
|
||||
// +1 for the dot
|
||||
};
|
||||
|
||||
for part in parts.iter() {
|
||||
len += part.len() + 1 // +1 for the dot
|
||||
}
|
||||
|
||||
len - 1
|
||||
}
|
||||
AccessorFunction(string) => string.len(),
|
||||
Malformed(string) => string.len(),
|
||||
}
|
||||
@ -250,10 +266,10 @@ where
|
||||
);
|
||||
} else {
|
||||
// We have multiple noncapitalized parts, so this must be field access.
|
||||
Ident::Access(MaybeQualified {
|
||||
module_parts: capitalized_parts.into_bump_slice(),
|
||||
value: noncapitalized_parts.into_bump_slice(),
|
||||
})
|
||||
Ident::Access {
|
||||
module_name: join_module_parts(arena, capitalized_parts.into_bump_slice()),
|
||||
parts: noncapitalized_parts.into_bump_slice(),
|
||||
}
|
||||
};
|
||||
|
||||
let state = state.advance_without_indenting(chars_parsed)?;
|
||||
@ -380,3 +396,21 @@ pub fn lowercase_ident<'a>() -> impl Parser<'a, &'a str> {
|
||||
pub fn unqualified_ident<'a>() -> impl Parser<'a, &'a str> {
|
||||
global_tag_or_ident(|first_char| first_char.is_alphabetic())
|
||||
}
|
||||
|
||||
pub fn join_module_parts<'a>(arena: &'a Bump, module_parts: &[&str]) -> &'a str {
|
||||
let capacity = module_parts.len() * 3; // Module parts tend to be 3+ characters.
|
||||
let mut buf = String::with_capacity_in(capacity, arena);
|
||||
let mut any_parts_added = false;
|
||||
|
||||
for part in module_parts {
|
||||
if any_parts_added {
|
||||
buf.push('.');
|
||||
} else {
|
||||
any_parts_added = true;
|
||||
}
|
||||
|
||||
buf.push_str(part);
|
||||
}
|
||||
|
||||
buf.into_bump_str()
|
||||
}
|
||||
|
287
src/parse/mod.rs
287
src/parse/mod.rs
@ -10,9 +10,11 @@ pub mod problems;
|
||||
pub mod string_literal;
|
||||
pub mod type_annotation;
|
||||
|
||||
use bumpalo::collections::string::String;
|
||||
|
||||
use crate::operator::{BinOp, CalledVia, UnaryOp};
|
||||
use crate::parse::ast::{
|
||||
AssignedField, Attempting, Def, Expr, MaybeQualified, Pattern, Spaceable, TypeAnnotation,
|
||||
AssignedField, Attempting, CommentOrNewline, Def, Expr, Pattern, Spaceable, TypeAnnotation,
|
||||
};
|
||||
use crate::parse::blankspace::{
|
||||
space0, space0_after, space0_around, space0_before, space1, space1_around, space1_before,
|
||||
@ -20,7 +22,7 @@ use crate::parse::blankspace::{
|
||||
use crate::parse::ident::{global_tag_or_ident, ident, lowercase_ident, Ident};
|
||||
use crate::parse::number_literal::number_literal;
|
||||
use crate::parse::parser::{
|
||||
allocated, char, not, not_followed_by, optional, sep_by1, string, then, unexpected,
|
||||
allocated, char, fail, not, not_followed_by, optional, sep_by1, string, then, unexpected,
|
||||
unexpected_eof, Either, Fail, FailReason, ParseResult, Parser, State,
|
||||
};
|
||||
use crate::region::{Located, Region};
|
||||
@ -34,13 +36,140 @@ pub fn expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||
move |arena, state| parse_expr(min_indent, arena, state)
|
||||
}
|
||||
|
||||
macro_rules! loc_parenthetical_expr {
|
||||
($min_indent:expr, $args_parser:expr) => {
|
||||
then(
|
||||
loc!(and!(
|
||||
between!(
|
||||
char('('),
|
||||
map_with_arena!(
|
||||
space0_around(
|
||||
loc!(move |arena, state| parse_expr($min_indent, arena, state)),
|
||||
$min_indent,
|
||||
),
|
||||
|arena: &'a Bump, loc_expr: Located<Expr<'a>>| {
|
||||
Located {
|
||||
region: loc_expr.region,
|
||||
value: Expr::ParensAround(arena.alloc(loc_expr.value)),
|
||||
}
|
||||
}
|
||||
),
|
||||
char(')')
|
||||
),
|
||||
optional(either!(
|
||||
// There may optionally be function args after the ')'
|
||||
// e.g. ((foo bar) baz)
|
||||
$args_parser,
|
||||
// If there aren't any args, there may be a '=' or ':' after it.
|
||||
//
|
||||
// (It's a syntax error to write e.g. `foo bar =` - so if there
|
||||
// were any args, there is definitely no need to parse '=' or ':'!)
|
||||
//
|
||||
// Also, there may be a '.' for field access (e.g. `(foo).bar`),
|
||||
// but we only want to look for that if there weren't any args,
|
||||
// as if there were any args they'd have consumed it anyway
|
||||
// e.g. in `((foo bar) baz.blah)` the `.blah` will be consumed by the `baz` parser
|
||||
either!(
|
||||
one_or_more!(skip_first!(char('.'), lowercase_ident())),
|
||||
and!(space0($min_indent), equals_with_indent())
|
||||
)
|
||||
))
|
||||
)),
|
||||
move |arena, state, loc_expr_with_extras: Located<(Located<Expr<'a>>, Option<Either<
|
||||
Vec<'a, Located<Expr<'a>>>
|
||||
, Either<Vec<'a, &'a str>, (&'a [CommentOrNewline<'a>], u16)>>>)>
|
||||
|
||||
|
||||
| {
|
||||
// We parse the parenthetical expression *and* the arguments after it
|
||||
// in one region, so that (for example) the region for Apply includes its args.
|
||||
let (loc_expr, opt_extras) = loc_expr_with_extras.value;
|
||||
|
||||
match opt_extras {
|
||||
Some(Either::First(loc_args)) => {
|
||||
let mut allocated_args = Vec::with_capacity_in(loc_args.len(), arena);
|
||||
|
||||
for loc_arg in loc_args {
|
||||
allocated_args.push(&*arena.alloc(loc_arg));
|
||||
}
|
||||
|
||||
Ok((
|
||||
Located {
|
||||
region: loc_expr_with_extras.region,
|
||||
value: Expr::Apply(
|
||||
arena.alloc(loc_expr),
|
||||
allocated_args,
|
||||
CalledVia::Space,
|
||||
),
|
||||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
// '=' after optional spaces
|
||||
Some(Either::Second(Either::Second((spaces_before_equals, equals_indent)))) => {
|
||||
let region = loc_expr.region;
|
||||
|
||||
// Re-parse the Expr as a Pattern.
|
||||
let pattern = match expr_to_pattern(arena, &loc_expr.value) {
|
||||
Ok(valid) => valid,
|
||||
Err(fail) => return Err((fail, state)),
|
||||
};
|
||||
|
||||
// Make sure we don't discard the spaces - might be comments in there!
|
||||
let value = if spaces_before_equals.is_empty() {
|
||||
pattern
|
||||
} else {
|
||||
Pattern::SpaceAfter(arena.alloc(pattern), spaces_before_equals)
|
||||
};
|
||||
|
||||
let loc_first_pattern = Located { region, value };
|
||||
|
||||
// Continue parsing the expression as a Def.
|
||||
let (spaces_after_equals, state) = space0($min_indent).parse(arena, state)?;
|
||||
let (parsed_expr, state) =
|
||||
parse_def_expr($min_indent, equals_indent, arena, state, loc_first_pattern)?;
|
||||
|
||||
let value = if spaces_after_equals.is_empty() {
|
||||
parsed_expr
|
||||
} else {
|
||||
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
|
||||
};
|
||||
|
||||
Ok((Located { value, region }, state))
|
||||
}
|
||||
// '.' and a record field immediately after ')', no optional spaces
|
||||
Some(Either::Second(Either::First(fields))) => {
|
||||
let mut value = loc_expr.value;
|
||||
|
||||
for field in fields {
|
||||
// Wrap the previous answer in the new one, so we end up
|
||||
// with a nested Expr. That way, `foo.bar.baz` gets represented
|
||||
// in the AST as if it had been written (foo.bar).baz all along.
|
||||
value = Expr::Access(arena.alloc(value), field);
|
||||
}
|
||||
|
||||
Ok((
|
||||
Located {
|
||||
region: loc_expr.region,
|
||||
value,
|
||||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
None => Ok((loc_expr, state)),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn loc_parse_expr_body_without_operators<'a>(
|
||||
min_indent: u16,
|
||||
arena: &'a Bump,
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, Located<Expr<'a>>> {
|
||||
one_of!(
|
||||
loc_parenthetical_expr(min_indent),
|
||||
loc_parenthetical_expr!(min_indent, loc_function_args(min_indent)),
|
||||
loc!(string_literal()),
|
||||
loc!(number_literal()),
|
||||
loc!(closure(min_indent)),
|
||||
@ -121,138 +250,15 @@ fn parse_expr<'a>(min_indent: u16, arena: &'a Bump, state: State<'a>) -> ParseRe
|
||||
expr_parser.parse(arena, state)
|
||||
}
|
||||
|
||||
pub fn loc_parenthetical_expr<'a>(min_indent: u16) -> impl Parser<'a, Located<Expr<'a>>> {
|
||||
then(
|
||||
loc!(and!(
|
||||
between!(
|
||||
char('('),
|
||||
map_with_arena!(
|
||||
space0_around(
|
||||
loc!(move |arena, state| parse_expr(min_indent, arena, state)),
|
||||
min_indent,
|
||||
),
|
||||
|arena: &'a Bump, loc_expr: Located<Expr<'a>>| {
|
||||
Located {
|
||||
region: loc_expr.region,
|
||||
value: Expr::ParensAround(arena.alloc(loc_expr.value)),
|
||||
}
|
||||
}
|
||||
),
|
||||
char(')')
|
||||
),
|
||||
optional(either!(
|
||||
// There may optionally be function args after the ')'
|
||||
// e.g. ((foo bar) baz)
|
||||
loc_function_args(min_indent),
|
||||
// If there aren't any args, there may be a '=' or ':' after it.
|
||||
//
|
||||
// (It's a syntax error to write e.g. `foo bar =` - so if there
|
||||
// were any args, there is definitely no need to parse '=' or ':'!)
|
||||
//
|
||||
// Also, there may be a '.' for field access (e.g. `(foo).bar`),
|
||||
// but we only want to look for that if there weren't any args,
|
||||
// as if there were any args they'd have consumed it anyway
|
||||
// e.g. in `((foo bar) baz.blah)` the `.blah` will be consumed by the `baz` parser
|
||||
either!(
|
||||
one_or_more!(skip_first!(char('.'), lowercase_ident())),
|
||||
and!(space0(min_indent), equals_with_indent())
|
||||
)
|
||||
))
|
||||
)),
|
||||
move |arena, state, loc_expr_with_extras| {
|
||||
// We parse the parenthetical expression *and* the arguments after it
|
||||
// in one region, so that (for example) the region for Apply includes its args.
|
||||
let (loc_expr, opt_extras) = loc_expr_with_extras.value;
|
||||
|
||||
match opt_extras {
|
||||
Some(Either::First(loc_args)) => {
|
||||
let mut allocated_args = Vec::with_capacity_in(loc_args.len(), arena);
|
||||
|
||||
for loc_arg in loc_args {
|
||||
allocated_args.push(&*arena.alloc(loc_arg));
|
||||
}
|
||||
|
||||
Ok((
|
||||
Located {
|
||||
region: loc_expr_with_extras.region,
|
||||
value: Expr::Apply(
|
||||
arena.alloc(loc_expr),
|
||||
allocated_args,
|
||||
CalledVia::Space,
|
||||
),
|
||||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
// '=' after optional spaces
|
||||
Some(Either::Second(Either::Second((spaces_before_equals, equals_indent)))) => {
|
||||
let region = loc_expr.region;
|
||||
|
||||
// Re-parse the Expr as a Pattern.
|
||||
let pattern = match expr_to_pattern(arena, &loc_expr.value) {
|
||||
Ok(valid) => valid,
|
||||
Err(fail) => return Err((fail, state)),
|
||||
};
|
||||
|
||||
// Make sure we don't discard the spaces - might be comments in there!
|
||||
let value = if spaces_before_equals.is_empty() {
|
||||
pattern
|
||||
} else {
|
||||
Pattern::SpaceAfter(arena.alloc(pattern), spaces_before_equals)
|
||||
};
|
||||
|
||||
let loc_first_pattern = Located { region, value };
|
||||
|
||||
// Continue parsing the expression as a Def.
|
||||
let (spaces_after_equals, state) = space0(min_indent).parse(arena, state)?;
|
||||
let (parsed_expr, state) =
|
||||
parse_def_expr(min_indent, equals_indent, arena, state, loc_first_pattern)?;
|
||||
|
||||
let value = if spaces_after_equals.is_empty() {
|
||||
parsed_expr
|
||||
} else {
|
||||
Expr::SpaceBefore(arena.alloc(parsed_expr), spaces_after_equals)
|
||||
};
|
||||
|
||||
Ok((Located { value, region }, state))
|
||||
}
|
||||
// '.' and a record field immediately after ')', no optional spaces
|
||||
Some(Either::Second(Either::First(fields))) => {
|
||||
let mut value = loc_expr.value;
|
||||
|
||||
for field in fields {
|
||||
// Wrap the previous answer in the new one, so we end up
|
||||
// with a nested Expr. That way, `foo.bar.baz` gets represented
|
||||
// in the AST as if it had been written (foo.bar).baz all along.
|
||||
value = Expr::Access(arena.alloc(value), field);
|
||||
}
|
||||
|
||||
Ok((
|
||||
Located {
|
||||
region: loc_expr.region,
|
||||
value,
|
||||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
None => Ok((loc_expr, state)),
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/// If the given Expr would parse the same way as a valid Pattern, convert it.
|
||||
/// Example: (foo) could be either an Expr::Var("foo") or Pattern::Identifier("foo")
|
||||
fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>, Fail> {
|
||||
match expr {
|
||||
Expr::Var(module_parts, value) => {
|
||||
if module_parts.is_empty() {
|
||||
Ok(Pattern::Identifier(value))
|
||||
Expr::Var { module_name, ident } => {
|
||||
if module_name.is_empty() {
|
||||
Ok(Pattern::Identifier(ident))
|
||||
} else {
|
||||
Ok(Pattern::QualifiedIdentifier(MaybeQualified {
|
||||
module_parts,
|
||||
value,
|
||||
}))
|
||||
Ok(Pattern::QualifiedIdentifier { module_name, ident })
|
||||
}
|
||||
}
|
||||
Expr::GlobalTag(value) => Ok(Pattern::GlobalTag(value)),
|
||||
@ -325,7 +331,7 @@ fn expr_to_pattern<'a>(arena: &'a Bump, expr: &Expr<'a>) -> Result<Pattern<'a>,
|
||||
| Expr::Closure(_, _)
|
||||
| Expr::BinOp(_)
|
||||
| Expr::Defs(_, _)
|
||||
| Expr::If(_)
|
||||
| Expr::If(_, _, _)
|
||||
| Expr::When(_, _)
|
||||
| Expr::MalformedClosure
|
||||
| Expr::PrecedenceConflict(_, _, _)
|
||||
@ -553,7 +559,7 @@ fn annotation_or_alias<'a>(
|
||||
PrivateTag(_) => {
|
||||
panic!("TODO gracefully handle trying to use a private tag as an annotation.");
|
||||
}
|
||||
QualifiedIdentifier(_) => {
|
||||
QualifiedIdentifier { .. } => {
|
||||
panic!("TODO gracefully handle trying to annotate a qualified identifier, e.g. `Foo.bar : ...`");
|
||||
}
|
||||
IntLiteral(_)
|
||||
@ -762,7 +768,7 @@ fn loc_parse_function_arg<'a>(
|
||||
state: State<'a>,
|
||||
) -> ParseResult<'a, Located<Expr<'a>>> {
|
||||
one_of!(
|
||||
loc_parenthetical_expr(min_indent),
|
||||
loc_parenthetical_expr!(min_indent, fail() /* don't parse args within args! */),
|
||||
loc!(string_literal()),
|
||||
loc!(number_literal()),
|
||||
loc!(closure(min_indent)),
|
||||
@ -1136,7 +1142,11 @@ pub fn if_expr<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||
)
|
||||
),
|
||||
|arena: &'a Bump, (condition, (then_branch, else_branch))| {
|
||||
Expr::If(arena.alloc((condition, then_branch, else_branch)))
|
||||
Expr::If(
|
||||
&*arena.alloc(condition),
|
||||
&*arena.alloc(then_branch),
|
||||
&*arena.alloc(else_branch),
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -1416,13 +1426,13 @@ fn ident_to_expr<'a>(arena: &'a Bump, src: Ident<'a>) -> Expr<'a> {
|
||||
match src {
|
||||
Ident::GlobalTag(string) => Expr::GlobalTag(string),
|
||||
Ident::PrivateTag(string) => Expr::PrivateTag(string),
|
||||
Ident::Access(info) => {
|
||||
let mut iter = info.value.iter();
|
||||
Ident::Access { module_name, parts } => {
|
||||
let mut iter = parts.iter();
|
||||
|
||||
// The first value in the iterator is the variable name,
|
||||
// e.g. `foo` in `foo.bar.baz`
|
||||
let mut answer = match iter.next() {
|
||||
Some(var) => Expr::Var(info.module_parts, var),
|
||||
Some(ident) => Expr::Var { module_name, ident },
|
||||
None => {
|
||||
panic!("Parsed an Ident::Access with no parts");
|
||||
}
|
||||
@ -1607,12 +1617,11 @@ pub fn record_literal<'a>(min_indent: u16) -> impl Parser<'a, Expr<'a>> {
|
||||
|
||||
/// This is mainly for matching tags in closure params, e.g. \@Foo -> ...
|
||||
fn private_tag<'a>() -> impl Parser<'a, &'a str> {
|
||||
// TODO should be refactored so the name is not allocated again.
|
||||
map_with_arena!(
|
||||
skip_first!(char('@'), global_tag()),
|
||||
|arena: &'a Bump, name: &'a str| {
|
||||
use bumpalo::collections::string::String;
|
||||
let mut buf = String::with_capacity_in(1 + name.len(), arena);
|
||||
|
||||
buf.push('@');
|
||||
buf.push_str(name);
|
||||
buf.into_bump_str()
|
||||
|
@ -197,6 +197,18 @@ pub struct Fail {
|
||||
pub reason: FailReason,
|
||||
}
|
||||
|
||||
pub fn fail<'a, T>() -> impl Parser<'a, T> {
|
||||
move |_arena, state: State<'a>| {
|
||||
Err((
|
||||
Fail {
|
||||
attempting: state.attempting,
|
||||
reason: FailReason::ConditionFailed,
|
||||
},
|
||||
state,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Parser<'a, Output> {
|
||||
fn parse(&self, _: &'a Bump, _: State<'a>) -> ParseResult<'a, Output>;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::collections::arena_join;
|
||||
use crate::parse::ast::{AssignedField, Attempting, CommentOrNewline, Tag, TypeAnnotation};
|
||||
use crate::parse::blankspace::{space0_around, space0_before, space1, space1_before};
|
||||
use crate::parse::ident::join_module_parts;
|
||||
use crate::parse::keyword;
|
||||
use crate::parse::parser::{
|
||||
allocated, char, not, optional, string, unexpected, unexpected_eof, Either, ParseResult,
|
||||
@ -349,7 +350,11 @@ fn parse_concrete_type<'a>(
|
||||
}
|
||||
|
||||
let state = state.advance_without_indenting(chars_parsed)?;
|
||||
let answer = TypeAnnotation::Apply(parts.into_bump_slice(), part_buf.into_bump_str(), &[]);
|
||||
let answer = TypeAnnotation::Apply(
|
||||
join_module_parts(arena, parts.into_bump_slice()),
|
||||
part_buf.into_bump_str(),
|
||||
&[],
|
||||
);
|
||||
|
||||
Ok((answer, state))
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
||||
use crate::collections::{MutMap, MutSet};
|
||||
use crate::can::ident::{Lowercase, TagName};
|
||||
use crate::collections::{ImSet, MutMap, MutSet};
|
||||
use crate::module::symbol::{Interns, ModuleId, Symbol};
|
||||
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||
use crate::types::{self, name_type_var};
|
||||
use crate::uniqueness::boolean_algebra::Bool;
|
||||
use crate::types::name_type_var;
|
||||
use crate::uniqueness::boolean_algebra::{Atom, Bool};
|
||||
|
||||
static WILDCARD: &str = "*";
|
||||
static EMPTY_RECORD: &str = "{}";
|
||||
@ -29,6 +30,11 @@ enum Parens {
|
||||
Unnecessary,
|
||||
}
|
||||
|
||||
struct Env<'a> {
|
||||
home: ModuleId,
|
||||
interns: &'a Interns,
|
||||
}
|
||||
|
||||
/// How many times a root variable appeared in Subs.
|
||||
///
|
||||
/// We only care about whether it was a single time or multiple times,
|
||||
@ -56,6 +62,29 @@ fn find_names_needed(
|
||||
use crate::subs::Content::*;
|
||||
use crate::subs::FlatType::*;
|
||||
|
||||
while let Some((recursive, _)) = subs.occurs(variable) {
|
||||
if let Content::Structure(FlatType::TagUnion(tags, ext_var)) = subs.get(recursive).content {
|
||||
let rec_var = subs.fresh_unnamed_flex_var();
|
||||
|
||||
let mut new_tags = MutMap::default();
|
||||
|
||||
for (label, args) in tags {
|
||||
let new_args = args
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|var| if var == recursive { rec_var } else { var })
|
||||
.collect();
|
||||
|
||||
new_tags.insert(label.clone(), new_args);
|
||||
}
|
||||
|
||||
let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, ext_var);
|
||||
subs.set_content(recursive, Content::Structure(flat_type));
|
||||
} else {
|
||||
panic!("unfixable recursive type in pretty_print_types")
|
||||
}
|
||||
}
|
||||
|
||||
match subs.get(variable).content {
|
||||
FlexVar(None) => {
|
||||
let root = subs.get_root_key(variable);
|
||||
@ -79,7 +108,7 @@ fn find_names_needed(
|
||||
FlexVar(Some(_)) => {
|
||||
// This root already has a name. Nothing to do here!
|
||||
}
|
||||
Structure(Apply { args, .. }) => {
|
||||
Structure(Apply(_, args)) => {
|
||||
for var in args {
|
||||
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
||||
}
|
||||
@ -105,6 +134,14 @@ fn find_names_needed(
|
||||
|
||||
find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
|
||||
}
|
||||
Structure(RecursiveTagUnion(rec_var, tags, ext_var)) => {
|
||||
for var in tags.values().flatten() {
|
||||
find_names_needed(*var, subs, roots, root_appearances, names_taken);
|
||||
}
|
||||
|
||||
find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
|
||||
find_names_needed(rec_var, subs, roots, root_appearances, names_taken);
|
||||
}
|
||||
Structure(Boolean(b)) => {
|
||||
for var in b.variables() {
|
||||
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
||||
@ -115,7 +152,7 @@ fn find_names_needed(
|
||||
// We must not accidentally generate names that collide with them!
|
||||
names_taken.insert(name);
|
||||
}
|
||||
Alias(_, _, args, _actual) => {
|
||||
Alias(_, args, _actual) => {
|
||||
// TODO should we also look in the actual variable?
|
||||
for (_, var) in args {
|
||||
find_names_needed(var, subs, roots, root_appearances, names_taken);
|
||||
@ -175,50 +212,64 @@ fn set_root_name(root: Variable, name: &Lowercase, subs: &mut Subs) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content_to_string(content: Content, subs: &mut Subs) -> String {
|
||||
pub fn content_to_string(
|
||||
content: Content,
|
||||
subs: &mut Subs,
|
||||
home: ModuleId,
|
||||
interns: &Interns,
|
||||
) -> String {
|
||||
let mut buf = String::new();
|
||||
let env = Env { home, interns };
|
||||
|
||||
write_content(content, subs, &mut buf, Parens::Unnecessary);
|
||||
write_content(&env, content, subs, &mut buf, Parens::Unnecessary);
|
||||
|
||||
buf
|
||||
}
|
||||
|
||||
fn write_content(content: Content, subs: &mut Subs, buf: &mut String, parens: Parens) {
|
||||
fn write_content(env: &Env, content: Content, subs: &mut Subs, buf: &mut String, parens: Parens) {
|
||||
use crate::subs::Content::*;
|
||||
|
||||
match content {
|
||||
FlexVar(Some(name)) => buf.push_str(name.as_str()),
|
||||
FlexVar(None) => buf.push_str(WILDCARD),
|
||||
RigidVar(name) => buf.push_str(name.as_str()),
|
||||
Structure(flat_type) => write_flat_type(flat_type, subs, buf, parens),
|
||||
Alias(module_name, name, args, _actual) => {
|
||||
buf.push_str(module_name.as_str());
|
||||
buf.push('.');
|
||||
buf.push_str(name.as_str());
|
||||
Structure(flat_type) => write_flat_type(env, flat_type, subs, buf, parens),
|
||||
Alias(symbol, args, _actual) => {
|
||||
let write_parens = parens == Parens::InTypeParam && !args.is_empty();
|
||||
|
||||
if write_parens {
|
||||
buf.push('(')
|
||||
}
|
||||
|
||||
write_symbol(env, symbol, buf);
|
||||
|
||||
for (_, var) in args {
|
||||
buf.push(' ');
|
||||
write_content(subs.get(var).content, subs, buf, parens);
|
||||
write_content(env, subs.get(var).content, subs, buf, parens);
|
||||
}
|
||||
|
||||
if write_parens {
|
||||
buf.push(')')
|
||||
}
|
||||
}
|
||||
Error => buf.push_str("<type mismatch>"),
|
||||
}
|
||||
}
|
||||
|
||||
fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, parens: Parens) {
|
||||
use crate::collections::ImMap;
|
||||
use crate::subs::Content::Structure;
|
||||
fn write_flat_type(
|
||||
env: &Env,
|
||||
flat_type: FlatType,
|
||||
subs: &mut Subs,
|
||||
buf: &mut String,
|
||||
parens: Parens,
|
||||
) {
|
||||
use crate::subs::FlatType::*;
|
||||
use crate::uniqueness::boolean_algebra;
|
||||
|
||||
match flat_type {
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
} => write_apply(module_name, name, args, subs, buf, parens),
|
||||
Apply(symbol, args) => write_apply(env, symbol, args, subs, buf, parens),
|
||||
EmptyRecord => buf.push_str(EMPTY_RECORD),
|
||||
EmptyTagUnion => buf.push_str(EMPTY_TAG_UNION),
|
||||
Func(args, ret) => write_fn(args, ret, subs, buf, parens),
|
||||
Func(args, ret) => write_fn(env, args, ret, subs, buf, parens),
|
||||
Record(fields, ext_var) => {
|
||||
use crate::unify::gather_fields;
|
||||
use crate::unify::RecordStructure;
|
||||
@ -252,7 +303,7 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren
|
||||
buf.push_str(label.as_str());
|
||||
|
||||
buf.push_str(" : ");
|
||||
write_content(subs.get(field_var).content, subs, buf, parens);
|
||||
write_content(env, subs.get(field_var).content, subs, buf, parens);
|
||||
}
|
||||
|
||||
buf.push_str(" }");
|
||||
@ -268,7 +319,7 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren
|
||||
//
|
||||
// e.g. the "*" at the end of `{ x: Int }*`
|
||||
// or the "r" at the end of `{ x: Int }r`
|
||||
write_content(content, subs, buf, parens)
|
||||
write_content(env, content, subs, buf, parens)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -276,6 +327,9 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren
|
||||
if tags.is_empty() {
|
||||
buf.push_str(EMPTY_TAG_UNION)
|
||||
} else {
|
||||
let interns = &env.interns;
|
||||
let home = env.home;
|
||||
|
||||
buf.push_str("[ ");
|
||||
|
||||
// Sort the fields so they always end up in the same order.
|
||||
@ -285,7 +339,11 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren
|
||||
sorted_fields.push((label.clone(), vars));
|
||||
}
|
||||
|
||||
sorted_fields.sort_by(|(a, _), (b, _)| a.as_str().cmp(b.as_str()));
|
||||
sorted_fields.sort_by(|(a, _), (b, _)| {
|
||||
a.clone()
|
||||
.into_string(interns, home)
|
||||
.cmp(&b.clone().into_string(&interns, home))
|
||||
});
|
||||
|
||||
let mut any_written_yet = false;
|
||||
|
||||
@ -295,11 +353,12 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren
|
||||
} else {
|
||||
any_written_yet = true;
|
||||
}
|
||||
buf.push_str(label.as_str());
|
||||
|
||||
buf.push_str(&label.into_string(&interns, home));
|
||||
|
||||
for var in vars {
|
||||
buf.push(' ');
|
||||
write_content(subs.get(var).content, subs, buf, parens);
|
||||
write_content(env, subs.get(var).content, subs, buf, parens);
|
||||
}
|
||||
}
|
||||
|
||||
@ -316,26 +375,66 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren
|
||||
//
|
||||
// e.g. the "*" at the end of `{ x: Int }*`
|
||||
// or the "r" at the end of `{ x: Int }r`
|
||||
write_content(content, subs, buf, parens)
|
||||
write_content(env, content, subs, buf, parens)
|
||||
}
|
||||
}
|
||||
}
|
||||
Boolean(b) => {
|
||||
// push global substitutions into the boolean
|
||||
let mut global_substitution = ImMap::default();
|
||||
|
||||
for v in b.variables() {
|
||||
if let Structure(Boolean(replacement)) = subs.get(v).content {
|
||||
global_substitution.insert(v, replacement);
|
||||
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||
if tags.is_empty() {
|
||||
buf.push_str(EMPTY_TAG_UNION)
|
||||
} else {
|
||||
let interns = &env.interns;
|
||||
let home = env.home;
|
||||
|
||||
buf.push_str("[ ");
|
||||
|
||||
// Sort the fields so they always end up in the same order.
|
||||
let mut sorted_fields = Vec::with_capacity(tags.len());
|
||||
|
||||
for (label, vars) in tags {
|
||||
sorted_fields.push((label.clone(), vars));
|
||||
}
|
||||
|
||||
// If the `ext` contains tags, merge them into the list of tags.
|
||||
// this can occur when inferring mutually recursive tags
|
||||
let ext_content = chase_ext_tag_union(subs, ext_var, &mut sorted_fields);
|
||||
|
||||
sorted_fields.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||
|
||||
let mut any_written_yet = false;
|
||||
|
||||
for (label, vars) in sorted_fields {
|
||||
if any_written_yet {
|
||||
buf.push_str(", ");
|
||||
} else {
|
||||
any_written_yet = true;
|
||||
}
|
||||
buf.push_str(&label.into_string(&interns, home));
|
||||
|
||||
for var in vars {
|
||||
buf.push(' ');
|
||||
write_content(env, subs.get(var).content, subs, buf, parens);
|
||||
}
|
||||
}
|
||||
|
||||
buf.push_str(" ]");
|
||||
|
||||
if let Some(content) = ext_content {
|
||||
// This is an open tag union, so print the variable
|
||||
// right after the ']'
|
||||
//
|
||||
// e.g. the "*" at the end of `{ x: Int }*`
|
||||
// or the "r" at the end of `{ x: Int }r`
|
||||
write_content(env, content, subs, buf, parens)
|
||||
}
|
||||
}
|
||||
|
||||
write_boolean(
|
||||
boolean_algebra::simplify(b.substitute(&global_substitution)),
|
||||
subs,
|
||||
buf,
|
||||
Parens::InTypeParam,
|
||||
);
|
||||
buf.push_str(" as ");
|
||||
write_content(env, subs.get(rec_var).content, subs, buf, parens)
|
||||
}
|
||||
Boolean(b) => {
|
||||
write_boolean(env, b, subs, buf, Parens::InTypeParam);
|
||||
}
|
||||
Erroneous(problem) => {
|
||||
buf.push_str(&format!("<Type Mismatch: {:?}>", problem));
|
||||
@ -343,125 +442,184 @@ fn write_flat_type(flat_type: FlatType, subs: &mut Subs, buf: &mut String, paren
|
||||
}
|
||||
}
|
||||
|
||||
fn write_boolean(boolean: Bool, subs: &mut Subs, buf: &mut String, parens: Parens) {
|
||||
let is_atom = boolean.is_var() || boolean == Bool::Zero || boolean == Bool::One;
|
||||
let write_parens = parens == Parens::InTypeParam && !is_atom;
|
||||
fn chase_ext_tag_union(
|
||||
subs: &mut Subs,
|
||||
var: Variable,
|
||||
fields: &mut Vec<(TagName, Vec<Variable>)>,
|
||||
) -> Option<Content> {
|
||||
use FlatType::*;
|
||||
match subs.get(var).content {
|
||||
Content::Structure(EmptyTagUnion) => None,
|
||||
Content::Structure(TagUnion(tags, ext_var))
|
||||
| Content::Structure(RecursiveTagUnion(_, tags, ext_var)) => {
|
||||
for (label, vars) in tags {
|
||||
fields.push((label.clone(), vars.to_vec()));
|
||||
}
|
||||
|
||||
if write_parens {
|
||||
buf.push_str("(");
|
||||
chase_ext_tag_union(subs, ext_var, fields)
|
||||
}
|
||||
|
||||
content => Some(content),
|
||||
}
|
||||
}
|
||||
|
||||
match boolean {
|
||||
Bool::Variable(var) => write_content(subs.get(var).content, subs, buf, parens),
|
||||
Bool::Or(p, q) => {
|
||||
write_boolean(*p, subs, buf, Parens::InTypeParam);
|
||||
buf.push_str(" | ");
|
||||
write_boolean(*q, subs, buf, Parens::InTypeParam);
|
||||
fn write_boolean(env: &Env, boolean: Bool, subs: &mut Subs, buf: &mut String, parens: Parens) {
|
||||
match boolean.simplify(subs) {
|
||||
Err(atom) => write_boolean_atom(env, atom, subs, buf, parens),
|
||||
Ok(variables) => {
|
||||
let mut buffers_set = ImSet::default();
|
||||
|
||||
for v in variables {
|
||||
let mut inner_buf: String = "".to_string();
|
||||
write_content(env, subs.get(v).content, subs, &mut inner_buf, parens);
|
||||
buffers_set.insert(inner_buf);
|
||||
}
|
||||
|
||||
let mut buffers: Vec<String> = buffers_set.into_iter().collect();
|
||||
buffers.sort();
|
||||
|
||||
let combined = buffers.join(" | ");
|
||||
|
||||
let write_parens = buffers.len() > 1;
|
||||
|
||||
if write_parens {
|
||||
buf.push_str("(");
|
||||
}
|
||||
buf.push_str(&combined);
|
||||
if write_parens {
|
||||
buf.push_str(")");
|
||||
}
|
||||
}
|
||||
Bool::And(p, q) => {
|
||||
write_boolean(*p, subs, buf, Parens::InTypeParam);
|
||||
buf.push_str(" & ");
|
||||
write_boolean(*q, subs, buf, Parens::InTypeParam);
|
||||
}
|
||||
Bool::Not(p) => {
|
||||
buf.push_str("!");
|
||||
write_boolean(*p, subs, buf, Parens::InTypeParam);
|
||||
}
|
||||
Bool::Zero => {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_boolean_atom(env: &Env, atom: Atom, subs: &mut Subs, buf: &mut String, parens: Parens) {
|
||||
match atom {
|
||||
Atom::Variable(var) => write_content(env, subs.get(var).content, subs, buf, parens),
|
||||
Atom::Zero => {
|
||||
buf.push_str("Attr.Shared");
|
||||
}
|
||||
Bool::One => {
|
||||
Atom::One => {
|
||||
buf.push_str("Attr.Unique");
|
||||
}
|
||||
};
|
||||
|
||||
if write_parens {
|
||||
buf.push_str(")");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_apply(
|
||||
module_name: ModuleName,
|
||||
type_name: Uppercase,
|
||||
env: &Env,
|
||||
symbol: Symbol,
|
||||
args: Vec<Variable>,
|
||||
subs: &mut Subs,
|
||||
buf: &mut String,
|
||||
parens: Parens,
|
||||
) {
|
||||
let write_parens = parens == Parens::InTypeParam && !args.is_empty();
|
||||
let module_name = module_name.as_str();
|
||||
let type_name = type_name.as_str();
|
||||
|
||||
// Hardcoded type aliases
|
||||
if module_name == "Str" && type_name == "Str" {
|
||||
buf.push_str("Str");
|
||||
} else if module_name == ModuleName::NUM && type_name == types::TYPE_NUM {
|
||||
let arg = args
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| panic!("Num did not have any type parameters somehow."));
|
||||
let arg_content = subs.get(arg).content;
|
||||
let mut arg_param = String::new();
|
||||
match symbol {
|
||||
Symbol::STR_STR => {
|
||||
buf.push_str("Str");
|
||||
}
|
||||
Symbol::NUM_NUM => {
|
||||
let arg = args
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| panic!("Num did not have any type parameters somehow."));
|
||||
let arg_content = subs.get(arg).content;
|
||||
let mut arg_param = String::new();
|
||||
|
||||
write_content(arg_content, subs, &mut arg_param, Parens::InTypeParam);
|
||||
let mut default_case = |subs, content| {
|
||||
if write_parens {
|
||||
buf.push_str("(");
|
||||
}
|
||||
|
||||
if arg_param == "Int.Integer" {
|
||||
buf.push_str("Int");
|
||||
} else if arg_param == "Float.FloatingPoint" {
|
||||
buf.push_str("Float");
|
||||
} else {
|
||||
write_content(env, content, subs, &mut arg_param, Parens::InTypeParam);
|
||||
buf.push_str("Num ");
|
||||
buf.push_str(&arg_param);
|
||||
|
||||
if write_parens {
|
||||
buf.push_str(")");
|
||||
}
|
||||
};
|
||||
|
||||
match &arg_content {
|
||||
Content::Structure(FlatType::Apply(symbol, nested_args)) => match *symbol {
|
||||
Symbol::INT_INTEGER if nested_args.is_empty() => {
|
||||
buf.push_str("Int");
|
||||
}
|
||||
Symbol::FLOAT_FLOATINGPOINT if nested_args.is_empty() => {
|
||||
buf.push_str("Float");
|
||||
}
|
||||
Symbol::ATTR_ATTR => match nested_args
|
||||
.get(1)
|
||||
.map(|v| subs.get_without_compacting(*v).content)
|
||||
{
|
||||
Some(Content::Structure(FlatType::Apply(
|
||||
double_nested_symbol,
|
||||
double_nested_args,
|
||||
))) => match double_nested_symbol {
|
||||
Symbol::INT_INTEGER if double_nested_args.is_empty() => {
|
||||
buf.push_str("Int");
|
||||
}
|
||||
Symbol::FLOAT_FLOATINGPOINT if double_nested_args.is_empty() => {
|
||||
buf.push_str("Float");
|
||||
}
|
||||
_ => default_case(subs, arg_content),
|
||||
},
|
||||
|
||||
_ => default_case(subs, arg_content),
|
||||
},
|
||||
_ => default_case(subs, arg_content),
|
||||
},
|
||||
_ => default_case(subs, arg_content),
|
||||
}
|
||||
}
|
||||
Symbol::LIST_LIST => {
|
||||
if write_parens {
|
||||
buf.push_str("(");
|
||||
}
|
||||
|
||||
buf.push_str("Num ");
|
||||
buf.push_str(&arg_param);
|
||||
buf.push_str("List ");
|
||||
|
||||
let arg = args
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| panic!("List did not have any type parameters somehow."));
|
||||
let arg_content = subs.get(arg).content;
|
||||
|
||||
write_content(env, arg_content, subs, buf, Parens::InTypeParam);
|
||||
|
||||
if write_parens {
|
||||
buf.push_str(")");
|
||||
}
|
||||
}
|
||||
} else if module_name == "List" && type_name == "List" {
|
||||
if write_parens {
|
||||
buf.push_str("(");
|
||||
}
|
||||
_ => {
|
||||
if write_parens {
|
||||
buf.push_str("(");
|
||||
}
|
||||
|
||||
buf.push_str("List ");
|
||||
write_symbol(env, symbol, buf);
|
||||
|
||||
let arg = args
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap_or_else(|| panic!("List did not have any type parameters somehow."));
|
||||
let arg_content = subs.get(arg).content;
|
||||
for arg in args {
|
||||
buf.push_str(" ");
|
||||
write_content(env, subs.get(arg).content, subs, buf, Parens::InTypeParam);
|
||||
}
|
||||
|
||||
write_content(arg_content, subs, buf, Parens::InTypeParam);
|
||||
|
||||
if write_parens {
|
||||
buf.push_str(")");
|
||||
}
|
||||
} else {
|
||||
if write_parens {
|
||||
buf.push_str("(");
|
||||
}
|
||||
|
||||
if module_name.is_empty() {
|
||||
buf.push_str(&type_name);
|
||||
} else {
|
||||
buf.push_str(&format!("{}.{}", module_name, type_name));
|
||||
}
|
||||
|
||||
for arg in args {
|
||||
buf.push_str(" ");
|
||||
write_content(subs.get(arg).content, subs, buf, Parens::InTypeParam);
|
||||
}
|
||||
|
||||
if write_parens {
|
||||
buf.push_str(")");
|
||||
if write_parens {
|
||||
buf.push_str(")");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_fn(args: Vec<Variable>, ret: Variable, subs: &mut Subs, buf: &mut String, parens: Parens) {
|
||||
fn write_fn(
|
||||
env: &Env,
|
||||
args: Vec<Variable>,
|
||||
ret: Variable,
|
||||
subs: &mut Subs,
|
||||
buf: &mut String,
|
||||
parens: Parens,
|
||||
) {
|
||||
let mut needs_comma = false;
|
||||
let use_parens = parens != Parens::Unnecessary;
|
||||
|
||||
@ -476,13 +634,28 @@ fn write_fn(args: Vec<Variable>, ret: Variable, subs: &mut Subs, buf: &mut Strin
|
||||
needs_comma = true;
|
||||
}
|
||||
|
||||
write_content(subs.get(arg).content, subs, buf, Parens::InFn);
|
||||
write_content(env, subs.get(arg).content, subs, buf, Parens::InFn);
|
||||
}
|
||||
|
||||
buf.push_str(" -> ");
|
||||
write_content(subs.get(ret).content, subs, buf, Parens::InFn);
|
||||
write_content(env, subs.get(ret).content, subs, buf, Parens::InFn);
|
||||
|
||||
if use_parens {
|
||||
buf.push_str(")");
|
||||
}
|
||||
}
|
||||
|
||||
fn write_symbol(env: &Env, symbol: Symbol, buf: &mut String) {
|
||||
let interns = &env.interns;
|
||||
let ident = symbol.ident_string(interns);
|
||||
let module_id = symbol.module_id();
|
||||
|
||||
// Don't qualify the symbol if it's in our home module
|
||||
if module_id == env.home {
|
||||
buf.push_str(ident);
|
||||
} else {
|
||||
buf.push_str(module_id.to_string(&interns));
|
||||
buf.push('.');
|
||||
buf.push_str(ident);
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ impl fmt::Debug for Region {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord)]
|
||||
#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
||||
pub struct Located<T> {
|
||||
pub region: Region,
|
||||
pub value: T,
|
||||
|
975
src/solve.rs
975
src/solve.rs
File diff suppressed because it is too large
Load Diff
336
src/subs.rs
336
src/subs.rs
@ -1,11 +1,12 @@
|
||||
use crate::can::ident::{Lowercase, ModuleName, TagName, Uppercase};
|
||||
use crate::can::ident::{Lowercase, TagName};
|
||||
use crate::collections::{ImMap, ImSet, MutMap, MutSet, SendMap};
|
||||
use crate::ena::unify::{InPlace, UnificationTable, UnifyKey};
|
||||
use crate::types;
|
||||
use crate::types::{name_type_var, ErrorType, Problem, RecordFieldLabel, TypeExt};
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::types::{name_type_var, ErrorType, Problem, TypeExt};
|
||||
use crate::uniqueness::boolean_algebra;
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::iter::{once, Iterator};
|
||||
use std::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct Mark(i32);
|
||||
@ -54,7 +55,7 @@ impl fmt::Debug for Subs {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct VarStore {
|
||||
next: AtomicUsize,
|
||||
next: AtomicU32,
|
||||
}
|
||||
|
||||
impl Default for VarStore {
|
||||
@ -69,7 +70,7 @@ impl VarStore {
|
||||
debug_assert!(next_var.0 >= Variable::FIRST_USER_SPACE_VAR.0);
|
||||
|
||||
VarStore {
|
||||
next: AtomicUsize::new(next_var.0),
|
||||
next: AtomicU32::new(next_var.0),
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +80,7 @@ impl VarStore {
|
||||
// Since the counter starts at 0, this will return 0 on first invocation,
|
||||
// and var_store.into() will return the number of Variables distributed
|
||||
// (in this case, 1).
|
||||
Variable(AtomicUsize::fetch_add(&self.next, 1, Ordering::Relaxed))
|
||||
Variable(AtomicU32::fetch_add(&self.next, 1, Ordering::Relaxed))
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +91,7 @@ impl Into<Variable> for VarStore {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct OptVariable(usize);
|
||||
pub struct OptVariable(u32);
|
||||
|
||||
impl OptVariable {
|
||||
pub const NONE: OptVariable = OptVariable(Variable::NULL.0);
|
||||
@ -136,18 +137,24 @@ impl Into<Option<Variable>> for OptVariable {
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Variable(usize);
|
||||
pub struct Variable(u32);
|
||||
|
||||
impl Variable {
|
||||
// Reserved for indicating the absence of a variable.
|
||||
// This lets us avoid using Option<Variable> for the Descriptor's
|
||||
// copy field, which is a relevant space savings because we make
|
||||
// a *ton* of Descriptors.
|
||||
//
|
||||
// Also relevant: because this has the value 0, Descriptors can 0-initialize
|
||||
// to it in bulk - which is relevant, because Descriptors get initialized in bulk.
|
||||
const NULL: Variable = Variable(0);
|
||||
|
||||
const FIRST_USER_SPACE_VAR: Variable = Variable(1);
|
||||
|
||||
pub fn unsafe_debug_variable(v: usize) -> Self {
|
||||
/// # Safety
|
||||
///
|
||||
/// This should only ever be called from tests!
|
||||
pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self {
|
||||
Variable(v)
|
||||
}
|
||||
}
|
||||
@ -167,11 +174,11 @@ impl fmt::Debug for Variable {
|
||||
impl UnifyKey for Variable {
|
||||
type Value = Descriptor;
|
||||
|
||||
fn index(&self) -> usize {
|
||||
fn index(&self) -> u32 {
|
||||
self.0
|
||||
}
|
||||
|
||||
fn from_index(index: usize) -> Self {
|
||||
fn from_index(index: u32) -> Self {
|
||||
Variable(index)
|
||||
}
|
||||
|
||||
@ -180,6 +187,24 @@ impl UnifyKey for Variable {
|
||||
}
|
||||
}
|
||||
|
||||
/// Used in SolvedType
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct VarId(u32);
|
||||
|
||||
impl VarId {
|
||||
pub fn from_var(var: Variable) -> Self {
|
||||
let Variable(n) = var;
|
||||
|
||||
VarId(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for VarId {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Subs {
|
||||
pub fn new(next_var: Variable) -> Self {
|
||||
let entries = next_var.0;
|
||||
@ -236,6 +261,10 @@ impl Subs {
|
||||
self.utable.get_root_key(key)
|
||||
}
|
||||
|
||||
pub fn get_root_key_without_compacting(&self, key: Variable) -> Variable {
|
||||
self.utable.get_root_key_without_compacting(key)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, key: Variable, r_value: Descriptor) {
|
||||
let l_key = self.utable.get_root_key(key);
|
||||
|
||||
@ -282,14 +311,27 @@ impl Subs {
|
||||
self.utable.unioned(left, right)
|
||||
}
|
||||
|
||||
pub fn redundant(&mut self, var: Variable) -> bool {
|
||||
pub fn redundant(&self, var: Variable) -> bool {
|
||||
self.utable.is_redirect(var)
|
||||
}
|
||||
|
||||
pub fn occurs(&mut self, var: Variable) -> bool {
|
||||
pub fn occurs(&mut self, var: Variable) -> Option<(Variable, Vec<Variable>)> {
|
||||
occurs(self, &ImSet::default(), var)
|
||||
}
|
||||
|
||||
pub fn explicit_substitute(
|
||||
&mut self,
|
||||
from: Variable,
|
||||
to: Variable,
|
||||
in_var: Variable,
|
||||
) -> Variable {
|
||||
let x = self.get_root_key(from);
|
||||
let y = self.get_root_key(to);
|
||||
let z = self.get_root_key(in_var);
|
||||
let mut seen = ImSet::default();
|
||||
explicit_substitute(self, x, y, z, &mut seen)
|
||||
}
|
||||
|
||||
pub fn var_to_error_type(&mut self, var: Variable) -> ErrorType {
|
||||
let names = get_var_names(self, var, ImMap::default());
|
||||
let mut taken = MutSet::default();
|
||||
@ -434,7 +476,7 @@ pub enum Content {
|
||||
/// name given in a user-written annotation
|
||||
RigidVar(Lowercase),
|
||||
Structure(FlatType),
|
||||
Alias(ModuleName, Uppercase, Vec<(Lowercase, Variable)>, Variable),
|
||||
Alias(Symbol, Vec<(Lowercase, Variable)>, Variable),
|
||||
Error,
|
||||
}
|
||||
|
||||
@ -442,9 +484,7 @@ impl Content {
|
||||
#[inline(always)]
|
||||
pub fn is_number(&self) -> bool {
|
||||
match &self {
|
||||
Content::Structure(FlatType::Apply {
|
||||
module_name, name, ..
|
||||
}) => module_name.as_str() == ModuleName::NUM && name.as_str() == types::TYPE_NUM,
|
||||
Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -452,14 +492,11 @@ impl Content {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum FlatType {
|
||||
Apply {
|
||||
module_name: ModuleName,
|
||||
name: Uppercase,
|
||||
args: Vec<Variable>,
|
||||
},
|
||||
Apply(Symbol, Vec<Variable>),
|
||||
Func(Vec<Variable>, Variable),
|
||||
Record(MutMap<RecordFieldLabel, Variable>, Variable),
|
||||
Record(MutMap<Lowercase, Variable>, Variable),
|
||||
TagUnion(MutMap<TagName, Vec<Variable>>, Variable),
|
||||
RecursiveTagUnion(Variable, MutMap<TagName, Vec<Variable>>, Variable),
|
||||
Erroneous(Problem),
|
||||
EmptyRecord,
|
||||
EmptyTagUnion,
|
||||
@ -474,53 +511,174 @@ pub enum Builtin {
|
||||
EmptyRecord,
|
||||
}
|
||||
|
||||
fn occurs(subs: &mut Subs, seen: &ImSet<Variable>, var: Variable) -> bool {
|
||||
fn occurs(
|
||||
subs: &mut Subs,
|
||||
seen: &ImSet<Variable>,
|
||||
input_var: Variable,
|
||||
) -> Option<(Variable, Vec<Variable>)> {
|
||||
use self::Content::*;
|
||||
use self::FlatType::*;
|
||||
|
||||
if seen.contains(&var) {
|
||||
true
|
||||
let root_var = subs.get_root_key(input_var);
|
||||
|
||||
if seen.contains(&root_var) {
|
||||
Some((root_var, vec![]))
|
||||
} else {
|
||||
match subs.get(var).content {
|
||||
FlexVar(_) | RigidVar(_) | Error => false,
|
||||
match subs.get(root_var).content {
|
||||
FlexVar(_) | RigidVar(_) | Error => None,
|
||||
|
||||
Structure(flat_type) => {
|
||||
let mut new_seen = seen.clone();
|
||||
|
||||
new_seen.insert(var);
|
||||
new_seen.insert(root_var);
|
||||
|
||||
match flat_type {
|
||||
Apply { args, .. } => args.into_iter().any(|var| occurs(subs, &new_seen, var)),
|
||||
Apply(_, args) => short_circuit(subs, root_var, &new_seen, args.iter()),
|
||||
Func(arg_vars, ret_var) => {
|
||||
occurs(subs, &new_seen, ret_var)
|
||||
|| arg_vars.into_iter().any(|var| occurs(subs, &new_seen, var))
|
||||
let it = once(&ret_var).chain(arg_vars.iter());
|
||||
short_circuit(subs, root_var, &new_seen, it)
|
||||
}
|
||||
Record(vars_by_field, ext_var) => {
|
||||
occurs(subs, &new_seen, ext_var)
|
||||
|| vars_by_field
|
||||
.into_iter()
|
||||
.any(|(_, var)| occurs(subs, &new_seen, var))
|
||||
let it = once(&ext_var).chain(vars_by_field.values());
|
||||
short_circuit(subs, root_var, &new_seen, it)
|
||||
}
|
||||
TagUnion(tags, ext_var) => {
|
||||
occurs(subs, &new_seen, ext_var)
|
||||
|| tags
|
||||
.values()
|
||||
.any(|vars| vars.iter().any(|var| occurs(subs, &new_seen, *var)))
|
||||
let it = once(&ext_var).chain(tags.values().flatten());
|
||||
short_circuit(subs, root_var, &new_seen, it)
|
||||
}
|
||||
Boolean(b) => b
|
||||
.variables()
|
||||
.iter()
|
||||
.any(|var| occurs(subs, &new_seen, *var)),
|
||||
EmptyRecord | EmptyTagUnion | Erroneous(_) => false,
|
||||
RecursiveTagUnion(_rec_var, tags, ext_var) => {
|
||||
// TODO rec_var is excluded here, verify that this is correct
|
||||
let it = once(&ext_var).chain(tags.values().flatten());
|
||||
short_circuit(subs, root_var, &new_seen, it)
|
||||
}
|
||||
Boolean(b) => {
|
||||
for var in b.variables().iter() {
|
||||
if let Some((v, mut vec)) = occurs(subs, &new_seen, *var) {
|
||||
vec.push(root_var);
|
||||
return Some((v, vec));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
EmptyRecord | EmptyTagUnion | Erroneous(_) => None,
|
||||
}
|
||||
}
|
||||
Alias(_, _, args, _) => {
|
||||
Alias(_, args, _) => {
|
||||
let mut new_seen = seen.clone();
|
||||
new_seen.insert(root_var);
|
||||
let it = args.iter().map(|(_, var)| var);
|
||||
short_circuit(subs, root_var, &new_seen, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_seen.insert(var);
|
||||
fn short_circuit<'a, T>(
|
||||
subs: &mut Subs,
|
||||
root_key: Variable,
|
||||
seen: &ImSet<Variable>,
|
||||
iter: T,
|
||||
) -> Option<(Variable, Vec<Variable>)>
|
||||
where
|
||||
T: Iterator<Item = &'a Variable>,
|
||||
{
|
||||
for var in iter {
|
||||
if let Some((v, mut vec)) = occurs(subs, seen, *var) {
|
||||
vec.push(root_key);
|
||||
return Some((v, vec));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
args.into_iter()
|
||||
.any(|(_, var)| occurs(subs, &new_seen, var))
|
||||
fn explicit_substitute(
|
||||
subs: &mut Subs,
|
||||
from: Variable,
|
||||
to: Variable,
|
||||
in_var: Variable,
|
||||
seen: &mut ImSet<Variable>,
|
||||
) -> Variable {
|
||||
use self::Content::*;
|
||||
use self::FlatType::*;
|
||||
let in_root = subs.get_root_key(in_var);
|
||||
if seen.contains(&in_root) {
|
||||
in_var
|
||||
} else {
|
||||
seen.insert(in_root);
|
||||
|
||||
if subs.get_root_key(from) == subs.get_root_key(in_var) {
|
||||
to
|
||||
} else {
|
||||
match subs.get(in_var).content {
|
||||
FlexVar(_) | RigidVar(_) | Error => in_var,
|
||||
|
||||
Structure(flat_type) => {
|
||||
match flat_type {
|
||||
Apply(symbol, args) => {
|
||||
let new_args = args
|
||||
.iter()
|
||||
.map(|var| explicit_substitute(subs, from, to, *var, seen))
|
||||
.collect();
|
||||
|
||||
subs.set_content(in_var, Structure(Apply(symbol, new_args)));
|
||||
}
|
||||
Func(arg_vars, ret_var) => {
|
||||
let new_arg_vars = arg_vars
|
||||
.iter()
|
||||
.map(|var| explicit_substitute(subs, from, to, *var, seen))
|
||||
.collect();
|
||||
let new_ret_var = explicit_substitute(subs, from, to, ret_var, seen);
|
||||
|
||||
subs.set_content(in_var, Structure(Func(new_arg_vars, new_ret_var)));
|
||||
}
|
||||
TagUnion(mut tags, ext_var) => {
|
||||
let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen);
|
||||
for (_, variables) in tags.iter_mut() {
|
||||
for var in variables.iter_mut() {
|
||||
*var = explicit_substitute(subs, from, to, *var, seen);
|
||||
}
|
||||
}
|
||||
subs.set_content(in_var, Structure(TagUnion(tags, new_ext_var)));
|
||||
}
|
||||
RecursiveTagUnion(rec_var, mut tags, ext_var) => {
|
||||
// NOTE rec_var is not substituted, verify that this is correct!
|
||||
let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen);
|
||||
for (_, variables) in tags.iter_mut() {
|
||||
for var in variables.iter_mut() {
|
||||
*var = explicit_substitute(subs, from, to, *var, seen);
|
||||
}
|
||||
}
|
||||
subs.set_content(
|
||||
in_var,
|
||||
Structure(RecursiveTagUnion(rec_var, tags, new_ext_var)),
|
||||
);
|
||||
}
|
||||
Record(mut vars_by_field, ext_var) => {
|
||||
let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen);
|
||||
|
||||
for (_, var) in vars_by_field.iter_mut() {
|
||||
*var = explicit_substitute(subs, from, to, *var, seen);
|
||||
}
|
||||
subs.set_content(in_var, Structure(Record(vars_by_field, new_ext_var)));
|
||||
}
|
||||
|
||||
// NOTE assume we never substitute into a Boolean
|
||||
EmptyRecord | EmptyTagUnion | Boolean(_) | Erroneous(_) => {}
|
||||
}
|
||||
|
||||
in_var
|
||||
}
|
||||
Alias(symbol, mut args, actual) => {
|
||||
for (_, var) in args.iter_mut() {
|
||||
*var = explicit_substitute(subs, from, to, *var, seen);
|
||||
}
|
||||
|
||||
let new_actual = explicit_substitute(subs, from, to, actual, seen);
|
||||
|
||||
subs.set_content(in_var, Alias(symbol, args, new_actual));
|
||||
|
||||
in_var
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -547,11 +705,11 @@ fn get_var_names(
|
||||
|
||||
RigidVar(name) => add_name(subs, 0, name, var, RigidVar, taken_names),
|
||||
|
||||
Alias(_, _, args, _) => args.into_iter().fold(taken_names, |answer, (_, arg_var)| {
|
||||
Alias(_, args, _) => args.into_iter().fold(taken_names, |answer, (_, arg_var)| {
|
||||
get_var_names(subs, arg_var, answer)
|
||||
}),
|
||||
Structure(flat_type) => match flat_type {
|
||||
FlatType::Apply { args, .. } => {
|
||||
FlatType::Apply(_, args) => {
|
||||
args.into_iter().fold(taken_names, |answer, arg_var| {
|
||||
get_var_names(subs, arg_var, answer)
|
||||
})
|
||||
@ -589,6 +747,19 @@ fn get_var_names(
|
||||
|
||||
taken_names
|
||||
}
|
||||
|
||||
FlatType::RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||
let taken_names = get_var_names(subs, ext_var, taken_names);
|
||||
let mut taken_names = get_var_names(subs, rec_var, taken_names);
|
||||
|
||||
for vars in tags.values() {
|
||||
for arg_var in vars {
|
||||
taken_names = get_var_names(subs, *arg_var, taken_names)
|
||||
}
|
||||
}
|
||||
|
||||
taken_names
|
||||
}
|
||||
FlatType::Boolean(b) => b
|
||||
.variables()
|
||||
.into_iter()
|
||||
@ -694,14 +865,14 @@ fn content_to_err_type(
|
||||
|
||||
RigidVar(name) => ErrorType::RigidVar(name),
|
||||
|
||||
Alias(module_name, name, args, aliased_to) => {
|
||||
Alias(symbol, args, aliased_to) => {
|
||||
let err_args = args
|
||||
.into_iter()
|
||||
.map(|(name, var)| (name, var_to_err_type(subs, state, var)))
|
||||
.collect();
|
||||
let err_type = var_to_err_type(subs, state, aliased_to);
|
||||
|
||||
ErrorType::Alias(module_name, name, err_args, Box::new(err_type))
|
||||
ErrorType::Alias(symbol, err_args, Box::new(err_type))
|
||||
}
|
||||
|
||||
Error => ErrorType::Error,
|
||||
@ -712,17 +883,13 @@ fn flat_type_to_err_type(subs: &mut Subs, state: &mut NameState, flat_type: Flat
|
||||
use self::FlatType::*;
|
||||
|
||||
match flat_type {
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
} => {
|
||||
Apply(symbol, args) => {
|
||||
let arg_types = args
|
||||
.into_iter()
|
||||
.map(|var| var_to_err_type(subs, state, var))
|
||||
.collect();
|
||||
|
||||
ErrorType::Type(module_name, name, arg_types)
|
||||
ErrorType::Type(symbol, arg_types)
|
||||
}
|
||||
|
||||
Func(arg_vars, ret_var) => {
|
||||
@ -794,6 +961,41 @@ fn flat_type_to_err_type(subs: &mut Subs, state: &mut NameState, flat_type: Flat
|
||||
}
|
||||
}
|
||||
|
||||
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||
let mut err_tags = SendMap::default();
|
||||
|
||||
for (tag, vars) in tags.into_iter() {
|
||||
let mut err_vars = Vec::with_capacity(vars.len());
|
||||
|
||||
for var in vars {
|
||||
err_vars.push(var_to_err_type(subs, state, var));
|
||||
}
|
||||
|
||||
err_tags.insert(tag, err_vars);
|
||||
}
|
||||
|
||||
match var_to_err_type(subs, state, ext_var).unwrap_alias() {
|
||||
ErrorType::RecursiveTagUnion(rec_var, sub_tags, sub_ext) => {
|
||||
ErrorType::RecursiveTagUnion(rec_var, sub_tags.union(err_tags), sub_ext)
|
||||
}
|
||||
|
||||
ErrorType::TagUnion(sub_tags, sub_ext) => {
|
||||
ErrorType::RecursiveTagUnion(rec_var, sub_tags.union(err_tags), sub_ext)
|
||||
}
|
||||
|
||||
ErrorType::FlexVar(var) => {
|
||||
ErrorType::RecursiveTagUnion(rec_var, err_tags, TypeExt::FlexOpen(var))
|
||||
}
|
||||
|
||||
ErrorType::RigidVar(var) => {
|
||||
ErrorType::RecursiveTagUnion(rec_var, err_tags, TypeExt::RigidOpen(var))
|
||||
}
|
||||
|
||||
other =>
|
||||
panic!("Tried to convert a recursive tag union extension to an error, but the tag union extension had the ErrorType of {:?}", other)
|
||||
}
|
||||
}
|
||||
|
||||
Boolean(b) => ErrorType::Boolean(b),
|
||||
|
||||
Erroneous(_) => ErrorType::Error,
|
||||
@ -816,7 +1018,7 @@ fn restore_content(subs: &mut Subs, content: &Content) {
|
||||
FlexVar(_) | RigidVar(_) | Error => (),
|
||||
|
||||
Structure(flat_type) => match flat_type {
|
||||
Apply { args, .. } => {
|
||||
Apply(_, args) => {
|
||||
for &var in args {
|
||||
subs.restore(var);
|
||||
}
|
||||
@ -847,6 +1049,16 @@ fn restore_content(subs: &mut Subs, content: &Content) {
|
||||
|
||||
subs.restore(*ext_var);
|
||||
}
|
||||
|
||||
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||
for var in tags.values().flatten() {
|
||||
subs.restore(*var);
|
||||
}
|
||||
|
||||
subs.restore(*ext_var);
|
||||
subs.restore(*rec_var);
|
||||
}
|
||||
|
||||
Boolean(b) => {
|
||||
for var in b.variables() {
|
||||
subs.restore(var);
|
||||
@ -854,7 +1066,7 @@ fn restore_content(subs: &mut Subs, content: &Content) {
|
||||
}
|
||||
Erroneous(_) => (),
|
||||
},
|
||||
Alias(_, _, args, var) => {
|
||||
Alias(_, args, var) => {
|
||||
for (_, arg_var) in args {
|
||||
subs.restore(*arg_var);
|
||||
}
|
||||
|
304
src/types/mod.rs
304
src/types/mod.rs
@ -1,15 +1,15 @@
|
||||
pub mod builtins;
|
||||
|
||||
use crate::can::ident::{Lowercase, ModuleName, TagName, Uppercase};
|
||||
use crate::can::ident::{Ident, Lowercase, TagName};
|
||||
use crate::can::pattern::Pattern;
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::{ImSet, MutSet, SendMap};
|
||||
use crate::ident::Ident;
|
||||
use crate::collections::{ImMap, ImSet, MutSet, SendMap};
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::operator::{ArgSide, BinOp};
|
||||
use crate::region::Located;
|
||||
use crate::region::Region;
|
||||
use crate::subs::Variable;
|
||||
use crate::uniqueness::boolean_algebra;
|
||||
use inlinable_string::InlinableString;
|
||||
use std::fmt;
|
||||
|
||||
pub const TYPE_NUM: &str = "Num";
|
||||
@ -22,26 +22,19 @@ pub enum Type {
|
||||
EmptyTagUnion,
|
||||
/// A function. The types of its arguments, then the type of its return value.
|
||||
Function(Vec<Type>, Box<Type>),
|
||||
Record(SendMap<RecordFieldLabel, Type>, Box<Type>),
|
||||
Record(SendMap<Lowercase, Type>, Box<Type>),
|
||||
TagUnion(Vec<(TagName, Vec<Type>)>, Box<Type>),
|
||||
Alias(ModuleName, Uppercase, Vec<(Lowercase, Variable)>, Box<Type>),
|
||||
Alias(Symbol, Vec<(Lowercase, Type)>, Box<Type>),
|
||||
RecursiveTagUnion(Variable, Vec<(TagName, Vec<Type>)>, Box<Type>),
|
||||
/// Applying a type to some arguments (e.g. Map.Map String Int)
|
||||
Apply {
|
||||
module_name: ModuleName,
|
||||
name: Uppercase,
|
||||
args: Vec<Type>,
|
||||
},
|
||||
Apply(Symbol, Vec<Type>),
|
||||
/// Boolean type used in uniqueness inference
|
||||
Boolean(boolean_algebra::Bool),
|
||||
Variable(Variable),
|
||||
/// recursive variants, e.g. [ Cons a r, Nil ] as r
|
||||
As(Box<Type>, Variable),
|
||||
/// A type error, which will code gen to a runtime error
|
||||
Erroneous(Problem),
|
||||
}
|
||||
|
||||
pub type RecordFieldLabel = Lowercase;
|
||||
|
||||
impl fmt::Debug for Type {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
@ -66,20 +59,8 @@ impl fmt::Debug for Type {
|
||||
}
|
||||
Type::Variable(var) => write!(f, "<{:?}>", var),
|
||||
|
||||
Type::Apply {
|
||||
module_name,
|
||||
name,
|
||||
args,
|
||||
} => {
|
||||
let module_name = module_name.as_str();
|
||||
|
||||
write!(f, "(")?;
|
||||
|
||||
if !module_name.is_empty() {
|
||||
write!(f, "{}.", module_name)?;
|
||||
}
|
||||
|
||||
write!(f, "{}", name)?;
|
||||
Type::Apply(symbol, args) => {
|
||||
write!(f, "({:?}", symbol)?;
|
||||
|
||||
for arg in args {
|
||||
write!(f, " {:?}", arg)?;
|
||||
@ -94,16 +75,17 @@ impl fmt::Debug for Type {
|
||||
|
||||
write!(f, ")")
|
||||
}
|
||||
Type::Alias(module_name, name, args, _actual) => {
|
||||
write!(f, "Alias {}.{}", module_name.as_str(), name,)?;
|
||||
Type::Alias(symbol, args, actual) => {
|
||||
write!(f, "Alias {:?}", symbol)?;
|
||||
|
||||
for (_, arg) in args {
|
||||
write!(f, " {:?}", arg)?;
|
||||
}
|
||||
|
||||
write!(f, "but really {:?}", actual)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Type::As(inner, variable) => write!(f, "({:?} as {:?})", inner, variable),
|
||||
Type::Record(fields, ext) => {
|
||||
write!(f, "{{")?;
|
||||
|
||||
@ -160,7 +142,7 @@ impl fmt::Debug for Type {
|
||||
any_written_yet = true;
|
||||
}
|
||||
|
||||
write!(f, "{}", label.as_str())?;
|
||||
write!(f, "{:?}", label)?;
|
||||
|
||||
for argument in arguments {
|
||||
write!(f, " {:?}", argument)?;
|
||||
@ -188,6 +170,51 @@ impl fmt::Debug for Type {
|
||||
}
|
||||
}
|
||||
}
|
||||
Type::RecursiveTagUnion(rec, tags, ext) => {
|
||||
write!(f, "[")?;
|
||||
|
||||
if !tags.is_empty() {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
|
||||
let mut any_written_yet = false;
|
||||
|
||||
for (label, arguments) in tags {
|
||||
if any_written_yet {
|
||||
write!(f, ", ")?;
|
||||
} else {
|
||||
any_written_yet = true;
|
||||
}
|
||||
|
||||
write!(f, "{:?}", label)?;
|
||||
|
||||
for argument in arguments {
|
||||
write!(f, " {:?}", argument)?;
|
||||
}
|
||||
}
|
||||
|
||||
if !tags.is_empty() {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
|
||||
write!(f, "]")?;
|
||||
|
||||
match *ext.clone() {
|
||||
Type::EmptyTagUnion => {
|
||||
// This is a closed variant. We're done!
|
||||
}
|
||||
other => {
|
||||
// This is an open tag union, so print the variable
|
||||
// right after the ']'
|
||||
//
|
||||
// e.g. the "*" at the end of `[ Foo ]*`
|
||||
// or the "r" at the end of `[ DivByZero ]r`
|
||||
other.fmt(f)?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(f, " as <{:?}>", rec)
|
||||
}
|
||||
Type::Boolean(b) => write!(f, "{:?}", b),
|
||||
}
|
||||
}
|
||||
@ -195,48 +222,28 @@ impl fmt::Debug for Type {
|
||||
|
||||
impl Type {
|
||||
pub fn num(args: Vec<Type>) -> Self {
|
||||
Type::Apply {
|
||||
module_name: ModuleName::NUM.into(),
|
||||
name: TYPE_NUM.into(),
|
||||
args,
|
||||
}
|
||||
Type::Apply(Symbol::NUM_NUM, args)
|
||||
}
|
||||
|
||||
pub fn float() -> Self {
|
||||
let floating_point = Type::Apply {
|
||||
module_name: ModuleName::FLOAT.into(),
|
||||
name: "FloatingPoint".into(),
|
||||
args: Vec::new(),
|
||||
};
|
||||
let floating_point = Type::Apply(Symbol::FLOAT_FLOATINGPOINT, Vec::new());
|
||||
|
||||
Type::num(vec![floating_point])
|
||||
}
|
||||
|
||||
pub fn int() -> Self {
|
||||
let integer = Type::Apply {
|
||||
module_name: ModuleName::INT.into(),
|
||||
name: "Integer".into(),
|
||||
args: Vec::new(),
|
||||
};
|
||||
let integer = Type::Apply(Symbol::INT_INTEGER, Vec::new());
|
||||
|
||||
Type::num(vec![integer])
|
||||
}
|
||||
|
||||
pub fn string() -> Self {
|
||||
Type::Apply {
|
||||
module_name: ModuleName::STR.into(),
|
||||
name: "Str".into(),
|
||||
args: Vec::new(),
|
||||
}
|
||||
Type::Apply(Symbol::STR_STR, Vec::new())
|
||||
}
|
||||
|
||||
/// This is needed to constrain `if` conditionals
|
||||
pub fn bool() -> Self {
|
||||
Type::Apply {
|
||||
module_name: ModuleName::BOOL.into(),
|
||||
name: "Bool".into(),
|
||||
args: Vec::new(),
|
||||
}
|
||||
Type::Apply(Symbol::BOOL_BOOL, Vec::new())
|
||||
}
|
||||
|
||||
pub fn arity(&self) -> usize {
|
||||
@ -254,58 +261,139 @@ impl Type {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn substitute(&mut self, substitutions: &ImMap<Variable, Type>) {
|
||||
use Type::*;
|
||||
|
||||
match self {
|
||||
Variable(v) => {
|
||||
if let Some(replacement) = substitutions.get(&v) {
|
||||
*self = replacement.clone();
|
||||
}
|
||||
}
|
||||
Function(args, ret) => {
|
||||
for arg in args {
|
||||
arg.substitute(substitutions);
|
||||
}
|
||||
ret.substitute(substitutions);
|
||||
}
|
||||
TagUnion(tags, ext) => {
|
||||
for (_, args) in tags {
|
||||
for x in args {
|
||||
x.substitute(substitutions);
|
||||
}
|
||||
}
|
||||
ext.substitute(substitutions);
|
||||
}
|
||||
RecursiveTagUnion(_, tags, ext) => {
|
||||
for (_, args) in tags {
|
||||
for x in args {
|
||||
x.substitute(substitutions);
|
||||
}
|
||||
}
|
||||
ext.substitute(substitutions);
|
||||
}
|
||||
Record(fields, ext) => {
|
||||
for x in fields.iter_mut() {
|
||||
x.substitute(substitutions);
|
||||
}
|
||||
ext.substitute(substitutions);
|
||||
}
|
||||
Alias(_, zipped, actual_type) => {
|
||||
for (_, value) in zipped.iter_mut() {
|
||||
value.substitute(substitutions);
|
||||
}
|
||||
actual_type.substitute(substitutions);
|
||||
}
|
||||
Apply(_, args) => {
|
||||
for arg in args {
|
||||
arg.substitute(substitutions);
|
||||
}
|
||||
}
|
||||
EmptyRec | EmptyTagUnion | Erroneous(_) | Boolean(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
// swap Apply with Alias if their module and tag match
|
||||
pub fn substitute_alias(
|
||||
&mut self,
|
||||
rep_module_name: &ModuleName,
|
||||
rep_name: &Uppercase,
|
||||
actual: &Type,
|
||||
) {
|
||||
pub fn substitute_alias(&mut self, rep_symbol: Symbol, actual: &Type) {
|
||||
use Type::*;
|
||||
|
||||
match self {
|
||||
Function(args, ret) => {
|
||||
for arg in args {
|
||||
arg.substitute_alias(rep_module_name, rep_name, actual);
|
||||
arg.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
ret.substitute_alias(rep_module_name, rep_name, actual);
|
||||
ret.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
TagUnion(tags, ext) => {
|
||||
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
|
||||
for (_, args) in tags {
|
||||
for x in args {
|
||||
x.substitute_alias(rep_module_name, rep_name, actual);
|
||||
x.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
}
|
||||
ext.substitute_alias(rep_module_name, rep_name, actual);
|
||||
ext.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
Record(fields, ext) => {
|
||||
for x in fields.iter_mut() {
|
||||
x.substitute_alias(rep_module_name, rep_name, actual);
|
||||
x.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
ext.substitute_alias(rep_module_name, rep_name, actual);
|
||||
ext.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
Alias(_, _, _, actual_type) => {
|
||||
actual_type.substitute_alias(rep_module_name, rep_name, actual);
|
||||
Alias(_, _, actual_type) => {
|
||||
actual_type.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
Apply {
|
||||
module_name, name, ..
|
||||
} if module_name == rep_module_name && name == rep_name => {
|
||||
Apply(symbol, _) if *symbol == rep_symbol => {
|
||||
*self = actual.clone();
|
||||
|
||||
if let Apply { args, .. } = self {
|
||||
if let Apply(_, args) = self {
|
||||
for arg in args {
|
||||
arg.substitute_alias(rep_module_name, rep_name, actual);
|
||||
arg.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
Apply { args, .. } => {
|
||||
Apply(_, args) => {
|
||||
for arg in args {
|
||||
arg.substitute_alias(rep_module_name, rep_name, actual);
|
||||
arg.substitute_alias(rep_symbol, actual);
|
||||
}
|
||||
}
|
||||
EmptyRec | EmptyTagUnion | Erroneous(_) | Variable(_) | Boolean(_) => {}
|
||||
}
|
||||
}
|
||||
|
||||
As(_, _) => unreachable!("As should be canonicalized away at this point"),
|
||||
pub fn contains_symbol(&self, rep_symbol: Symbol) -> bool {
|
||||
use Type::*;
|
||||
|
||||
match self {
|
||||
Function(args, ret) => {
|
||||
ret.contains_symbol(rep_symbol)
|
||||
|| args.iter().any(|arg| arg.contains_symbol(rep_symbol))
|
||||
}
|
||||
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
|
||||
ext.contains_symbol(rep_symbol)
|
||||
|| tags
|
||||
.iter()
|
||||
.map(|v| v.1.iter())
|
||||
.flatten()
|
||||
.any(|arg| arg.contains_symbol(rep_symbol))
|
||||
}
|
||||
|
||||
Record(fields, ext) => {
|
||||
ext.contains_symbol(rep_symbol)
|
||||
|| fields.values().any(|arg| arg.contains_symbol(rep_symbol))
|
||||
}
|
||||
Alias(alias_symbol, _, actual_type) => {
|
||||
alias_symbol == &rep_symbol || actual_type.contains_symbol(rep_symbol)
|
||||
}
|
||||
Apply(symbol, _) if *symbol == rep_symbol => true,
|
||||
Apply(_, args) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)),
|
||||
EmptyRec | EmptyTagUnion | Erroneous(_) | Variable(_) | Boolean(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// a shallow dealias, continue until the first constructor is not an alias.
|
||||
pub fn shallow_dealias(&self) -> &Self {
|
||||
match self {
|
||||
Type::Alias(_, _, actual) => actual.shallow_dealias(),
|
||||
_ => self,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -343,18 +431,27 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
|
||||
}
|
||||
variables_help(ext, accum);
|
||||
}
|
||||
Alias(_, _, args, actual) => {
|
||||
RecursiveTagUnion(rec, tags, ext) => {
|
||||
for (_, args) in tags {
|
||||
for x in args {
|
||||
variables_help(x, accum);
|
||||
}
|
||||
}
|
||||
variables_help(ext, accum);
|
||||
|
||||
// just check that this is actually a recursive type
|
||||
debug_assert!(accum.contains(rec));
|
||||
|
||||
// this rec var doesn't need to be in flex_vars or rigid_vars
|
||||
accum.remove(rec);
|
||||
}
|
||||
Alias(_, args, actual) => {
|
||||
for (_, x) in args {
|
||||
accum.insert(*x);
|
||||
variables_help(x, accum);
|
||||
}
|
||||
variables_help(actual, accum);
|
||||
}
|
||||
As(inner, variable) => {
|
||||
variables_help(inner, accum);
|
||||
// the `inner` type should contain the bound variable
|
||||
debug_assert!(accum.contains(variable));
|
||||
}
|
||||
Apply { args, .. } => {
|
||||
Apply(_, args) => {
|
||||
for x in args {
|
||||
variables_help(x, accum);
|
||||
}
|
||||
@ -442,13 +539,13 @@ pub enum Reason {
|
||||
IfBranch { index: usize },
|
||||
ElemInList,
|
||||
RecordUpdateValue(Lowercase),
|
||||
RecordUpdateKeys(Ident, SendMap<Lowercase, Type>),
|
||||
RecordUpdateKeys(Symbol, SendMap<Lowercase, Type>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum Constraint {
|
||||
Eq(Type, Expected<Type>, Region),
|
||||
Lookup(ModuleName, Symbol, Expected<Type>, Region),
|
||||
Lookup(Symbol, Expected<Type>, Region),
|
||||
Pattern(Region, PatternCategory, Type, PExpected<Type>),
|
||||
True, // Used for things that always unify, e.g. blanks and runtime errors
|
||||
SaveTheEnvironment,
|
||||
@ -474,15 +571,26 @@ pub struct LetConstraint {
|
||||
pub rigid_vars: Vec<Variable>,
|
||||
pub flex_vars: Vec<Variable>,
|
||||
pub def_types: SendMap<Symbol, Located<Type>>,
|
||||
pub def_aliases: SendMap<Symbol, Alias>,
|
||||
pub defs_constraint: Constraint,
|
||||
pub ret_constraint: Constraint,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Alias {
|
||||
pub region: Region,
|
||||
pub vars: Vec<Located<(Lowercase, Variable)>>,
|
||||
pub typ: Type,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Problem {
|
||||
CanonicalizationProblem,
|
||||
Mismatch(Mismatch, ErrorType, ErrorType),
|
||||
CircularType(Symbol, ErrorType, Region),
|
||||
UnrecognizedIdent(InlinableString),
|
||||
Shadowed(Region, Located<Ident>),
|
||||
InvalidModule,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
@ -497,18 +605,14 @@ pub enum Mismatch {
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum ErrorType {
|
||||
Infinite,
|
||||
Type(ModuleName, Uppercase, Vec<ErrorType>),
|
||||
Type(Symbol, Vec<ErrorType>),
|
||||
FlexVar(Lowercase),
|
||||
RigidVar(Lowercase),
|
||||
Record(SendMap<RecordFieldLabel, ErrorType>, TypeExt),
|
||||
Record(SendMap<Lowercase, ErrorType>, TypeExt),
|
||||
TagUnion(SendMap<TagName, Vec<ErrorType>>, TypeExt),
|
||||
RecursiveTagUnion(Variable, SendMap<TagName, Vec<ErrorType>>, TypeExt),
|
||||
Function(Vec<ErrorType>, Box<ErrorType>),
|
||||
Alias(
|
||||
ModuleName,
|
||||
Uppercase,
|
||||
Vec<(Lowercase, ErrorType)>,
|
||||
Box<ErrorType>,
|
||||
),
|
||||
Alias(Symbol, Vec<(Lowercase, ErrorType)>, Box<ErrorType>),
|
||||
Boolean(boolean_algebra::Bool),
|
||||
Error,
|
||||
}
|
||||
@ -516,7 +620,7 @@ pub enum ErrorType {
|
||||
impl ErrorType {
|
||||
pub fn unwrap_alias(self) -> ErrorType {
|
||||
match self {
|
||||
ErrorType::Alias(_, _, _, real) => real.unwrap_alias(),
|
||||
ErrorType::Alias(_, _, real) => real.unwrap_alias(),
|
||||
real => real,
|
||||
}
|
||||
}
|
||||
|
293
src/unify.rs
293
src/unify.rs
@ -1,12 +1,26 @@
|
||||
use crate::can::ident::{Lowercase, ModuleName, TagName, Uppercase};
|
||||
use crate::collections::{relative_complement, union, ImMap, MutMap};
|
||||
use crate::can::ident::{Lowercase, TagName};
|
||||
use crate::collections::{relative_complement, union, MutMap, SendSet};
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::subs::Content::{self, *};
|
||||
use crate::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable};
|
||||
use crate::types::RecordFieldLabel;
|
||||
use crate::types::{Mismatch, Problem};
|
||||
use crate::uniqueness::boolean_algebra;
|
||||
use crate::uniqueness::boolean_algebra::{Atom, Bool};
|
||||
use std::hash::Hash;
|
||||
|
||||
macro_rules! mismatch {
|
||||
() => {{
|
||||
if cfg!(debug_assertions) {
|
||||
println!(
|
||||
"Mismatch in {} Line {} Column {}",
|
||||
file!(),
|
||||
line!(),
|
||||
column!()
|
||||
);
|
||||
}
|
||||
vec![Mismatch::TypeMismatch]
|
||||
}};
|
||||
}
|
||||
|
||||
type Pool = Vec<Variable>;
|
||||
|
||||
struct Context {
|
||||
@ -17,7 +31,7 @@ struct Context {
|
||||
}
|
||||
|
||||
pub struct RecordStructure {
|
||||
pub fields: MutMap<RecordFieldLabel, Variable>,
|
||||
pub fields: MutMap<Lowercase, Variable>,
|
||||
pub ext: Variable,
|
||||
}
|
||||
|
||||
@ -69,15 +83,14 @@ pub fn unify_pool(subs: &mut Subs, pool: &mut Pool, var1: Variable, var2: Variab
|
||||
}
|
||||
|
||||
fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
||||
// println!( "{:?} {:?} ~ {:?} {:?}", ctx.first, ctx.first_desc.content, ctx.second, ctx.second_desc.content);
|
||||
match &ctx.first_desc.content {
|
||||
FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, &ctx.second_desc.content),
|
||||
FlexVar(opt_name) => unify_flex(subs, pool, &ctx, opt_name, &ctx.second_desc.content),
|
||||
RigidVar(name) => unify_rigid(subs, &ctx, name, &ctx.second_desc.content),
|
||||
Structure(flat_type) => {
|
||||
unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content)
|
||||
}
|
||||
Alias(home, name, args, real_var) => {
|
||||
unify_alias(subs, pool, &ctx, home, name, args, *real_var)
|
||||
}
|
||||
Alias(symbol, args, real_var) => unify_alias(subs, pool, &ctx, *symbol, args, *real_var),
|
||||
Error => {
|
||||
// Error propagates. Whatever we're comparing it to doesn't matter!
|
||||
merge(subs, &ctx, Error)
|
||||
@ -90,8 +103,7 @@ fn unify_alias(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
ctx: &Context,
|
||||
home: &ModuleName,
|
||||
name: &Uppercase,
|
||||
symbol: Symbol,
|
||||
args: &[(Lowercase, Variable)],
|
||||
real_var: Variable,
|
||||
) -> Outcome {
|
||||
@ -100,23 +112,22 @@ fn unify_alias(
|
||||
match other_content {
|
||||
FlexVar(_) => {
|
||||
// Alias wins
|
||||
merge(
|
||||
subs,
|
||||
&ctx,
|
||||
Alias(home.clone(), name.clone(), args.to_owned(), real_var),
|
||||
)
|
||||
merge(subs, &ctx, Alias(symbol, args.to_owned(), real_var))
|
||||
}
|
||||
RigidVar(_) => unify_pool(subs, pool, real_var, ctx.second),
|
||||
Alias(other_home, other_name, other_args, other_real_var) => {
|
||||
if name == other_name && home == other_home {
|
||||
Alias(other_symbol, other_args, other_real_var) => {
|
||||
if symbol == *other_symbol {
|
||||
if args.len() == other_args.len() {
|
||||
let mut problems = Vec::new();
|
||||
for ((_, l_var), (_, r_var)) in args.iter().zip(other_args.iter()) {
|
||||
unify_pool(subs, pool, *l_var, *r_var);
|
||||
problems.extend(unify_pool(subs, pool, *l_var, *r_var));
|
||||
}
|
||||
|
||||
merge(subs, &ctx, other_content.clone())
|
||||
problems.extend(merge(subs, &ctx, other_content.clone()));
|
||||
|
||||
problems
|
||||
} else {
|
||||
mismatch()
|
||||
mismatch!()
|
||||
}
|
||||
} else {
|
||||
unify_pool(subs, pool, real_var, *other_real_var)
|
||||
@ -137,18 +148,27 @@ fn unify_structure(
|
||||
) -> Outcome {
|
||||
match other {
|
||||
FlexVar(_) => {
|
||||
// If the other is flex, Structure wins!
|
||||
merge(subs, ctx, Structure(flat_type.clone()))
|
||||
// TODO special-case boolean here
|
||||
match flat_type {
|
||||
FlatType::Boolean(Bool(Atom::Variable(var), _rest)) => {
|
||||
unify_pool(subs, pool, *var, ctx.second)
|
||||
}
|
||||
_ => {
|
||||
// If the other is flex, Structure wins!
|
||||
merge(subs, ctx, Structure(flat_type.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
RigidVar(_) => {
|
||||
// Type mismatch! Rigid can only unify with flex.
|
||||
mismatch()
|
||||
mismatch!()
|
||||
}
|
||||
|
||||
Structure(ref other_flat_type) => {
|
||||
// Unify the two flat types
|
||||
unify_flat_type(subs, pool, ctx, flat_type, other_flat_type)
|
||||
}
|
||||
Alias(_, _, _, real_var) => unify_pool(subs, pool, ctx.first, *real_var),
|
||||
Alias(_, _, real_var) => unify_pool(subs, pool, ctx.first, *real_var),
|
||||
Error => merge(subs, ctx, Error),
|
||||
}
|
||||
}
|
||||
@ -250,8 +270,8 @@ fn unify_shared_fields(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
ctx: &Context,
|
||||
shared_fields: MutMap<RecordFieldLabel, (Variable, Variable)>,
|
||||
other_fields: MutMap<RecordFieldLabel, Variable>,
|
||||
shared_fields: MutMap<Lowercase, (Variable, Variable)>,
|
||||
other_fields: MutMap<Lowercase, Variable>,
|
||||
ext: Variable,
|
||||
) -> Outcome {
|
||||
let mut matching_fields = MutMap::default();
|
||||
@ -270,7 +290,7 @@ fn unify_shared_fields(
|
||||
|
||||
merge(subs, ctx, Structure(flat_type))
|
||||
} else {
|
||||
mismatch()
|
||||
mismatch!()
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,6 +300,7 @@ fn unify_tag_union(
|
||||
ctx: &Context,
|
||||
rec1: TagUnionStructure,
|
||||
rec2: TagUnionStructure,
|
||||
recursion: (Option<Variable>, Option<Variable>),
|
||||
) -> Outcome {
|
||||
let tags1 = rec1.tags;
|
||||
let tags2 = rec2.tags;
|
||||
@ -288,11 +309,27 @@ fn unify_tag_union(
|
||||
let unique_tags1 = relative_complement(&tags1, &tags2);
|
||||
let unique_tags2 = relative_complement(&tags2, &tags1);
|
||||
|
||||
let recursion_var = match recursion {
|
||||
(None, None) => None,
|
||||
(Some(v), None) | (None, Some(v)) => Some(v),
|
||||
(Some(v1), Some(v2)) => {
|
||||
unify_pool(subs, pool, v1, v2);
|
||||
Some(v1)
|
||||
}
|
||||
};
|
||||
|
||||
if unique_tags1.is_empty() {
|
||||
if unique_tags2.is_empty() {
|
||||
let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext);
|
||||
let mut tag_problems =
|
||||
unify_shared_tags(subs, pool, ctx, shared_tags, MutMap::default(), rec1.ext);
|
||||
let mut tag_problems = unify_shared_tags(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
shared_tags,
|
||||
MutMap::default(),
|
||||
rec1.ext,
|
||||
recursion_var,
|
||||
);
|
||||
|
||||
tag_problems.extend(ext_problems);
|
||||
|
||||
@ -301,8 +338,15 @@ fn unify_tag_union(
|
||||
let flat_type = FlatType::TagUnion(unique_tags2, rec2.ext);
|
||||
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
||||
let ext_problems = unify_pool(subs, pool, rec1.ext, sub_record);
|
||||
let mut tag_problems =
|
||||
unify_shared_tags(subs, pool, ctx, shared_tags, MutMap::default(), sub_record);
|
||||
let mut tag_problems = unify_shared_tags(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
shared_tags,
|
||||
MutMap::default(),
|
||||
sub_record,
|
||||
recursion_var,
|
||||
);
|
||||
|
||||
tag_problems.extend(ext_problems);
|
||||
|
||||
@ -312,8 +356,15 @@ fn unify_tag_union(
|
||||
let flat_type = FlatType::TagUnion(unique_tags1, rec1.ext);
|
||||
let sub_record = fresh(subs, pool, ctx, Structure(flat_type));
|
||||
let ext_problems = unify_pool(subs, pool, sub_record, rec2.ext);
|
||||
let mut tag_problems =
|
||||
unify_shared_tags(subs, pool, ctx, shared_tags, MutMap::default(), sub_record);
|
||||
let mut tag_problems = unify_shared_tags(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
shared_tags,
|
||||
MutMap::default(),
|
||||
sub_record,
|
||||
recursion_var,
|
||||
);
|
||||
|
||||
tag_problems.extend(ext_problems);
|
||||
|
||||
@ -331,7 +382,8 @@ fn unify_tag_union(
|
||||
let rec1_problems = unify_pool(subs, pool, rec1.ext, sub2);
|
||||
let rec2_problems = unify_pool(subs, pool, sub1, rec2.ext);
|
||||
|
||||
let mut tag_problems = unify_shared_tags(subs, pool, ctx, shared_tags, other_tags, ext);
|
||||
let mut tag_problems =
|
||||
unify_shared_tags(subs, pool, ctx, shared_tags, other_tags, ext, recursion_var);
|
||||
|
||||
tag_problems.reserve(rec1_problems.len() + rec2_problems.len());
|
||||
tag_problems.extend(rec1_problems);
|
||||
@ -348,6 +400,7 @@ fn unify_shared_tags(
|
||||
shared_tags: MutMap<TagName, (Vec<Variable>, Vec<Variable>)>,
|
||||
other_tags: MutMap<TagName, Vec<Variable>>,
|
||||
ext: Variable,
|
||||
recursion_var: Option<Variable>,
|
||||
) -> Outcome {
|
||||
let mut matching_tags = MutMap::default();
|
||||
let num_shared_tags = shared_tags.len();
|
||||
@ -373,11 +426,15 @@ fn unify_shared_tags(
|
||||
}
|
||||
|
||||
if num_shared_tags == matching_tags.len() {
|
||||
let flat_type = FlatType::TagUnion(union(matching_tags, &other_tags), ext);
|
||||
let flat_type = if let Some(rec) = recursion_var {
|
||||
FlatType::RecursiveTagUnion(rec, union(matching_tags, &other_tags), ext)
|
||||
} else {
|
||||
FlatType::TagUnion(union(matching_tags, &other_tags), ext)
|
||||
};
|
||||
|
||||
merge(subs, ctx, Structure(flat_type))
|
||||
} else {
|
||||
mismatch()
|
||||
mismatch!()
|
||||
}
|
||||
}
|
||||
|
||||
@ -423,61 +480,65 @@ fn unify_flat_type(
|
||||
let union1 = gather_tags(subs, tags1.clone(), *ext1);
|
||||
let union2 = gather_tags(subs, tags2.clone(), *ext2);
|
||||
|
||||
unify_tag_union(subs, pool, ctx, union1, union2)
|
||||
unify_tag_union(subs, pool, ctx, union1, union2, (None, None))
|
||||
}
|
||||
|
||||
(Boolean(b1_raw), Boolean(b2_raw)) => {
|
||||
// first propagate Subs substitiutions into the booleans
|
||||
let mut global_substitution = ImMap::default();
|
||||
(TagUnion(tags1, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => {
|
||||
let union1 = gather_tags(subs, tags1.clone(), *ext1);
|
||||
let union2 = gather_tags(subs, tags2.clone(), *ext2);
|
||||
|
||||
for v in b1_raw
|
||||
.variables()
|
||||
unify_tag_union(
|
||||
subs,
|
||||
pool,
|
||||
ctx,
|
||||
union1,
|
||||
union2,
|
||||
(None, Some(*recursion_var)),
|
||||
)
|
||||
}
|
||||
|
||||
(RecursiveTagUnion(rec1, tags1, ext1), RecursiveTagUnion(rec2, tags2, ext2)) => {
|
||||
let union1 = gather_tags(subs, tags1.clone(), *ext1);
|
||||
let union2 = gather_tags(subs, tags2.clone(), *ext2);
|
||||
|
||||
unify_tag_union(subs, pool, ctx, union1, union2, (Some(*rec1), Some(*rec2)))
|
||||
}
|
||||
|
||||
(Boolean(Bool(free1, rest1)), Boolean(Bool(free2, rest2))) => {
|
||||
// unify the free variables
|
||||
let (new_free, mut free_var_problems) = unify_free_atoms(subs, pool, *free1, *free2);
|
||||
|
||||
let combined_rest: SendSet<Atom> = rest1
|
||||
.clone()
|
||||
.into_iter()
|
||||
.chain(b2_raw.variables().into_iter())
|
||||
{
|
||||
if let Structure(Boolean(replacement)) = subs.get(v).content {
|
||||
global_substitution.insert(v, replacement);
|
||||
.chain(rest2.clone().into_iter())
|
||||
.collect::<SendSet<Atom>>();
|
||||
|
||||
let mut combined = if let Err(false) = chase_atom(subs, new_free) {
|
||||
// if the container is shared, all elements must be shared too
|
||||
for atom in combined_rest {
|
||||
let (_, atom_problems) = unify_free_atoms(subs, pool, atom, Atom::Zero);
|
||||
free_var_problems.extend(atom_problems);
|
||||
}
|
||||
}
|
||||
|
||||
let b1 = boolean_algebra::simplify(b1_raw.substitute(&global_substitution));
|
||||
let b2 = boolean_algebra::simplify(b2_raw.substitute(&global_substitution));
|
||||
|
||||
if let Some(substitution) = boolean_algebra::try_unify(b1, b2) {
|
||||
for (var, replacement) in substitution {
|
||||
subs.set_content(var, Structure(FlatType::Boolean(replacement)));
|
||||
}
|
||||
|
||||
vec![]
|
||||
Bool(Atom::Zero, SendSet::default())
|
||||
} else {
|
||||
mismatch()
|
||||
}
|
||||
Bool(new_free, combined_rest)
|
||||
};
|
||||
|
||||
combined.apply_subs(subs);
|
||||
|
||||
// force first and second to equal this new variable
|
||||
let content = Content::Structure(FlatType::Boolean(combined));
|
||||
merge(subs, ctx, content);
|
||||
|
||||
free_var_problems
|
||||
}
|
||||
|
||||
(
|
||||
Apply {
|
||||
module_name: l_module_name,
|
||||
name: l_type_name,
|
||||
args: l_args,
|
||||
},
|
||||
Apply {
|
||||
module_name: r_module_name,
|
||||
name: r_type_name,
|
||||
args: r_args,
|
||||
},
|
||||
) if l_module_name == r_module_name && l_type_name == r_type_name => {
|
||||
(Apply(l_symbol, l_args), Apply(r_symbol, r_args)) if l_symbol == r_symbol => {
|
||||
let problems = unify_zip(subs, pool, l_args.iter(), r_args.iter());
|
||||
|
||||
if problems.is_empty() {
|
||||
merge(
|
||||
subs,
|
||||
ctx,
|
||||
Structure(Apply {
|
||||
module_name: (*r_module_name).clone(),
|
||||
name: (*r_type_name).clone(),
|
||||
args: (*r_args).clone(),
|
||||
}),
|
||||
)
|
||||
merge(subs, ctx, Structure(Apply(*r_symbol, (*r_args).clone())))
|
||||
} else {
|
||||
problems
|
||||
}
|
||||
@ -496,7 +557,46 @@ fn unify_flat_type(
|
||||
problems
|
||||
}
|
||||
}
|
||||
_ => mismatch(),
|
||||
(_other1, _other2) => {
|
||||
// Can't unify other1 and other2
|
||||
// dbg!(&_other1, &_other2);
|
||||
mismatch!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn chase_atom(subs: &mut Subs, atom: Atom) -> Result<Variable, bool> {
|
||||
match atom {
|
||||
Atom::Zero => Err(false),
|
||||
Atom::One => Err(true),
|
||||
Atom::Variable(var) => match subs.get(var).content {
|
||||
Content::Structure(FlatType::Boolean(Bool(first, rest))) => {
|
||||
debug_assert!(rest.is_empty());
|
||||
chase_atom(subs, first)
|
||||
}
|
||||
_ => Ok(var),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_free_atoms(subs: &mut Subs, pool: &mut Pool, b1: Atom, b2: Atom) -> (Atom, Vec<Mismatch>) {
|
||||
match (b1, b2) {
|
||||
(Atom::Variable(v1), Atom::Variable(v2)) => {
|
||||
(Atom::Variable(v1), unify_pool(subs, pool, v1, v2))
|
||||
}
|
||||
(Atom::Variable(var), other) | (other, Atom::Variable(var)) => {
|
||||
subs.set_content(
|
||||
var,
|
||||
Content::Structure(FlatType::Boolean(Bool(other, SendSet::default()))),
|
||||
);
|
||||
|
||||
(other, vec![])
|
||||
}
|
||||
(Atom::Zero, Atom::Zero) => (Atom::Zero, vec![]),
|
||||
(Atom::One, Atom::One) => (Atom::One, vec![]),
|
||||
_ => unreachable!(
|
||||
"invalid boolean unification. Because we never infer One, this should never happen!"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@ -506,7 +606,9 @@ where
|
||||
{
|
||||
let mut problems = Vec::new();
|
||||
|
||||
for (&l_var, &r_var) in left_iter.zip(right_iter) {
|
||||
let it = left_iter.zip(right_iter);
|
||||
|
||||
for (&l_var, &r_var) in it {
|
||||
problems.extend(unify_pool(subs, pool, l_var, r_var));
|
||||
}
|
||||
|
||||
@ -523,9 +625,9 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
|
||||
RigidVar(_) | Structure(_) => {
|
||||
// Type mismatch! Rigid can only unify with flex, even if the
|
||||
// rigid names are the same.
|
||||
mismatch()
|
||||
mismatch!()
|
||||
}
|
||||
Alias(_, _, _, _) => {
|
||||
Alias(_, _, _) => {
|
||||
panic!("TODO unify_rigid Alias");
|
||||
}
|
||||
Error => {
|
||||
@ -538,6 +640,7 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
|
||||
#[inline(always)]
|
||||
fn unify_flex(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
ctx: &Context,
|
||||
opt_name: &Option<Lowercase>,
|
||||
other: &Content,
|
||||
@ -547,18 +650,25 @@ fn unify_flex(
|
||||
// If both are flex, and only left has a name, keep the name around.
|
||||
merge(subs, ctx, FlexVar(opt_name.clone()))
|
||||
}
|
||||
FlexVar(Some(_)) | RigidVar(_) | Structure(_) | Alias(_, _, _, _) => {
|
||||
|
||||
Structure(FlatType::Boolean(Bool(Atom::Variable(var), _rest))) => {
|
||||
unify_pool(subs, pool, ctx.first, *var)
|
||||
}
|
||||
|
||||
FlexVar(Some(_)) | RigidVar(_) | Structure(_) | Alias(_, _, _) => {
|
||||
// TODO special-case boolean here
|
||||
// In all other cases, if left is flex, defer to right.
|
||||
// (This includes using right's name if both are flex and named.)
|
||||
merge(subs, ctx, other.clone())
|
||||
}
|
||||
|
||||
Error => merge(subs, ctx, Error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gather_fields(
|
||||
subs: &mut Subs,
|
||||
fields: MutMap<RecordFieldLabel, Variable>,
|
||||
fields: MutMap<Lowercase, Variable>,
|
||||
var: Variable,
|
||||
) -> RecordStructure {
|
||||
use crate::subs::FlatType::*;
|
||||
@ -568,7 +678,7 @@ pub fn gather_fields(
|
||||
gather_fields(subs, union(fields, &sub_fields), sub_ext)
|
||||
}
|
||||
|
||||
Alias(_, _, _, var) => {
|
||||
Alias(_, _, var) => {
|
||||
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
|
||||
gather_fields(subs, fields, var)
|
||||
}
|
||||
@ -589,7 +699,7 @@ fn gather_tags(
|
||||
gather_tags(subs, union(tags, &sub_tags), sub_ext)
|
||||
}
|
||||
|
||||
Alias(_, _, _, var) => {
|
||||
Alias(_, _, var) => {
|
||||
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
|
||||
gather_tags(subs, tags, var)
|
||||
}
|
||||
@ -632,8 +742,3 @@ fn fresh(subs: &mut Subs, pool: &mut Pool, ctx: &Context, content: Content) -> V
|
||||
pool,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mismatch() -> Outcome {
|
||||
vec![Mismatch::TypeMismatch]
|
||||
}
|
||||
|
@ -1,601 +1,132 @@
|
||||
// Based on work by Edsko de Vries for tfp 2007
|
||||
//
|
||||
// http://www.edsko.net/tcd/pub/tfp07-prototype-snapshot.tar.gz
|
||||
//
|
||||
// Thank you Edsko!
|
||||
//
|
||||
// quoting from that work:
|
||||
//
|
||||
// > Main reference for this module is "Boolean Reasoning: The Logic of
|
||||
// > Boolean Equations" by Frank Markham Brown.
|
||||
use crate::collections::{ImMap, ImSet};
|
||||
use crate::subs::Variable;
|
||||
use self::Atom::*;
|
||||
use crate::collections::{ImSet, SendSet};
|
||||
use crate::subs::{Content, FlatType, Subs, Variable};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum Bool {
|
||||
pub struct Bool(pub Atom, pub SendSet<Atom>);
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
|
||||
pub enum Atom {
|
||||
Zero,
|
||||
One,
|
||||
And(Box<Bool>, Box<Bool>),
|
||||
Or(Box<Bool>, Box<Bool>),
|
||||
Not(Box<Bool>),
|
||||
Variable(Variable),
|
||||
}
|
||||
|
||||
use self::Bool::*;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn not(nested: Bool) -> Bool {
|
||||
Not(Box::new(nested))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn and(left: Bool, right: Bool) -> Bool {
|
||||
And(Box::new(left), Box::new(right))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn or(left: Bool, right: Bool) -> Bool {
|
||||
Or(Box::new(left), Box::new(right))
|
||||
}
|
||||
|
||||
pub fn any<I>(iterable: I) -> Bool
|
||||
where
|
||||
I: IntoIterator<Item = Bool>,
|
||||
{
|
||||
let mut it = iterable.into_iter();
|
||||
if let Some(first) = it.next() {
|
||||
it.fold(first, |x, y| or(y, x))
|
||||
} else {
|
||||
Zero
|
||||
impl Atom {
|
||||
pub fn apply_subs(&mut self, subs: &mut Subs) {
|
||||
match self {
|
||||
Atom::Zero | Atom::One => {}
|
||||
Atom::Variable(v) => match subs.get(*v).content {
|
||||
Content::Structure(FlatType::Boolean(Bool(mut atom, rest))) if rest.is_empty() => {
|
||||
atom.apply_subs(subs);
|
||||
*self = atom;
|
||||
}
|
||||
_ => {
|
||||
*self = Atom::Variable(subs.get_root_key(*v));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all<I>(iterable: I) -> Bool
|
||||
where
|
||||
I: IntoIterator<Item = Bool>,
|
||||
{
|
||||
let mut it = iterable.into_iter();
|
||||
if let Some(first) = it.next() {
|
||||
it.fold(first, and)
|
||||
} else {
|
||||
One
|
||||
}
|
||||
}
|
||||
|
||||
type Substitution = ImMap<Variable, Bool>;
|
||||
|
||||
type Product<A> = ImSet<A>;
|
||||
type Sum<A> = ImSet<A>;
|
||||
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
impl Bool {
|
||||
pub fn variables(&self) -> ImSet<Variable> {
|
||||
let mut result = ImSet::default();
|
||||
pub fn shared() -> Self {
|
||||
Bool(Zero, SendSet::default())
|
||||
}
|
||||
|
||||
pub fn unique() -> Self {
|
||||
Bool(One, SendSet::default())
|
||||
}
|
||||
|
||||
pub fn variable(variable: Variable) -> Self {
|
||||
Bool(Variable(variable), SendSet::default())
|
||||
}
|
||||
|
||||
pub fn with_free(variable: Variable, rest: Vec<Atom>) -> Self {
|
||||
let atom_set: SendSet<Atom> = rest.into_iter().collect();
|
||||
Bool(Variable(variable), atom_set)
|
||||
}
|
||||
|
||||
pub fn variables(&self) -> SendSet<Variable> {
|
||||
let mut result = SendSet::default();
|
||||
|
||||
if let Variable(v) = self.0 {
|
||||
result.insert(v);
|
||||
}
|
||||
|
||||
for atom in &self.1 {
|
||||
if let Variable(v) = atom {
|
||||
result.insert(v.clone());
|
||||
}
|
||||
}
|
||||
|
||||
self.variables_help(&mut result);
|
||||
result
|
||||
}
|
||||
|
||||
fn variables_help(&self, vars: &mut ImSet<Variable>) {
|
||||
match self {
|
||||
Zero => (),
|
||||
One => (),
|
||||
And(left, right) => {
|
||||
left.variables_help(vars);
|
||||
right.variables_help(vars)
|
||||
pub fn apply_subs(&mut self, subs: &mut Subs) {
|
||||
self.0.apply_subs(subs);
|
||||
|
||||
for atom in self.1.iter_mut() {
|
||||
atom.apply_subs(subs);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simplify(&self, subs: &mut Subs) -> Result<ImSet<Variable>, Atom> {
|
||||
match self.0 {
|
||||
Atom::Zero => Err(Atom::Zero),
|
||||
Atom::One => Err(Atom::One),
|
||||
Atom::Variable(var) => {
|
||||
let mut result = ImSet::default();
|
||||
result.insert(var);
|
||||
|
||||
for atom in &self.1 {
|
||||
match atom {
|
||||
Atom::Zero => {}
|
||||
Atom::One => return Err(Atom::One),
|
||||
Atom::Variable(v) => match subs.get(*v).content {
|
||||
Content::Structure(FlatType::Boolean(nested)) => {
|
||||
match nested.simplify(subs) {
|
||||
Ok(variables) => {
|
||||
for var in variables {
|
||||
result.insert(var);
|
||||
}
|
||||
}
|
||||
Err(Atom::Zero) => {}
|
||||
Err(Atom::One) => return Err(Atom::One),
|
||||
Err(Atom::Variable(_)) => panic!("TODO nested variable"),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
result.insert(*v);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
Or(left, right) => {
|
||||
left.variables_help(vars);
|
||||
right.variables_help(vars)
|
||||
}
|
||||
Not(nested) => nested.variables_help(vars),
|
||||
Variable(var) => {
|
||||
vars.insert(*var);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_variables<F>(&self, f: &mut F) -> Self
|
||||
where
|
||||
F: FnMut(Variable) -> Variable,
|
||||
{
|
||||
match self {
|
||||
Zero => Zero,
|
||||
One => One,
|
||||
And(left, right) => and(left.map_variables(f), right.map_variables(f)),
|
||||
Or(left, right) => or(left.map_variables(f), right.map_variables(f)),
|
||||
Not(nested) => not(nested.map_variables(f)),
|
||||
Variable(current) => Variable(f(*current)),
|
||||
}
|
||||
}
|
||||
let mut new_bound = SendSet::default();
|
||||
|
||||
pub fn substitute(&self, substitutions: &Substitution) -> Self {
|
||||
match self {
|
||||
Zero => Zero,
|
||||
One => One,
|
||||
And(left, right) => and(
|
||||
left.substitute(substitutions),
|
||||
right.substitute(substitutions),
|
||||
),
|
||||
Or(left, right) => or(
|
||||
left.substitute(substitutions),
|
||||
right.substitute(substitutions),
|
||||
),
|
||||
Not(nested) => not(nested.substitute(substitutions)),
|
||||
Variable(current) => match substitutions.get(current) {
|
||||
Some(new) => new.clone(),
|
||||
None => Variable(*current),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn is_var(&self) -> bool {
|
||||
match self {
|
||||
Variable(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn not(nested: Bool) -> Bool {
|
||||
not(nested)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn and(left: Bool, right: Bool) -> Bool {
|
||||
and(left, right)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn or(left: Bool, right: Bool) -> Bool {
|
||||
or(left, right)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simplify(term: Bool) -> Bool {
|
||||
let normalized = normalize_term(term);
|
||||
let a = term_to_sop(normalized);
|
||||
let b = normalize_sop(a);
|
||||
let after_bcf = bcf(b);
|
||||
sop_to_term_vector(simplify_sop_vector(after_bcf))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn sop_to_term(sop: Sop) -> Bool {
|
||||
let mut accum: Vec<Vec<Bool>> = Vec::new();
|
||||
|
||||
for s in sop {
|
||||
accum.push(s.iter().cloned().collect());
|
||||
}
|
||||
|
||||
accum.sort_by(|x, y| (y.len().cmp(&x.len())));
|
||||
|
||||
any(accum.into_iter().map(all))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn sop_to_term_vector(sop: Vec<Vec<Bool>>) -> Bool {
|
||||
any(sop.into_iter().rev().map(all))
|
||||
}
|
||||
|
||||
pub fn simplify_sop_vector(sorted: Vec<Vec<Bool>>) -> Vec<Vec<Bool>> {
|
||||
// sort by length longest to shortest (proxy for how many variables there are)
|
||||
// sorted.sort_by(|x, y| y.len().cmp(&x.len()));
|
||||
|
||||
let backup = sorted.clone();
|
||||
|
||||
let mut p: Vec<Vec<Bool>> = Vec::new();
|
||||
|
||||
for (i, t) in sorted.into_iter().enumerate() {
|
||||
let ts = &backup[i + 1..];
|
||||
ts.to_vec().extend(p.clone());
|
||||
|
||||
if included(all(t.clone()), sop_to_term_vector(ts.to_vec())) {
|
||||
// do nothing
|
||||
let new_free = if let Variable(v) = self.0 {
|
||||
Variable(f(v))
|
||||
} else {
|
||||
p.insert(0, t.into_iter().collect());
|
||||
}
|
||||
}
|
||||
self.0
|
||||
};
|
||||
|
||||
p
|
||||
}
|
||||
|
||||
/// Blake canonical form
|
||||
pub fn bcf(sop: Sop) -> Vec<Vec<Bool>> {
|
||||
absorptive(syllogistic(sop))
|
||||
}
|
||||
|
||||
pub fn syllogistic(terms: Sop) -> Sop {
|
||||
let mut cs_prime = ImSet::default();
|
||||
|
||||
for c in cartesian_product(terms.clone())
|
||||
.iter()
|
||||
.filter_map(|(x, y)| consensus(x, y))
|
||||
{
|
||||
if !terms
|
||||
.clone()
|
||||
.into_iter()
|
||||
.any(|x| included_term(c.clone(), x))
|
||||
{
|
||||
cs_prime.insert(c);
|
||||
}
|
||||
}
|
||||
|
||||
if cs_prime.is_empty() {
|
||||
terms
|
||||
} else {
|
||||
syllogistic(terms.union(cs_prime))
|
||||
}
|
||||
}
|
||||
|
||||
/// Absorption (apply the identify p + pq = p)
|
||||
fn absorptive(sop: Sop) -> Vec<Vec<Bool>> {
|
||||
let mut accum: Vec<Vec<Bool>> = Vec::new();
|
||||
|
||||
for s in sop {
|
||||
accum.push(s.iter().cloned().collect());
|
||||
}
|
||||
|
||||
accum.sort_by(|x, y| (x.len().cmp(&y.len())));
|
||||
|
||||
absorptive_vector(accum)
|
||||
}
|
||||
|
||||
pub fn absorptive_vector(mut accum: Vec<Vec<Bool>>) -> Vec<Vec<Bool>> {
|
||||
for product in accum.clone().into_iter() {
|
||||
accum.retain(|v| !absorbs_vector(&product, v));
|
||||
accum.insert(0, product);
|
||||
}
|
||||
|
||||
accum
|
||||
}
|
||||
|
||||
pub fn absorbs_vector(p: &[Bool], q: &[Bool]) -> bool {
|
||||
p.iter().all(|x| q.contains(x))
|
||||
}
|
||||
|
||||
fn consensus(p: &Product<Bool>, q: &Product<Bool>) -> Option<Product<Bool>> {
|
||||
let mut it = oppositions(p, q).into_iter();
|
||||
|
||||
// oppositions must have exactly one element
|
||||
if let Some(x) = it.next() {
|
||||
if it.next().is_none() {
|
||||
let compx = not(x.clone());
|
||||
|
||||
return Some(
|
||||
p.clone()
|
||||
.into_iter()
|
||||
.chain(q.clone().into_iter())
|
||||
.filter(|y| *y != x && *y != compx)
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn oppositions(ps: &Product<Bool>, qs: &Product<Bool>) -> Product<Bool> {
|
||||
let it1 = ps
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|p| qs.contains(¬(p.clone())));
|
||||
let it2 = qs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|q| ps.contains(¬(q.clone())));
|
||||
|
||||
it1.chain(it2).collect()
|
||||
}
|
||||
|
||||
pub fn try_unify(p: Bool, q: Bool) -> Option<Substitution> {
|
||||
let (sub, consistency) = unify(p, q);
|
||||
|
||||
let substitution = sub
|
||||
.into_iter()
|
||||
.filter(|(x, p)| *p != Variable(*x))
|
||||
.collect();
|
||||
|
||||
if consistency == Zero {
|
||||
Some(substitution)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn unify(p: Bool, q: Bool) -> (Substitution, Bool) {
|
||||
let condition = q.is_var() && !p.is_var();
|
||||
let t = if condition {
|
||||
or(and(q.clone(), not(p.clone())), and(not(q), p))
|
||||
} else {
|
||||
or(and(p.clone(), not(q.clone())), and(not(p), q))
|
||||
};
|
||||
|
||||
let mut sorted_names: Vec<Variable> = t.variables().into_iter().collect();
|
||||
sorted_names.sort_by(|x, y| y.cmp(x));
|
||||
unify0(sorted_names, t)
|
||||
}
|
||||
|
||||
fn unify0(mut names: Vec<Variable>, term: Bool) -> (Substitution, Bool) {
|
||||
if let Some(x) = names.pop() {
|
||||
let xs = names;
|
||||
|
||||
let mut sub_zero = ImMap::default();
|
||||
sub_zero.insert(x, Zero);
|
||||
|
||||
let mut sub_one = ImMap::default();
|
||||
sub_one.insert(x, One);
|
||||
|
||||
let subbed_zero = term.substitute(&sub_zero);
|
||||
let subbed_one = term.substitute(&sub_one);
|
||||
|
||||
let e = and(subbed_zero.clone(), subbed_one.clone());
|
||||
|
||||
let (mut se, cc) = unify0(xs, e);
|
||||
|
||||
let input = or(
|
||||
subbed_zero.substitute(&se),
|
||||
and(Variable(x), (not(subbed_one)).substitute(&se)),
|
||||
);
|
||||
|
||||
let replacement = simplify(input);
|
||||
|
||||
se.insert(x, replacement);
|
||||
|
||||
(se, cc)
|
||||
} else {
|
||||
(ImMap::default(), simplify(term))
|
||||
}
|
||||
}
|
||||
|
||||
// --- Simplification ---
|
||||
|
||||
/// Normalization of terms. Applies (in bottom-up fashion) the identities
|
||||
///
|
||||
/// x * 1 = x
|
||||
/// x * 0 = 0
|
||||
/// x + 1 = 1
|
||||
/// x + 0 = x
|
||||
/// !1 = 0
|
||||
/// !0 = 1
|
||||
pub fn normalize_term(term: Bool) -> Bool {
|
||||
match term {
|
||||
And(left, right) => {
|
||||
let p = normalize_term(*left);
|
||||
let q = normalize_term(*right);
|
||||
|
||||
match (p == One, p == Zero, q == One, q == Zero) {
|
||||
(true, _, _, _) => q,
|
||||
(_, true, _, _) => Zero,
|
||||
(_, _, true, _) => p,
|
||||
(_, _, _, true) => Zero,
|
||||
_ => and(p, q),
|
||||
for atom in &self.1 {
|
||||
if let Variable(v) = atom {
|
||||
new_bound.insert(Variable(f(*v)));
|
||||
} else {
|
||||
new_bound.insert(atom.clone());
|
||||
}
|
||||
}
|
||||
Or(left, right) => {
|
||||
let p = normalize_term(*left);
|
||||
let q = normalize_term(*right);
|
||||
|
||||
match (p == One, p == Zero, q == One, q == Zero) {
|
||||
(true, _, _, _) => One,
|
||||
(_, true, _, _) => q,
|
||||
(_, _, true, _) => One,
|
||||
(_, _, _, true) => p,
|
||||
_ => or(p, q),
|
||||
}
|
||||
}
|
||||
Not(nested) => {
|
||||
let p = normalize_term(*nested);
|
||||
|
||||
match (p == One, p == Zero) {
|
||||
(true, _) => Zero,
|
||||
(_, true) => One,
|
||||
_ => not(p),
|
||||
}
|
||||
}
|
||||
_ => term,
|
||||
}
|
||||
}
|
||||
|
||||
// --- Inclusion ---
|
||||
|
||||
pub fn included(g: Bool, h: Bool) -> bool {
|
||||
contradiction(and(g, not(h)))
|
||||
}
|
||||
|
||||
fn included_term(g: Product<Bool>, h: Product<Bool>) -> bool {
|
||||
included(all(g), all(h))
|
||||
}
|
||||
|
||||
// --- Tautology / Contradiction ---
|
||||
|
||||
fn tautology(term: Bool) -> bool {
|
||||
normalize_pos(term_to_pos(normalize_term(term))).is_empty()
|
||||
}
|
||||
|
||||
pub fn contradiction(term: Bool) -> bool {
|
||||
tautology(not(term))
|
||||
}
|
||||
|
||||
// --- Normalization of POS / SOP
|
||||
|
||||
type Pos = Product<Sum<Bool>>;
|
||||
type Sop = Sum<Product<Bool>>;
|
||||
|
||||
fn term_to_pos(term: Bool) -> Pos {
|
||||
conj_to_list(cnf(term))
|
||||
.into_iter()
|
||||
.map(disj_to_list)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn term_to_sop(term: Bool) -> Sop {
|
||||
disj_to_list(dnf(term))
|
||||
.into_iter()
|
||||
.map(conj_to_list)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn conj_to_list(term: Bool) -> Product<Bool> {
|
||||
match term {
|
||||
And(left, right) => {
|
||||
let p = conj_to_list(*left);
|
||||
let q = conj_to_list(*right);
|
||||
|
||||
p.union(q)
|
||||
}
|
||||
_ => unit(term),
|
||||
}
|
||||
}
|
||||
|
||||
fn disj_to_list(term: Bool) -> Sum<Bool> {
|
||||
match term {
|
||||
Or(left, right) => {
|
||||
let p = disj_to_list(*left);
|
||||
let q = disj_to_list(*right);
|
||||
|
||||
p.union(q)
|
||||
}
|
||||
_ => unit(term),
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_pos(pos: Pos) -> Pos {
|
||||
let singleton_one = unit(One);
|
||||
|
||||
pos.into_iter()
|
||||
.map(normalize_disj)
|
||||
.filter(|normalized| *normalized != singleton_one)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn normalize_sop(sop: Sop) -> Sop {
|
||||
let singleton_zero = unit(Zero);
|
||||
|
||||
sop.into_iter()
|
||||
.map(normalize_conj)
|
||||
.filter(|normalized| *normalized != singleton_zero)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn cartesian_product<A>(set: ImSet<A>) -> ImSet<(A, A)>
|
||||
where
|
||||
A: Eq + Clone + core::hash::Hash,
|
||||
{
|
||||
let mut result = ImSet::default();
|
||||
|
||||
for x in set.clone().into_iter() {
|
||||
for y in set.clone() {
|
||||
if x == y {
|
||||
break;
|
||||
}
|
||||
|
||||
result.insert((x.clone(), y));
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
fn unit<A>(a: A) -> ImSet<A>
|
||||
where
|
||||
A: Clone + Eq + core::hash::Hash,
|
||||
{
|
||||
let mut result = ImSet::default();
|
||||
result.insert(a);
|
||||
result
|
||||
}
|
||||
|
||||
fn normalize_disj(mut sum: Sum<Bool>) -> Sum<Bool> {
|
||||
let is_always_true =
|
||||
sum.clone().into_iter().any(|x| sum.contains(¬(x))) || sum.contains(&One);
|
||||
|
||||
if is_always_true {
|
||||
unit(One)
|
||||
} else {
|
||||
sum.remove(&Zero);
|
||||
sum
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_conj(mut product: Product<Bool>) -> Product<Bool> {
|
||||
let is_always_false = product
|
||||
.clone()
|
||||
.into_iter()
|
||||
.any(|x| product.contains(¬(x)))
|
||||
|| product.contains(&Zero);
|
||||
|
||||
if is_always_false {
|
||||
unit(Zero)
|
||||
} else {
|
||||
product.remove(&One);
|
||||
product
|
||||
}
|
||||
}
|
||||
|
||||
/// Conjunction Normal Form
|
||||
fn cnf(term: Bool) -> Bool {
|
||||
match nnf(term) {
|
||||
And(p, q) => and(cnf(*p), cnf(*q)),
|
||||
Or(p, q) => distr_cnf(cnf(*p), cnf(*q)),
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
fn distr_cnf(p: Bool, q: Bool) -> Bool {
|
||||
match p {
|
||||
And(p1, p2) => and(distr_cnf(*p1, q.clone()), distr_cnf(*p2, q)),
|
||||
_ => distr_cnf_help(p, q),
|
||||
}
|
||||
}
|
||||
|
||||
fn distr_cnf_help(p: Bool, q: Bool) -> Bool {
|
||||
match q {
|
||||
And(q1, q2) => and(distr_cnf(p.clone(), *q1), distr_cnf(p, *q2)),
|
||||
_ => or(p, q),
|
||||
}
|
||||
}
|
||||
|
||||
/// Disjunction Normal Form
|
||||
pub fn dnf(term: Bool) -> Bool {
|
||||
match nnf(term) {
|
||||
And(p, q) => distr_dnf(dnf(*p), dnf(*q)),
|
||||
Or(p, q) => or(dnf(*p), dnf(*q)),
|
||||
other => other,
|
||||
}
|
||||
}
|
||||
|
||||
fn distr_dnf(p: Bool, q: Bool) -> Bool {
|
||||
match p {
|
||||
Or(p1, p2) => or(distr_dnf(*p1, q.clone()), distr_dnf(*p2, q)),
|
||||
_ => distr_dnf_help(p, q),
|
||||
}
|
||||
}
|
||||
|
||||
fn distr_dnf_help(p: Bool, q: Bool) -> Bool {
|
||||
match q {
|
||||
Or(q1, q2) => or(distr_dnf(p.clone(), *q1), distr_dnf(p, *q2)),
|
||||
_ => and(p, q),
|
||||
}
|
||||
}
|
||||
|
||||
/// Negation Normal Form
|
||||
pub fn nnf(term: Bool) -> Bool {
|
||||
match term {
|
||||
Not(n) => nnf_help(*n),
|
||||
_ => term,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn nnf_help(term: Bool) -> Bool {
|
||||
match term {
|
||||
Zero => One,
|
||||
One => Zero,
|
||||
And(p, q) => or(nnf_help(*p), nnf_help(*q)),
|
||||
Or(p, q) => and(nnf_help(*p), nnf_help(*q)),
|
||||
// double negation
|
||||
Not(nested) => nnf(*nested),
|
||||
Variable(_) => not(term),
|
||||
Bool(new_free, new_bound)
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +0,0 @@
|
||||
use crate::types::Type;
|
||||
use crate::uniqueness::boolean_algebra::Bool;
|
||||
|
||||
type Uniqueness = Bool;
|
||||
|
||||
pub fn attr_type(uniq: Uniqueness, typ: Type) -> Type {
|
||||
crate::constrain::builtins::builtin_type("Attr", "Attr", vec![Type::Boolean(uniq), typ])
|
||||
}
|
||||
|
||||
pub fn shared_type() -> Uniqueness {
|
||||
Bool::Zero
|
||||
}
|
||||
|
||||
/// We usually just leave a type parameter unbound (written `*`) when it's unique
|
||||
#[allow(dead_code)]
|
||||
pub fn unique_type() -> Uniqueness {
|
||||
Bool::One
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,39 +1,139 @@
|
||||
use crate::can::symbol::Symbol;
|
||||
use crate::collections::ImMap;
|
||||
use crate::can::expr::Expr;
|
||||
use crate::can::ident::Lowercase;
|
||||
use crate::collections::{ImMap, ImSet};
|
||||
use crate::module::symbol::Symbol;
|
||||
use crate::region::Located;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq, PartialOrd)]
|
||||
pub enum ReferenceCount {
|
||||
Seen,
|
||||
Unique,
|
||||
Access(FieldAccess),
|
||||
Update(ImSet<Lowercase>, FieldAccess),
|
||||
Shared,
|
||||
}
|
||||
|
||||
impl ReferenceCount {
|
||||
pub fn add(_a: &ReferenceCount, _b: &ReferenceCount) -> Self {
|
||||
Self::Shared
|
||||
pub fn add(a: &ReferenceCount, b: &ReferenceCount) -> Self {
|
||||
use ReferenceCount::*;
|
||||
match (a, b) {
|
||||
// Shared
|
||||
(Shared, _) | (_, Shared) => Shared,
|
||||
|
||||
// Update
|
||||
(Update(_, _), Update(_, _)) => Shared,
|
||||
(Update(_, _), Access(_)) => Shared,
|
||||
(Update(_, _), Unique) => Shared,
|
||||
(Unique, Update(_, _)) => Shared,
|
||||
|
||||
(Access(fa1), Update(overwritten, fa2)) => correct_overwritten(fa1, overwritten, fa2),
|
||||
(Seen, Update(overwritten, fa)) => Update(overwritten.clone(), fa.clone()),
|
||||
(Update(overwritten, fa), Seen) => Update(overwritten.clone(), fa.clone()),
|
||||
|
||||
// Access
|
||||
(Access(fa1), Access(fa2)) => {
|
||||
let mut fa = fa1.clone();
|
||||
fa.sequential_merge(fa2);
|
||||
|
||||
Access(fa)
|
||||
}
|
||||
(Access(fa1), Unique) => {
|
||||
// unique usage is like an update where no fields are overwritten
|
||||
correct_overwritten(fa1, &ImSet::default(), &FieldAccess::default())
|
||||
}
|
||||
(Unique, Access(_)) => Shared,
|
||||
(Seen, Access(fa)) | (Access(fa), Seen) => Access(fa.clone()),
|
||||
|
||||
// Unique
|
||||
(Unique, Unique) => Shared,
|
||||
(Seen, Unique) | (Unique, Seen) => Unique,
|
||||
(Seen, Seen) => Seen,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn or(a: &ReferenceCount, b: &ReferenceCount) -> Self {
|
||||
use ReferenceCount::*;
|
||||
match (a, b) {
|
||||
(Self::Unique, Self::Unique) => Self::Unique,
|
||||
_ => Self::Shared,
|
||||
(_, Shared) | (Shared, _) => Shared,
|
||||
|
||||
(Update(w1, fa1), Update(w2, fa2)) => {
|
||||
let mut fa = fa1.clone();
|
||||
fa.parallel_merge(&fa2.clone());
|
||||
Update(w1.clone().intersection(w2.clone()), fa)
|
||||
}
|
||||
(Update(w, fa), Unique) | (Unique, Update(w, fa)) => Update(w.clone(), fa.clone()),
|
||||
(Update(w, fa1), Access(fa2)) | (Access(fa2), Update(w, fa1)) => {
|
||||
let mut fa = fa1.clone();
|
||||
fa.parallel_merge(&fa2.clone());
|
||||
Update(w.clone(), fa)
|
||||
}
|
||||
|
||||
(Access(fa1), Access(fa2)) => {
|
||||
let mut fa = fa1.clone();
|
||||
fa.parallel_merge(&fa2.clone());
|
||||
Access(fa)
|
||||
}
|
||||
(Access(fa), Unique) | (Unique, Access(fa)) => Access(fa.clone()),
|
||||
|
||||
(Unique, Unique) => Unique,
|
||||
(Seen, other) | (other, Seen) => other.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn correct_overwritten(
|
||||
fa1: &FieldAccess,
|
||||
overwritten: &ImSet<Lowercase>,
|
||||
fa2: &FieldAccess,
|
||||
) -> ReferenceCount {
|
||||
use ReferenceCount::*;
|
||||
|
||||
let mut fa = fa1.clone();
|
||||
fa.sequential_merge(fa2);
|
||||
|
||||
// fields that are accessed, but not overwritten in the update, must be shared!
|
||||
for (label, (rc, nested)) in fa.fields.clone().keys().zip(fa.fields.iter_mut()) {
|
||||
if !overwritten.contains(&label.clone().into()) {
|
||||
make_subtree_shared(rc, nested);
|
||||
}
|
||||
}
|
||||
|
||||
Update(overwritten.clone(), fa)
|
||||
}
|
||||
|
||||
fn make_subtree_shared(rc: &mut ReferenceCount, nested: &mut FieldAccess) {
|
||||
if rc > &mut ReferenceCount::Seen {
|
||||
*rc = ReferenceCount::Shared;
|
||||
}
|
||||
|
||||
for (nested_rc, nested_nested) in nested.fields.iter_mut() {
|
||||
make_subtree_shared(nested_rc, nested_nested);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct VarUsage {
|
||||
usage: ImMap<Symbol, ReferenceCount>,
|
||||
}
|
||||
|
||||
impl IntoIterator for VarUsage {
|
||||
type Item = (Symbol, ReferenceCount);
|
||||
type IntoIter = im_rc::hashmap::ConsumingIter<(Symbol, ReferenceCount)>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.usage.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
impl VarUsage {
|
||||
pub fn default() -> VarUsage {
|
||||
VarUsage {
|
||||
usage: (ImMap::default()),
|
||||
}
|
||||
let empty: ImMap<Symbol, ReferenceCount> = ImMap::default();
|
||||
|
||||
VarUsage { usage: empty }
|
||||
}
|
||||
|
||||
pub fn register_with(&mut self, symbol: &Symbol, rc: &ReferenceCount) {
|
||||
let value = match self.usage.get(symbol) {
|
||||
pub fn register_with(&mut self, symbol: Symbol, rc: &ReferenceCount) {
|
||||
let value = match self.usage.get(&symbol) {
|
||||
None => rc.clone(),
|
||||
Some(current) => ReferenceCount::add(current, rc),
|
||||
};
|
||||
@ -41,22 +141,22 @@ impl VarUsage {
|
||||
self.usage.insert(symbol.clone(), value);
|
||||
}
|
||||
|
||||
pub fn register(&mut self, symbol: &Symbol) {
|
||||
pub fn register(&mut self, symbol: Symbol) {
|
||||
use self::ReferenceCount::*;
|
||||
self.register_with(symbol, &Unique);
|
||||
}
|
||||
|
||||
pub fn unregister(&mut self, symbol: &Symbol) {
|
||||
self.usage.remove(symbol);
|
||||
pub fn unregister(&mut self, symbol: Symbol) {
|
||||
self.usage.remove(&symbol);
|
||||
}
|
||||
|
||||
pub fn get_usage(&self, symbol: &Symbol) -> Option<&ReferenceCount> {
|
||||
self.usage.get(symbol)
|
||||
pub fn get_usage(&self, symbol: Symbol) -> Option<&ReferenceCount> {
|
||||
self.usage.get(&symbol)
|
||||
}
|
||||
|
||||
pub fn add(&mut self, other: &Self) {
|
||||
for (symbol, v) in &other.usage {
|
||||
self.register_with(symbol, v);
|
||||
self.register_with(*symbol, v);
|
||||
}
|
||||
}
|
||||
|
||||
@ -71,3 +171,323 @@ impl VarUsage {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, PartialEq, PartialOrd)]
|
||||
pub struct FieldAccess {
|
||||
pub fields: ImMap<String, (ReferenceCount, FieldAccess)>,
|
||||
}
|
||||
|
||||
type FAMap = std::collections::HashMap<String, ReferenceCount>;
|
||||
|
||||
impl Into<FAMap> for FieldAccess {
|
||||
fn into(self) -> FAMap {
|
||||
let mut result = std::collections::HashMap::default();
|
||||
|
||||
for (name, (rc, nested_access)) in self.fields {
|
||||
result.insert(name.clone(), rc);
|
||||
|
||||
let nested_map: FAMap = nested_access.into();
|
||||
|
||||
for (n_name, n_rc) in nested_map {
|
||||
result.insert(name.clone() + "." + &n_name, n_rc);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl FieldAccess {
|
||||
pub fn from_chain(access_chain: Vec<Lowercase>) -> Self {
|
||||
use ReferenceCount::*;
|
||||
let strings: Vec<String> = access_chain
|
||||
.iter()
|
||||
.map(|v| v.as_str().to_string())
|
||||
.collect();
|
||||
|
||||
let mut accum = FieldAccess::default();
|
||||
let mut is_final = true;
|
||||
for field in strings.into_iter().rev() {
|
||||
let mut next = FieldAccess::default();
|
||||
let uniq = if is_final {
|
||||
is_final = false;
|
||||
Unique
|
||||
} else {
|
||||
Seen
|
||||
};
|
||||
next.fields.insert(field.to_string(), (uniq, accum));
|
||||
accum = FieldAccess {
|
||||
fields: next.fields,
|
||||
};
|
||||
}
|
||||
|
||||
accum
|
||||
}
|
||||
|
||||
fn or_subtree(&mut self, constraint: &ReferenceCount) {
|
||||
for (rc, nested) in self.fields.iter_mut() {
|
||||
*rc = ReferenceCount::or(rc, constraint);
|
||||
nested.or_subtree(constraint);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parallel_merge(&mut self, other: &Self) {
|
||||
for (field_name, (other_rc, other_nested)) in other.fields.clone() {
|
||||
if self.fields.contains_key(&field_name) {
|
||||
if let Some((self_rc, self_nested)) = self.fields.get_mut(&field_name) {
|
||||
self_nested.parallel_merge(&other_nested);
|
||||
*self_rc = ReferenceCount::or(self_rc, &other_rc);
|
||||
}
|
||||
} else {
|
||||
self.fields.insert(field_name, (other_rc, other_nested));
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn sequential_merge(&mut self, other: &Self) {
|
||||
for (field_name, (other_rc, mut other_nested)) in other.fields.clone() {
|
||||
if self.fields.contains_key(&field_name) {
|
||||
if let Some((self_rc, self_nested)) = self.fields.get_mut(&field_name) {
|
||||
*self_rc = ReferenceCount::add(self_rc, &other_rc);
|
||||
if *self_rc > ReferenceCount::Seen {
|
||||
// e.g. we access `rec.foo` and `rec.foo.bar`.
|
||||
// Since a reference to `rec.foo` exists, there are at least two references to `foo.bar`
|
||||
// (`foo.bar` itself and `.bar rec.foo`)
|
||||
// Therefore fields of the subtrees must be shared!
|
||||
self_nested.or_subtree(&ReferenceCount::Shared);
|
||||
other_nested.or_subtree(&ReferenceCount::Shared);
|
||||
}
|
||||
|
||||
self_nested.sequential_merge(&other_nested);
|
||||
}
|
||||
} else {
|
||||
self.fields.insert(field_name, (other_rc, other_nested));
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn parallel(&mut self, access_chain: Vec<Lowercase>) {
|
||||
let other = Self::from_chain(access_chain);
|
||||
self.parallel_merge(&other);
|
||||
}
|
||||
|
||||
pub fn sequential(&mut self, access_chain: Vec<Lowercase>) {
|
||||
let other = Self::from_chain(access_chain);
|
||||
self.sequential_merge(&other);
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.fields.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) {
|
||||
use Expr::*;
|
||||
use ReferenceCount::*;
|
||||
|
||||
match expr {
|
||||
RuntimeError(_)
|
||||
| Int(_, _)
|
||||
| Float(_, _)
|
||||
| Str(_)
|
||||
| BlockStr(_)
|
||||
| EmptyRecord
|
||||
| Accessor { .. } => {}
|
||||
|
||||
Var(symbol) => usage.register(*symbol),
|
||||
|
||||
If {
|
||||
branches,
|
||||
final_else,
|
||||
..
|
||||
} => {
|
||||
let mut branch_usage = VarUsage::default();
|
||||
for (loc_cond, loc_then) in branches {
|
||||
annotate_usage(&loc_cond.value, usage);
|
||||
|
||||
let mut then_usage = VarUsage::default();
|
||||
|
||||
annotate_usage(&loc_then.value, &mut then_usage);
|
||||
|
||||
branch_usage.or(&then_usage);
|
||||
}
|
||||
|
||||
let mut else_usage = VarUsage::default();
|
||||
annotate_usage(&final_else.value, &mut else_usage);
|
||||
|
||||
branch_usage.or(&else_usage);
|
||||
usage.add(&branch_usage);
|
||||
}
|
||||
When {
|
||||
loc_cond, branches, ..
|
||||
} => {
|
||||
annotate_usage(&loc_cond.value, usage);
|
||||
|
||||
let mut branches_usage = VarUsage::default();
|
||||
for (_, loc_branch) in branches {
|
||||
let mut current_usage = VarUsage::default();
|
||||
|
||||
annotate_usage(&loc_branch.value, &mut current_usage);
|
||||
|
||||
branches_usage.or(¤t_usage);
|
||||
}
|
||||
|
||||
usage.add(&branches_usage);
|
||||
}
|
||||
|
||||
List { loc_elems, .. } => {
|
||||
for loc_elem in loc_elems {
|
||||
annotate_usage(&loc_elem.value, usage);
|
||||
}
|
||||
}
|
||||
LetNonRec(def, loc_expr, _, _) => {
|
||||
annotate_usage(&def.loc_expr.value, usage);
|
||||
annotate_usage(&loc_expr.value, usage);
|
||||
}
|
||||
LetRec(defs, loc_expr, _, _) => {
|
||||
// TODO test this with a practical example.
|
||||
if defs.len() == 1 {
|
||||
// just like a letrec, but mark defined symbol as Shared
|
||||
let def = &defs[0];
|
||||
for (symbol, _) in def.pattern_vars.clone() {
|
||||
usage.register_with(symbol, &Shared);
|
||||
}
|
||||
annotate_usage(&def.loc_expr.value, usage);
|
||||
} else {
|
||||
let mut rec_usage = VarUsage::default();
|
||||
// care is required. If f1 and f2 are mutually recursive, and f1 accesses a record
|
||||
// whilst f2 updates that record, the record must be marked as Shared, disallowing
|
||||
// a mutable update in f2
|
||||
for def in defs {
|
||||
for (symbol, _) in def.pattern_vars.clone() {
|
||||
usage.register_with(symbol, &Shared);
|
||||
}
|
||||
|
||||
let mut current_usage = VarUsage::default();
|
||||
annotate_usage(&def.loc_expr.value, &mut current_usage);
|
||||
|
||||
let mut a = rec_usage.clone();
|
||||
let b = rec_usage.clone();
|
||||
|
||||
a.add(¤t_usage);
|
||||
current_usage.add(&b);
|
||||
current_usage.or(&a);
|
||||
|
||||
rec_usage.add(¤t_usage);
|
||||
}
|
||||
|
||||
usage.add(&rec_usage);
|
||||
}
|
||||
|
||||
annotate_usage(&loc_expr.value, usage);
|
||||
}
|
||||
Call(fun, loc_args, _) => {
|
||||
annotate_usage(&fun.1.value, usage);
|
||||
|
||||
for (_, arg) in loc_args {
|
||||
annotate_usage(&arg.value, usage);
|
||||
}
|
||||
}
|
||||
Closure(_, _, _, _, body) => {
|
||||
annotate_usage(&body.0.value, usage);
|
||||
}
|
||||
|
||||
Tag { arguments, .. } => {
|
||||
for (_, loc_expr) in arguments {
|
||||
annotate_usage(&loc_expr.value, usage);
|
||||
}
|
||||
}
|
||||
Record(_, fields) => {
|
||||
for (_, field) in fields {
|
||||
annotate_usage(&field.loc_expr.value, usage);
|
||||
}
|
||||
}
|
||||
Expr::Update {
|
||||
symbol, updates, ..
|
||||
} => {
|
||||
let mut labels = ImSet::default();
|
||||
|
||||
for (label, field) in updates {
|
||||
annotate_usage(&field.loc_expr.value, usage);
|
||||
labels.insert(label.clone());
|
||||
}
|
||||
|
||||
usage.register_with(
|
||||
*symbol,
|
||||
&ReferenceCount::Update(labels, FieldAccess::default()),
|
||||
);
|
||||
}
|
||||
Expr::Access {
|
||||
field, loc_expr, ..
|
||||
} => {
|
||||
let mut chain = Vec::new();
|
||||
if let Some(symbol) = get_access_chain(&loc_expr.value, &mut chain) {
|
||||
chain.push(field.clone());
|
||||
|
||||
let fa = FieldAccess::from_chain(chain);
|
||||
|
||||
usage.register_with(*symbol, &ReferenceCount::Access(fa));
|
||||
} else {
|
||||
annotate_usage(&loc_expr.value, usage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_access_chain<'a>(expr: &'a Expr, chain: &mut Vec<Lowercase>) -> Option<&'a Symbol> {
|
||||
use Expr::*;
|
||||
|
||||
match expr {
|
||||
Expr::Access {
|
||||
field, loc_expr, ..
|
||||
} => {
|
||||
let symbol = get_access_chain(&loc_expr.value, chain)?;
|
||||
|
||||
chain.push(field.clone());
|
||||
|
||||
Some(symbol)
|
||||
}
|
||||
Var(symbol) => Some(symbol),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const LIST_ELEMENTS_LABEL: &str = "list_elements";
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn special_case_builtins(usage: &mut VarUsage, _symbol: Symbol, loc_args: Vec<Located<Expr>>) {
|
||||
use ReferenceCount::*;
|
||||
match 0 {
|
||||
// Symbol::LIST_GET => {
|
||||
0 => {
|
||||
// functions are always fully applied, so this is safe
|
||||
let index = &loc_args[0];
|
||||
let list = &loc_args[1];
|
||||
// index is an integer, its uniqueness doesn't matter
|
||||
annotate_usage(&index.value, usage);
|
||||
if let Expr::Var(symbol) = &list.value {
|
||||
let fa = FieldAccess::from_chain(vec![LIST_ELEMENTS_LABEL.into()]);
|
||||
usage.register_with(*symbol, &Access(fa));
|
||||
} else {
|
||||
annotate_usage(&list.value, usage);
|
||||
}
|
||||
}
|
||||
1 => {
|
||||
let index = &loc_args[0];
|
||||
let value = &loc_args[1];
|
||||
let list = &loc_args[2];
|
||||
|
||||
annotate_usage(&index.value, usage);
|
||||
annotate_usage(&value.value, usage);
|
||||
|
||||
if let Expr::Var(symbol) = &list.value {
|
||||
let fa = FieldAccess::from_chain(vec![LIST_ELEMENTS_LABEL.into()]);
|
||||
let overwritten: ImSet<Lowercase> = ImSet::default();
|
||||
usage.register_with(*symbol, &Update(overwritten, fa));
|
||||
} else {
|
||||
annotate_usage(&list.value, usage);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
interface Dep1
|
||||
exposes [ three, str ]
|
||||
exposes [ three, str, Unit, Identity ]
|
||||
imports [ Dep3.Blah.{ foo } ]
|
||||
|
||||
one = 1
|
||||
@ -9,3 +9,7 @@ two = 2
|
||||
three = 3.0
|
||||
|
||||
str = "string!"
|
||||
|
||||
Unit : [ Unit ]
|
||||
|
||||
Identity a : [ Identity a ]
|
||||
|
@ -4,6 +4,9 @@ interface Dep2
|
||||
|
||||
one = 1
|
||||
|
||||
foo = "foo" # TODO FIXME this should be reported as shadowing!
|
||||
|
||||
two = 2.0
|
||||
|
||||
foo = "foo"
|
||||
one = 1
|
||||
|
||||
|
18
tests/fixtures/build/interface_with_deps/ManualAttr.roc
vendored
Normal file
18
tests/fixtures/build/interface_with_deps/ManualAttr.roc
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
interface ManualAttr
|
||||
exposes []
|
||||
imports []
|
||||
|
||||
# manually replicates the Attr wrapping that uniqueness inference uses, to try and find out why they are different
|
||||
# It is very important that there are no signatures here! elm uses an optimization that leads to less copying when
|
||||
# signatures are given.
|
||||
|
||||
map =
|
||||
unAttr = \Attr _ foobar -> foobar
|
||||
|
||||
r = Attr unknown "bar"
|
||||
|
||||
s = Attr unknown2 { left : Attr Shared "foo" }
|
||||
|
||||
when True is
|
||||
_ -> { y : r }
|
||||
_ -> { y : (unAttr s).left }
|
@ -1,15 +1,34 @@
|
||||
interface Primary
|
||||
exposes [ blah, str ]
|
||||
imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar } ]
|
||||
imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Result ]
|
||||
|
||||
blah = 1
|
||||
blah = {}
|
||||
|
||||
two = 2 # TODO FIXME this should error due to shadowing!
|
||||
str = Dep1.str
|
||||
|
||||
str = foo
|
||||
|
||||
alwaysThree = \_ -> Dep1.three
|
||||
# alwaysThree = \_ -> Dep1.three # TODO FIXME for some reason this infers as a circular type
|
||||
alwaysThree = \_ -> "foo"
|
||||
|
||||
identity = \a -> a
|
||||
|
||||
three = identity (alwaysThree {})
|
||||
# z = identity (alwaysThree {}) # TODO FIXME for some reason this infers as a circular type
|
||||
# z = identity 3 # TODO FIXME for some reason this also infers as a circular type
|
||||
|
||||
z : Dep1.Unit
|
||||
z = Unit
|
||||
|
||||
w : Dep1.Identity {}
|
||||
w = Identity {}
|
||||
|
||||
succeed : a -> Dep1.Identity a
|
||||
succeed = \x -> Identity x
|
||||
|
||||
map = Result.withDefault
|
||||
|
||||
yay : Result.Result e {}
|
||||
yay =
|
||||
v = Ok "foo"
|
||||
|
||||
f = \_ -> {}
|
||||
|
||||
Result.map v f
|
||||
|
@ -4,4 +4,4 @@ interface Principal
|
||||
|
||||
identity = \a -> a
|
||||
|
||||
intVal = identity 5
|
||||
intVal = identity "hi"
|
||||
|
31
tests/fixtures/build/interface_with_deps/Result.roc
vendored
Normal file
31
tests/fixtures/build/interface_with_deps/Result.roc
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
interface Result
|
||||
exposes [ Result, withDefault, map, andThen, ConsList ]
|
||||
imports []
|
||||
|
||||
Result e a : [ Ok a, Err e ]
|
||||
|
||||
ConsList a : [ Cons a (ConsList a), Nil ]
|
||||
|
||||
listMap : ConsList a, (a -> b) -> ConsList b
|
||||
listMap = \list, f ->
|
||||
when list is
|
||||
Nil -> Nil
|
||||
Cons x xs -> Cons (f x) (listMap xs f)
|
||||
|
||||
map : Result e a, (a -> b) -> Result e b
|
||||
map = \result, f ->
|
||||
when result is
|
||||
Ok v -> Ok (f v)
|
||||
Err e -> Err e
|
||||
|
||||
withDefault : Result x a, a -> a
|
||||
withDefault = \result, default ->
|
||||
when result is
|
||||
Ok v -> v
|
||||
Err _ -> default
|
||||
|
||||
andThen : Result e a, (a -> Result e b) -> Result e b
|
||||
andThen = \result, f ->
|
||||
when result is
|
||||
Ok v -> f v
|
||||
Err e -> Err e
|
@ -4,13 +4,13 @@ use self::bumpalo::Bump;
|
||||
use roc::can::env::Env;
|
||||
use roc::can::expr::Output;
|
||||
use roc::can::expr::{canonicalize_expr, Expr};
|
||||
use roc::can::ident::Ident;
|
||||
use roc::can::operator;
|
||||
use roc::can::problem::Problem;
|
||||
use roc::can::scope::Scope;
|
||||
use roc::can::symbol::Symbol;
|
||||
use roc::collections::{ImMap, ImSet, MutMap, SendSet};
|
||||
use roc::constrain::expr::constrain_expr;
|
||||
use roc::ident::Ident;
|
||||
use roc::module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
||||
use roc::parse;
|
||||
use roc::parse::ast::{self, Attempting};
|
||||
use roc::parse::blankspace::space0_before;
|
||||
@ -21,29 +21,8 @@ use roc::types::{Constraint, Expected, Type};
|
||||
use std::hash::Hash;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// Used in the with_larger_debug_stack() function, for tests that otherwise
|
||||
/// run out of stack space in debug builds (but don't in --release builds)
|
||||
#[allow(dead_code)]
|
||||
const EXPANDED_STACK_SIZE: usize = 4 * 1024 * 1024;
|
||||
|
||||
/// Without this, some tests pass in `cargo test --release` but fail without
|
||||
/// the --release flag because they run out of stack space. This increases
|
||||
/// stack size for debug builds only, while leaving the stack space at the default
|
||||
/// amount for release builds.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(debug_assertions)]
|
||||
pub fn with_larger_debug_stack<F>(run_test: F)
|
||||
where
|
||||
F: FnOnce() -> (),
|
||||
F: Send,
|
||||
F: 'static,
|
||||
{
|
||||
std::thread::Builder::new()
|
||||
.stack_size(EXPANDED_STACK_SIZE)
|
||||
.spawn(run_test)
|
||||
.expect("Error while spawning expanded dev stack size thread")
|
||||
.join()
|
||||
.expect("Error while joining expanded dev stack size thread")
|
||||
pub fn test_home() -> ModuleId {
|
||||
ModuleIds::default().get_or_insert(&"Test".into())
|
||||
}
|
||||
|
||||
/// In --release builds, don't increase the stack size. Run the test normally.
|
||||
@ -78,16 +57,25 @@ pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn can_expr(expr_str: &str) -> (Expr, Output, Vec<Problem>, VarStore, Variable, Constraint) {
|
||||
let (loc_expr, output, problems, var_store, var, constraint) =
|
||||
can_expr_with(&Bump::new(), "blah", expr_str, &ImMap::default());
|
||||
|
||||
(loc_expr.value, output, problems, var_store, var, constraint)
|
||||
pub fn can_expr(expr_str: &str) -> CanExprOut {
|
||||
can_expr_with(&Bump::new(), test_home(), expr_str)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn uniq_expr(expr_str: &str) -> (Output, Vec<Problem>, Subs, Variable, Constraint) {
|
||||
uniq_expr_with(&Bump::new(), expr_str, &ImMap::default())
|
||||
pub fn uniq_expr(
|
||||
expr_str: &str,
|
||||
) -> (
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
Variable,
|
||||
Constraint,
|
||||
ModuleId,
|
||||
Interns,
|
||||
) {
|
||||
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
|
||||
|
||||
uniq_expr_with(&Bump::new(), expr_str, declared_idents)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -95,17 +83,32 @@ pub fn uniq_expr_with(
|
||||
arena: &Bump,
|
||||
expr_str: &str,
|
||||
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||
) -> (Output, Vec<Problem>, Subs, Variable, Constraint) {
|
||||
let home = "Test";
|
||||
let (loc_expr, output, problems, var_store1, variable, _) =
|
||||
can_expr_with(arena, home, expr_str, &ImMap::default());
|
||||
) -> (
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
Subs,
|
||||
Variable,
|
||||
Constraint,
|
||||
ModuleId,
|
||||
Interns,
|
||||
) {
|
||||
let home = test_home();
|
||||
let CanExprOut {
|
||||
loc_expr,
|
||||
output,
|
||||
problems,
|
||||
var_store: var_store1,
|
||||
var,
|
||||
interns,
|
||||
..
|
||||
} = can_expr_with(arena, home, expr_str);
|
||||
|
||||
// double check
|
||||
let var_store2 = VarStore::new(var_store1.fresh());
|
||||
|
||||
let expected2 = Expected::NoExpectation(Type::Variable(variable));
|
||||
let expected2 = Expected::NoExpectation(Type::Variable(var));
|
||||
let constraint2 = roc::uniqueness::constrain_declaration(
|
||||
home.into(),
|
||||
home,
|
||||
&var_store2,
|
||||
Region::zero(),
|
||||
loc_expr,
|
||||
@ -115,23 +118,22 @@ pub fn uniq_expr_with(
|
||||
|
||||
let subs2 = Subs::new(var_store2.into());
|
||||
|
||||
(output, problems, subs2, variable, constraint2)
|
||||
(output, problems, subs2, var, constraint2, home, interns)
|
||||
}
|
||||
|
||||
pub struct CanExprOut {
|
||||
pub loc_expr: Located<Expr>,
|
||||
pub output: Output,
|
||||
pub problems: Vec<Problem>,
|
||||
pub home: ModuleId,
|
||||
pub interns: Interns,
|
||||
pub var_store: VarStore,
|
||||
pub var: Variable,
|
||||
pub constraint: Constraint,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn can_expr_with(
|
||||
arena: &Bump,
|
||||
name: &str,
|
||||
expr_str: &str,
|
||||
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
||||
) -> (
|
||||
Located<Expr>,
|
||||
Output,
|
||||
Vec<Problem>,
|
||||
VarStore,
|
||||
Variable,
|
||||
Constraint,
|
||||
) {
|
||||
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
|
||||
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
|
||||
panic!(
|
||||
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
|
||||
@ -140,9 +142,9 @@ pub fn can_expr_with(
|
||||
});
|
||||
|
||||
let var_store = VarStore::default();
|
||||
let variable = var_store.fresh();
|
||||
let expected = Expected::NoExpectation(Type::Variable(variable));
|
||||
let home = "Test";
|
||||
let var = var_store.fresh();
|
||||
let expected = Expected::NoExpectation(Type::Variable(var));
|
||||
let module_ids = ModuleIds::default();
|
||||
|
||||
// Desugar operators (convert them to Apply calls, taking into account
|
||||
// operator precedence and associativity rules), before doing other canonicalization.
|
||||
@ -153,11 +155,10 @@ pub fn can_expr_with(
|
||||
// rules multiple times unnecessarily.
|
||||
let loc_expr = operator::desugar_expr(arena, &loc_expr);
|
||||
|
||||
// If we're canonicalizing the declaration `foo = ...` inside the `Main` module,
|
||||
// scope_prefix will be "Main.foo$" and its first closure will be named "Main.foo$0"
|
||||
let scope_prefix = format!("{}.{}$", home, name).into();
|
||||
let mut scope = Scope::new(home.clone().into(), scope_prefix, declared_idents.clone());
|
||||
let mut env = Env::new(home.into());
|
||||
let mut scope = Scope::new(home);
|
||||
let dep_idents = IdentIds::exposed_builtins();
|
||||
let home_ident_ids = IdentIds::default();
|
||||
let mut env = Env::new(home, dep_idents, &module_ids, home_ident_ids);
|
||||
let (loc_expr, output) = canonicalize_expr(
|
||||
&mut env,
|
||||
&var_store,
|
||||
@ -169,21 +170,38 @@ pub fn can_expr_with(
|
||||
let constraint = constrain_expr(
|
||||
&roc::constrain::expr::Env {
|
||||
rigids: ImMap::default(),
|
||||
module_name: home.into(),
|
||||
home,
|
||||
},
|
||||
loc_expr.region,
|
||||
&loc_expr.value,
|
||||
expected,
|
||||
);
|
||||
|
||||
(
|
||||
let mut all_ident_ids = MutMap::default();
|
||||
|
||||
// When pretty printing types, we may need the exposed builtins,
|
||||
// so include them in the Interns we'll ultimately return.
|
||||
for (module_id, ident_ids) in IdentIds::exposed_builtins() {
|
||||
all_ident_ids.insert(module_id, ident_ids);
|
||||
}
|
||||
|
||||
all_ident_ids.insert(home, env.ident_ids);
|
||||
|
||||
let interns = Interns {
|
||||
module_ids: env.module_ids.clone(),
|
||||
all_ident_ids,
|
||||
};
|
||||
|
||||
CanExprOut {
|
||||
loc_expr,
|
||||
output,
|
||||
env.problems,
|
||||
problems: env.problems,
|
||||
home: env.home,
|
||||
var_store,
|
||||
variable,
|
||||
interns,
|
||||
var,
|
||||
constraint,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
@ -291,7 +309,7 @@ pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
|
||||
let mut used = ImSet::default();
|
||||
variable_usage_help(con, &mut declared, &mut used);
|
||||
|
||||
used.remove(&Variable::unsafe_debug_variable(1));
|
||||
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
|
||||
|
||||
let mut used_vec: Vec<Variable> = used.into_iter().collect();
|
||||
used_vec.sort();
|
||||
@ -315,7 +333,7 @@ fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mu
|
||||
used.insert(v);
|
||||
}
|
||||
}
|
||||
Lookup(_, _, expectation, _) => {
|
||||
Lookup(_, expectation, _) => {
|
||||
for v in expectation.get_type_ref().variables() {
|
||||
used.insert(v);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -10,66 +10,26 @@ mod helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_canonicalize {
|
||||
use crate::helpers::{can_expr_with, with_larger_debug_stack};
|
||||
use crate::helpers::{can_expr_with, test_home, CanExprOut};
|
||||
use bumpalo::Bump;
|
||||
use roc::can::expr::Expr::{self, *};
|
||||
use roc::can::expr::Output;
|
||||
use roc::can::expr::Recursive;
|
||||
use roc::can::problem::Problem;
|
||||
use roc::can::problem::RuntimeError;
|
||||
use roc::can::procedure::References;
|
||||
use roc::can::symbol::Symbol;
|
||||
use roc::collections::{ImMap, ImSet, SendMap};
|
||||
use roc::ident::Ident;
|
||||
use roc::can::problem::{Problem, RuntimeError};
|
||||
use roc::region::{Located, Region};
|
||||
use std::{f64, i64};
|
||||
|
||||
fn sym(name: &str) -> Symbol {
|
||||
Symbol::new("Test.Blah$", name)
|
||||
}
|
||||
|
||||
struct Out<'a> {
|
||||
locals: Vec<&'a str>,
|
||||
globals: Vec<&'a str>,
|
||||
calls: Vec<&'a str>,
|
||||
tail_call: Option<&'a str>,
|
||||
}
|
||||
|
||||
impl<'a> Into<Output> for Out<'a> {
|
||||
fn into(self) -> Output {
|
||||
let references = References {
|
||||
locals: vec_to_set(self.locals),
|
||||
globals: vec_to_set(self.globals),
|
||||
calls: vec_to_set(self.calls),
|
||||
};
|
||||
|
||||
let tail_call = self.tail_call.map(sym);
|
||||
let rigids = SendMap::default();
|
||||
|
||||
Output {
|
||||
references,
|
||||
tail_call,
|
||||
rigids,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn vec_to_set<'a>(vec: Vec<&'a str>) -> ImSet<Symbol> {
|
||||
ImSet::from(vec.into_iter().map(sym).collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn assert_can(input: &str, expected: Expr) {
|
||||
let arena = Bump::new();
|
||||
let (actual, _, _, _, _, _) = can_expr_with(&arena, "Blah", input, &ImMap::default());
|
||||
let actual_out = can_expr_with(&arena, test_home(), input);
|
||||
|
||||
assert_eq!(actual.value, expected);
|
||||
assert_eq!(actual_out.loc_expr.value, expected);
|
||||
}
|
||||
|
||||
fn assert_can_float(input: &str, expected: f64) {
|
||||
let arena = Bump::new();
|
||||
let (loc_actual, _, _, _, _, _) = can_expr_with(&arena, "Blah", input, &ImMap::default());
|
||||
let actual_out = can_expr_with(&arena, test_home(), input);
|
||||
|
||||
match loc_actual.value {
|
||||
match actual_out.loc_expr.value {
|
||||
Expr::Float(_, actual) => {
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
@ -80,9 +40,9 @@ mod test_canonicalize {
|
||||
}
|
||||
fn assert_can_int(input: &str, expected: i64) {
|
||||
let arena = Bump::new();
|
||||
let (loc_actual, _, _, _, _, _) = can_expr_with(&arena, "Blah", input, &ImMap::default());
|
||||
let actual_out = can_expr_with(&arena, test_home(), input);
|
||||
|
||||
match loc_actual.value {
|
||||
match actual_out.loc_expr.value {
|
||||
Expr::Int(_, actual) => {
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
@ -201,68 +161,68 @@ mod test_canonicalize {
|
||||
|
||||
// LOCALS
|
||||
|
||||
#[test]
|
||||
fn closure_args_are_not_locals() {
|
||||
// "arg" shouldn't make it into output.locals, because
|
||||
// it only exists in the closure's arguments.
|
||||
let arena = Bump::new();
|
||||
let src = indoc!(
|
||||
r#"
|
||||
func = \arg -> arg
|
||||
// TODO rewrite this test to check only for UnusedDef reports
|
||||
// #[test]
|
||||
// fn closure_args_are_not_locals() {
|
||||
// // "arg" shouldn't make it into output.locals, because
|
||||
// // it only exists in the closure's arguments.
|
||||
// let arena = Bump::new();
|
||||
// let src = indoc!(
|
||||
// r#"
|
||||
// func = \arg -> arg
|
||||
|
||||
func 2
|
||||
"#
|
||||
);
|
||||
let (_actual, output, problems, _var_store, _vars, _constraint) =
|
||||
can_expr_with(&arena, "Blah", src, &ImMap::default());
|
||||
// func 2
|
||||
// "#
|
||||
// );
|
||||
// let (_actual, output, problems, _var_store, _vars, _constraint) =
|
||||
// can_expr_with(&arena, test_home(), src);
|
||||
|
||||
assert_eq!(problems, vec![]);
|
||||
// assert_eq!(problems, vec![]);
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
Out {
|
||||
locals: vec!["func"],
|
||||
globals: vec![],
|
||||
calls: vec!["func"],
|
||||
tail_call: None
|
||||
}
|
||||
.into()
|
||||
);
|
||||
}
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// lookups: vec!["func"],
|
||||
// calls: vec!["func"],
|
||||
// tail_call: None
|
||||
// }
|
||||
// .into_output(scope)
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn call_by_pointer_for_fn_args() {
|
||||
// This function will get passed in as a pointer.
|
||||
let src = indoc!(
|
||||
r#"
|
||||
apply = \f, x -> f x
|
||||
// TODO rewrite this test to check only for UnusedDef reports
|
||||
// #[test]
|
||||
// fn call_by_pointer_for_fn_args() {
|
||||
// // This function will get passed in as a pointer.
|
||||
// let src = indoc!(
|
||||
// r#"
|
||||
// apply = \f, x -> f x
|
||||
|
||||
identity = \a -> a
|
||||
// identity = \a -> a
|
||||
|
||||
apply identity 5
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let (_actual, output, problems, _var_store, _vars, _constraint) =
|
||||
can_expr_with(&arena, "Blah", src, &ImMap::default());
|
||||
// apply identity 5
|
||||
// "#
|
||||
// );
|
||||
// let arena = Bump::new();
|
||||
// let (_actual, output, problems, _var_store, _vars, _constraint) =
|
||||
// can_expr_with(&arena, test_home(), src);
|
||||
|
||||
assert_eq!(problems, vec![]);
|
||||
// assert_eq!(problems, vec![]);
|
||||
|
||||
assert_eq!(
|
||||
output,
|
||||
Out {
|
||||
locals: vec!["identity", "apply"],
|
||||
globals: vec![],
|
||||
calls: vec!["f", "apply"],
|
||||
tail_call: None
|
||||
}
|
||||
.into()
|
||||
);
|
||||
}
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// lookups: vec!["identity", "apply"],
|
||||
// calls: vec!["f", "apply"],
|
||||
// tail_call: None
|
||||
// }
|
||||
// .into()
|
||||
// );
|
||||
// }
|
||||
|
||||
fn get_closure(expr: &Expr, i: usize) -> roc::can::expr::Recursive {
|
||||
match expr {
|
||||
LetRec(assignments, body, _) => {
|
||||
LetRec(assignments, body, _, _) => {
|
||||
match &assignments.get(i).map(|def| &def.loc_expr.value) {
|
||||
Some(Closure(_, _, recursion, _, _)) => recursion.clone(),
|
||||
Some(other @ _) => {
|
||||
@ -277,7 +237,7 @@ mod test_canonicalize {
|
||||
}
|
||||
}
|
||||
}
|
||||
LetNonRec(def, body, _) => {
|
||||
LetNonRec(def, body, _, _) => {
|
||||
if i > 0 {
|
||||
// recurse in the body (not the def!)
|
||||
get_closure(&body.value, i - 1)
|
||||
@ -291,87 +251,103 @@ mod test_canonicalize {
|
||||
}
|
||||
}
|
||||
// Closure(_, recursion, _, _) if i == 0 => recursion.clone(),
|
||||
_ => panic!("expression is not a Defs, but a {:?}", expr),
|
||||
_ => panic!(
|
||||
"expression is not a LetRec or a LetNonRec, but rather {:?}",
|
||||
expr
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recognize_tail_calls() {
|
||||
with_larger_debug_stack(|| {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
g = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
_ -> g (x - 1)
|
||||
let src = indoc!(
|
||||
r#"
|
||||
g = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
_ -> g (x - 1)
|
||||
|
||||
h = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
_ -> g (x - 1)
|
||||
|
||||
p = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
1 -> g (x - 1)
|
||||
_ -> p (x - 1)
|
||||
h = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
_ -> g (x - 1)
|
||||
|
||||
p = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
1 -> g (x - 1)
|
||||
_ -> p (x - 1)
|
||||
|
||||
|
||||
# variables must be (indirectly) referenced in the body for analysis to work
|
||||
# { x: p, y: h }
|
||||
g
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let (actual, _output, _problems, _var_store, _vars, _constraint) =
|
||||
can_expr_with(&arena, "Blah", src, &ImMap::default());
|
||||
# variables must be (indirectly) referenced in the body for analysis to work
|
||||
# { x: p, y: h }
|
||||
g
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
loc_expr, problems, ..
|
||||
} = can_expr_with(&arena, test_home(), src);
|
||||
|
||||
let detected0 = get_closure(&actual.value, 0);
|
||||
let detected1 = get_closure(&actual.value, 1);
|
||||
let detected2 = get_closure(&actual.value, 2);
|
||||
// There should be two UnusedDef problems: one for h, and one for p
|
||||
assert_eq!(problems.len(), 2);
|
||||
assert!(problems.iter().all(|problem| match problem {
|
||||
Problem::UnusedDef(_, _) => true,
|
||||
_ => false,
|
||||
}));
|
||||
|
||||
assert_eq!(detected0, Recursive::TailRecursive);
|
||||
assert_eq!(detected1, Recursive::NotRecursive);
|
||||
assert_eq!(detected2, Recursive::TailRecursive);
|
||||
});
|
||||
let actual = loc_expr.value;
|
||||
// NOTE: the indices associated with each of these can change!
|
||||
// They come out of a hashmap, and are not sorted.
|
||||
let g_detected = get_closure(&actual, 0);
|
||||
let h_detected = get_closure(&actual, 2);
|
||||
let p_detected = get_closure(&actual, 1);
|
||||
|
||||
assert_eq!(g_detected, Recursive::TailRecursive);
|
||||
assert_eq!(h_detected, Recursive::NotRecursive);
|
||||
assert_eq!(p_detected, Recursive::TailRecursive);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_tail_call() {
|
||||
with_larger_debug_stack(|| {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
let src = indoc!(
|
||||
r#"
|
||||
g = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
_ -> g (x + 1)
|
||||
|
||||
0
|
||||
g 0
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let (actual, _output, _problems, _var_store, _vars, _constraint) =
|
||||
can_expr_with(&arena, "Blah", src, &ImMap::default());
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
loc_expr, problems, ..
|
||||
} = can_expr_with(&arena, test_home(), src);
|
||||
assert_eq!(problems, Vec::new());
|
||||
|
||||
let detected = get_closure(&actual.value, 0);
|
||||
assert_eq!(detected, Recursive::TailRecursive);
|
||||
});
|
||||
let detected = get_closure(&loc_expr.value, 0);
|
||||
assert_eq!(detected, Recursive::TailRecursive);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn immediate_tail_call() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
f = \x -> f x
|
||||
f = \x -> f x
|
||||
|
||||
0
|
||||
"#
|
||||
f 0
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let (actual, _output, _problems, _var_store, _vars, _constraint) =
|
||||
can_expr_with(&arena, "Blah", src, &ImMap::default());
|
||||
let CanExprOut {
|
||||
loc_expr, problems, ..
|
||||
} = can_expr_with(&arena, test_home(), src);
|
||||
|
||||
assert_eq!(problems, Vec::new());
|
||||
|
||||
let detected = get_closure(&loc_expr.value, 0);
|
||||
|
||||
let detected = get_closure(&actual.value, 0);
|
||||
assert_eq!(detected, Recursive::TailRecursive);
|
||||
}
|
||||
|
||||
@ -383,135 +359,144 @@ mod test_canonicalize {
|
||||
when q x is
|
||||
_ -> 0
|
||||
|
||||
0
|
||||
q 0
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let (actual, _output, _problems, _var_store, _vars, _constraint) =
|
||||
can_expr_with(&arena, "Blah", src, &ImMap::default());
|
||||
let CanExprOut {
|
||||
loc_expr, problems, ..
|
||||
} = can_expr_with(&arena, test_home(), src);
|
||||
|
||||
let detected = get_closure(&actual.value, 0);
|
||||
assert_eq!(problems, Vec::new());
|
||||
let detected = get_closure(&loc_expr.value, 0);
|
||||
assert_eq!(detected, Recursive::Recursive);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mutual_recursion() {
|
||||
with_larger_debug_stack(|| {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
q = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
_ -> p (x - 1)
|
||||
fn good_mutual_recursion() {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
q = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
_ -> p (x - 1)
|
||||
|
||||
p = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
_ -> q (x - 1)
|
||||
p = \x ->
|
||||
when x is
|
||||
0 -> 0
|
||||
_ -> q (x - 1)
|
||||
|
||||
p
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let (actual, _output, _problems, _var_store, _vars, _constraint) =
|
||||
can_expr_with(&arena, "Blah", src, &ImMap::default());
|
||||
q p
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
loc_expr, problems, ..
|
||||
} = can_expr_with(&arena, test_home(), src);
|
||||
assert_eq!(problems, Vec::new());
|
||||
|
||||
let detected = get_closure(&actual.value, 0);
|
||||
assert_eq!(detected, Recursive::Recursive);
|
||||
let actual = loc_expr.value;
|
||||
let detected = get_closure(&actual, 0);
|
||||
assert_eq!(detected, Recursive::Recursive);
|
||||
|
||||
let detected = get_closure(&actual.value, 1);
|
||||
assert_eq!(detected, Recursive::Recursive);
|
||||
});
|
||||
let detected = get_closure(&actual, 1);
|
||||
assert_eq!(detected, Recursive::Recursive);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_self_recursion() {
|
||||
with_larger_debug_stack(|| {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
let src = indoc!(
|
||||
r#"
|
||||
boom = \_ -> boom {}
|
||||
|
||||
boom
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let (actual, _output, _problems, _var_store, _vars, _constraint) =
|
||||
can_expr_with(&arena, "Blah", src, &ImMap::default());
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
loc_expr, problems, ..
|
||||
} = can_expr_with(&arena, test_home(), src);
|
||||
|
||||
let is_circular_def =
|
||||
if let RuntimeError(RuntimeError::CircularDef(_, _)) = actual.value {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
assert_eq!(problems, Vec::new());
|
||||
|
||||
assert_eq!(is_circular_def, false);
|
||||
});
|
||||
let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_, _)) = loc_expr.value
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
assert_eq!(is_circular_def, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_self_recursion() {
|
||||
with_larger_debug_stack(|| {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
let src = indoc!(
|
||||
r#"
|
||||
x = x
|
||||
|
||||
x
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let (actual, _output, problems, _var_store, _vars, _constraint) =
|
||||
can_expr_with(&arena, "Blah", src, &ImMap::default());
|
||||
"#
|
||||
);
|
||||
|
||||
let is_circular_def =
|
||||
if let RuntimeError(RuntimeError::CircularDef(_, _)) = actual.value {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
loc_expr, problems, ..
|
||||
} = can_expr_with(&arena, test_home(), src);
|
||||
|
||||
let problem = Problem::CircularAssignment(vec![Located::at(
|
||||
Region::new(0, 0, 0, 1),
|
||||
Ident::Unqualified("x".into()),
|
||||
)]);
|
||||
let is_circular_def = if let RuntimeError(RuntimeError::CircularDef(_, _)) = loc_expr.value
|
||||
{
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
assert_eq!(is_circular_def, true);
|
||||
assert_eq!(problems, vec![problem]);
|
||||
});
|
||||
let problem = Problem::RuntimeError(RuntimeError::CircularDef(
|
||||
vec![Located::at(Region::new(0, 0, 0, 1), "x".into())],
|
||||
vec![(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5))],
|
||||
));
|
||||
|
||||
assert_eq!(is_circular_def, true);
|
||||
assert_eq!(problems, vec![problem]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_mutual_recursion() {
|
||||
with_larger_debug_stack(|| {
|
||||
let src = indoc!(
|
||||
r#"
|
||||
let src = indoc!(
|
||||
r#"
|
||||
x = y
|
||||
y = z
|
||||
z = x
|
||||
|
||||
x
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let (actual, _output, problems, _var_store, _vars, _constraint) =
|
||||
can_expr_with(&arena, "Blah", src, &ImMap::default());
|
||||
"#
|
||||
);
|
||||
let arena = Bump::new();
|
||||
let CanExprOut {
|
||||
loc_expr, problems, ..
|
||||
} = can_expr_with(&arena, test_home(), src);
|
||||
|
||||
let is_circular_def =
|
||||
if let RuntimeError(RuntimeError::CircularDef(_, _)) = actual.value {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let problem = Problem::RuntimeError(RuntimeError::CircularDef(
|
||||
vec![
|
||||
Located::at(Region::new(0, 0, 0, 1), "x".into()),
|
||||
Located::at(Region::new(1, 1, 0, 1), "y".into()),
|
||||
Located::at(Region::new(2, 2, 0, 1), "z".into()),
|
||||
],
|
||||
vec![
|
||||
(Region::new(0, 0, 0, 1), Region::new(0, 0, 4, 5)),
|
||||
(Region::new(1, 1, 0, 1), Region::new(1, 1, 4, 5)),
|
||||
(Region::new(2, 2, 0, 1), Region::new(2, 2, 4, 5)),
|
||||
],
|
||||
));
|
||||
|
||||
let problem = Problem::CircularAssignment(vec![
|
||||
Located::at(Region::new(0, 0, 0, 1), Ident::Unqualified("x".into())),
|
||||
Located::at(Region::new(1, 1, 0, 1), Ident::Unqualified("y".into())),
|
||||
Located::at(Region::new(2, 2, 0, 1), Ident::Unqualified("z".into())),
|
||||
]);
|
||||
assert_eq!(problems, vec![problem]);
|
||||
|
||||
assert_eq!(is_circular_def, true);
|
||||
assert_eq!(problems, vec![problem]);
|
||||
});
|
||||
match loc_expr.value {
|
||||
RuntimeError(RuntimeError::CircularDef(_, _)) => (),
|
||||
actual => {
|
||||
panic!("Expected a CircularDef runtime error, but got {:?}", actual);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//#[test]
|
||||
@ -530,7 +515,7 @@ mod test_canonicalize {
|
||||
|
||||
// assert_eq!(
|
||||
// problems,
|
||||
// vec![Problem::UnusedAssignment(loc(Ident::Unqualified(
|
||||
// vec![Problem::UnusedAssignment(loc((
|
||||
// "unused".to_string()
|
||||
// )))]
|
||||
// );
|
||||
@ -538,8 +523,7 @@ mod test_canonicalize {
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// locals: vec!["func", "local"],
|
||||
// globals: vec![],
|
||||
// lookups: vec!["func", "local"],
|
||||
// calls: vec!["func"],
|
||||
// tail_call: None
|
||||
// }
|
||||
@ -563,16 +547,15 @@ mod test_canonicalize {
|
||||
// assert_eq!(
|
||||
// problems,
|
||||
// vec![
|
||||
// Problem::UnusedAssignment(loc(Ident::Unqualified("unused".to_string()))),
|
||||
// Problem::UnusedAssignment(loc(Ident::Unqualified("func".to_string()))),
|
||||
// Problem::UnusedAssignment(loc(("unused".to_string()))),
|
||||
// Problem::UnusedAssignment(loc(("func".to_string()))),
|
||||
// ]
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// locals: vec!["local"],
|
||||
// globals: vec![],
|
||||
// lookups: vec!["local"],
|
||||
// calls: vec![],
|
||||
// tail_call: None
|
||||
// }
|
||||
@ -580,39 +563,33 @@ mod test_canonicalize {
|
||||
// );
|
||||
//}
|
||||
|
||||
//// UNRECOGNIZED
|
||||
// // UNRECOGNIZED
|
||||
|
||||
//#[test]
|
||||
//fn basic_unrecognized_constant() {
|
||||
// let (expr, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// x
|
||||
// "#
|
||||
// ));
|
||||
// #[test]
|
||||
// fn basic_unrecognized_constant() {
|
||||
// let (expr, output, problems, _) = can_expr(indoc!(
|
||||
// r#"
|
||||
// x
|
||||
// "#
|
||||
// ));
|
||||
|
||||
// assert_eq!(
|
||||
// problems,
|
||||
// vec![Problem::UnrecognizedConstant(loc(Ident::Unqualified(
|
||||
// "x".to_string()
|
||||
// )))]
|
||||
// );
|
||||
// assert_eq!(
|
||||
// problems,
|
||||
// vec![Problem::LookupNotInScope(loc(("x".to_string())))]
|
||||
// );
|
||||
|
||||
// assert_eq!(
|
||||
// expr,
|
||||
// UnrecognizedConstant(loc(Ident::Unqualified("x".to_string())))
|
||||
// );
|
||||
// assert_eq!(expr, LookupNotInScope(loc(("x".to_string()))));
|
||||
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// locals: vec![],
|
||||
// globals: vec![],
|
||||
// calls: vec![],
|
||||
// tail_call: None
|
||||
// }
|
||||
// .into()
|
||||
// );
|
||||
//}
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// lookups: vec![],
|
||||
// calls: vec![],
|
||||
// tail_call: None
|
||||
// }
|
||||
// .into()
|
||||
// );
|
||||
// }
|
||||
|
||||
//#[test]
|
||||
//fn complex_unrecognized_constant() {
|
||||
@ -627,7 +604,7 @@ mod test_canonicalize {
|
||||
|
||||
// assert_eq!(
|
||||
// problems,
|
||||
// vec![Problem::UnrecognizedConstant(loc(Ident::Unqualified(
|
||||
// vec![Problem::LookupNotInScope(loc((
|
||||
// "z".to_string()
|
||||
// )))]
|
||||
// );
|
||||
@ -635,8 +612,7 @@ mod test_canonicalize {
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// locals: vec!["a", "b"],
|
||||
// globals: vec![],
|
||||
// lookups: vec!["a", "b"],
|
||||
// calls: vec![],
|
||||
// tail_call: None
|
||||
// }
|
||||
@ -665,8 +641,7 @@ mod test_canonicalize {
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// locals: vec!["c"],
|
||||
// globals: vec![],
|
||||
// lookups: vec!["c"],
|
||||
// calls: vec![],
|
||||
// tail_call: None
|
||||
// }
|
||||
@ -693,8 +668,7 @@ mod test_canonicalize {
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// locals: vec!["fibonacci"],
|
||||
// globals: vec![],
|
||||
// lookups: vec!["fibonacci"],
|
||||
// calls: vec!["fibonacci"],
|
||||
// tail_call: None
|
||||
// }
|
||||
@ -727,8 +701,7 @@ mod test_canonicalize {
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// locals: vec!["factorial", "factorialHelp"],
|
||||
// globals: vec![],
|
||||
// lookups: vec!["factorial", "factorialHelp"],
|
||||
// calls: vec!["factorial", "factorialHelp"],
|
||||
// tail_call: None
|
||||
// }
|
||||
@ -754,8 +727,7 @@ mod test_canonicalize {
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// locals: vec!["a", "b"],
|
||||
// globals: vec![],
|
||||
// lookups: vec!["a", "b"],
|
||||
// calls: vec![],
|
||||
// tail_call: None
|
||||
// }
|
||||
@ -783,8 +755,7 @@ mod test_canonicalize {
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// locals: vec!["increment", "x", "y", "z"],
|
||||
// globals: vec![],
|
||||
// lookups: vec!["increment", "x", "y", "z"],
|
||||
// calls: vec!["increment"],
|
||||
// tail_call: None
|
||||
// }
|
||||
@ -823,8 +794,7 @@ mod test_canonicalize {
|
||||
// assert_eq!(
|
||||
// output,
|
||||
// Out {
|
||||
// locals: vec!["func1", "func2", "x", "y", "z"],
|
||||
// globals: vec![],
|
||||
// lookups: vec!["func1", "func2", "x", "y", "z"],
|
||||
// calls: vec!["func1", "func2"],
|
||||
// tail_call: None
|
||||
// }
|
||||
|
@ -10,8 +10,8 @@ extern crate roc;
|
||||
mod helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_emit {
|
||||
use crate::helpers::can_expr;
|
||||
mod test_crane {
|
||||
use crate::helpers::{can_expr, CanExprOut};
|
||||
use bumpalo::Bump;
|
||||
use cranelift::prelude::{AbiParam, ExternalName, FunctionBuilder, FunctionBuilderContext};
|
||||
use cranelift_codegen::ir::InstBuilder;
|
||||
@ -44,10 +44,10 @@ mod test_emit {
|
||||
let mut ctx = module.make_context();
|
||||
let mut func_ctx = FunctionBuilderContext::new();
|
||||
|
||||
let (expr, _output, _problems, var_store, variable, constraint) = can_expr($src);
|
||||
let CanExprOut { loc_expr, var_store, var, constraint, .. } = can_expr($src);
|
||||
let subs = Subs::new(var_store.into());
|
||||
let mut unify_problems = Vec::new();
|
||||
let (content, solved) = infer_expr(subs, &mut unify_problems, &constraint, variable);
|
||||
let (content, solved) = infer_expr(subs, &mut unify_problems, &constraint, var);
|
||||
let shared_builder = settings::builder();
|
||||
let shared_flags = settings::Flags::new(shared_builder);
|
||||
let cfg = match isa::lookup(HOST) {
|
||||
@ -79,7 +79,7 @@ mod test_emit {
|
||||
};
|
||||
|
||||
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
|
||||
let mono_expr = Expr::new(&arena, &env.subs, expr, &mut procs);
|
||||
let mono_expr = Expr::new(&arena, &env.subs, loc_expr.value, &mut procs);
|
||||
let mut scope = ImMap::default();
|
||||
let mut declared = Vec::with_capacity(procs.len());
|
||||
|
||||
@ -169,10 +169,10 @@ mod test_emit {
|
||||
macro_rules! assert_llvm_evals_to {
|
||||
($src:expr, $expected:expr, $ty:ty) => {
|
||||
let arena = Bump::new();
|
||||
let (expr, _output, _problems, var_store, variable, constraint) = can_expr($src);
|
||||
let CanExprOut { loc_expr, var_store, var, constraint, .. } = can_expr($src);
|
||||
let subs = Subs::new(var_store.into());
|
||||
let mut unify_problems = Vec::new();
|
||||
let (content, solved) = infer_expr(subs, &mut unify_problems, &constraint, variable);
|
||||
let (content, solved) = infer_expr(subs, &mut unify_problems, &constraint, var);
|
||||
|
||||
let context = Context::create();
|
||||
let module = context.create_module("app");
|
||||
@ -213,7 +213,7 @@ mod test_emit {
|
||||
};
|
||||
|
||||
// Populate Procs and get the low-level Expr from the canonical Expr
|
||||
let main_body = Expr::new(&arena, &env.subs, expr, &mut procs);
|
||||
let main_body = Expr::new(&arena, &env.subs, loc_expr.value, &mut procs);
|
||||
|
||||
// Add all the Procs to the module
|
||||
for (name, opt_proc) in procs.clone() {
|
||||
|
@ -1548,6 +1548,126 @@ mod test_format {
|
||||
));
|
||||
}
|
||||
|
||||
// ACCESSOR
|
||||
|
||||
#[test]
|
||||
fn accessor() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
.id
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
user.name
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
(getUser userId users).name
|
||||
"#
|
||||
));
|
||||
}
|
||||
// UNARY OP
|
||||
|
||||
#[test]
|
||||
fn unary_op() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
y = -4
|
||||
|
||||
!x
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
// BINARY OP
|
||||
|
||||
#[test]
|
||||
fn binary_op() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
1 == 1
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
2 != 3
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
2 != 3
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
isLast
|
||||
&& isEmpty
|
||||
&& isLoaded
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
1
|
||||
* 2
|
||||
/ 3
|
||||
// 4
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
1
|
||||
* 2
|
||||
/ 3
|
||||
// 4
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
2 % 3
|
||||
%% 5
|
||||
+ 7
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
2
|
||||
% 3
|
||||
%% 5
|
||||
+ 7
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
isGreenLight
|
||||
&& isRedLight && isYellowLight
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
isGreenLight
|
||||
&& isRedLight
|
||||
&& isYellowLight
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// MODULES
|
||||
|
||||
#[test]
|
||||
|
@ -10,7 +10,7 @@ mod helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_infer {
|
||||
use crate::helpers::{assert_correct_variable_usage, can_expr, with_larger_debug_stack};
|
||||
use crate::helpers::{assert_correct_variable_usage, can_expr, CanExprOut};
|
||||
use roc::infer::infer_expr;
|
||||
use roc::pretty_print_types::{content_to_string, name_all_type_vars};
|
||||
use roc::subs::Subs;
|
||||
@ -18,25 +18,34 @@ mod test_infer {
|
||||
// HELPERS
|
||||
|
||||
fn infer_eq_help(src: &str) -> (Vec<roc::types::Problem>, String) {
|
||||
let (_expr, output, _, var_store, variable, constraint) = can_expr(src);
|
||||
let CanExprOut {
|
||||
output,
|
||||
var_store,
|
||||
var,
|
||||
constraint,
|
||||
home,
|
||||
interns,
|
||||
..
|
||||
} = can_expr(src);
|
||||
let mut subs = Subs::new(var_store.into());
|
||||
|
||||
assert_correct_variable_usage(&constraint);
|
||||
|
||||
for (var, name) in output.rigids {
|
||||
for (name, var) in output.rigids {
|
||||
subs.rigid_var(var, name);
|
||||
}
|
||||
|
||||
let mut unify_problems = Vec::new();
|
||||
let (content, solved) = infer_expr(subs, &mut unify_problems, &constraint, variable);
|
||||
let (content, solved) = infer_expr(subs, &mut unify_problems, &constraint, var);
|
||||
let mut subs = solved.into_inner();
|
||||
|
||||
name_all_type_vars(variable, &mut subs);
|
||||
name_all_type_vars(var, &mut subs);
|
||||
|
||||
let actual_str = content_to_string(content, &mut subs);
|
||||
let actual_str = content_to_string(content, &mut subs, home, &interns);
|
||||
|
||||
(unify_problems, actual_str)
|
||||
}
|
||||
|
||||
fn infer_eq(src: &str, expected: &str) {
|
||||
let (_, actual) = infer_eq_help(src);
|
||||
|
||||
@ -49,9 +58,7 @@ mod test_infer {
|
||||
if !problems.is_empty() {
|
||||
// fail with an assert, but print the problems normally so rust doesn't try to diff
|
||||
// an empty vec with the problems.
|
||||
println!("expected:\n{:?}\ninfered:\n{:?}", expected, actual);
|
||||
dbg!(&problems);
|
||||
assert_eq!(0, 1);
|
||||
panic!("expected:\n{:?}\ninferred:\n{:?}", expected, actual);
|
||||
}
|
||||
assert_eq!(actual, expected.to_string());
|
||||
}
|
||||
@ -891,10 +898,9 @@ mod test_infer {
|
||||
|
||||
#[test]
|
||||
fn record_with_bound_var() {
|
||||
with_larger_debug_stack(|| {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
fn = \rec ->
|
||||
x = rec.x
|
||||
|
||||
@ -902,10 +908,9 @@ mod test_infer {
|
||||
|
||||
fn
|
||||
"#
|
||||
),
|
||||
"{ x : a }b -> { x : a }b",
|
||||
);
|
||||
});
|
||||
),
|
||||
"{ x : a }b -> { x : a }b",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -928,12 +933,12 @@ mod test_infer {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
foo: Int -> Bool
|
||||
foo: Str -> {}
|
||||
|
||||
foo 2
|
||||
foo "hi"
|
||||
"#
|
||||
),
|
||||
"Bool",
|
||||
"{}",
|
||||
);
|
||||
}
|
||||
|
||||
@ -961,25 +966,12 @@ mod test_infer {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x, y } : { x : (Int -> custom) , y : Int }
|
||||
{ x, y } : { x : ({} -> custom), y : {} }
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
"Int -> custom",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_pattern_match_infer() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
when foo is
|
||||
{ x: 4 }-> x
|
||||
"#
|
||||
),
|
||||
"Int",
|
||||
"{} -> custom",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1059,7 +1051,7 @@ mod test_infer {
|
||||
r#"\@Foo -> 42
|
||||
"#
|
||||
),
|
||||
"[ Test.@Foo ]* -> Int",
|
||||
"[ @Foo ]* -> Int",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1095,26 +1087,24 @@ mod test_infer {
|
||||
r#"@Foo "happy" 2020
|
||||
"#
|
||||
),
|
||||
"[ Test.@Foo Str Int ]*",
|
||||
"[ @Foo Str Int ]*",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_extraction() {
|
||||
with_larger_debug_stack(|| {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
f = \x ->
|
||||
when x is
|
||||
{ a, b } -> a
|
||||
|
||||
f
|
||||
"#
|
||||
),
|
||||
"{ a : a, b : * }* -> a",
|
||||
);
|
||||
});
|
||||
),
|
||||
"{ a : a, b : * }* -> a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1159,11 +1149,11 @@ mod test_infer {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
when Foo 4 is
|
||||
when Foo "blah" is
|
||||
Foo x -> x
|
||||
"#
|
||||
),
|
||||
"Int",
|
||||
"Str",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1172,11 +1162,11 @@ mod test_infer {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
when @Foo 4 is
|
||||
when @Foo "blah" is
|
||||
@Foo x -> x
|
||||
"#
|
||||
),
|
||||
"Int",
|
||||
"Str",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1195,8 +1185,9 @@ mod test_infer {
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore] // TODO FIXME un-ignore this when Int is a builtin type alias
|
||||
fn annotation_using_num_used() {
|
||||
// There was a problem where `int`, because it is only an annotation
|
||||
// There was a problem where `Int`, because it is only an annotation
|
||||
// wasn't added to the vars_by_symbol.
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
@ -1337,7 +1328,7 @@ mod test_infer {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_map() {
|
||||
fn result_map_explicit() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
@ -1354,6 +1345,26 @@ mod test_infer {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_map_alias() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
Result e a : [ Ok a, Err e ]
|
||||
|
||||
map : (a -> b), Result e a -> Result e b
|
||||
map = \f, result ->
|
||||
when result is
|
||||
Ok v -> Ok (f v)
|
||||
Err e -> Err e
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"(a -> b), Result e a -> Result e b",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_from_load() {
|
||||
infer_eq_without_problem(
|
||||
@ -1393,13 +1404,64 @@ mod test_infer {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
foo : Str.Str as Foo -> Test.Foo
|
||||
foo : Str.Str as Foo -> Foo
|
||||
foo = \x -> "foo"
|
||||
|
||||
foo
|
||||
"#
|
||||
),
|
||||
"Foo -> Foo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_alias_in_let() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo : Str.Str
|
||||
|
||||
foo : Foo -> Foo
|
||||
foo = \x -> "foo"
|
||||
|
||||
foo
|
||||
"#
|
||||
),
|
||||
"Test.Foo -> Test.Foo",
|
||||
"Foo -> Foo",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn use_alias_with_argument_in_let() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo a : { foo : a }
|
||||
|
||||
v : Foo (Num.Num Int.Integer)
|
||||
v = { foo: 42 }
|
||||
|
||||
v
|
||||
"#
|
||||
),
|
||||
"Foo Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_alias() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo a : { foo : a }
|
||||
|
||||
id : Foo a -> Foo a
|
||||
id = \x -> x
|
||||
|
||||
id
|
||||
"#
|
||||
),
|
||||
"Foo a -> Foo a",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1414,36 +1476,51 @@ mod test_infer {
|
||||
empty
|
||||
"#
|
||||
),
|
||||
"Test.List a",
|
||||
"List a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linked_list_singleton() {
|
||||
with_larger_debug_stack(|| {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
singleton : a -> [ Cons a (Test.List a), Nil ] as List a
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
singleton : a -> [ Cons a (List a), Nil ] as List a
|
||||
singleton = \x -> Cons x Nil
|
||||
|
||||
singleton
|
||||
"#
|
||||
),
|
||||
"a -> Test.List a",
|
||||
);
|
||||
});
|
||||
),
|
||||
"a -> List a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peano_length() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
Peano : [ S Peano, Z ]
|
||||
|
||||
length : Peano -> Num.Num Int.Integer
|
||||
length = \peano ->
|
||||
when peano is
|
||||
Z -> 0
|
||||
S v -> length v
|
||||
|
||||
length
|
||||
"#
|
||||
),
|
||||
"Peano -> Int",
|
||||
);
|
||||
}
|
||||
|
||||
// currently fails, cyclic type
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn peano_map() {
|
||||
with_larger_debug_stack(|| {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
map : [ S Test.Peano, Z ] as Peano -> Test.Peano
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
map : [ S Peano, Z ] as Peano -> Peano
|
||||
map = \peano ->
|
||||
when peano is
|
||||
Z -> Z
|
||||
@ -1451,22 +1528,16 @@ mod test_infer {
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"Test.Peano",
|
||||
);
|
||||
});
|
||||
),
|
||||
"Peano -> Peano",
|
||||
);
|
||||
}
|
||||
|
||||
// currently fails, cyclic type
|
||||
// ending in `List b` will currently give a rigid unification error
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn linked_list_map() {
|
||||
with_larger_debug_stack(|| {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
map : (a -> a), [ Cons a (Test.List a), Nil ] as List a -> Test.List a
|
||||
fn infer_linked_list_map() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
map = \f, list ->
|
||||
when list is
|
||||
Nil -> Nil
|
||||
@ -1478,9 +1549,362 @@ mod test_infer {
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * Int",
|
||||
);
|
||||
});
|
||||
),
|
||||
"(a -> b), [ Cons a c, Nil ]* as c -> [ Cons b d, Nil ]* as d",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typecheck_linked_list_map() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
List a : [ Cons a (List a), Nil ]
|
||||
|
||||
map : (a -> b), List a -> List b
|
||||
map = \f, list ->
|
||||
when list is
|
||||
Nil -> Nil
|
||||
Cons x xs ->
|
||||
a = f x
|
||||
b = map f xs
|
||||
|
||||
Cons a b
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"(a -> b), List a -> List b",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatch_in_alias_args_gets_reported() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo a : a
|
||||
|
||||
r : Foo {}
|
||||
r = {}
|
||||
|
||||
s : Foo Str.Str
|
||||
s = "bar"
|
||||
|
||||
when {} is
|
||||
_ -> s
|
||||
_ -> r
|
||||
"#
|
||||
),
|
||||
"<type mismatch>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatch_in_apply_gets_reported() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r : { x : (Num.Num Int.Integer) }
|
||||
r = { x : 1 }
|
||||
|
||||
s : { left : { x : Num.Num Float.FloatingPoint } }
|
||||
s = { left: { x : 3.14 } }
|
||||
|
||||
when 0 is
|
||||
1 -> s.left
|
||||
0 -> r
|
||||
"#
|
||||
),
|
||||
"<type mismatch>",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatch_in_tag_gets_reported() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r : [ Ok Str.Str ]
|
||||
r = Ok 1
|
||||
|
||||
s : { left: [ Ok {} ] }
|
||||
s = { left: Ok 3.14 }
|
||||
|
||||
when 0 is
|
||||
1 -> s.left
|
||||
0 -> r
|
||||
"#
|
||||
),
|
||||
"<type mismatch>",
|
||||
);
|
||||
}
|
||||
// doesn't currently print the error, but does report a problem
|
||||
// #[test]
|
||||
// fn nums() {
|
||||
// infer_eq_without_problem(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// s : Num.Num a
|
||||
// s = 3.1
|
||||
//
|
||||
// s
|
||||
// "#
|
||||
// ),
|
||||
// "<type mismatch>",
|
||||
// );
|
||||
// }
|
||||
//
|
||||
#[test]
|
||||
fn manual_attr() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = Attr unknown "bar"
|
||||
|
||||
s = Attr unknown2 { left : Attr Shared "foo" }
|
||||
|
||||
when True is
|
||||
_ -> { x : ((\Attr _ val -> val) s).left, y : r }
|
||||
_ -> { x : ((\Attr _ val -> val) s).left, y : ((\Attr _ val -> val) s).left }
|
||||
"#
|
||||
),
|
||||
"{ x : [ Attr [ Shared ]* Str ]*, y : [ Attr [ Shared ]* Str ]* }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peano_map_alias() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
Peano : [ S Peano, Z ]
|
||||
|
||||
map : Peano -> Peano
|
||||
map = \peano ->
|
||||
when peano is
|
||||
Z -> Z
|
||||
S rest ->
|
||||
map rest |> S
|
||||
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"Peano -> Peano",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rigid_in_letnonrec() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
List a : [ Cons a (List a), Nil ]
|
||||
|
||||
toEmpty : List a -> List a
|
||||
toEmpty = \_ ->
|
||||
result : List a
|
||||
result = Nil
|
||||
|
||||
result
|
||||
|
||||
toEmpty
|
||||
"#
|
||||
),
|
||||
"List a -> List a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rigid_in_letrec() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
List a : [ Cons a (List a), Nil ]
|
||||
|
||||
toEmpty : List a -> List a
|
||||
toEmpty = \_ ->
|
||||
result : List a
|
||||
result = Nil
|
||||
|
||||
toEmpty result
|
||||
|
||||
toEmpty
|
||||
"#
|
||||
),
|
||||
"List a -> List a",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_record_pattern_with_annotation() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x, y } : { x : Str.Str, y : Num.Num Float.FloatingPoint }
|
||||
{ x, y } = { x : "foo", y : 3.14 }
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
"Str",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_record_pattern_with_annotation_alias() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo : { x : Str.Str, y : Num.Num Float.FloatingPoint }
|
||||
|
||||
{ x, y } : Foo
|
||||
{ x, y } = { x : "foo", y : 3.14 }
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
"Str",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peano_map_infer() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
map = \peano ->
|
||||
when peano is
|
||||
Z -> Z
|
||||
S rest ->
|
||||
map rest |> S
|
||||
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"[ S a, Z ]* as a -> [ S b, Z ]* as b",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_record_pattern_with_alias_annotation() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo : { x : Str.Str, y : Num.Num Float.FloatingPoint }
|
||||
|
||||
{ x, y } : Foo
|
||||
{ x, y } = { x : "foo", y : 3.14 }
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
"Str",
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn let_tag_pattern_with_annotation() {
|
||||
// infer_eq_without_problem(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// UserId x : [ UserId Int ]
|
||||
// UserId x = UserId 42
|
||||
//
|
||||
// x
|
||||
// "#
|
||||
// ),
|
||||
// "Int",
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn typecheck_record_linked_list_map() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
List q : [ Cons { x: q, xs: List q }, Nil ]
|
||||
|
||||
map : (a -> b), List a -> List b
|
||||
map = \f, list ->
|
||||
when list is
|
||||
Nil -> Nil
|
||||
Cons { x, xs } ->
|
||||
Cons { x: f x, xs : map f xs }
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"(a -> b), List a -> List b",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn infer_record_linked_list_map() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
map = \f, list ->
|
||||
when list is
|
||||
Nil -> Nil
|
||||
Cons { x, xs } ->
|
||||
Cons { x: f x, xs : map f xs }
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"(a -> b), [ Cons { x : a, xs : c }*, Nil ]* as c -> [ Cons { x : b, xs : d }, Nil ]* as d",
|
||||
);
|
||||
}
|
||||
|
||||
// infinite loop in type_to_var
|
||||
// #[test]
|
||||
// fn typecheck_mutually_recursive_tag_union() {
|
||||
// infer_eq_without_problem(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// ListA a b : [ Cons a (ListB b a), Nil ]
|
||||
// ListB a b : [ Cons a (ListA b a), Nil ]
|
||||
//
|
||||
// List q : [ Cons q (List q), Nil ]
|
||||
//
|
||||
// toAs : (b -> a), ListA a b -> List a
|
||||
// toAs = \f, lista ->
|
||||
// when lista is
|
||||
// Nil -> Nil
|
||||
// Cons a listb ->
|
||||
// when listb is
|
||||
// Nil -> Nil
|
||||
// Cons b newLista ->
|
||||
// Cons a (Cons (f b) (toAs f newLista))
|
||||
//
|
||||
// toAs
|
||||
// "#
|
||||
// ),
|
||||
// "(b -> a), ListA a b -> List a",
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn infer_mutually_recursive_tag_union() {
|
||||
infer_eq_without_problem(
|
||||
indoc!(
|
||||
r#"
|
||||
toAs = \f, lista ->
|
||||
when lista is
|
||||
Nil -> Nil
|
||||
Cons a listb ->
|
||||
when listb is
|
||||
Nil -> Nil
|
||||
Cons b newLista ->
|
||||
Cons a (Cons (f b) (toAs f newLista))
|
||||
|
||||
toAs
|
||||
"#
|
||||
),
|
||||
"(a -> b), [ Cons c [ Cons a d, Nil ]*, Nil ]* as d -> [ Cons c [ Cons b e ]*, Nil ]* as e"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -13,19 +13,15 @@ mod helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_load {
|
||||
use crate::helpers::{builtins_dir, fixtures_dir};
|
||||
use crate::helpers::fixtures_dir;
|
||||
use inlinable_string::InlinableString;
|
||||
use roc::can::def::Declaration::*;
|
||||
use roc::collections::MutMap;
|
||||
use roc::load::{load, LoadedModule};
|
||||
use roc::module::symbol::ModuleId;
|
||||
use roc::pretty_print_types::{content_to_string, name_all_type_vars};
|
||||
use roc::solve::ModuleSubs;
|
||||
use roc::solve::SubsByModule;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// TODO change solve::SubsByModule to be this
|
||||
type SubsByModule = MutMap<ModuleId, ModuleSubs>;
|
||||
|
||||
// HELPERS
|
||||
|
||||
fn test_async<F: std::future::Future>(future: F) -> F::Output {
|
||||
@ -50,7 +46,7 @@ mod test_load {
|
||||
async fn load_without_builtins(
|
||||
dir_name: &str,
|
||||
module_name: &str,
|
||||
subs_by_module: &mut SubsByModule,
|
||||
subs_by_module: SubsByModule,
|
||||
) -> LoadedModule {
|
||||
let src_dir = fixtures_dir().join(dir_name);
|
||||
let filename = src_dir.join(format!("{}.roc", module_name));
|
||||
@ -60,6 +56,7 @@ mod test_load {
|
||||
assert_eq!(loaded_module.problems, Vec::new());
|
||||
|
||||
let expected_name = loaded_module
|
||||
.interns
|
||||
.module_ids
|
||||
.get_name(loaded_module.module_id)
|
||||
.expect("Test ModuleID not found in module_ids");
|
||||
@ -72,7 +69,7 @@ mod test_load {
|
||||
// async fn load_with_builtins(
|
||||
// dir_name: &str,
|
||||
// module_name: &str,
|
||||
// subs_by_module: &mut SubsByModule,
|
||||
// subs_by_module: SubsByModule,
|
||||
// ) -> LoadedModule {
|
||||
// load_builtins(subs_by_module).await;
|
||||
|
||||
@ -91,10 +88,12 @@ mod test_load {
|
||||
// }
|
||||
|
||||
fn expect_types(loaded_module: LoadedModule, expected_types: HashMap<&str, &str>) {
|
||||
let home = loaded_module.module_id;
|
||||
let mut subs = loaded_module.solved.into_inner();
|
||||
|
||||
assert_eq!(loaded_module.problems, Vec::new());
|
||||
assert_eq!(expected_types.len(), loaded_module.declarations.len());
|
||||
|
||||
let num_decls = loaded_module.declarations.len();
|
||||
|
||||
for decl in loaded_module.declarations {
|
||||
let def = match decl {
|
||||
@ -115,26 +114,35 @@ mod test_load {
|
||||
|
||||
name_all_type_vars(expr_var, &mut subs);
|
||||
|
||||
let actual_str = content_to_string(content, &mut subs);
|
||||
let expected_type = expected_types
|
||||
.get(symbol.as_str())
|
||||
.unwrap_or_else(|| panic!("Defs included an unexpected symbol: {:?}", symbol));
|
||||
let actual_str =
|
||||
content_to_string(content, &mut subs, home, &loaded_module.interns);
|
||||
let fully_qualified = symbol
|
||||
.fully_qualified(&loaded_module.interns, home)
|
||||
.to_string();
|
||||
let expected_type =
|
||||
expected_types
|
||||
.get(fully_qualified.as_str())
|
||||
.unwrap_or_else(|| {
|
||||
panic!("Defs included an unexpected symbol: {:?}", fully_qualified)
|
||||
});
|
||||
|
||||
assert_eq!((&symbol, expected_type), (&symbol, &actual_str.as_str()));
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(expected_types.len(), num_decls);
|
||||
}
|
||||
|
||||
// TESTS
|
||||
|
||||
#[test]
|
||||
fn interface_with_deps() {
|
||||
let mut subs_by_module = MutMap::default();
|
||||
let subs_by_module = MutMap::default();
|
||||
let src_dir = fixtures_dir().join("interface_with_deps");
|
||||
let filename = src_dir.join("Primary.roc");
|
||||
|
||||
test_async(async {
|
||||
let loaded = load(src_dir, filename, &mut subs_by_module).await;
|
||||
let loaded = load(src_dir, filename, subs_by_module).await;
|
||||
let loaded_module = loaded.expect("Test module failed to load");
|
||||
assert_eq!(loaded_module.problems, Vec::new());
|
||||
|
||||
@ -145,60 +153,63 @@ mod test_load {
|
||||
.sum();
|
||||
|
||||
let expected_name = loaded_module
|
||||
.interns
|
||||
.module_ids
|
||||
.get_name(loaded_module.module_id)
|
||||
.expect("Test ModuleID not found in module_ids");
|
||||
|
||||
assert_eq!(expected_name, &InlinableString::from("Primary"));
|
||||
assert_eq!(def_count, 6);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_only_builtins() {
|
||||
let mut subs_by_module = MutMap::default();
|
||||
let src_dir = builtins_dir();
|
||||
let filename = src_dir.join("Defaults.roc");
|
||||
|
||||
test_async(async {
|
||||
let loaded = load(src_dir, filename, &mut subs_by_module).await;
|
||||
let loaded_module = loaded.expect("Test module failed to load");
|
||||
assert_eq!(loaded_module.problems, Vec::new());
|
||||
|
||||
let def_count: usize = loaded_module
|
||||
.declarations
|
||||
.iter()
|
||||
.map(|decl| decl.def_count())
|
||||
.sum();
|
||||
|
||||
let module_ids = loaded_module.module_ids;
|
||||
let expected_name = module_ids
|
||||
.get_name(loaded_module.module_id)
|
||||
.expect("Test ModuleID not found in module_ids");
|
||||
|
||||
assert_eq!(expected_name, &InlinableString::from("Defaults"));
|
||||
assert_eq!(def_count, 0);
|
||||
|
||||
let mut all_loaded_modules: Vec<InlinableString> = subs_by_module
|
||||
.keys()
|
||||
.map(|module_id| module_ids.get_name(*module_id).unwrap().clone())
|
||||
.collect();
|
||||
|
||||
let expected: Vec<InlinableString> =
|
||||
vec!["Float".into(), "Int".into(), "Map".into(), "Set".into()];
|
||||
|
||||
all_loaded_modules.sort();
|
||||
|
||||
assert_eq!(all_loaded_modules, expected);
|
||||
assert_eq!(def_count, 9);
|
||||
});
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn load_only_builtins() {
|
||||
// let subs_by_module = MutMap::default();
|
||||
// let src_dir = builtins_dir();
|
||||
// let filename = src_dir.join("Defaults.roc");
|
||||
|
||||
// test_async(async {
|
||||
// let module_ids_to_load: Vec<ModuleId> =
|
||||
// subs_by_module.keys().map(|module_id| *module_id).collect();
|
||||
// let loaded = load(src_dir, filename, subs_by_module).await;
|
||||
// let loaded_module = loaded.expect("Test module failed to load");
|
||||
// assert_eq!(loaded_module.problems, Vec::new());
|
||||
|
||||
// let def_count: usize = loaded_module
|
||||
// .declarations
|
||||
// .iter()
|
||||
// .map(|decl| decl.def_count())
|
||||
// .sum();
|
||||
|
||||
// let module_ids = loaded_module.interns.module_ids;
|
||||
// let expected_name = module_ids
|
||||
// .get_name(loaded_module.module_id)
|
||||
// .expect("Test ModuleID not found in module_ids");
|
||||
|
||||
// assert_eq!(expected_name, &InlinableString::from("Defaults"));
|
||||
// assert_eq!(def_count, 0);
|
||||
|
||||
// let mut all_loaded_modules: Vec<InlinableString> = module_ids_to_load
|
||||
// .iter()
|
||||
// .map(|&module_id| module_ids.get_name(module_id).unwrap().clone())
|
||||
// .collect();
|
||||
|
||||
// let expected: Vec<InlinableString> =
|
||||
// vec!["Float".into(), "Int".into(), "Map".into(), "Set".into()];
|
||||
|
||||
// all_loaded_modules.sort();
|
||||
|
||||
// assert_eq!(all_loaded_modules, expected);
|
||||
// });
|
||||
// }
|
||||
|
||||
// #[test]
|
||||
// fn interface_with_builtins() {
|
||||
// test_async(async {
|
||||
// let mut subs_by_module = MutMap::default();
|
||||
// let subs_by_module = MutMap::default();
|
||||
// let loaded_module =
|
||||
// load_with_builtins("interface_with_deps", "WithBuiltins", &mut subs_by_module)
|
||||
// load_with_builtins("interface_with_deps", "WithBuiltins", subs_by_module)
|
||||
// .await;
|
||||
|
||||
// assert_eq!(loaded_module.problems, Vec::new());
|
||||
@ -242,22 +253,22 @@ mod test_load {
|
||||
// #[test]
|
||||
// fn load_and_infer_with_builtins() {
|
||||
// test_async(async {
|
||||
// let mut subs_by_module = MutMap::default();
|
||||
// let subs_by_module = MutMap::default();
|
||||
// let loaded_module =
|
||||
// load_with_builtins("interface_with_deps", "WithBuiltins", &mut subs_by_module)
|
||||
// load_with_builtins("interface_with_deps", "WithBuiltins", subs_by_module)
|
||||
// .await;
|
||||
|
||||
// expect_types(
|
||||
// loaded_module,
|
||||
// hashmap! {
|
||||
// "WithBuiltins.floatTest" => "Float",
|
||||
// "WithBuiltins.divisionFn" => "Float, Float -> Float",
|
||||
// "WithBuiltins.divisionTest" => "Float",
|
||||
// "WithBuiltins.intTest" => "Int",
|
||||
// "WithBuiltins.x" => "Float",
|
||||
// "WithBuiltins.constantInt" => "Int",
|
||||
// "WithBuiltins.divDep1ByDep2" => "Float",
|
||||
// "WithBuiltins.fromDep2" => "Float",
|
||||
// "floatTest" => "Float",
|
||||
// "divisionFn" => "Float, Float -> Float",
|
||||
// "divisionTest" => "Float",
|
||||
// "intTest" => "Int",
|
||||
// "x" => "Float",
|
||||
// "constantInt" => "Int",
|
||||
// "divDep1ByDep2" => "Float",
|
||||
// "fromDep2" => "Float",
|
||||
// },
|
||||
// );
|
||||
// });
|
||||
@ -266,16 +277,39 @@ mod test_load {
|
||||
#[test]
|
||||
fn load_principal_types() {
|
||||
test_async(async {
|
||||
let mut subs_by_module = MutMap::default();
|
||||
let subs_by_module = MutMap::default();
|
||||
let loaded_module =
|
||||
load_without_builtins("interface_with_deps", "Principal", &mut subs_by_module)
|
||||
.await;
|
||||
load_without_builtins("interface_with_deps", "Principal", subs_by_module).await;
|
||||
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"Principal.intVal" => "Int",
|
||||
"Principal.identity" => "a -> a",
|
||||
"intVal" => "Str",
|
||||
"identity" => "a -> a",
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_dep_types() {
|
||||
test_async(async {
|
||||
let subs_by_module = MutMap::default();
|
||||
let loaded_module =
|
||||
load_without_builtins("interface_with_deps", "Primary", subs_by_module).await;
|
||||
|
||||
expect_types(
|
||||
loaded_module,
|
||||
hashmap! {
|
||||
"blah" => "{}",
|
||||
"str" => "Str",
|
||||
"alwaysThree" => "* -> Str",
|
||||
"identity" => "a -> a",
|
||||
"z" => "Dep1.Unit",
|
||||
"w" => "Dep1.Identity {}",
|
||||
"succeed" => "a -> Dep1.Identity a",
|
||||
"yay" => "Result.Result e {}",
|
||||
"map" => "Result.Result * a, a -> a",
|
||||
},
|
||||
);
|
||||
});
|
||||
@ -286,9 +320,9 @@ mod test_load {
|
||||
// test_async(async {
|
||||
// use roc::types::{ErrorType, Mismatch, Problem, TypeExt};
|
||||
|
||||
// let mut subs_by_module = MutMap::default();
|
||||
// let subs_by_module = MutMap::default();
|
||||
// let loaded_module =
|
||||
// load_without_builtins("interface_with_deps", "Records", &mut subs_by_module).await;
|
||||
// load_without_builtins("interface_with_deps", "Records", subs_by_module).await;
|
||||
|
||||
// // NOTE: `a` here is unconstrained, so unifies with <type error>
|
||||
// let expected_types = hashmap! {
|
||||
@ -344,22 +378,22 @@ mod test_load {
|
||||
// #[test]
|
||||
// fn load_and_infer_without_builtins() {
|
||||
// test_async(async {
|
||||
// let mut subs_by_module = MutMap::default();
|
||||
// let subs_by_module = MutMap::default();
|
||||
// let loaded_module = load_without_builtins(
|
||||
// "interface_with_deps",
|
||||
// "WithoutBuiltins",
|
||||
// &mut subs_by_module,
|
||||
// subs_by_module,
|
||||
// )
|
||||
// .await;
|
||||
|
||||
// expect_types(
|
||||
// loaded_module,
|
||||
// hashmap! {
|
||||
// "WithoutBuiltins.alwaysThreePointZero" => "* -> Float",
|
||||
// "WithoutBuiltins.answer" => "Int",
|
||||
// "WithoutBuiltins.fromDep2" => "Float",
|
||||
// "WithoutBuiltins.identity" => "a -> a",
|
||||
// "WithoutBuiltins.threePointZero" => "Float",
|
||||
// "alwaysThreePointZero" => "* -> Float",
|
||||
// "answer" => "Int",
|
||||
// "fromDep2" => "Float",
|
||||
// "identity" => "a -> a",
|
||||
// "threePointZero" => "Float",
|
||||
// },
|
||||
// );
|
||||
// });
|
||||
|
@ -272,8 +272,11 @@ mod test_parse {
|
||||
Located::new(0, 0, 16, 20, label1),
|
||||
Located::new(0, 0, 22, 26, label2)
|
||||
];
|
||||
let module_parts = bumpalo::vec![in &arena; "Foo", "Bar"].into_bump_slice();
|
||||
let update_target = Located::new(0, 0, 2, 13, Var(module_parts, "baz"));
|
||||
let var = Var {
|
||||
module_name: "Foo.Bar",
|
||||
ident: "baz",
|
||||
};
|
||||
let update_target = Located::new(0, 0, 2, 13, var);
|
||||
let expected = Record {
|
||||
update: Some(&*arena.alloc(update_target)),
|
||||
fields,
|
||||
@ -349,8 +352,12 @@ mod test_parse {
|
||||
// Subtraction is special when it comes to parsing, because of unary negation.
|
||||
|
||||
let arena = Bump::new();
|
||||
let var = Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
};
|
||||
let tuple = arena.alloc((
|
||||
Located::new(0, 0, 0, 1, Var(&[], "x")),
|
||||
Located::new(0, 0, 0, 1, var),
|
||||
Located::new(0, 0, 2, 3, Plus),
|
||||
Located::new(0, 0, 4, 5, Int("2")),
|
||||
));
|
||||
@ -367,8 +374,12 @@ mod test_parse {
|
||||
//
|
||||
// Subtraction is special when it comes to parsing, because of unary negation.
|
||||
let arena = Bump::new();
|
||||
let var = Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
};
|
||||
let tuple = arena.alloc((
|
||||
Located::new(0, 0, 0, 1, Var(&[], "x")),
|
||||
Located::new(0, 0, 0, 1, var),
|
||||
Located::new(0, 0, 2, 3, Minus),
|
||||
Located::new(0, 0, 4, 5, Int("2")),
|
||||
));
|
||||
@ -524,11 +535,18 @@ mod test_parse {
|
||||
// This is an edge case with minus because of unary negation.
|
||||
// (x- y) should parse like subtraction (x - (y)), not function application (x (-y))
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let var1 = Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
};
|
||||
let var2 = Var {
|
||||
module_name: "",
|
||||
ident: "y",
|
||||
};
|
||||
let tuple = arena.alloc((
|
||||
Located::new(0, 0, 0, 1, Var(module_parts, "x")),
|
||||
Located::new(0, 0, 0, 1, var1),
|
||||
Located::new(0, 0, 1, 2, Minus),
|
||||
Located::new(0, 0, 3, 4, Var(module_parts, "y")),
|
||||
Located::new(0, 0, 3, 4, var2),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "x- y");
|
||||
@ -586,10 +604,18 @@ mod test_parse {
|
||||
#[test]
|
||||
fn equals() {
|
||||
let arena = Bump::new();
|
||||
let var1 = Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
};
|
||||
let var2 = Var {
|
||||
module_name: "",
|
||||
ident: "y",
|
||||
};
|
||||
let tuple = arena.alloc((
|
||||
Located::new(0, 0, 0, 1, Var(&[], "x")),
|
||||
Located::new(0, 0, 0, 1, var1),
|
||||
Located::new(0, 0, 1, 3, Equals),
|
||||
Located::new(0, 0, 3, 4, Var(&[], "y")),
|
||||
Located::new(0, 0, 3, 4, var2),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "x==y");
|
||||
@ -600,10 +626,18 @@ mod test_parse {
|
||||
#[test]
|
||||
fn equals_with_spaces() {
|
||||
let arena = Bump::new();
|
||||
let var1 = Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
};
|
||||
let var2 = Var {
|
||||
module_name: "",
|
||||
ident: "y",
|
||||
};
|
||||
let tuple = arena.alloc((
|
||||
Located::new(0, 0, 0, 1, Var(&[], "x")),
|
||||
Located::new(0, 0, 0, 1, var1),
|
||||
Located::new(0, 0, 2, 4, Equals),
|
||||
Located::new(0, 0, 5, 6, Var(&[], "y")),
|
||||
Located::new(0, 0, 5, 6, var2),
|
||||
));
|
||||
let expected = BinOp(tuple);
|
||||
let actual = parse_with(&arena, "x == y");
|
||||
@ -616,8 +650,10 @@ mod test_parse {
|
||||
#[test]
|
||||
fn basic_var() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let expected = Var(module_parts, "whee");
|
||||
let expected = Var {
|
||||
module_name: "",
|
||||
ident: "whee",
|
||||
};
|
||||
let actual = parse_with(&arena, "whee");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -626,8 +662,10 @@ mod test_parse {
|
||||
#[test]
|
||||
fn parenthetical_var() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let expected = ParensAround(arena.alloc(Var(module_parts, "whee")));
|
||||
let expected = ParensAround(arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "whee",
|
||||
}));
|
||||
let actual = parse_with(&arena, "(whee)");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -636,8 +674,10 @@ mod test_parse {
|
||||
#[test]
|
||||
fn qualified_var() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = bumpalo::vec![in &arena; "One", "Two"].into_bump_slice();
|
||||
let expected = Var(module_parts, "whee");
|
||||
let expected = Var {
|
||||
module_name: "One.Two",
|
||||
ident: "whee",
|
||||
};
|
||||
let actual = parse_with(&arena, "One.Two.whee");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
@ -695,6 +735,24 @@ mod test_parse {
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_parenthetical_global_tag_args() {
|
||||
let arena = Bump::new();
|
||||
let int1 = ParensAround(arena.alloc(Int("12")));
|
||||
let int2 = ParensAround(arena.alloc(Int("34")));
|
||||
let arg1 = arena.alloc(Located::new(0, 0, 6, 8, int1));
|
||||
let arg2 = arena.alloc(Located::new(0, 0, 11, 13, int2));
|
||||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||||
let expected = Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 0, 4, Expr::GlobalTag("Whee"))),
|
||||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
let actual = parse_with(&arena, "Whee (12) (34)");
|
||||
|
||||
assert_eq!(Ok(expected), actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn qualified_global_tag() {
|
||||
let arena = Bump::new();
|
||||
@ -785,8 +843,10 @@ mod test_parse {
|
||||
#[test]
|
||||
fn basic_field() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let var = Var(module_parts, "rec");
|
||||
let var = Var {
|
||||
module_name: "",
|
||||
ident: "rec",
|
||||
};
|
||||
let expected = Access(arena.alloc(var), "field");
|
||||
let actual = parse_with(&arena, "rec.field");
|
||||
|
||||
@ -796,8 +856,10 @@ mod test_parse {
|
||||
#[test]
|
||||
fn parenthetical_basic_field() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let paren_var = ParensAround(arena.alloc(Var(module_parts, "rec")));
|
||||
let paren_var = ParensAround(arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "rec",
|
||||
}));
|
||||
let expected = Access(arena.alloc(paren_var), "field");
|
||||
let actual = parse_with(&arena, "(rec).field");
|
||||
|
||||
@ -807,8 +869,10 @@ mod test_parse {
|
||||
#[test]
|
||||
fn parenthetical_field_qualified_var() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = bumpalo::vec![in &arena; "One", "Two"].into_bump_slice();
|
||||
let paren_var = ParensAround(arena.alloc(Var(module_parts, "rec")));
|
||||
let paren_var = ParensAround(arena.alloc(Var {
|
||||
module_name: "One.Two",
|
||||
ident: "rec",
|
||||
}));
|
||||
let expected = Access(arena.alloc(paren_var), "field");
|
||||
let actual = parse_with(&arena, "(One.Two.rec).field");
|
||||
|
||||
@ -818,8 +882,10 @@ mod test_parse {
|
||||
#[test]
|
||||
fn multiple_fields() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let var = Var(module_parts, "rec");
|
||||
let var = Var {
|
||||
module_name: "",
|
||||
ident: "rec",
|
||||
};
|
||||
let expected = Access(
|
||||
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")),
|
||||
"ghi",
|
||||
@ -832,8 +898,10 @@ mod test_parse {
|
||||
#[test]
|
||||
fn qualified_field() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = bumpalo::vec![in &arena; "One", "Two"].into_bump_slice();
|
||||
let var = Var(module_parts, "rec");
|
||||
let var = Var {
|
||||
module_name: "One.Two",
|
||||
ident: "rec",
|
||||
};
|
||||
let expected = Access(
|
||||
arena.alloc(Access(arena.alloc(Access(arena.alloc(var), "abc")), "def")),
|
||||
"ghi",
|
||||
@ -848,11 +916,19 @@ mod test_parse {
|
||||
#[test]
|
||||
fn basic_apply() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let arg = arena.alloc(Located::new(0, 0, 5, 6, Int("1")));
|
||||
let args = bumpalo::vec![in &arena; &*arg];
|
||||
let expected = Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 0, 4, Var(module_parts, "whee"))),
|
||||
arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "whee",
|
||||
},
|
||||
)),
|
||||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
@ -864,12 +940,20 @@ mod test_parse {
|
||||
#[test]
|
||||
fn apply_two_args() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Int("12")));
|
||||
let arg2 = arena.alloc(Located::new(0, 0, 10, 12, Int("34")));
|
||||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||||
let expected = Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 0, 4, Var(module_parts, "whee"))),
|
||||
arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "whee",
|
||||
},
|
||||
)),
|
||||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
@ -881,13 +965,48 @@ mod test_parse {
|
||||
#[test]
|
||||
fn apply_three_args() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let arg1 = arena.alloc(Located::new(0, 0, 2, 3, Var(module_parts, "b")));
|
||||
let arg2 = arena.alloc(Located::new(0, 0, 4, 5, Var(module_parts, "c")));
|
||||
let arg3 = arena.alloc(Located::new(0, 0, 6, 7, Var(module_parts, "d")));
|
||||
let arg1 = arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
3,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "b",
|
||||
},
|
||||
));
|
||||
let arg2 = arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
4,
|
||||
5,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "c",
|
||||
},
|
||||
));
|
||||
let arg3 = arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
6,
|
||||
7,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "d",
|
||||
},
|
||||
));
|
||||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2, &*arg3];
|
||||
let expected = Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 0, 1, Var(module_parts, "a"))),
|
||||
arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "a",
|
||||
},
|
||||
)),
|
||||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
@ -899,10 +1018,12 @@ mod test_parse {
|
||||
#[test]
|
||||
fn parenthetical_apply() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let arg = arena.alloc(Located::new(0, 0, 7, 8, Int("1")));
|
||||
let args = bumpalo::vec![in &arena; &*arg];
|
||||
let parens_var = Expr::ParensAround(arena.alloc(Var(module_parts, "whee")));
|
||||
let parens_var = Expr::ParensAround(arena.alloc(Var {
|
||||
module_name: "",
|
||||
ident: "whee",
|
||||
}));
|
||||
let expected = Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 1, 5, parens_var)),
|
||||
args,
|
||||
@ -918,9 +1039,17 @@ mod test_parse {
|
||||
#[test]
|
||||
fn unary_negation() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate);
|
||||
let loc_arg1_expr = Located::new(0, 0, 1, 4, Var(module_parts, "foo"));
|
||||
let loc_arg1_expr = Located::new(
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
4,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "foo",
|
||||
},
|
||||
);
|
||||
let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op);
|
||||
let actual = parse_with(&arena, "-foo");
|
||||
|
||||
@ -930,9 +1059,17 @@ mod test_parse {
|
||||
#[test]
|
||||
fn unary_not() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Not);
|
||||
let loc_arg1_expr = Located::new(0, 0, 1, 5, Var(module_parts, "blah"));
|
||||
let loc_arg1_expr = Located::new(
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
5,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "blah",
|
||||
},
|
||||
);
|
||||
let expected = UnaryOp(arena.alloc(loc_arg1_expr), loc_op);
|
||||
let actual = parse_with(&arena, "!blah");
|
||||
|
||||
@ -942,13 +1079,30 @@ mod test_parse {
|
||||
#[test]
|
||||
fn apply_unary_negation() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let arg1 = arena.alloc(Located::new(0, 0, 7, 9, Int("12")));
|
||||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate);
|
||||
let arg2 = arena.alloc(Located::new(0, 0, 10, 13, Var(module_parts, "foo")));
|
||||
let arg2 = arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
10,
|
||||
13,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "foo",
|
||||
},
|
||||
));
|
||||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||||
let apply_expr = Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 1, 5, Var(module_parts, "whee"))),
|
||||
arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
5,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "whee",
|
||||
},
|
||||
)),
|
||||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
@ -961,13 +1115,30 @@ mod test_parse {
|
||||
#[test]
|
||||
fn apply_unary_not() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let arg1 = arena.alloc(Located::new(0, 0, 7, 9, Int("12")));
|
||||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Not);
|
||||
let arg2 = arena.alloc(Located::new(0, 0, 10, 13, Var(module_parts, "foo")));
|
||||
let arg2 = arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
10,
|
||||
13,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "foo",
|
||||
},
|
||||
));
|
||||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||||
let apply_expr = Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 1, 5, Var(module_parts, "whee"))),
|
||||
arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
5,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "whee",
|
||||
},
|
||||
)),
|
||||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
@ -980,13 +1151,30 @@ mod test_parse {
|
||||
#[test]
|
||||
fn unary_negation_with_parens() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let arg1 = arena.alloc(Located::new(0, 0, 8, 10, Int("12")));
|
||||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Negate);
|
||||
let arg2 = arena.alloc(Located::new(0, 0, 11, 14, Var(module_parts, "foo")));
|
||||
let arg2 = arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
11,
|
||||
14,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "foo",
|
||||
},
|
||||
));
|
||||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||||
let apply_expr = Expr::ParensAround(arena.alloc(Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 2, 6, Var(module_parts, "whee"))),
|
||||
arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
6,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "whee",
|
||||
},
|
||||
)),
|
||||
args,
|
||||
CalledVia::Space,
|
||||
)));
|
||||
@ -999,13 +1187,30 @@ mod test_parse {
|
||||
#[test]
|
||||
fn unary_not_with_parens() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let arg1 = arena.alloc(Located::new(0, 0, 8, 10, Int("12")));
|
||||
let loc_op = Located::new(0, 0, 0, 1, UnaryOp::Not);
|
||||
let arg2 = arena.alloc(Located::new(0, 0, 11, 14, Var(module_parts, "foo")));
|
||||
let arg2 = arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
11,
|
||||
14,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "foo",
|
||||
},
|
||||
));
|
||||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||||
let apply_expr = Expr::ParensAround(arena.alloc(Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 2, 6, Var(module_parts, "whee"))),
|
||||
arena.alloc(Located::new(
|
||||
0,
|
||||
0,
|
||||
2,
|
||||
6,
|
||||
Var {
|
||||
module_name: "",
|
||||
ident: "whee",
|
||||
},
|
||||
)),
|
||||
args,
|
||||
CalledVia::Space,
|
||||
)));
|
||||
@ -1018,15 +1223,22 @@ mod test_parse {
|
||||
#[test]
|
||||
fn unary_negation_arg() {
|
||||
let arena = Bump::new();
|
||||
let module_parts = Vec::new_in(&arena).into_bump_slice();
|
||||
let arg1 = arena.alloc(Located::new(0, 0, 6, 8, Int("12")));
|
||||
let loc_op = Located::new(0, 0, 9, 10, UnaryOp::Negate);
|
||||
let loc_arg1_expr = Located::new(0, 0, 10, 13, Var(module_parts, "foo"));
|
||||
let var1 = Var {
|
||||
module_name: "",
|
||||
ident: "foo",
|
||||
};
|
||||
let loc_arg1_expr = Located::new(0, 0, 10, 13, var1);
|
||||
let arg_op = UnaryOp(arena.alloc(loc_arg1_expr), loc_op);
|
||||
let arg2 = arena.alloc(Located::new(0, 0, 9, 13, arg_op));
|
||||
let args = bumpalo::vec![in &arena; &*arg1, &*arg2];
|
||||
let var2 = Var {
|
||||
module_name: "",
|
||||
ident: "whee",
|
||||
};
|
||||
let expected = Expr::Apply(
|
||||
arena.alloc(Located::new(0, 0, 0, 4, Var(module_parts, "whee"))),
|
||||
arena.alloc(Located::new(0, 0, 0, 4, var2)),
|
||||
args,
|
||||
CalledVia::Space,
|
||||
);
|
||||
@ -1285,7 +1497,7 @@ mod test_parse {
|
||||
let arena = Bump::new();
|
||||
let newline = bumpalo::vec![in &arena; Newline];
|
||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
let applied_ann = TypeAnnotation::Apply(&[], "Int", &[]);
|
||||
let applied_ann = TypeAnnotation::Apply("", "Int", &[]);
|
||||
let signature = Def::Annotation(
|
||||
Located::new(0, 0, 0, 3, Identifier("foo")),
|
||||
Located::new(0, 0, 6, 9, applied_ann),
|
||||
@ -1326,10 +1538,10 @@ mod test_parse {
|
||||
let loc_b = Located::new(0, 0, 32, 33, TypeAnnotation::BoundVariable("b"));
|
||||
let applied_ann_args = bumpalo::vec![in &arena; loc_x, loc_y];
|
||||
let applied_ann =
|
||||
TypeAnnotation::Apply(&["Foo", "Bar"], "Baz", applied_ann_args.into_bump_slice());
|
||||
TypeAnnotation::Apply("Foo.Bar", "Baz", applied_ann_args.into_bump_slice());
|
||||
let loc_applied_ann = &*arena.alloc(Located::new(0, 0, 6, 21, applied_ann));
|
||||
let applied_as_args = bumpalo::vec![in &arena; loc_a, loc_b];
|
||||
let applied_as = TypeAnnotation::Apply(&[], "Blah", applied_as_args.into_bump_slice());
|
||||
let applied_as = TypeAnnotation::Apply("", "Blah", applied_as_args.into_bump_slice());
|
||||
let loc_applied_as = &*arena.alloc(Located::new(0, 0, 25, 33, applied_as));
|
||||
let as_ann = TypeAnnotation::As(loc_applied_ann, &[], loc_applied_as);
|
||||
let signature = Def::Annotation(
|
||||
@ -1366,7 +1578,7 @@ mod test_parse {
|
||||
let applied_ann_args = bumpalo::vec![in &arena; loc_a, loc_b];
|
||||
let applied_alias_args = bumpalo::vec![in &arena; loc_x, loc_y];
|
||||
let applied_alias =
|
||||
TypeAnnotation::Apply(&["Foo", "Bar"], "Baz", applied_alias_args.into_bump_slice());
|
||||
TypeAnnotation::Apply("Foo.Bar", "Baz", applied_alias_args.into_bump_slice());
|
||||
let signature = Def::Alias {
|
||||
name: Located::new(0, 0, 0, 4, "Blah"),
|
||||
vars: applied_ann_args.into_bump_slice(),
|
||||
@ -1398,9 +1610,9 @@ mod test_parse {
|
||||
let newline = bumpalo::vec![in &arena; Newline];
|
||||
let newlines = bumpalo::vec![in &arena; Newline, Newline];
|
||||
|
||||
let int_type = TypeAnnotation::Apply(&[], "Int", &[]);
|
||||
let float_type = TypeAnnotation::Apply(&[], "Float", &[]);
|
||||
let bool_type = TypeAnnotation::Apply(&[], "Bool", &[]);
|
||||
let int_type = TypeAnnotation::Apply("", "Int", &[]);
|
||||
let float_type = TypeAnnotation::Apply("", "Float", &[]);
|
||||
let bool_type = TypeAnnotation::Apply("", "Bool", &[]);
|
||||
|
||||
let arguments = bumpalo::vec![in &arena;
|
||||
Located::new(0, 0, 6, 9, int_type),
|
||||
@ -1456,8 +1668,8 @@ mod test_parse {
|
||||
name: Located::new(0, 0, 8, 13, "@True"),
|
||||
args: &[],
|
||||
};
|
||||
let tag2arg1 = Located::new(0, 0, 24, 27, TypeAnnotation::Apply(&[], "Two", &[]));
|
||||
let tag2arg2 = Located::new(0, 0, 28, 34, TypeAnnotation::Apply(&[], "Things", &[]));
|
||||
let tag2arg1 = Located::new(0, 0, 24, 27, TypeAnnotation::Apply("", "Two", &[]));
|
||||
let tag2arg2 = Located::new(0, 0, 28, 34, TypeAnnotation::Apply("", "Things", &[]));
|
||||
let tag2args = bumpalo::vec![in &arena; tag2arg1, tag2arg2];
|
||||
let tag2 = Tag::Private {
|
||||
name: Located::new(0, 0, 15, 23, "@Perhaps"),
|
||||
@ -1511,7 +1723,7 @@ mod test_parse {
|
||||
name: Located::new(0, 0, 8, 13, "@True"),
|
||||
args: &[],
|
||||
};
|
||||
let tag2arg = Located::new(0, 0, 24, 29, TypeAnnotation::Apply(&[], "Thing", &[]));
|
||||
let tag2arg = Located::new(0, 0, 24, 29, TypeAnnotation::Apply("", "Thing", &[]));
|
||||
let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||||
let tag2 = Tag::Private {
|
||||
name: Located::new(0, 0, 15, 23, "@Perhaps"),
|
||||
@ -1564,7 +1776,7 @@ mod test_parse {
|
||||
name: Located::new(0, 0, 8, 12, "True"),
|
||||
args: &[],
|
||||
};
|
||||
let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply(&[], "Thing", &[]));
|
||||
let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply("", "Thing", &[]));
|
||||
let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||||
let tag2 = Tag::Global {
|
||||
name: Located::new(0, 0, 14, 21, "Perhaps"),
|
||||
@ -1618,7 +1830,7 @@ mod test_parse {
|
||||
name: Located::new(0, 0, 8, 12, "True"),
|
||||
args: &[],
|
||||
};
|
||||
let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply(&[], "Thing", &[]));
|
||||
let tag2arg = Located::new(0, 0, 22, 27, TypeAnnotation::Apply("", "Thing", &[]));
|
||||
let tag2args = bumpalo::vec![in &arena; tag2arg];
|
||||
let tag2 = Tag::Global {
|
||||
name: Located::new(0, 0, 14, 21, "Perhaps"),
|
||||
@ -1690,7 +1902,11 @@ mod test_parse {
|
||||
guard: None,
|
||||
});
|
||||
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
||||
let loc_cond = Located::new(0, 0, 5, 6, Var(&[], "x"));
|
||||
let var = Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
};
|
||||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||
let actual = parse_with(
|
||||
&arena,
|
||||
@ -1732,7 +1948,11 @@ mod test_parse {
|
||||
guard: None,
|
||||
});
|
||||
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
||||
let loc_cond = Located::new(0, 0, 5, 6, Var(&[], "x"));
|
||||
let var = Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
};
|
||||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||
let actual = parse_with(
|
||||
&arena,
|
||||
@ -1780,7 +2000,11 @@ mod test_parse {
|
||||
guard: None,
|
||||
});
|
||||
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
||||
let loc_cond = Located::new(0, 0, 5, 6, Var(&[], "x"));
|
||||
let var = Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
};
|
||||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||
let actual = parse_with(
|
||||
&arena,
|
||||
@ -1828,7 +2052,11 @@ mod test_parse {
|
||||
guard: None,
|
||||
});
|
||||
let branches = bumpalo::vec![in &arena; branch1, branch2];
|
||||
let loc_cond = Located::new(0, 0, 5, 6, Var(&[], "x"));
|
||||
let var = Var {
|
||||
module_name: "",
|
||||
ident: "x",
|
||||
};
|
||||
let loc_cond = Located::new(0, 0, 5, 6, var);
|
||||
let expected = Expr::When(arena.alloc(loc_cond), branches);
|
||||
let actual = parse_with(
|
||||
&arena,
|
||||
|
@ -17,34 +17,35 @@ mod test_infer_uniq {
|
||||
// HELPERS
|
||||
|
||||
fn infer_eq_help(src: &str) -> (Vec<roc::types::Problem>, String) {
|
||||
let (_output, _problems, subs, variable, constraint) = uniq_expr(src);
|
||||
let (output, _problems, mut subs, variable, constraint, home, interns) = uniq_expr(src);
|
||||
|
||||
assert_correct_variable_usage(&constraint);
|
||||
|
||||
for (name, var) in output.rigids {
|
||||
subs.rigid_var(var, name);
|
||||
}
|
||||
|
||||
let mut unify_problems = Vec::new();
|
||||
let (content, solved) = infer_expr(subs, &mut unify_problems, &constraint, variable);
|
||||
let mut subs = solved.into_inner();
|
||||
|
||||
name_all_type_vars(variable, &mut subs);
|
||||
|
||||
let actual_str = content_to_string(content, &mut subs);
|
||||
let actual_str = content_to_string(content, &mut subs, home, &interns);
|
||||
|
||||
(unify_problems, actual_str)
|
||||
}
|
||||
fn infer_eq(src: &str, expected: &str) {
|
||||
fn infer_eq_ignore_problems(src: &str, expected: &str) {
|
||||
let (_, actual) = infer_eq_help(src);
|
||||
|
||||
assert_eq!(actual, expected.to_string());
|
||||
}
|
||||
|
||||
fn infer_eq_without_problem(src: &str, expected: &str) {
|
||||
fn infer_eq(src: &str, expected: &str) {
|
||||
let (problems, actual) = infer_eq_help(src);
|
||||
|
||||
if !problems.is_empty() {
|
||||
// fail with an assert, but print the problems normally so rust doesn't try to diff
|
||||
// an empty vec with the problems.
|
||||
println!("expected:\n{:?}\ninfered:\n{:?}", expected, actual);
|
||||
assert_eq!(0, 1);
|
||||
panic!("expected:\n{:?}\ninferred:\n{:?}", expected, actual);
|
||||
}
|
||||
assert_eq!(actual, expected.to_string());
|
||||
}
|
||||
@ -271,7 +272,7 @@ mod test_infer_uniq {
|
||||
|
||||
#[test]
|
||||
fn mismatch_heterogeneous_list() {
|
||||
infer_eq(
|
||||
infer_eq_ignore_problems(
|
||||
indoc!(
|
||||
r#"
|
||||
[ "foo", 5 ]
|
||||
@ -283,7 +284,7 @@ mod test_infer_uniq {
|
||||
|
||||
#[test]
|
||||
fn mismatch_heterogeneous_nested_list() {
|
||||
infer_eq(
|
||||
infer_eq_ignore_problems(
|
||||
indoc!(
|
||||
r#"
|
||||
[ [ "foo", 5 ] ]
|
||||
@ -295,7 +296,7 @@ mod test_infer_uniq {
|
||||
|
||||
#[test]
|
||||
fn mismatch_heterogeneous_nested_empty_list() {
|
||||
infer_eq(
|
||||
infer_eq_ignore_problems(
|
||||
indoc!(
|
||||
r#"
|
||||
[ [ 1 ], [ [] ] ]
|
||||
@ -526,8 +527,7 @@ mod test_infer_uniq {
|
||||
identity
|
||||
"#
|
||||
),
|
||||
// TODO investigate why not shared?
|
||||
"Attr.Attr * (a -> a)",
|
||||
"Attr.Attr Attr.Shared (a -> a)",
|
||||
);
|
||||
}
|
||||
|
||||
@ -629,17 +629,18 @@ mod test_infer_uniq {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identity_of_identity() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
(\val -> val) (\val -> val)
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (a -> a)",
|
||||
);
|
||||
}
|
||||
// TODO when symbols are unique, this should work again
|
||||
// #[test]
|
||||
// fn identity_of_identity() {
|
||||
// infer_eq(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// (\val -> val) (\val -> val)
|
||||
// "#
|
||||
// ),
|
||||
// "Attr.Attr * (a -> a)",
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn recursive_identity() {
|
||||
@ -945,7 +946,7 @@ mod test_infer_uniq {
|
||||
r#"\@Foo -> 42
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr * [ Test.@Foo ]* -> Attr.Attr * Int)",
|
||||
"Attr.Attr * (Attr.Attr * [ @Foo ]* -> Attr.Attr * Int)",
|
||||
);
|
||||
}
|
||||
|
||||
@ -981,7 +982,7 @@ mod test_infer_uniq {
|
||||
r#"@Foo "happy" 2020
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * [ Test.@Foo (Attr.Attr * Str) (Attr.Attr * Int) ]*",
|
||||
"Attr.Attr * [ @Foo (Attr.Attr * Str) (Attr.Attr * Int) ]*",
|
||||
);
|
||||
}
|
||||
|
||||
@ -993,12 +994,12 @@ mod test_infer_uniq {
|
||||
.left
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr (a | *) { left : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
"Attr.Attr * (Attr.Attr (* | a) { left : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_field_access() {
|
||||
fn record_field_access_syntax() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
@ -1017,7 +1018,7 @@ mod test_infer_uniq {
|
||||
\{ left, right } -> { left, right }
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr (b | a) { left : (Attr.Attr a c), right : (Attr.Attr b d) }* -> Attr.Attr * { left : (Attr.Attr a c), right : (Attr.Attr b d) })",
|
||||
"Attr.Attr * (Attr.Attr (* | a | b) { left : (Attr.Attr a c), right : (Attr.Attr b d) }* -> Attr.Attr * { left : (Attr.Attr a c), right : (Attr.Attr b d) })",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1044,7 +1045,7 @@ mod test_infer_uniq {
|
||||
),
|
||||
// NOTE: Foo loses the relation to the uniqueness attribute `a`
|
||||
// That is fine. Whenever we try to extract from it, the relation will be enforced
|
||||
"Attr.Attr * (Attr.Attr a [ Foo (Attr.Attr a b) ]* -> Attr.Attr * [ Foo (Attr.Attr a b) ]*)",
|
||||
"Attr.Attr * (Attr.Attr (* | a) [ Foo (Attr.Attr a b) ]* -> Attr.Attr * [ Foo (Attr.Attr a b) ]*)",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1059,7 +1060,7 @@ mod test_infer_uniq {
|
||||
// TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers?
|
||||
// i.e. the `b` could be ignored in this example, is that true in general?
|
||||
// seems like it because we don't really extract anything.
|
||||
"Attr.Attr * (Attr.Attr (b | a) [ Foo (Attr.Attr a c) (Attr.Attr b *) ]* -> Attr.Attr * [ Foo (Attr.Attr a c) (Attr.Attr * Str) ]*)"
|
||||
"Attr.Attr * (Attr.Attr (* | a | b) [ Foo (Attr.Attr b c) (Attr.Attr a *) ]* -> Attr.Attr * [ Foo (Attr.Attr b c) (Attr.Attr * Str) ]*)",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1094,13 +1095,13 @@ mod test_infer_uniq {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
x : Int
|
||||
x : Num.Num Int.Integer
|
||||
x = 4
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
"Attr.Attr a Int",
|
||||
"Attr.Attr * Int",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1112,7 +1113,7 @@ mod test_infer_uniq {
|
||||
\{ left } -> left
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a { left : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
"Attr.Attr * (Attr.Attr (* | a) { left : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1121,25 +1122,25 @@ mod test_infer_uniq {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\{ x } -> x
|
||||
\{ left } -> left
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a { x : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
"Attr.Attr * (Attr.Attr (* | a) { left : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_identity_def() {
|
||||
infer_eq_without_problem(
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
numIdentity : Num.Num a -> Num.Num a
|
||||
numIdentity : Num.Num p -> Num.Num p
|
||||
numIdentity = \x -> x
|
||||
|
||||
numIdentity
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a (Num b) -> Attr.Attr a (Num b))",
|
||||
"Attr.Attr * (Attr.Attr a (Num (Attr.Attr b p)) -> Attr.Attr a (Num (Attr.Attr b p)))",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1149,12 +1150,12 @@ mod test_infer_uniq {
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
x = r.x
|
||||
x = r.left
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr (a | *) { x : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
"Attr.Attr * (Attr.Attr (* | a) { left : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1164,21 +1165,21 @@ mod test_infer_uniq {
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
x = r.x
|
||||
x = r.left
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr (a | *) { x : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
"Attr.Attr * (Attr.Attr (* | a) { left : (Attr.Attr a b) }* -> Attr.Attr a b)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn num_identity_applied() {
|
||||
infer_eq_without_problem(
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
numIdentity : Num.Num b -> Num.Num b
|
||||
numIdentity : Num.Num p -> Num.Num p
|
||||
numIdentity = \foo -> foo
|
||||
|
||||
p = numIdentity 42
|
||||
@ -1186,19 +1187,76 @@ mod test_infer_uniq {
|
||||
|
||||
{ numIdentity, p, q }
|
||||
"#
|
||||
), "Attr.Attr * { numIdentity : (Attr.Attr * (Attr.Attr a (Num b) -> Attr.Attr a (Num b))), p : (Attr.Attr * Int), q : (Attr.Attr * Float) }"
|
||||
),
|
||||
"Attr.Attr * { numIdentity : (Attr.Attr Attr.Shared (Attr.Attr a (Num (Attr.Attr b p)) -> Attr.Attr a (Num (Attr.Attr b p)))), p : (Attr.Attr * Int), q : (Attr.Attr * Float) }"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sharing_analysis_record_update_use_twice_access() {
|
||||
fn sharing_analysis_record_twice_access() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
v = r.x
|
||||
w = r.x
|
||||
|
||||
r
|
||||
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a { x : (Attr.Attr Attr.Shared b) }c -> Attr.Attr a { x : (Attr.Attr Attr.Shared b) }c)" ,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sharing_analysis_record_access_two_fields() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
v = r.x
|
||||
w = r.y
|
||||
|
||||
r
|
||||
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a { x : (Attr.Attr Attr.Shared b), y : (Attr.Attr Attr.Shared c) }d -> Attr.Attr a { x : (Attr.Attr Attr.Shared b), y : (Attr.Attr Attr.Shared c) }d)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sharing_analysis_record_alias() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
v = r.x
|
||||
w = r.y
|
||||
|
||||
p = r
|
||||
|
||||
p
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a { x : (Attr.Attr Attr.Shared b), y : (Attr.Attr Attr.Shared c) }d -> Attr.Attr a { x : (Attr.Attr Attr.Shared b), y : (Attr.Attr Attr.Shared c) }d)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sharing_analysis_record_access_field_twice() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\r -> { r & x: r.x, y: r.y }
|
||||
"#
|
||||
\r ->
|
||||
n = r.x
|
||||
m = r.x
|
||||
|
||||
r
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr Attr.Shared { x : (Attr.Attr Attr.Shared a), y : (Attr.Attr Attr.Shared b) }c -> Attr.Attr Attr.Shared { x : (Attr.Attr Attr.Shared a), y : (Attr.Attr Attr.Shared b) }c)" ,
|
||||
"Attr.Attr * (Attr.Attr a { x : (Attr.Attr Attr.Shared b) }c -> Attr.Attr a { x : (Attr.Attr Attr.Shared b) }c)",
|
||||
);
|
||||
}
|
||||
|
||||
@ -1210,13 +1268,83 @@ mod test_infer_uniq {
|
||||
\r -> { r & x: r.x, y: r.x }
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr Attr.Shared { x : (Attr.Attr Attr.Shared a), y : (Attr.Attr Attr.Shared a) }b -> Attr.Attr Attr.Shared { x : (Attr.Attr Attr.Shared a), y : (Attr.Attr Attr.Shared a) }b)"
|
||||
"Attr.Attr * (Attr.Attr a { x : (Attr.Attr Attr.Shared b), y : (Attr.Attr Attr.Shared b) }c -> Attr.Attr a { x : (Attr.Attr Attr.Shared b), y : (Attr.Attr Attr.Shared b) }c)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_access_nested_field() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
v = r.foo.bar
|
||||
w = r.foo.baz
|
||||
|
||||
r
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr (a | b) { foo : (Attr.Attr a { bar : (Attr.Attr Attr.Shared d), baz : (Attr.Attr Attr.Shared c) }e) }f -> Attr.Attr (a | b) { foo : (Attr.Attr a { bar : (Attr.Attr Attr.Shared d), baz : (Attr.Attr Attr.Shared c) }e) }f)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_access_nested_field_is_safe() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
v = r.foo.bar
|
||||
|
||||
x = v
|
||||
y = v
|
||||
|
||||
r
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr (a | b) { foo : (Attr.Attr a { bar : (Attr.Attr Attr.Shared c) }d) }e -> Attr.Attr (a | b) { foo : (Attr.Attr a { bar : (Attr.Attr Attr.Shared c) }d) }e)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_update_is_safe() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
|
||||
s = { r & y: r.x }
|
||||
|
||||
p = s.x
|
||||
q = s.y
|
||||
|
||||
s
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a { x : (Attr.Attr Attr.Shared b), y : (Attr.Attr Attr.Shared b) }c -> Attr.Attr a { x : (Attr.Attr Attr.Shared b), y : (Attr.Attr Attr.Shared b) }c)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn triple_nested_record() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
if True then
|
||||
r.foo.bar.baz
|
||||
else
|
||||
r.tic.tac.toe
|
||||
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr (* | a | b | c | d | e) { foo : (Attr.Attr (a | c | d) { bar : (Attr.Attr (c | d) { baz : (Attr.Attr d f) }*) }*), tic : (Attr.Attr (b | d | e) { tac : (Attr.Attr (b | d) { toe : (Attr.Attr d f) }*) }*) }* -> Attr.Attr d f)"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn when_with_annotation() {
|
||||
infer_eq_without_problem(
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
x : Num.Num Int.Integer
|
||||
@ -1235,7 +1363,7 @@ mod test_infer_uniq {
|
||||
// TODO add more realistic recursive example when able
|
||||
#[test]
|
||||
fn factorial_is_shared() {
|
||||
infer_eq_without_problem(
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
factorial = \n ->
|
||||
@ -1251,21 +1379,484 @@ mod test_infer_uniq {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO add more realistic recursive example when able
|
||||
#[test]
|
||||
fn factorial_without_recursive_case_can_be_unique() {
|
||||
infer_eq_without_problem(
|
||||
#[ignore]
|
||||
fn quicksort_swap() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
factorial = \n ->
|
||||
when n is
|
||||
0 -> 1
|
||||
_ -> 1
|
||||
swap : Num.Num Int.Integer, Num.Num Int.Integer, List.List a -> List.List a
|
||||
swap \i, j, list ->
|
||||
when Pair (List.get i list) (List.get j list) is
|
||||
Pair (Ok atI) (Ok atJ) ->
|
||||
list
|
||||
|> List.set i atJ
|
||||
|> List.set j atI
|
||||
_ ->
|
||||
list
|
||||
|
||||
factorial
|
||||
swap
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr * Int -> Attr.Attr * Int)",
|
||||
"Attr.Attr * (Attr.Attr Attr.Shared Int, Attr.Attr Attr.Shared Int, Attr.Attr a (List b) -> Attr.Attr a (List b))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn quicksort() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
swap : Int, Int, List a -> List a
|
||||
swap \i, j, list ->
|
||||
when Pair (List.get i list) (List.get j list) is
|
||||
Pair (Ok atI) (Ok atJ) ->
|
||||
list
|
||||
|> List.set i atJ
|
||||
|> List.set j atI
|
||||
_ ->
|
||||
list
|
||||
|
||||
partition : Int, Int, List Int -> [ Pair Int (List Int) ]
|
||||
partition = \low, high, initialList ->
|
||||
when List.get high initialList is
|
||||
Ok pivot ->
|
||||
|
||||
go \i, j, list =
|
||||
if j < high then
|
||||
when List.get j list is
|
||||
Ok value ->
|
||||
if value <= pivot then
|
||||
go (i + 1) (j + 1) (swap (i + 1) j list)
|
||||
else
|
||||
go i (j + 1) list
|
||||
|
||||
_ ->
|
||||
Pair i list
|
||||
else
|
||||
Pair i list
|
||||
|
||||
Pair newI newList = go (low - 1) low initialList
|
||||
|
||||
Pair (newI + 1) (swap (newI + 1) high newList)
|
||||
|
||||
Err _ ->
|
||||
Pair (low - 1) initialList
|
||||
|
||||
quicksort : List Int, Int, Int -> List Int
|
||||
quicksort = \list, low, high ->
|
||||
Pair partitionIndex partitioned = partition low high list
|
||||
|
||||
arr
|
||||
|> quicksort low (partitionIndex - 1)
|
||||
|> quicksort (partitionIndex + 1) high
|
||||
|
||||
quicksort
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr Attr.Shared Int, Attr.Attr Attr.Shared Int, Attr.Attr a (List b) -> Attr.Attr a (List b))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_branch_unique_branch_access() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = { left: 20 }
|
||||
s = { left: 20 }
|
||||
|
||||
if True then
|
||||
r.left
|
||||
else
|
||||
v = s.left
|
||||
s.left
|
||||
|
||||
"#
|
||||
),
|
||||
"Attr.Attr Attr.Shared Int",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_branch_unique_branch_nested_access() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = { left: 20 }
|
||||
s = { left: 20 }
|
||||
|
||||
if True then
|
||||
{ y: r.left }
|
||||
else
|
||||
v = s.left
|
||||
{ y: s.left }
|
||||
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * { y : (Attr.Attr Attr.Shared Int) }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_branch_unique_branch_current() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = "foo"
|
||||
s = { left : "foo" }
|
||||
|
||||
when 0 is
|
||||
1 -> { x: s.left, y: s.left }
|
||||
0 -> { x: s.left, y: r }
|
||||
)
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * { x : (Attr.Attr Attr.Shared Str), y : (Attr.Attr Attr.Shared Str) }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_branch_unique_branch_curr() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = "foo"
|
||||
s = { left : "foo" }
|
||||
|
||||
v = s.left
|
||||
|
||||
when 0 is
|
||||
1 -> { x: v, y: v }
|
||||
0 -> { x: v, y: r }
|
||||
)
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * { x : (Attr.Attr Attr.Shared Str), y : (Attr.Attr Attr.Shared Str) }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicated_record() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
s = { left: 20, right: 20 }
|
||||
|
||||
{ left: s, right: s }
|
||||
"#
|
||||
),
|
||||
// it's fine that the inner fields are not shared: only shared extraction is possible
|
||||
"Attr.Attr * { left : (Attr.Attr Attr.Shared { left : (Attr.Attr * Int), right : (Attr.Attr * Int) }), right : (Attr.Attr Attr.Shared { left : (Attr.Attr * Int), right : (Attr.Attr * Int) }) }",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_succeed_alias() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
Result e a : [ Err e, Ok a ]
|
||||
|
||||
succeed : q -> Result p q
|
||||
succeed = \x -> Ok x
|
||||
|
||||
succeed
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a q -> Attr.Attr * (Result (Attr.Attr * p) (Attr.Attr a q)))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_succeed() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
succeed : p -> [ Err e, Ok p ]
|
||||
succeed = \x -> Ok x
|
||||
|
||||
succeed
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a p -> Attr.Attr * [ Err (Attr.Attr * e), Ok (Attr.Attr a p) ])",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_singleton_alias() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
List a : [ Cons a (List a), Nil ]
|
||||
|
||||
singleton : p -> List p
|
||||
singleton = \x -> Cons x Nil
|
||||
|
||||
singleton
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a p -> Attr.Attr * (List (Attr.Attr a p)))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_singleton_as() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
singleton : p -> [ Cons p (List p), Nil ] as List p
|
||||
singleton = \x -> Cons x Nil
|
||||
|
||||
singleton
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr a p -> Attr.Attr * (List (Attr.Attr a p)))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_singleton_infer() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
singleton = \x -> Cons x Nil
|
||||
|
||||
singleton
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (a -> Attr.Attr * [ Cons a (Attr.Attr * [ Nil ]*) ]*)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_map_alias() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
List a : [ Cons a (List a), Nil ]
|
||||
|
||||
map : (p -> q), List p -> List q
|
||||
map = \f, list ->
|
||||
when list is
|
||||
Nil -> Nil
|
||||
Cons x xs ->
|
||||
a = f x
|
||||
b = map f xs
|
||||
|
||||
Cons a b
|
||||
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"Attr.Attr Attr.Shared (Attr.Attr Attr.Shared (Attr.Attr a p -> Attr.Attr b q), Attr.Attr * (List (Attr.Attr a p)) -> Attr.Attr * (List (Attr.Attr b q)))" ,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_map_infer() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
map = \f, list ->
|
||||
when list is
|
||||
Nil -> Nil
|
||||
Cons x xs ->
|
||||
a = f x
|
||||
b = map f xs
|
||||
|
||||
Cons a b
|
||||
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"Attr.Attr Attr.Shared (Attr.Attr Attr.Shared (Attr.Attr a b -> c), Attr.Attr d [ Cons (Attr.Attr a b) (Attr.Attr d e), Nil ]* as e -> Attr.Attr f [ Cons c (Attr.Attr f g), Nil ]* as g)" ,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peano_map_alias() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
Peano : [ S Peano, Z ]
|
||||
|
||||
map : Peano -> Peano
|
||||
map = \peano ->
|
||||
when peano is
|
||||
Z -> Z
|
||||
S rest ->
|
||||
map rest |> S
|
||||
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"Attr.Attr Attr.Shared (Attr.Attr * Peano -> Attr.Attr * Peano)",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn peano_map_infer() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
map = \peano ->
|
||||
when peano is
|
||||
Z -> Z
|
||||
S rest ->
|
||||
map rest |> S
|
||||
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"Attr.Attr Attr.Shared (Attr.Attr a [ S (Attr.Attr a b), Z ]* as b -> Attr.Attr c [ S (Attr.Attr c d), Z ]* as d)",
|
||||
);
|
||||
}
|
||||
|
||||
// This snippet exhibits the rank issue. Seems to only occur when using recursive types with
|
||||
// recursive functions.
|
||||
#[test]
|
||||
fn rigids_in_signature() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
List a : [ Cons a (List a), Nil ]
|
||||
|
||||
map : (p -> q), p -> List q
|
||||
map = \f, x -> map f x
|
||||
|
||||
map
|
||||
"#
|
||||
),
|
||||
"Attr.Attr Attr.Shared (Attr.Attr * (Attr.Attr a p -> Attr.Attr b q), Attr.Attr a p -> Attr.Attr * (List (Attr.Attr b q)))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rigid_in_letnonrec() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
List a : [ Cons a (List a), Nil ]
|
||||
|
||||
toEmpty : List p -> List p
|
||||
toEmpty = \_ ->
|
||||
result : List p
|
||||
result = Nil
|
||||
|
||||
result
|
||||
|
||||
toEmpty
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * (Attr.Attr * (List (Attr.Attr a p)) -> Attr.Attr * (List (Attr.Attr a p)))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rigid_in_letrec() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
List a : [ Cons a (List a), Nil ]
|
||||
|
||||
toEmpty : List p -> List p
|
||||
toEmpty = \_ ->
|
||||
result : List p
|
||||
result = Nil
|
||||
|
||||
toEmpty result
|
||||
|
||||
toEmpty
|
||||
"#
|
||||
),
|
||||
"Attr.Attr Attr.Shared (Attr.Attr * (List (Attr.Attr a p)) -> Attr.Attr * (List (Attr.Attr a p)))",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_record_pattern_with_annotation() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
{ x, y } : { x : Str.Str, y : Num.Num Float.FloatingPoint }
|
||||
{ x, y } = { x : "foo", y : 3.14 }
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * Str",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_record_pattern_with_annotation_alias() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
Foo : { x : Str.Str, y : Num.Num Float.FloatingPoint }
|
||||
|
||||
{ x, y } : Foo
|
||||
{ x, y } = { x : "foo", y : 3.14 }
|
||||
|
||||
x
|
||||
"#
|
||||
),
|
||||
"Attr.Attr * Str",
|
||||
);
|
||||
}
|
||||
|
||||
// infinite loop in type_to_var
|
||||
// #[test]
|
||||
// fn typecheck_mutually_recursive_tag_union() {
|
||||
// infer_eq(
|
||||
// indoc!(
|
||||
// r#"
|
||||
// ListA a b : [ Cons a (ListB b a), Nil ]
|
||||
// ListB a b : [ Cons a (ListA b a), Nil ]
|
||||
//
|
||||
// List q : [ Cons q (List q), Nil ]
|
||||
//
|
||||
// toAs : (q -> p), ListA p q -> List p
|
||||
// toAs = \f, lista ->
|
||||
// when lista is
|
||||
// Nil -> Nil
|
||||
// Cons a listb ->
|
||||
// when listb is
|
||||
// Nil -> Nil
|
||||
// Cons b newLista ->
|
||||
// Cons a (Cons (f b) (toAs f newLista))
|
||||
//
|
||||
// toAs
|
||||
// "#
|
||||
// ),
|
||||
// "Attr.Attr Attr.Shared (Attr.Attr Attr.Shared (Attr.Attr a q -> Attr.Attr b p), Attr.Attr * (ListA (Attr.Attr b p) (Attr.Attr a q)) -> Attr.Attr * (List (Attr.Attr b p)))"
|
||||
// );
|
||||
// }
|
||||
|
||||
#[test]
|
||||
fn infer_mutually_recursive_tag_union() {
|
||||
infer_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
toAs = \f, lista ->
|
||||
when lista is
|
||||
Nil -> Nil
|
||||
Cons a listb ->
|
||||
when listb is
|
||||
Nil -> Nil
|
||||
Cons b newLista ->
|
||||
Cons a (Cons (f b) (toAs f newLista))
|
||||
|
||||
toAs
|
||||
"#
|
||||
),
|
||||
"Attr.Attr Attr.Shared (Attr.Attr Attr.Shared (Attr.Attr a b -> c), Attr.Attr d [ Cons (Attr.Attr e f) (Attr.Attr * [ Cons (Attr.Attr a b) (Attr.Attr d g), Nil ]*), Nil ]* as g -> Attr.Attr h [ Cons (Attr.Attr e f) (Attr.Attr * [ Cons c (Attr.Attr h i) ]*), Nil ]* as i)",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
641
tests/test_usage_analysis.rs
Normal file
641
tests/test_usage_analysis.rs
Normal file
@ -0,0 +1,641 @@
|
||||
#[macro_use]
|
||||
extern crate maplit;
|
||||
#[macro_use]
|
||||
extern crate pretty_assertions;
|
||||
#[macro_use]
|
||||
extern crate indoc;
|
||||
|
||||
extern crate bumpalo;
|
||||
extern crate roc;
|
||||
|
||||
mod helpers;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_usage_analysis {
|
||||
use crate::helpers::{can_expr, test_home, CanExprOut};
|
||||
use roc::can::ident::Lowercase;
|
||||
use roc::collections::{ImMap, ImSet};
|
||||
use roc::module::symbol::Interns;
|
||||
use roc::uniqueness::sharing::FieldAccess;
|
||||
use roc::uniqueness::sharing::ReferenceCount::{self, *};
|
||||
use roc::uniqueness::sharing::VarUsage;
|
||||
|
||||
fn field_access_seq(
|
||||
accesses: Vec<Vec<&str>>,
|
||||
expected: std::collections::HashMap<&str, ReferenceCount>,
|
||||
) {
|
||||
let mut state = FieldAccess::default();
|
||||
|
||||
for access in accesses {
|
||||
let temp: Vec<Lowercase> = access.into_iter().map(|v| v.into()).collect();
|
||||
state.sequential(temp);
|
||||
}
|
||||
|
||||
let mut im_expected: std::collections::HashMap<String, ReferenceCount> =
|
||||
std::collections::HashMap::default();
|
||||
|
||||
for (k, v) in expected {
|
||||
im_expected.insert(k.into(), v);
|
||||
}
|
||||
|
||||
let actual: std::collections::HashMap<String, ReferenceCount> = state.into();
|
||||
|
||||
assert_eq!(actual, im_expected);
|
||||
}
|
||||
|
||||
fn field_access_par(
|
||||
accesses: Vec<Vec<&str>>,
|
||||
expected: std::collections::HashMap<&str, ReferenceCount>,
|
||||
) {
|
||||
let mut state = FieldAccess::default();
|
||||
|
||||
for access in accesses {
|
||||
let temp: Vec<Lowercase> = access.into_iter().map(|v| v.into()).collect();
|
||||
state.parallel(temp);
|
||||
}
|
||||
|
||||
let mut im_expected: std::collections::HashMap<String, ReferenceCount> =
|
||||
std::collections::HashMap::default();
|
||||
|
||||
for (k, v) in expected {
|
||||
im_expected.insert(k.into(), v);
|
||||
}
|
||||
|
||||
let actual: std::collections::HashMap<String, ReferenceCount> = state.into();
|
||||
|
||||
assert_eq!(actual, im_expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_access_two_fields() {
|
||||
field_access_seq(
|
||||
vec![vec!["foo"], vec!["bar"]],
|
||||
hashmap![
|
||||
"foo" => Unique,
|
||||
"bar" => Unique,
|
||||
],
|
||||
);
|
||||
|
||||
field_access_par(
|
||||
vec![vec!["foo"], vec!["bar"]],
|
||||
hashmap![
|
||||
"foo" => Unique,
|
||||
"bar" => Unique,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_access_repeated_field_seq() {
|
||||
field_access_seq(
|
||||
vec![vec!["foo"], vec!["foo"]],
|
||||
hashmap![
|
||||
"foo" => Shared,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_access_repeated_field_par() {
|
||||
field_access_par(
|
||||
vec![vec!["foo"], vec!["foo"]],
|
||||
hashmap![
|
||||
"foo" => Unique,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_access_nested_field_seq() {
|
||||
field_access_seq(
|
||||
vec![vec!["foo", "bar"], vec!["foo"]],
|
||||
hashmap![
|
||||
"foo" => Unique,
|
||||
"foo.bar" => Shared,
|
||||
],
|
||||
);
|
||||
field_access_seq(
|
||||
vec![vec!["foo"], vec!["foo", "bar"]],
|
||||
hashmap![
|
||||
"foo" => Unique,
|
||||
"foo.bar" => Shared,
|
||||
],
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn usage_access_nested_field_par() {
|
||||
field_access_par(
|
||||
vec![vec!["foo", "bar"], vec!["foo"]],
|
||||
hashmap![
|
||||
"foo" => Unique,
|
||||
"foo.bar" => Unique,
|
||||
],
|
||||
);
|
||||
field_access_par(
|
||||
vec![vec!["foo"], vec!["foo", "bar"]],
|
||||
hashmap![
|
||||
"foo" => Unique,
|
||||
"foo.bar" => Unique,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_access_deeply_nested_field_seq() {
|
||||
field_access_seq(
|
||||
vec![vec!["foo", "bar", "baz"], vec!["foo", "bar"]],
|
||||
hashmap![
|
||||
"foo" => Seen,
|
||||
"foo.bar" => Unique,
|
||||
"foo.bar.baz" => Shared,
|
||||
],
|
||||
);
|
||||
field_access_seq(
|
||||
vec![vec!["foo", "bar"], vec!["foo", "bar", "baz"]],
|
||||
hashmap![
|
||||
"foo" => Seen,
|
||||
"foo.bar" => Unique,
|
||||
"foo.bar.baz" => Shared,
|
||||
],
|
||||
);
|
||||
}
|
||||
fn usage_eq<F>(src: &str, get_expected: F)
|
||||
where
|
||||
F: FnOnce(Interns) -> VarUsage,
|
||||
{
|
||||
let CanExprOut {
|
||||
loc_expr, interns, ..
|
||||
} = can_expr(src);
|
||||
|
||||
use roc::uniqueness::sharing::annotate_usage;
|
||||
let mut usage = VarUsage::default();
|
||||
annotate_usage(&loc_expr.value, &mut usage);
|
||||
|
||||
assert_eq!(usage, get_expected(interns))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_factorial() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
factorial = \n ->
|
||||
when n is
|
||||
0 -> 1
|
||||
1 -> 1
|
||||
m -> factorial m
|
||||
|
||||
factorial
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
let mut usage = VarUsage::default();
|
||||
|
||||
usage.register_with(interns.symbol(home, "m".into()), &Unique);
|
||||
usage.register_with(interns.symbol(home, "n".into()), &Unique);
|
||||
usage.register_with(interns.symbol(home, "factorial".into()), &Shared);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_record_access() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { foo : 42, bar : "baz" }
|
||||
rec.foo
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
let mut usage = VarUsage::default();
|
||||
let fa = FieldAccess::from_chain(vec!["foo".into()]);
|
||||
|
||||
usage.register_with(
|
||||
interns.symbol(home, "rec".into()),
|
||||
&ReferenceCount::Access(fa),
|
||||
);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_record_update() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { foo : 42, bar : "baz" }
|
||||
{ rec & foo: rec.foo }
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
let mut usage = VarUsage::default();
|
||||
let fa = FieldAccess::from_chain(vec!["foo".into()]);
|
||||
|
||||
let overwritten = hashset!["foo".into()].into();
|
||||
usage.register_with(
|
||||
interns.symbol(home, "rec".into()),
|
||||
&ReferenceCount::Update(overwritten, fa),
|
||||
);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_then_unique() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { foo : 42, bar : "baz" }
|
||||
v = { rec & foo: 53 }
|
||||
|
||||
rec
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
let mut usage = VarUsage::default();
|
||||
usage.register_with(interns.symbol(home, "rec".into()), &ReferenceCount::Shared);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn access_then_unique() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
rec = { foo : 42, bar : "baz" }
|
||||
v = rec.foo
|
||||
|
||||
rec
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
let mut usage = VarUsage::default();
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert(
|
||||
"foo".into(),
|
||||
(ReferenceCount::Shared, FieldAccess::default()),
|
||||
);
|
||||
let fa = FieldAccess { fields: fields };
|
||||
usage.register_with(
|
||||
interns.symbol(home, "rec".into()),
|
||||
&ReferenceCount::Update(ImSet::default(), fa),
|
||||
);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn access_then_alias() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
v = r.x
|
||||
w = r.y
|
||||
|
||||
p = r
|
||||
|
||||
p
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
let mut usage = VarUsage::default();
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("x".into(), (ReferenceCount::Shared, FieldAccess::default()));
|
||||
fields.insert("y".into(), (ReferenceCount::Shared, FieldAccess::default()));
|
||||
|
||||
usage.register_with(interns.symbol(home, "p".into()), &ReferenceCount::Unique);
|
||||
usage.register_with(
|
||||
interns.symbol(home, "r".into()),
|
||||
&ReferenceCount::Update(ImSet::default(), FieldAccess { fields }),
|
||||
);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn access_nested_then_unique() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
\r ->
|
||||
v = r.foo.bar
|
||||
w = r.foo.baz
|
||||
|
||||
r
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
let mut usage = VarUsage::default();
|
||||
|
||||
let mut nested_fields = ImMap::default();
|
||||
nested_fields.insert(
|
||||
"bar".into(),
|
||||
(ReferenceCount::Shared, FieldAccess::default()),
|
||||
);
|
||||
nested_fields.insert(
|
||||
"baz".into(),
|
||||
(ReferenceCount::Shared, FieldAccess::default()),
|
||||
);
|
||||
let nested_fa = FieldAccess {
|
||||
fields: nested_fields,
|
||||
};
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("foo".into(), (ReferenceCount::Seen, nested_fa));
|
||||
|
||||
let fa = FieldAccess { fields: fields };
|
||||
usage.register_with(
|
||||
interns.symbol(home, "r".into()),
|
||||
&ReferenceCount::Update(ImSet::default(), fa),
|
||||
);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_record_update_unique_not_overwritten() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = { x : 42, y : 2020 }
|
||||
s = { r & y: r.x }
|
||||
|
||||
p = s.x
|
||||
q = s.y
|
||||
|
||||
42
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
let mut usage = VarUsage::default();
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("x".into(), (ReferenceCount::Shared, FieldAccess::default()));
|
||||
let fa = FieldAccess { fields: fields };
|
||||
let overwritten = hashset!["y".into()].into();
|
||||
usage.register_with(
|
||||
interns.symbol(home, "r".into()),
|
||||
&ReferenceCount::Update(overwritten, fa),
|
||||
);
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("x".into(), (ReferenceCount::Unique, FieldAccess::default()));
|
||||
fields.insert("y".into(), (ReferenceCount::Unique, FieldAccess::default()));
|
||||
let fa = FieldAccess { fields: fields };
|
||||
usage.register_with(
|
||||
interns.symbol(home, "s".into()),
|
||||
&ReferenceCount::Access(fa),
|
||||
);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_record_update_unique_overwritten() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = { x : 42, y : 2020 }
|
||||
s = { r & x: 0, y: r.x }
|
||||
|
||||
p = s.x
|
||||
q = s.y
|
||||
|
||||
42
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
// pub fields: ImMap<String, (ReferenceCount, FieldAccess)>,
|
||||
let mut usage = VarUsage::default();
|
||||
|
||||
let fa = FieldAccess::from_chain(vec!["x".into()]);
|
||||
let overwritten = hashset!["x".into(), "y".into()].into();
|
||||
usage.register_with(
|
||||
interns.symbol(home, "r".into()),
|
||||
&ReferenceCount::Update(overwritten, fa),
|
||||
);
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("x".into(), (ReferenceCount::Unique, FieldAccess::default()));
|
||||
fields.insert("y".into(), (ReferenceCount::Unique, FieldAccess::default()));
|
||||
let fa = FieldAccess { fields: fields };
|
||||
usage.register_with(
|
||||
interns.symbol(home, "s".into()),
|
||||
&ReferenceCount::Access(fa),
|
||||
);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_if_access() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = { x : 42, y : 2020 }
|
||||
|
||||
if True then r.x else r.y
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
// pub fields: ImMap<String, (ReferenceCount, FieldAccess)>,
|
||||
let mut usage = VarUsage::default();
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("x".into(), (ReferenceCount::Unique, FieldAccess::default()));
|
||||
fields.insert("y".into(), (ReferenceCount::Unique, FieldAccess::default()));
|
||||
let fa = FieldAccess { fields: fields };
|
||||
usage.register_with(
|
||||
interns.symbol(home, "r".into()),
|
||||
&ReferenceCount::Access(fa),
|
||||
);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_if_update() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = { x : 42, y : 2020 }
|
||||
|
||||
if True then { r & y: r.x } else r
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
// pub fields: ImMap<String, (ReferenceCount, FieldAccess)>,
|
||||
let mut usage = VarUsage::default();
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("x".into(), (ReferenceCount::Shared, FieldAccess::default()));
|
||||
let fa = FieldAccess { fields: fields };
|
||||
usage.register_with(
|
||||
interns.symbol(home, "r".into()),
|
||||
&ReferenceCount::Update(hashset!["y".into()].into(), fa),
|
||||
);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_nested_if_access() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = { x : 42, y : 2020 }
|
||||
|
||||
if True then r.x else if False then r.x else r.y
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let home = test_home();
|
||||
// pub fields: ImMap<String, (ReferenceCount, FieldAccess)>,
|
||||
let mut usage = VarUsage::default();
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("x".into(), (ReferenceCount::Unique, FieldAccess::default()));
|
||||
fields.insert("y".into(), (ReferenceCount::Unique, FieldAccess::default()));
|
||||
let fa = FieldAccess { fields: fields };
|
||||
usage.register_with(
|
||||
interns.symbol(home, "r".into()),
|
||||
&ReferenceCount::Access(fa),
|
||||
);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_closures_with_same_bound_name() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
(\val -> val) (\val -> val)
|
||||
"#
|
||||
),
|
||||
|_interns| {
|
||||
let home = test_home();
|
||||
let mut usage = VarUsage::default();
|
||||
|
||||
usage.register_with(Interns::from_index(home, 1), &ReferenceCount::Unique);
|
||||
usage.register_with(Interns::from_index(home, 3), &ReferenceCount::Unique);
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_branch_unique_branch() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = { x: 20, y: 20 }
|
||||
s = { x: 20, y: 20 }
|
||||
|
||||
if True then
|
||||
{ x: s.x, y: r.y }
|
||||
else
|
||||
{ x: s.x, y: s.x }
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let mut usage = VarUsage::default();
|
||||
let home = test_home();
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("y".into(), (ReferenceCount::Unique, FieldAccess::default()));
|
||||
let fa_r = FieldAccess { fields: fields };
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("x".into(), (ReferenceCount::Shared, FieldAccess::default()));
|
||||
let fa_s = FieldAccess { fields: fields };
|
||||
|
||||
let r = interns.symbol(home, "r".into());
|
||||
let s = interns.symbol(home, "s".into());
|
||||
|
||||
usage.register_with(r, &ReferenceCount::Access(fa_r));
|
||||
usage.register_with(s, &ReferenceCount::Access(fa_s));
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_branch_unique_branch_access() {
|
||||
usage_eq(
|
||||
indoc!(
|
||||
r#"
|
||||
r = { x: 20 }
|
||||
s = { x: 20 }
|
||||
|
||||
if True then
|
||||
{ y: r.x }
|
||||
else
|
||||
v = s.x
|
||||
{ y: s.x }
|
||||
"#
|
||||
),
|
||||
|interns| {
|
||||
let mut usage = VarUsage::default();
|
||||
let home = test_home();
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("x".into(), (ReferenceCount::Unique, FieldAccess::default()));
|
||||
let fa_r = FieldAccess { fields: fields };
|
||||
|
||||
let mut fields = ImMap::default();
|
||||
fields.insert("x".into(), (ReferenceCount::Shared, FieldAccess::default()));
|
||||
let fa_s = FieldAccess { fields: fields };
|
||||
|
||||
let r = interns.symbol(home, "r".into());
|
||||
let s = interns.symbol(home, "s".into());
|
||||
|
||||
usage.register_with(r, &ReferenceCount::Access(fa_r));
|
||||
usage.register_with(s, &ReferenceCount::Access(fa_s));
|
||||
|
||||
usage
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user