mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Improve error message reporting
This commit is contained in:
parent
2e90cfc184
commit
6df252a54e
8 changed files with 209 additions and 208 deletions
|
@ -1,12 +1,11 @@
|
|||
use crate::collections::ImMap;
|
||||
use crate::solve;
|
||||
use crate::subs::{Content, Subs, Variable};
|
||||
use crate::types::Constraint;
|
||||
use crate::unify::Problems;
|
||||
use crate::types::{Constraint, Problem};
|
||||
|
||||
pub fn infer_expr(
|
||||
subs: &mut Subs,
|
||||
problems: &mut Problems,
|
||||
problems: &mut Vec<Problem>,
|
||||
constraint: &Constraint,
|
||||
expr_var: Variable,
|
||||
) -> Content {
|
||||
|
|
|
@ -12,8 +12,7 @@ use crate::region::{Located, Region};
|
|||
use crate::solve;
|
||||
use crate::subs::VarStore;
|
||||
use crate::subs::{Subs, Variable};
|
||||
use crate::types::Constraint;
|
||||
use crate::unify::Problems;
|
||||
use crate::types::{Constraint, Problem};
|
||||
use bumpalo::Bump;
|
||||
use futures::future::join_all;
|
||||
use std::fs::read_to_string;
|
||||
|
@ -356,7 +355,7 @@ fn expose(
|
|||
|
||||
pub fn solve_loaded(
|
||||
module: &Module,
|
||||
problems: &mut Problems,
|
||||
problems: &mut Vec<Problem>,
|
||||
subs: &mut Subs,
|
||||
loaded_deps: LoadedDeps,
|
||||
) {
|
||||
|
|
|
@ -104,7 +104,7 @@ fn find_names_needed(
|
|||
Alias(_, _, _, _) => {
|
||||
panic!("TODO find_names_needed Alias");
|
||||
}
|
||||
Error(_) | Structure(Erroneous(_)) | Structure(EmptyRecord) => {
|
||||
Error | Structure(Erroneous(_)) | Structure(EmptyRecord) => {
|
||||
// Errors and empty records don't need names.
|
||||
}
|
||||
}
|
||||
|
@ -177,7 +177,7 @@ fn write_content(content: Content, subs: &mut Subs, buf: &mut String, parens: Pa
|
|||
Alias(_, _, _, _) => {
|
||||
panic!("TODO write_content Alias");
|
||||
}
|
||||
Error(_) => buf.push_str("<type mismatch>"),
|
||||
Error => buf.push_str("<type mismatch>"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
24
src/solve.rs
24
src/solve.rs
|
@ -6,7 +6,7 @@ use crate::subs::{Content, Descriptor, FlatType, Mark, Rank, Subs, Variable};
|
|||
use crate::types::Constraint::{self, *};
|
||||
use crate::types::Problem;
|
||||
use crate::types::Type::{self, *};
|
||||
use crate::unify::{unify, Problems};
|
||||
use crate::unify::{unify, Unified};
|
||||
|
||||
type Env = ImMap<Symbol, Variable>;
|
||||
|
||||
|
@ -62,7 +62,7 @@ struct State {
|
|||
|
||||
pub fn run(
|
||||
vars_by_symbol: &Env,
|
||||
problems: &mut Problems,
|
||||
problems: &mut Vec<Problem>,
|
||||
subs: &mut Subs,
|
||||
constraint: &Constraint,
|
||||
) {
|
||||
|
@ -89,7 +89,7 @@ fn solve(
|
|||
state: State,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
problems: &mut Problems,
|
||||
problems: &mut Vec<Problem>,
|
||||
subs: &mut Subs,
|
||||
constraint: &Constraint,
|
||||
) -> State {
|
||||
|
@ -99,7 +99,9 @@ fn solve(
|
|||
// TODO use region?
|
||||
let actual = type_to_var(subs, typ.clone());
|
||||
let expected = type_to_var(subs, expected_type.clone().get_type());
|
||||
let vars = unify(subs, problems, actual, expected);
|
||||
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
||||
|
||||
problems.extend(mismatches);
|
||||
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
|
@ -116,7 +118,9 @@ fn solve(
|
|||
)
|
||||
}));
|
||||
let expected = type_to_var(subs, expected_type.clone().get_type());
|
||||
let vars = unify(subs, problems, actual, expected);
|
||||
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
||||
|
||||
problems.extend(mismatches);
|
||||
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
|
@ -143,7 +147,9 @@ fn solve(
|
|||
// TODO use region?
|
||||
let actual = type_to_var(subs, typ.clone());
|
||||
let expected = type_to_var(subs, expected.clone().get_type());
|
||||
let vars = unify(subs, problems, actual, expected);
|
||||
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
||||
|
||||
problems.extend(mismatches);
|
||||
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
|
@ -349,7 +355,7 @@ fn type_to_variable(subs: &mut Subs, aliases: &ImMap<Lowercase, Variable>, typ:
|
|||
|
||||
fn check_for_infinite_type(
|
||||
subs: &mut Subs,
|
||||
problems: &mut Problems,
|
||||
problems: &mut Vec<Problem>,
|
||||
symbol: Symbol,
|
||||
loc_var: Located<Variable>,
|
||||
) {
|
||||
|
@ -359,7 +365,7 @@ fn check_for_infinite_type(
|
|||
let error_type = subs.var_to_error_type(var);
|
||||
let problem = Problem::CircularType(symbol, error_type, loc_var.region);
|
||||
|
||||
subs.set_content(var, Content::Error(problem.clone()));
|
||||
subs.set_content(var, Content::Error);
|
||||
|
||||
problems.push(problem);
|
||||
}
|
||||
|
@ -490,7 +496,7 @@ fn adjust_rank_content(
|
|||
use crate::subs::FlatType::*;
|
||||
|
||||
match content {
|
||||
FlexVar(_) | RigidVar(_) | Error(_) => group_rank,
|
||||
FlexVar(_) | RigidVar(_) | Error => group_rank,
|
||||
|
||||
Structure(flat_type) => {
|
||||
match flat_type {
|
||||
|
|
19
src/subs.rs
19
src/subs.rs
|
@ -315,6 +315,17 @@ impl From<Content> for Descriptor {
|
|||
}
|
||||
}
|
||||
|
||||
impl Descriptor {
|
||||
pub fn error() -> Self {
|
||||
Descriptor {
|
||||
content: Content::Error,
|
||||
rank: Rank::none(),
|
||||
mark: Mark::none(),
|
||||
copy: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum Content {
|
||||
/// A type variable which the user did not name in an annotation,
|
||||
|
@ -328,7 +339,7 @@ pub enum Content {
|
|||
RigidVar(Lowercase),
|
||||
Structure(FlatType),
|
||||
Alias(ModuleName, Uppercase, Vec<(Lowercase, Variable)>, Variable),
|
||||
Error(Problem),
|
||||
Error,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
|
@ -360,7 +371,7 @@ fn occurs(subs: &mut Subs, seen: &ImSet<Variable>, var: Variable) -> bool {
|
|||
true
|
||||
} else {
|
||||
match subs.get(var).content {
|
||||
FlexVar(_) | RigidVar(_) | Error(_) => false,
|
||||
FlexVar(_) | RigidVar(_) | Error => false,
|
||||
|
||||
Structure(flat_type) => {
|
||||
let mut new_seen = seen.clone();
|
||||
|
@ -408,7 +419,7 @@ fn get_var_names(
|
|||
subs.set_mark(var, Mark::get_var_names());
|
||||
|
||||
match desc.content {
|
||||
Error(_) | FlexVar(None) => taken_names,
|
||||
Error | FlexVar(None) => taken_names,
|
||||
FlexVar(Some(name)) => {
|
||||
add_name(subs, 0, name, var, |name| FlexVar(Some(name)), taken_names)
|
||||
}
|
||||
|
@ -553,7 +564,7 @@ fn content_to_err_type(
|
|||
ErrorType::Alias(module_name, name, err_args, Box::new(err_type))
|
||||
}
|
||||
|
||||
Error(_) => ErrorType::Error,
|
||||
Error => ErrorType::Error,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
14
src/types.rs
14
src/types.rs
|
@ -280,13 +280,19 @@ pub struct LetConstraint {
|
|||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Problem {
|
||||
GenericMismatch,
|
||||
ExtraArguments,
|
||||
MissingArguments,
|
||||
CanonicalizationProblem,
|
||||
Mismatch(Mismatch, ErrorType, ErrorType),
|
||||
CircularType(Symbol, ErrorType, Region),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Mismatch {
|
||||
TypeMismatch,
|
||||
ExtraArguments { expected: usize, actual: usize },
|
||||
MissingArguments { expected: usize, actual: usize },
|
||||
IfConditionNotBool,
|
||||
InconsistentIfElse,
|
||||
InconsistentCaseBranches,
|
||||
CircularType(Symbol, ErrorType, Region),
|
||||
CanonicalizationProblem,
|
||||
}
|
||||
|
||||
|
|
344
src/unify.rs
344
src/unify.rs
|
@ -2,9 +2,11 @@ use crate::can::ident::{Lowercase, ModuleName, Uppercase};
|
|||
use crate::collections::ImMap;
|
||||
use crate::subs::Content::{self, *};
|
||||
use crate::subs::{Descriptor, FlatType, Mark, Subs, Variable};
|
||||
use crate::types::Problem;
|
||||
use crate::types::{Mismatch, Problem};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
type Pool = Vec<Variable>;
|
||||
|
||||
struct Context {
|
||||
first: Variable,
|
||||
first_desc: Descriptor,
|
||||
|
@ -17,27 +19,36 @@ struct RecordStructure {
|
|||
ext: Variable,
|
||||
}
|
||||
|
||||
pub type Problems = Vec<Problem>;
|
||||
pub type Pool = Vec<Variable>;
|
||||
pub struct Unified {
|
||||
pub vars: Pool,
|
||||
pub mismatches: Vec<Problem>,
|
||||
}
|
||||
|
||||
type Outcome = Vec<Mismatch>;
|
||||
|
||||
#[inline(always)]
|
||||
pub fn unify(subs: &mut Subs, problems: &mut Problems, var1: Variable, var2: Variable) -> Pool {
|
||||
let mut pool = Vec::new();
|
||||
pub fn unify(subs: &mut Subs, var1: Variable, var2: Variable) -> Unified {
|
||||
let mut vars = Vec::new();
|
||||
let mismatches = unify_pool(subs, &mut vars, var1, var2)
|
||||
.into_iter()
|
||||
.map(|problem| {
|
||||
let type1 = subs.var_to_error_type(var1);
|
||||
let type2 = subs.var_to_error_type(var2);
|
||||
|
||||
unify_pool(subs, &mut pool, problems, var1, var2);
|
||||
subs.union(var1, var2, Descriptor::error());
|
||||
|
||||
pool
|
||||
Problem::Mismatch(problem, type1, type2)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Unified { vars, mismatches }
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn unify_pool(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
problems: &mut Problems,
|
||||
var1: Variable,
|
||||
var2: Variable,
|
||||
) {
|
||||
if !subs.equivalent(var1, var2) {
|
||||
pub fn unify_pool(subs: &mut Subs, pool: &mut Pool, var1: Variable, var2: Variable) -> Outcome {
|
||||
if subs.equivalent(var1, var2) {
|
||||
Vec::new()
|
||||
} else {
|
||||
let ctx = Context {
|
||||
first: var1,
|
||||
first_desc: subs.get(var1),
|
||||
|
@ -45,46 +56,37 @@ pub fn unify_pool(
|
|||
second_desc: subs.get(var2),
|
||||
};
|
||||
|
||||
unify_context(subs, pool, problems, ctx);
|
||||
unify_context(subs, pool, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_context(subs: &mut Subs, pool: &mut Pool, problems: &mut Problems, ctx: Context) {
|
||||
fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
||||
match &ctx.first_desc.content {
|
||||
FlexVar(opt_name) => unify_flex(subs, problems, &ctx, opt_name, &ctx.second_desc.content),
|
||||
RigidVar(name) => unify_rigid(subs, problems, &ctx, name, &ctx.second_desc.content),
|
||||
Structure(flat_type) => unify_structure(
|
||||
subs,
|
||||
pool,
|
||||
problems,
|
||||
&ctx,
|
||||
flat_type,
|
||||
&ctx.second_desc.content,
|
||||
),
|
||||
Alias(home, name, args, real_var) => {
|
||||
unify_alias(subs, pool, problems, &ctx, home, name, args, *real_var)
|
||||
FlexVar(opt_name) => unify_flex(subs, &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)
|
||||
}
|
||||
Error(problem) => {
|
||||
Alias(home, name, args, real_var) => {
|
||||
unify_alias(subs, pool, &ctx, home, name, args, *real_var)
|
||||
}
|
||||
Error => {
|
||||
// Error propagates. Whatever we're comparing it to doesn't matter!
|
||||
merge(subs, &ctx, Error(problem.clone()));
|
||||
problems.push(problem.clone());
|
||||
merge(subs, &ctx, Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO trim down this arg list
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[inline(always)]
|
||||
fn unify_alias(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
problems: &mut Problems,
|
||||
ctx: &Context,
|
||||
home: &ModuleName,
|
||||
name: &Uppercase,
|
||||
args: &[(Lowercase, Variable)],
|
||||
real_var: Variable,
|
||||
) {
|
||||
) -> Outcome {
|
||||
let other_content = &ctx.second_desc.content;
|
||||
|
||||
match other_content {
|
||||
|
@ -94,40 +96,34 @@ fn unify_alias(
|
|||
subs,
|
||||
&ctx,
|
||||
Alias(home.clone(), name.clone(), args.to_owned(), real_var),
|
||||
);
|
||||
)
|
||||
}
|
||||
RigidVar(_) => unify_pool(subs, pool, problems, real_var, ctx.second),
|
||||
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 {
|
||||
match args.len().cmp(&other_args.len()) {
|
||||
Ordering::Greater => {
|
||||
let problem = Problem::ExtraArguments;
|
||||
|
||||
merge(subs, &ctx, Error(problem.clone()));
|
||||
problems.push(problem);
|
||||
}
|
||||
Ordering::Less => {
|
||||
let problem = Problem::MissingArguments;
|
||||
|
||||
merge(subs, &ctx, Error(problem.clone()));
|
||||
problems.push(problem);
|
||||
}
|
||||
Ordering::Greater => vec![Mismatch::ExtraArguments {
|
||||
expected: other_args.len(),
|
||||
actual: args.len(),
|
||||
}],
|
||||
Ordering::Less => vec![Mismatch::MissingArguments {
|
||||
expected: other_args.len(),
|
||||
actual: args.len(),
|
||||
}],
|
||||
Ordering::Equal => {
|
||||
for ((_, l_var), (_, r_var)) in args.iter().zip(other_args.iter()) {
|
||||
unify_pool(subs, pool, problems, *l_var, *r_var);
|
||||
unify_pool(subs, pool, *l_var, *r_var);
|
||||
}
|
||||
merge(subs, &ctx, other_content.clone());
|
||||
|
||||
merge(subs, &ctx, other_content.clone())
|
||||
}
|
||||
}
|
||||
} else {
|
||||
unify_pool(subs, pool, problems, real_var, *other_real_var)
|
||||
unify_pool(subs, pool, real_var, *other_real_var)
|
||||
}
|
||||
}
|
||||
Structure(_) => unify_pool(subs, pool, problems, real_var, ctx.second),
|
||||
Error(problem) => {
|
||||
merge(subs, ctx, Error(problem.clone()));
|
||||
problems.push(problem.clone());
|
||||
}
|
||||
Structure(_) => unify_pool(subs, pool, real_var, ctx.second),
|
||||
Error => merge(subs, ctx, Error),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,43 +131,35 @@ fn unify_alias(
|
|||
fn unify_structure(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
problems: &mut Problems,
|
||||
ctx: &Context,
|
||||
flat_type: &FlatType,
|
||||
other: &Content,
|
||||
) {
|
||||
) -> Outcome {
|
||||
match other {
|
||||
FlexVar(_) => {
|
||||
// If the other is flex, Structure wins!
|
||||
merge(subs, ctx, Structure(flat_type.clone()));
|
||||
merge(subs, ctx, Structure(flat_type.clone()))
|
||||
}
|
||||
RigidVar(_) => {
|
||||
let problem = Problem::GenericMismatch;
|
||||
// Type mismatch! Rigid can only unify with flex.
|
||||
merge(subs, ctx, Error(problem.clone()));
|
||||
problems.push(problem);
|
||||
mismatch()
|
||||
}
|
||||
Structure(ref other_flat_type) => {
|
||||
// Unify the two flat types
|
||||
unify_flat_type(subs, pool, problems, ctx, flat_type, other_flat_type)
|
||||
}
|
||||
Alias(_, _, _, real_var) => unify_pool(subs, pool, problems, ctx.first, *real_var),
|
||||
Error(problem) => {
|
||||
// Error propagates.
|
||||
merge(subs, ctx, Error(problem.clone()));
|
||||
problems.push(problem.clone());
|
||||
unify_flat_type(subs, pool, ctx, flat_type, other_flat_type)
|
||||
}
|
||||
Alias(_, _, _, real_var) => unify_pool(subs, pool, ctx.first, *real_var),
|
||||
Error => merge(subs, ctx, Error),
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_record(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
problems: &mut Problems,
|
||||
ctx: &Context,
|
||||
rec1: RecordStructure,
|
||||
rec2: RecordStructure,
|
||||
) {
|
||||
) -> Outcome {
|
||||
let fields1 = rec1.fields;
|
||||
let fields2 = rec2.fields;
|
||||
let shared_fields = fields1
|
||||
|
@ -182,47 +170,34 @@ fn unify_record(
|
|||
|
||||
if unique_fields1.is_empty() {
|
||||
if unique_fields2.is_empty() {
|
||||
unify_pool(subs, pool, problems, rec1.ext, rec2.ext);
|
||||
unify_shared_fields(
|
||||
subs,
|
||||
pool,
|
||||
problems,
|
||||
ctx,
|
||||
shared_fields,
|
||||
ImMap::default(),
|
||||
rec1.ext,
|
||||
)
|
||||
let ext_problems = unify_pool(subs, pool, rec1.ext, rec2.ext);
|
||||
let mut field_problems =
|
||||
unify_shared_fields(subs, pool, ctx, shared_fields, ImMap::default(), rec1.ext);
|
||||
|
||||
field_problems.extend(ext_problems);
|
||||
|
||||
field_problems
|
||||
} else {
|
||||
let flat_type = FlatType::Record(unique_fields2, 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 field_problems =
|
||||
unify_shared_fields(subs, pool, ctx, shared_fields, ImMap::default(), sub_record);
|
||||
|
||||
unify_pool(subs, pool, problems, rec1.ext, sub_record);
|
||||
field_problems.extend(ext_problems);
|
||||
|
||||
unify_shared_fields(
|
||||
subs,
|
||||
pool,
|
||||
problems,
|
||||
ctx,
|
||||
shared_fields,
|
||||
ImMap::default(),
|
||||
sub_record,
|
||||
);
|
||||
field_problems
|
||||
}
|
||||
} else if unique_fields2.is_empty() {
|
||||
let flat_type = FlatType::Record(unique_fields1, 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 field_problems =
|
||||
unify_shared_fields(subs, pool, ctx, shared_fields, ImMap::default(), sub_record);
|
||||
|
||||
unify_pool(subs, pool, problems, sub_record, rec2.ext);
|
||||
field_problems.extend(ext_problems);
|
||||
|
||||
unify_shared_fields(
|
||||
subs,
|
||||
pool,
|
||||
problems,
|
||||
ctx,
|
||||
shared_fields,
|
||||
ImMap::default(),
|
||||
sub_record,
|
||||
);
|
||||
field_problems
|
||||
} else {
|
||||
let other_fields = unique_fields1.clone().union(unique_fields2.clone());
|
||||
let ext = fresh(subs, pool, ctx, Content::FlexVar(None));
|
||||
|
@ -231,33 +206,35 @@ fn unify_record(
|
|||
let flat_type2 = FlatType::Record(unique_fields2, rec2.ext);
|
||||
let sub2 = fresh(subs, pool, ctx, Structure(flat_type2));
|
||||
|
||||
unify_pool(subs, pool, problems, rec1.ext, sub2);
|
||||
unify_pool(subs, pool, problems, sub1, rec2.ext);
|
||||
let rec1_problems = unify_pool(subs, pool, rec1.ext, sub2);
|
||||
let rec2_problems = unify_pool(subs, pool, sub1, rec2.ext);
|
||||
|
||||
unify_shared_fields(subs, pool, problems, ctx, shared_fields, other_fields, ext);
|
||||
let mut field_problems =
|
||||
unify_shared_fields(subs, pool, ctx, shared_fields, other_fields, ext);
|
||||
|
||||
field_problems.reserve(rec1_problems.len() + rec2_problems.len());
|
||||
field_problems.extend(rec1_problems);
|
||||
field_problems.extend(rec2_problems);
|
||||
|
||||
field_problems
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_shared_fields(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
problems: &mut Problems,
|
||||
ctx: &Context,
|
||||
shared_fields: ImMap<Lowercase, (Variable, Variable)>,
|
||||
other_fields: ImMap<Lowercase, Variable>,
|
||||
ext: Variable,
|
||||
) {
|
||||
) -> Outcome {
|
||||
let mut matching_fields = ImMap::default();
|
||||
let num_shared_fields = shared_fields.len();
|
||||
|
||||
for (name, (actual, expected)) in shared_fields {
|
||||
let prev_problem_count = problems.len();
|
||||
let problems = unify_pool(subs, pool, actual, expected);
|
||||
|
||||
// TODO another way to do this might be to pass around a problems vec
|
||||
// and check to see if its length increased after doing this unification.
|
||||
unify_pool(subs, pool, problems, actual, expected);
|
||||
|
||||
if problems.len() == prev_problem_count {
|
||||
if problems.is_empty() {
|
||||
matching_fields.insert(name, actual);
|
||||
}
|
||||
}
|
||||
|
@ -265,13 +242,9 @@ fn unify_shared_fields(
|
|||
if num_shared_fields == matching_fields.len() {
|
||||
let flat_type = FlatType::Record(matching_fields.union(other_fields), ext);
|
||||
|
||||
merge(subs, ctx, Structure(flat_type));
|
||||
merge(subs, ctx, Structure(flat_type))
|
||||
} else {
|
||||
let problem = Problem::GenericMismatch;
|
||||
|
||||
// Type mismatch! Rigid can only unify with flex.
|
||||
merge(subs, ctx, Error(problem.clone()));
|
||||
problems.push(problem);
|
||||
mismatch()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -279,31 +252,28 @@ fn unify_shared_fields(
|
|||
fn unify_flat_type(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
problems: &mut Problems,
|
||||
ctx: &Context,
|
||||
left: &FlatType,
|
||||
right: &FlatType,
|
||||
) {
|
||||
) -> Outcome {
|
||||
use crate::subs::FlatType::*;
|
||||
|
||||
match (left, right) {
|
||||
(EmptyRecord, EmptyRecord) => {
|
||||
merge(subs, ctx, Structure(left.clone()));
|
||||
}
|
||||
(EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())),
|
||||
|
||||
(Record(fields, ext), EmptyRecord) if fields.is_empty() => {
|
||||
unify_pool(subs, pool, problems, *ext, ctx.second)
|
||||
unify_pool(subs, pool, *ext, ctx.second)
|
||||
}
|
||||
|
||||
(EmptyRecord, Record(fields, ext)) if fields.is_empty() => {
|
||||
unify_pool(subs, pool, problems, ctx.first, *ext)
|
||||
unify_pool(subs, pool, ctx.first, *ext)
|
||||
}
|
||||
|
||||
(Record(fields1, ext1), Record(fields2, ext2)) => {
|
||||
let rec1 = gather_fields(subs, problems, fields1.clone(), *ext1);
|
||||
let rec2 = gather_fields(subs, problems, fields2.clone(), *ext2);
|
||||
let rec1 = gather_fields(subs, fields1.clone(), *ext1);
|
||||
let rec2 = gather_fields(subs, fields2.clone(), *ext2);
|
||||
|
||||
unify_record(subs, pool, problems, ctx, rec1, rec2)
|
||||
unify_record(subs, pool, ctx, rec1, rec2)
|
||||
}
|
||||
(
|
||||
Apply {
|
||||
|
@ -317,75 +287,81 @@ fn unify_flat_type(
|
|||
args: r_args,
|
||||
},
|
||||
) if l_module_name == r_module_name && l_type_name == r_type_name => {
|
||||
unify_zip(subs, pool, problems, l_args.iter(), r_args.iter());
|
||||
let problems = unify_zip(subs, pool, l_args.iter(), r_args.iter());
|
||||
|
||||
merge(
|
||||
subs,
|
||||
ctx,
|
||||
Structure(Apply {
|
||||
module_name: (*r_module_name).clone(),
|
||||
name: (*r_type_name).clone(),
|
||||
args: (*r_args).clone(),
|
||||
}),
|
||||
);
|
||||
if problems.is_empty() {
|
||||
merge(
|
||||
subs,
|
||||
ctx,
|
||||
Structure(Apply {
|
||||
module_name: (*r_module_name).clone(),
|
||||
name: (*r_type_name).clone(),
|
||||
args: (*r_args).clone(),
|
||||
}),
|
||||
)
|
||||
} else {
|
||||
problems
|
||||
}
|
||||
}
|
||||
(Func(l_args, l_ret), Func(r_args, r_ret)) => match l_args.len().cmp(&r_args.len()) {
|
||||
Ordering::Greater => merge(subs, ctx, Error(Problem::ExtraArguments)),
|
||||
Ordering::Less => merge(subs, ctx, Error(Problem::MissingArguments)),
|
||||
Ordering::Greater => vec![Mismatch::ExtraArguments {
|
||||
expected: l_args.len(),
|
||||
actual: r_args.len(),
|
||||
}],
|
||||
Ordering::Less => vec![Mismatch::MissingArguments {
|
||||
expected: l_args.len(),
|
||||
actual: r_args.len(),
|
||||
}],
|
||||
Ordering::Equal => {
|
||||
unify_zip(subs, pool, problems, l_args.iter(), r_args.iter());
|
||||
unify_pool(subs, pool, problems, *l_ret, *r_ret);
|
||||
merge(subs, ctx, Structure(Func((*r_args).clone(), *r_ret)));
|
||||
let arg_problems = unify_zip(subs, pool, l_args.iter(), r_args.iter());
|
||||
let ret_problems = unify_pool(subs, pool, *l_ret, *r_ret);
|
||||
|
||||
if arg_problems.is_empty() && ret_problems.is_empty() {
|
||||
merge(subs, ctx, Structure(Func((*r_args).clone(), *r_ret)))
|
||||
} else {
|
||||
let mut problems = ret_problems;
|
||||
|
||||
problems.extend(arg_problems);
|
||||
|
||||
problems
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let problem = Problem::GenericMismatch;
|
||||
|
||||
merge(subs, ctx, Error(problem.clone()));
|
||||
problems.push(problem);
|
||||
}
|
||||
_ => mismatch(),
|
||||
}
|
||||
}
|
||||
|
||||
fn unify_zip<'a, I>(
|
||||
subs: &mut Subs,
|
||||
pool: &mut Pool,
|
||||
problems: &mut Problems,
|
||||
left_iter: I,
|
||||
right_iter: I,
|
||||
) where
|
||||
fn unify_zip<'a, I>(subs: &mut Subs, pool: &mut Pool, left_iter: I, right_iter: I) -> Outcome
|
||||
where
|
||||
I: Iterator<Item = &'a Variable>,
|
||||
{
|
||||
let mut problems = Vec::new();
|
||||
|
||||
for (&l_var, &r_var) in left_iter.zip(right_iter) {
|
||||
unify_pool(subs, pool, problems, l_var, r_var);
|
||||
problems.extend(unify_pool(subs, pool, l_var, r_var));
|
||||
}
|
||||
|
||||
problems
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn unify_rigid(
|
||||
subs: &mut Subs,
|
||||
problems: &mut Problems,
|
||||
ctx: &Context,
|
||||
name: &Lowercase,
|
||||
other: &Content,
|
||||
) {
|
||||
fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content) -> Outcome {
|
||||
match other {
|
||||
FlexVar(_) => {
|
||||
// If the other is flex, rigid wins!
|
||||
merge(subs, ctx, RigidVar(name.clone()));
|
||||
merge(subs, ctx, RigidVar(name.clone()))
|
||||
}
|
||||
RigidVar(_) | Structure(_) => {
|
||||
// Type mismatch! Rigid can only unify with flex, even if the
|
||||
// rigid names are the same.
|
||||
merge(subs, ctx, Error(Problem::GenericMismatch));
|
||||
mismatch()
|
||||
}
|
||||
Alias(_, _, _, _) => {
|
||||
panic!("TODO unify_rigid Alias");
|
||||
}
|
||||
Error(problem) => {
|
||||
Error => {
|
||||
// Error propagates.
|
||||
merge(subs, ctx, Error(problem.clone()));
|
||||
problems.push(problem.clone());
|
||||
merge(subs, ctx, Error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -393,31 +369,26 @@ fn unify_rigid(
|
|||
#[inline(always)]
|
||||
fn unify_flex(
|
||||
subs: &mut Subs,
|
||||
problems: &mut Problems,
|
||||
ctx: &Context,
|
||||
opt_name: &Option<Lowercase>,
|
||||
other: &Content,
|
||||
) {
|
||||
) -> Outcome {
|
||||
match other {
|
||||
FlexVar(None) => {
|
||||
// If both are flex, and only left has a name, keep the name around.
|
||||
merge(subs, ctx, FlexVar(opt_name.clone()));
|
||||
merge(subs, ctx, FlexVar(opt_name.clone()))
|
||||
}
|
||||
FlexVar(Some(_)) | RigidVar(_) | Structure(_) | Alias(_, _, _, _) => {
|
||||
// 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(problem) => {
|
||||
merge(subs, ctx, Error(problem.clone()));
|
||||
problems.push(problem.clone());
|
||||
merge(subs, ctx, other.clone())
|
||||
}
|
||||
Error => merge(subs, ctx, Error),
|
||||
}
|
||||
}
|
||||
|
||||
fn gather_fields(
|
||||
subs: &mut Subs,
|
||||
problems: &mut Problems,
|
||||
fields: ImMap<Lowercase, Variable>,
|
||||
var: Variable,
|
||||
) -> RecordStructure {
|
||||
|
@ -425,19 +396,19 @@ fn gather_fields(
|
|||
|
||||
match subs.get(var).content {
|
||||
Structure(Record(sub_fields, sub_ext)) => {
|
||||
gather_fields(subs, problems, fields.union(sub_fields), sub_ext)
|
||||
gather_fields(subs, fields.union(sub_fields), sub_ext)
|
||||
}
|
||||
|
||||
Alias(_, _, _, var) => {
|
||||
// TODO according to elm/compiler: "TODO may be dropping useful alias info here"
|
||||
gather_fields(subs, problems, fields, var)
|
||||
gather_fields(subs, fields, var)
|
||||
}
|
||||
|
||||
_ => RecordStructure { fields, ext: var },
|
||||
}
|
||||
}
|
||||
|
||||
fn merge(subs: &mut Subs, ctx: &Context, content: Content) {
|
||||
fn merge(subs: &mut Subs, ctx: &Context, content: Content) -> Outcome {
|
||||
let rank = ctx.first_desc.rank.min(ctx.second_desc.rank);
|
||||
let desc = Descriptor {
|
||||
content,
|
||||
|
@ -447,6 +418,8 @@ fn merge(subs: &mut Subs, ctx: &Context, content: Content) {
|
|||
};
|
||||
|
||||
subs.union(ctx.first, ctx.second, desc);
|
||||
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
fn register(subs: &mut Subs, desc: Descriptor, pool: &mut Pool) -> Variable {
|
||||
|
@ -469,3 +442,8 @@ fn fresh(subs: &mut Subs, pool: &mut Pool, ctx: &Context, content: Content) -> V
|
|||
pool,
|
||||
)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn mismatch() -> Outcome {
|
||||
vec![Mismatch::TypeMismatch]
|
||||
}
|
||||
|
|
|
@ -157,6 +157,8 @@ mod test_load {
|
|||
let mut unify_problems = Vec::new();
|
||||
solve_loaded(&module, &mut unify_problems, &mut subs, deps);
|
||||
|
||||
assert_eq!(unify_problems, Vec::new());
|
||||
|
||||
let expected_types = hashmap! {
|
||||
"WithBuiltins.floatTest" => "Float",
|
||||
"WithBuiltins.divisionFn" => "Float, Float -> Float",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue