Merge trunk into emit-records

This commit is contained in:
Dan Bruder 2020-02-14 06:53:56 -05:00
commit ae30a99689
68 changed files with 9574 additions and 6091 deletions

722
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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
View 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,
},

View File

@ -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

View File

@ -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(),
}
}

File diff suppressed because it is too large Load Diff

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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")

View File

@ -11,4 +11,3 @@ pub mod problem;
pub mod procedure;
pub mod scope;
pub mod string;
pub mod symbol;

View File

@ -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),

View File

@ -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,
});

View File

@ -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(_) => (),
}
}

View File

@ -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 */)>,

View File

@ -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)
}
}

View File

@ -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 });
}
}

View File

@ -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
}
}

View File

@ -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())
}

View File

@ -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),
})),

View File

@ -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,
}))
}

View File

@ -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");
}
}

View File

@ -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);
}

View File

@ -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

View File

@ -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
}

View File

@ -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);

View File

@ -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) => {

View File

@ -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);
}
}
}

View File

@ -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),
}
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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>>,

View File

@ -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.)
}

View File

@ -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!

View File

@ -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(()),

View File

@ -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,

View File

@ -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()
}

View File

@ -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()

View File

@ -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>;
}

View File

@ -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))
}

View File

@ -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);
}
}

View File

@ -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,

File diff suppressed because it is too large Load Diff

View File

@ -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);
}

View File

@ -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,
}
}

View File

@ -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]
}

View File

@ -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(&not(p.clone())));
let it2 = qs
.clone()
.into_iter()
.filter(|q| ps.contains(&not(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(&not(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(&not(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)
}
}

View File

@ -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

View File

@ -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(&current_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(&current_usage);
current_usage.add(&b);
current_usage.or(&a);
rec_usage.add(&current_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);
}
}
_ => {}
}
}

View File

@ -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 ]

View File

@ -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

View 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 }

View File

@ -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

View File

@ -4,4 +4,4 @@ interface Principal
identity = \a -> a
intVal = identity 5
intVal = identity "hi"

View 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

View File

@ -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

View File

@ -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
// }

View File

@ -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() {

View File

@ -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]

View File

@ -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"
);
}
}

View File

@ -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",
// },
// );
// });

View File

@ -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,

View File

@ -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)",
);
}
}

View 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
},
);
}
}