mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 16:44:33 +00:00
Update solve and unify
This commit is contained in:
parent
c41167ec6e
commit
5eb843326c
4 changed files with 188 additions and 115 deletions
51
src/can/ident.rs
Normal file
51
src/can/ident.rs
Normal file
|
@ -0,0 +1,51 @@
|
|||
use crate::ident::UnqualifiedIdent;
|
||||
use std::fmt;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct ModuleName(Box<str>);
|
||||
|
||||
/// An uncapitalized identifier, such as a field name or local variable
|
||||
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Lowercase(Box<str>);
|
||||
|
||||
/// A capitalized identifier, such as a tag name or module name
|
||||
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Uppercase(Box<str>);
|
||||
|
||||
impl Lowercase {
|
||||
pub fn from_unqualified_ident(ident: &UnqualifiedIdent<'_>) -> Self {
|
||||
Self(ident.as_str().into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Box<str>> for Lowercase {
|
||||
fn into(self) -> Box<str> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Rather than displaying as this:
|
||||
///
|
||||
/// Lowercase("foo")
|
||||
///
|
||||
/// ...instead display as this:
|
||||
///
|
||||
/// 'foo'
|
||||
impl fmt::Debug for Lowercase {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "'{}'", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Rather than displaying as this:
|
||||
///
|
||||
/// Uppercase("Foo")
|
||||
///
|
||||
/// ...instead display as this:
|
||||
///
|
||||
/// 'Foo'
|
||||
impl fmt::Debug for Uppercase {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "'{}'", self.0)
|
||||
}
|
||||
}
|
33
src/solve.rs
33
src/solve.rs
|
@ -3,6 +3,7 @@ use crate::collections::ImMap;
|
|||
use crate::subs::{Content, Descriptor, FlatType, Subs, Variable};
|
||||
use crate::types::Constraint::{self, *};
|
||||
use crate::types::Type::{self, *};
|
||||
use crate::unify::unify;
|
||||
|
||||
type Env = ImMap<Symbol, Variable>;
|
||||
|
||||
|
@ -11,10 +12,10 @@ pub fn solve(env: &Env, subs: &mut Subs, constraint: &Constraint) {
|
|||
True => (),
|
||||
Eq(typ, expected_type, _region) => {
|
||||
// TODO use region?
|
||||
let actual = type_to_variable(subs, typ.clone());
|
||||
let expected = type_to_variable(subs, expected_type.clone().get_type());
|
||||
let actual = type_to_var(subs, typ.clone());
|
||||
let expected = type_to_var(subs, expected_type.clone().get_type());
|
||||
|
||||
subs.union(actual, expected);
|
||||
unify(subs, actual, expected);
|
||||
}
|
||||
Lookup(symbol, expected_type, _region) => {
|
||||
// TODO use region?
|
||||
|
@ -22,9 +23,9 @@ pub fn solve(env: &Env, subs: &mut Subs, constraint: &Constraint) {
|
|||
subs.copy_var(*env.get(&symbol).unwrap_or_else(|| {
|
||||
panic!("Could not find symbol {:?} in env {:?}", symbol, env)
|
||||
}));
|
||||
let expected = type_to_variable(subs, expected_type.clone().get_type());
|
||||
let expected = type_to_var(subs, expected_type.clone().get_type());
|
||||
|
||||
subs.union(actual, expected);
|
||||
unify(subs, actual, expected);
|
||||
}
|
||||
And(sub_constraints) => {
|
||||
for sub_constraint in sub_constraints.iter() {
|
||||
|
@ -33,10 +34,10 @@ pub fn solve(env: &Env, subs: &mut Subs, constraint: &Constraint) {
|
|||
}
|
||||
Pattern(_region, _category, typ, expected) => {
|
||||
// TODO use region?
|
||||
let actual = type_to_variable(subs, typ.clone());
|
||||
let expected = type_to_variable(subs, expected.clone().get_type());
|
||||
let actual = type_to_var(subs, typ.clone());
|
||||
let expected = type_to_var(subs, expected.clone().get_type());
|
||||
|
||||
subs.union(actual, expected);
|
||||
unify(subs, actual, expected);
|
||||
}
|
||||
Let(let_con) => {
|
||||
match &let_con.ret_constraint {
|
||||
|
@ -58,7 +59,7 @@ pub fn solve(env: &Env, subs: &mut Subs, constraint: &Constraint) {
|
|||
// inserted earlier in solving. (If we allowed
|
||||
// shadowing, we'd need to do something fancier here.)
|
||||
if !new_env.contains_key(&symbol) {
|
||||
let var = type_to_variable(subs, loc_type.value.clone());
|
||||
let var = type_to_var(subs, loc_type.value.clone());
|
||||
|
||||
new_env.insert(symbol.clone(), var);
|
||||
}
|
||||
|
@ -75,7 +76,11 @@ pub fn solve(env: &Env, subs: &mut Subs, constraint: &Constraint) {
|
|||
}
|
||||
}
|
||||
|
||||
fn type_to_variable(subs: &mut Subs, typ: Type) -> Variable {
|
||||
fn type_to_var(subs: &mut Subs, typ: Type) -> Variable {
|
||||
type_to_variable(subs, &ImMap::default(), typ)
|
||||
}
|
||||
|
||||
fn type_to_variable(subs: &mut Subs, aliases: &ImMap<Box<str>, Variable>, typ: Type) -> Variable {
|
||||
match typ {
|
||||
Variable(var) => var,
|
||||
Apply {
|
||||
|
@ -86,7 +91,7 @@ fn type_to_variable(subs: &mut Subs, typ: Type) -> Variable {
|
|||
let mut arg_vars = Vec::with_capacity(args.len());
|
||||
|
||||
for arg in args {
|
||||
arg_vars.push(type_to_variable(subs, arg.clone()))
|
||||
arg_vars.push(type_to_variable(subs, aliases, arg.clone()))
|
||||
}
|
||||
|
||||
let flat_type = FlatType::Apply {
|
||||
|
@ -107,11 +112,11 @@ fn type_to_variable(subs: &mut Subs, typ: Type) -> Variable {
|
|||
let mut arg_vars = Vec::with_capacity(args.len());
|
||||
|
||||
for arg in args {
|
||||
arg_vars.push(type_to_variable(subs, arg.clone()))
|
||||
arg_vars.push(type_to_variable(subs, aliases, arg.clone()))
|
||||
}
|
||||
|
||||
let ret_var = type_to_variable(subs, *ret_type);
|
||||
let content: Content = Content::Structure(FlatType::Func(arg_vars, ret_var));
|
||||
let ret_var = type_to_variable(subs, aliases, *ret_type);
|
||||
let content = Content::Structure(FlatType::Func(arg_vars, ret_var));
|
||||
|
||||
subs.fresh(Descriptor::from(content))
|
||||
}
|
||||
|
|
80
src/subs.rs
80
src/subs.rs
|
@ -1,9 +1,47 @@
|
|||
use crate::ena::unify::{InPlace, UnificationTable, UnifyKey};
|
||||
use crate::types::Problem;
|
||||
use crate::unify;
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct Mark(u8);
|
||||
|
||||
impl Mark {
|
||||
#[inline(always)]
|
||||
pub fn none() -> Mark {
|
||||
Mark(0)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn occurs() -> Mark {
|
||||
Mark(1)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_var_names() -> Mark {
|
||||
Mark(2)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn next(self) -> Mark {
|
||||
Mark(self.0 - 1)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Mark {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self == &Mark::none() {
|
||||
write!(f, "Mark::none")
|
||||
} else if self == &Mark::occurs() {
|
||||
write!(f, "Mark::occurs")
|
||||
} else if self == &Mark::get_var_names() {
|
||||
write!(f, "Mark::get_var_names")
|
||||
} else {
|
||||
write!(f, "Mark({})", self.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Subs {
|
||||
utable: UnificationTable<InPlace<Variable>>,
|
||||
|
@ -91,15 +129,11 @@ impl Subs {
|
|||
}
|
||||
|
||||
/// Unions two keys without the possibility of failure.
|
||||
pub fn union(&mut self, left: Variable, right: Variable) {
|
||||
pub fn union(&mut self, left: Variable, right: Variable, desc: Descriptor) {
|
||||
let l_root = self.utable.get_root_key(left);
|
||||
let r_root = self.utable.get_root_key(right);
|
||||
|
||||
if l_root != r_root {
|
||||
let combined = unify::unify_vars(self, l_root, r_root);
|
||||
|
||||
self.utable.unify_roots(l_root, r_root, combined)
|
||||
}
|
||||
self.utable.unify_roots(l_root, r_root, desc)
|
||||
}
|
||||
|
||||
pub fn get(&mut self, key: Variable) -> Descriptor {
|
||||
|
@ -112,9 +146,8 @@ impl Subs {
|
|||
|
||||
pub fn set(&mut self, key: Variable, r_value: Descriptor) {
|
||||
let l_key = self.utable.get_root_key(key);
|
||||
let unified = unify::unify_var_val(self, l_key, &r_value);
|
||||
|
||||
self.utable.update_value(l_key, |node| node.value = unified);
|
||||
self.utable.update_value(l_key, |node| node.value = r_value);
|
||||
}
|
||||
|
||||
pub fn copy_var(&mut self, var: Variable) -> Variable {
|
||||
|
@ -123,20 +156,9 @@ impl Subs {
|
|||
var
|
||||
}
|
||||
|
||||
// pub fn set_rank(&mut self, key: Variable, rank: usize) {
|
||||
// let mut descriptor = self.utable.probe_value(key);
|
||||
|
||||
// descriptor.rank = rank;
|
||||
|
||||
// let result = self.utable.unify_var_value(key, descriptor);
|
||||
|
||||
// // Updating the rank should never fail!
|
||||
// debug_assert_eq!(result, Ok(()));
|
||||
// }
|
||||
|
||||
// pub fn equivalent(&mut self, left: Variable, right: Variable) -> bool {
|
||||
// self.utable.unioned(left, right)
|
||||
// }
|
||||
pub fn equivalent(&mut self, left: Variable, right: Variable) -> bool {
|
||||
self.utable.unioned(left, right)
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
|
@ -152,17 +174,23 @@ fn unnamed_flex_var() -> Content {
|
|||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Descriptor {
|
||||
pub content: Content,
|
||||
pub rank: usize,
|
||||
pub mark: u32,
|
||||
pub rank: u8,
|
||||
pub mark: Mark,
|
||||
pub copy: Option<Variable>,
|
||||
}
|
||||
|
||||
impl Default for Descriptor {
|
||||
fn default() -> Self {
|
||||
unnamed_flex_var().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Content> for Descriptor {
|
||||
fn from(content: Content) -> Descriptor {
|
||||
Descriptor {
|
||||
content,
|
||||
rank: 0,
|
||||
mark: 2, // no mark
|
||||
mark: Mark::none(),
|
||||
copy: None,
|
||||
}
|
||||
}
|
||||
|
|
139
src/unify.rs
139
src/unify.rs
|
@ -1,61 +1,68 @@
|
|||
use crate::subs::Content::{self, *};
|
||||
use crate::subs::{Descriptor, FlatType, Subs, Variable};
|
||||
use crate::subs::{Descriptor, FlatType, Mark, Subs, Variable};
|
||||
use crate::types::Problem;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn unify_vars(subs: &mut Subs, left_key: Variable, right_key: Variable) -> Descriptor {
|
||||
let right = subs.get(right_key);
|
||||
|
||||
unify_var_val(subs, left_key, &right)
|
||||
struct Context {
|
||||
first: Variable,
|
||||
first_desc: Descriptor,
|
||||
second: Variable,
|
||||
second_desc: Descriptor,
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn unify_var_val(subs: &mut Subs, left_key: Variable, right: &Descriptor) -> Descriptor {
|
||||
let left = subs.get(left_key);
|
||||
pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable) {
|
||||
if !subs.equivalent(var1, var2) {
|
||||
let ctx = Context {
|
||||
first: var1,
|
||||
first_desc: subs.get(var1),
|
||||
second: var2,
|
||||
second_desc: subs.get(var2),
|
||||
};
|
||||
|
||||
unify(subs, &left, right)
|
||||
unify_context(subs, &ctx)
|
||||
}
|
||||
}
|
||||
|
||||
fn unify(subs: &mut Subs, left: &Descriptor, right: &Descriptor) -> Descriptor {
|
||||
match left.content {
|
||||
FlexVar(ref opt_name) => unify_flex(opt_name, &right.content),
|
||||
RigidVar(ref name) => unify_rigid(name, &right.content),
|
||||
Structure(ref flat_type) => unify_structure(subs, flat_type, &right.content),
|
||||
fn unify_context(subs: &mut Subs, ctx: &Context) {
|
||||
match ctx.first_desc.content {
|
||||
FlexVar(ref opt_name) => unify_flex(subs, ctx, opt_name, &ctx.second_desc.content),
|
||||
RigidVar(ref name) => unify_rigid(subs, ctx, name, &ctx.second_desc.content),
|
||||
Structure(ref flat_type) => unify_structure(subs, ctx, flat_type, &ctx.second_desc.content),
|
||||
Error(ref problem) => {
|
||||
// Error propagates. Whatever we're comparing it to doesn't matter!
|
||||
from_content(Error(problem.clone()))
|
||||
merge(subs, ctx, Error(problem.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unify_structure(subs: &mut Subs, flat_type: &FlatType, other: &Content) -> Descriptor {
|
||||
fn unify_structure(subs: &mut Subs, ctx: &Context, flat_type: &FlatType, other: &Content) {
|
||||
match other {
|
||||
FlexVar(_) => {
|
||||
// If the other is flex, Structure wins!
|
||||
from_content(Structure(flat_type.clone()))
|
||||
merge(subs, ctx, Structure(flat_type.clone()))
|
||||
}
|
||||
RigidVar(_) => {
|
||||
// Type mismatch! Rigid can only unify with flex.
|
||||
from_content(Error(Problem::GenericMismatch))
|
||||
merge(subs, ctx, Error(Problem::GenericMismatch))
|
||||
}
|
||||
Structure(ref other_flat_type) => {
|
||||
// Unify the two flat types
|
||||
unify_flat_type(subs, flat_type, other_flat_type)
|
||||
unify_flat_type(subs, ctx, flat_type, other_flat_type)
|
||||
}
|
||||
Error(problem) => {
|
||||
// Error propagates.
|
||||
from_content(Error(problem.clone()))
|
||||
merge(subs, ctx, Error(problem.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unify_flat_type(subs: &mut Subs, left: &FlatType, right: &FlatType) -> Descriptor {
|
||||
fn unify_flat_type(subs: &mut Subs, ctx: &Context, left: &FlatType, right: &FlatType) {
|
||||
use crate::subs::FlatType::*;
|
||||
|
||||
match (left, right) {
|
||||
(EmptyRecord, EmptyRecord) => from_content(Structure(left.clone())),
|
||||
(EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())),
|
||||
(
|
||||
Apply {
|
||||
module_name: l_module_name,
|
||||
|
@ -68,103 +75,85 @@ fn unify_flat_type(subs: &mut Subs, left: &FlatType, right: &FlatType) -> Descri
|
|||
args: r_args,
|
||||
},
|
||||
) if l_module_name == r_module_name && l_type_name == r_type_name => {
|
||||
let args = unify_args(subs, l_args.iter(), r_args.iter());
|
||||
let flat_type = Apply {
|
||||
module_name: l_module_name.clone(),
|
||||
name: l_type_name.clone(),
|
||||
args,
|
||||
};
|
||||
unify_zip(subs, l_args.iter(), r_args.iter());
|
||||
|
||||
from_content(Structure(flat_type))
|
||||
merge(
|
||||
subs,
|
||||
ctx,
|
||||
Structure(Apply {
|
||||
module_name: (*r_module_name).clone(),
|
||||
name: (*r_type_name).clone(),
|
||||
args: (*r_args).clone(),
|
||||
}),
|
||||
)
|
||||
}
|
||||
(Func(l_args, l_ret), Func(r_args, r_ret)) => {
|
||||
if l_args.len() == r_args.len() {
|
||||
let args = unify_args(subs, l_args.iter(), r_args.iter());
|
||||
let ret = union_vars(subs, *l_ret, *r_ret);
|
||||
let flat_type = Func(args, ret);
|
||||
unify_zip(subs, l_args.iter(), r_args.iter());
|
||||
unify(subs, *l_ret, *r_ret);
|
||||
|
||||
from_content(Structure(flat_type))
|
||||
merge(subs, ctx, Structure(Func((*r_args).clone(), *r_ret)))
|
||||
} else if l_args.len() > r_args.len() {
|
||||
from_content(Error(Problem::ExtraArguments))
|
||||
merge(subs, ctx, Error(Problem::ExtraArguments))
|
||||
} else {
|
||||
from_content(Error(Problem::MissingArguments))
|
||||
merge(subs, ctx, Error(Problem::MissingArguments))
|
||||
}
|
||||
}
|
||||
_ => from_content(Error(Problem::GenericMismatch)),
|
||||
_ => merge(subs, ctx, Error(Problem::GenericMismatch)),
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_args<'a, I>(subs: &mut Subs, left_iter: I, right_iter: I) -> Vec<Variable>
|
||||
fn unify_zip<'a, I>(subs: &mut Subs, left_iter: I, right_iter: I)
|
||||
where
|
||||
I: Iterator<Item = &'a Variable>,
|
||||
{
|
||||
left_iter
|
||||
.zip(right_iter)
|
||||
.map(|(&l_var, &r_var)| {
|
||||
// Look up the descriptors we have for these variables, and unify them.
|
||||
let descriptor = unify_vars(subs, l_var, r_var);
|
||||
|
||||
// set r_var to be the unioned value, then union l_var to r_var
|
||||
subs.set(r_var, descriptor);
|
||||
subs.union(l_var, r_var);
|
||||
|
||||
r_var
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn union_vars(subs: &mut Subs, l_var: Variable, r_var: Variable) -> Variable {
|
||||
// Look up the descriptors we have for these variables, and unify them.
|
||||
let descriptor = unify_vars(subs, l_var, r_var);
|
||||
|
||||
// set r_var to be the unioned value, then union l_var to r_var
|
||||
subs.set(r_var, descriptor);
|
||||
subs.union(l_var, r_var);
|
||||
|
||||
r_var
|
||||
for (&l_var, &r_var) in left_iter.zip(right_iter) {
|
||||
unify(subs, l_var, r_var);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unify_rigid(name: &str, other: &Content) -> Descriptor {
|
||||
fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &str, other: &Content) {
|
||||
match other {
|
||||
FlexVar(_) => {
|
||||
// If the other is flex, rigid wins!
|
||||
from_content(RigidVar(name.into()))
|
||||
merge(subs, ctx, RigidVar(name.into()))
|
||||
}
|
||||
RigidVar(_) | Structure(_) => {
|
||||
// Type mismatch! Rigid can only unify with flex, even if the
|
||||
// rigid names are the same.
|
||||
from_content(Error(Problem::GenericMismatch))
|
||||
merge(subs, ctx, Error(Problem::GenericMismatch))
|
||||
}
|
||||
Error(problem) => {
|
||||
// Error propagates.
|
||||
from_content(Error(problem.clone()))
|
||||
merge(subs, ctx, Error(problem.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unify_flex(opt_name: &Option<Box<str>>, other: &Content) -> Descriptor {
|
||||
fn unify_flex(subs: &mut Subs, ctx: &Context, opt_name: &Option<Box<str>>, other: &Content) {
|
||||
match other {
|
||||
FlexVar(None) => {
|
||||
// If both are flex, and only left has a name, keep the name around.
|
||||
from_content(FlexVar(opt_name.clone()))
|
||||
merge(subs, ctx, FlexVar(opt_name.clone()))
|
||||
}
|
||||
FlexVar(Some(_)) | RigidVar(_) | Structure(_) | Error(_) => {
|
||||
// In all other cases, if left is flex, defer to right.
|
||||
// (This includes using right's name if both are flex and named.)
|
||||
from_content(other.clone())
|
||||
merge(subs, ctx, other.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// TODO this was f/k/a merge() - got rid of the rank stuff...good idea? Bad?
|
||||
/// TODO it used to be { rank: std::cmp::min(left_rank, right_rank), ... }
|
||||
fn from_content(content: Content) -> Descriptor {
|
||||
Descriptor {
|
||||
fn merge(subs: &mut Subs, ctx: &Context, content: Content) {
|
||||
let rank = ctx.first_desc.rank.min(ctx.second_desc.rank);
|
||||
let desc = Descriptor {
|
||||
content,
|
||||
rank: 0,
|
||||
mark: 2, // no mark
|
||||
rank,
|
||||
mark: Mark::none(),
|
||||
copy: None,
|
||||
}
|
||||
};
|
||||
|
||||
subs.union(ctx.first, ctx.second, desc);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue