λ Support functions! #5

Open
acw wants to merge 59 commits from awick/functions into develop
17 changed files with 267 additions and 24 deletions
Showing only changes of commit 9d41cf0da7 - Show all commits

View File

@@ -0,0 +1,5 @@
x = 1;
function add_x(y) x + y
a = 3;
result = add_x(a);
print result;

View File

@@ -53,10 +53,9 @@ pub enum BackendError {
impl From<BackendError> for Diagnostic<usize> { impl From<BackendError> for Diagnostic<usize> {
fn from(value: BackendError) -> Self { fn from(value: BackendError) -> Self {
match value { match value {
BackendError::Cranelift(me) => { BackendError::Cranelift(me) => Diagnostic::error()
Diagnostic::error().with_message(format!("Internal cranelift error: {}", me)) .with_message(format!("Internal cranelift error: {}", me))
.with_notes(vec![format!("{:?}", me)]) .with_notes(vec![format!("{:?}", me)]),
}
BackendError::BuiltinError(me) => { BackendError::BuiltinError(me) => {
Diagnostic::error().with_message(format!("Internal runtime function error: {}", me)) Diagnostic::error().with_message(format!("Internal runtime function error: {}", me))
} }

View File

@@ -4,7 +4,8 @@ use crate::eval::PrimitiveType;
use crate::ir::{Expression, Primitive, Program, TopLevel, Type, Value, ValueOrRef, Variable}; use crate::ir::{Expression, Primitive, Program, TopLevel, Type, Value, ValueOrRef, Variable};
use crate::syntax::{ConstantType, Location}; use crate::syntax::{ConstantType, Location};
use cranelift_codegen::ir::{ use cranelift_codegen::ir::{
self, entities, types, AbiParam, Function, GlobalValue, InstBuilder, MemFlags, Signature, UserFuncName self, entities, types, AbiParam, Function, GlobalValue, InstBuilder, MemFlags, Signature,
UserFuncName,
}; };
use cranelift_codegen::isa::CallConv; use cranelift_codegen::isa::CallConv;
use cranelift_codegen::Context; use cranelift_codegen::Context;
@@ -29,7 +30,9 @@ impl ReferenceBuilder {
ReferenceBuilder::Global(ty, gv) => { ReferenceBuilder::Global(ty, gv) => {
let cranelift_type = ir::Type::from(*ty); let cranelift_type = ir::Type::from(*ty);
let ptr_value = builder.ins().symbol_value(types::I64, *gv); let ptr_value = builder.ins().symbol_value(types::I64, *gv);
let value = builder.ins().load(cranelift_type, MemFlags::new(), ptr_value, 0); let value = builder
.ins()
.load(cranelift_type, MemFlags::new(), ptr_value, 0);
(value, *ty) (value, *ty)
} }
@@ -435,7 +438,8 @@ impl<M: Module> Backend<M> {
// Look up the value for the variable. Because this might be a // Look up the value for the variable. Because this might be a
// global variable (and that requires special logic), we just turn // global variable (and that requires special logic), we just turn
// this into an `Expression` and re-use the logic in that implementation. // this into an `Expression` and re-use the logic in that implementation.
let fake_ref = ValueOrRef::Ref(ann, Type::Primitive(PrimitiveType::U8), var.clone()); let fake_ref =
ValueOrRef::Ref(ann, Type::Primitive(PrimitiveType::U8), var.clone());
let (val, vtype) = self.compile_value_or_ref(fake_ref, variables, builder)?; let (val, vtype) = self.compile_value_or_ref(fake_ref, variables, builder)?;
let vtype_repr = builder.ins().iconst(types::I64, vtype as i64); let vtype_repr = builder.ins().iconst(types::I64, vtype as i64);
@@ -473,6 +477,39 @@ impl<M: Module> Backend<M> {
variables.insert(name, ReferenceBuilder::Local(value_type, variable)); variables.insert(name, ReferenceBuilder::Local(value_type, variable));
Ok((builder.ins().iconst(types::I64, 0), ConstantType::Void)) Ok((builder.ins().iconst(types::I64, 0), ConstantType::Void))
} }
Expression::Call(_, _, function, args) => {
let (arguments, _argument_types): (Vec<_>, Vec<_>) = args
.into_iter()
.map(|x| self.compile_value_or_ref(x, variables, builder))
.collect::<Result<Vec<(_,_)>,BackendError>>()?
.into_iter()
.unzip();
match *function {
ValueOrRef::Value(_, _, _) => {
panic!("Can't use a value for a function")
}
ValueOrRef::Ref(_, result_type, name) => match self.defined_functions.get(&name) {
None => panic!("Couldn't find function {} to call", name),
Some(function) => {
let func_ref = self.module.declare_func_in_func(*function, builder.func);
let call = builder.ins().call(func_ref, &arguments);
let results = builder.inst_results(call);
match results {
[] => Ok((builder.ins().iconst(types::I64, 0), ConstantType::Void)),
[result] => match result_type {
Type::Primitive(ct) => Ok((*result, ct.into())),
Type::Function(_, _) => panic!("return value is a function?"),
}
_ => panic!("don't support multi-value returns yet"),
}
}
}
}
}
} }
} }

View File

@@ -39,6 +39,7 @@ mod primtype;
mod value; mod value;
use cranelift_module::ModuleError; use cranelift_module::ModuleError;
use internment::ArcIntern;
pub use primop::PrimOpError; pub use primop::PrimOpError;
pub use primtype::PrimitiveType; pub use primtype::PrimitiveType;
pub use value::Value; pub use value::Value;
@@ -76,6 +77,15 @@ pub enum EvalError<IR> {
UnknownPrimType(#[from] UnknownPrimType), UnknownPrimType(#[from] UnknownPrimType),
#[error("Variable lookup failed for {1} at {0:?}")] #[error("Variable lookup failed for {1} at {0:?}")]
LookupFailed(crate::syntax::Location, String), LookupFailed(crate::syntax::Location, String),
#[error("Attempted to call something that wasn't a function at {0:?} (it was a {1})")]
NotAFunction(crate::syntax::Location, Value<IR>),
#[error("Wrong argument call for function ({1:?}) at {0:?}; expected {2}, saw {3}")]
WrongArgCount(
crate::syntax::Location,
Option<ArcIntern<String>>,
usize,
usize,
),
} }
impl<IR1: Clone, IR2: Clone> PartialEq<EvalError<IR1>> for EvalError<IR2> { impl<IR1: Clone, IR2: Clone> PartialEq<EvalError<IR1>> for EvalError<IR2> {
@@ -129,6 +139,16 @@ impl<IR1: Clone, IR2: Clone> PartialEq<EvalError<IR1>> for EvalError<IR2> {
EvalError::UnknownPrimType(b) => a == b, EvalError::UnknownPrimType(b) => a == b,
_ => false, _ => false,
}, },
EvalError::NotAFunction(a, b) => match other {
EvalError::NotAFunction(x, y) => a == x && b == y,
_ => false,
},
EvalError::WrongArgCount(a, b, c, d) => match other {
EvalError::WrongArgCount(w, x, y, z) => a == w && b == x && c == y && d == z,
_ => false,
},
} }
} }
} }

View File

@@ -1,5 +1,7 @@
use crate::eval::PrimitiveType; use crate::eval::PrimitiveType;
use crate::ir::{Expression, Primitive, Program, TopLevel, Type, TypeWithVoid, Value, ValueOrRef, Variable}; use crate::ir::{
Expression, Primitive, Program, TopLevel, Type, TypeWithVoid, Value, ValueOrRef, Variable,
};
use crate::syntax::Location; use crate::syntax::Location;
use crate::util::scoped_map::ScopedMap; use crate::util::scoped_map::ScopedMap;
use proptest::strategy::{NewTree, Strategy, ValueTree}; use proptest::strategy::{NewTree, Strategy, ValueTree};
@@ -300,7 +302,8 @@ fn generate_random_expression(
if !next_type.is_void() { if !next_type.is_void() {
let name = generate_random_name(rng); let name = generate_random_name(rng);
env.insert(name.clone(), next_type.clone()); env.insert(name.clone(), next_type.clone());
next = Expression::Bind(Location::manufactured(), name, next_type, Box::new(next)); next =
Expression::Bind(Location::manufactured(), name, next_type, Box::new(next));
} }
stmts.push(next); stmts.push(next);
} }

View File

@@ -160,6 +160,7 @@ pub enum Expression<Type> {
Primitive(Location, Type, Primitive, Vec<ValueOrRef<Type>>), Primitive(Location, Type, Primitive, Vec<ValueOrRef<Type>>),
Block(Location, Type, Vec<Expression<Type>>), Block(Location, Type, Vec<Expression<Type>>),
Print(Location, Variable), Print(Location, Variable),
Call(Location, Type, Box<ValueOrRef<Type>>, Vec<ValueOrRef<Type>>),
Bind(Location, Variable, Type, Box<Expression<Type>>), Bind(Location, Variable, Type, Box<Expression<Type>>),
} }
@@ -173,6 +174,7 @@ impl<Type: Clone + TypeWithVoid> Expression<Type> {
Expression::Primitive(_, t, _, _) => t.clone(), Expression::Primitive(_, t, _, _) => t.clone(),
Expression::Block(_, t, _) => t.clone(), Expression::Block(_, t, _) => t.clone(),
Expression::Print(_, _) => Type::void(), Expression::Print(_, _) => Type::void(),
Expression::Call(_, t, _, _) => t.clone(),
Expression::Bind(_, _, _, _) => Type::void(), Expression::Bind(_, _, _, _) => Type::void(),
} }
} }
@@ -186,6 +188,7 @@ impl<Type: Clone + TypeWithVoid> Expression<Type> {
Expression::Primitive(l, _, _, _) => l, Expression::Primitive(l, _, _, _) => l,
Expression::Block(l, _, _) => l, Expression::Block(l, _, _) => l,
Expression::Print(l, _) => l, Expression::Print(l, _) => l,
Expression::Call(l, _, _, _) => l,
Expression::Bind(l, _, _, _) => l, Expression::Bind(l, _, _, _) => l,
} }
} }
@@ -221,6 +224,12 @@ where
Expression::Primitive(_, _, op, exprs) => { Expression::Primitive(_, _, op, exprs) => {
allocator.text(format!("!!{:?} with {} arguments!!", op, exprs.len())) allocator.text(format!("!!{:?} with {} arguments!!", op, exprs.len()))
} }
Expression::Call(_, _, fun, args) => {
let args = args.iter().map(|x| x.pretty(allocator));
let comma_sepped_args =
allocator.intersperse(args, crate::syntax::pretty::CommaSep {});
fun.pretty(allocator).append(comma_sepped_args.parens())
}
Expression::Block(_, _, exprs) => match exprs.split_last() { Expression::Block(_, _, exprs) => match exprs.split_last() {
None => allocator.text("()"), None => allocator.text("()"),
Some((last, &[])) => last.pretty(allocator), Some((last, &[])) => last.pretty(allocator),
@@ -660,13 +669,20 @@ impl TryFrom<TypeOrVar> for Type {
fn try_from(value: TypeOrVar) -> Result<Self, Self::Error> { fn try_from(value: TypeOrVar) -> Result<Self, Self::Error> {
match value { match value {
TypeOrVar::Function(args, ret) => { TypeOrVar::Function(args, ret) => {
let args = args let converted_args = args
.into_iter() .iter()
.cloned()
.map(Type::try_from) .map(Type::try_from)
.collect::<Result<_, _>>()?; .collect::<Result<_, _>>();
let ret = Type::try_from(*ret)?; let converted_ret = Type::try_from((*ret).clone());
if let Ok(args) = converted_args {
if let Ok(ret) = converted_ret {
return Ok(Type::Function(args, Box::new(ret)));
}
}
Ok(Type::Function(args, Box::new(ret))) Err(TypeOrVar::Function(args, ret))
} }
TypeOrVar::Primitive(t) => Ok(Type::Primitive(t)), TypeOrVar::Primitive(t) => Ok(Type::Primitive(t)),

View File

@@ -103,6 +103,33 @@ where
env.insert(name.clone(), value); env.insert(name.clone(), value);
Ok(Value::Void) Ok(Value::Void)
} }
Expression::Call(loc, _, fun, args) => {
let function = fun.eval(env)?;
match function {
Value::Closure(name, mut env, arguments, body) => {
if args.len() != arguments.len() {
return Err(EvalError::WrongArgCount(
loc.clone(),
name,
arguments.len(),
args.len(),
));
}
env.new_scope();
for (name, value) in arguments.into_iter().zip(args.into_iter()) {
let value = value.eval(&mut env)?;
env.insert(name, value);
}
let result = body.eval(&mut env, stdout)?;
env.release_scope();
Ok(result)
}
_ => Err(EvalError::NotAFunction(loc.clone(), function)),
}
}
} }
} }
} }

View File

@@ -38,7 +38,7 @@ impl<T: Clone> Expression<T> {
let mut tlvs = expr.get_top_level_variables(); let mut tlvs = expr.get_top_level_variables();
tlvs.insert(name.clone(), ty.clone()); tlvs.insert(name.clone(), ty.clone());
tlvs tlvs
}, }
_ => HashMap::new(), _ => HashMap::new(),
} }
} }

View File

@@ -37,7 +37,7 @@ lalrpop_mod!(
parser, parser,
"/syntax/parser.rs" "/syntax/parser.rs"
); );
mod pretty; pub mod pretty;
mod validate; mod validate;
#[cfg(test)] #[cfg(test)]

View File

@@ -124,6 +124,7 @@ pub enum Expression {
Reference(Location, String), Reference(Location, String),
Cast(Location, String, Box<Expression>), Cast(Location, String, Box<Expression>),
Primitive(Location, String, Vec<Expression>), Primitive(Location, String, Vec<Expression>),
Call(Location, Box<Expression>, Vec<Expression>),
Block(Location, Vec<Statement>), Block(Location, Vec<Statement>),
} }
@@ -146,6 +147,10 @@ impl PartialEq for Expression {
Expression::Primitive(_, prim2, args2) => prim1 == prim2 && args1 == args2, Expression::Primitive(_, prim2, args2) => prim1 == prim2 && args1 == args2,
_ => false, _ => false,
}, },
Expression::Call(_, f1, a1) => match other {
Expression::Call(_, f2, a2) => f1 == f2 && a1 == a2,
_ => false,
},
Expression::Block(_, stmts1) => match other { Expression::Block(_, stmts1) => match other {
Expression::Block(_, stmts2) => stmts1 == stmts2, Expression::Block(_, stmts2) => stmts1 == stmts2,
_ => false, _ => false,
@@ -162,6 +167,7 @@ impl Expression {
Expression::Reference(loc, _) => loc, Expression::Reference(loc, _) => loc,
Expression::Cast(loc, _, _) => loc, Expression::Cast(loc, _, _) => loc,
Expression::Primitive(loc, _, _) => loc, Expression::Primitive(loc, _, _) => loc,
Expression::Call(loc, _, _) => loc,
Expression::Block(loc, _) => loc, Expression::Block(loc, _) => loc,
} }
} }

View File

@@ -118,6 +118,33 @@ impl Expression {
Ok(Value::calculate(op, arg_values)?) Ok(Value::calculate(op, arg_values)?)
} }
Expression::Call(loc, fun, args) => {
let function = fun.eval(stdout, env)?;
match function {
Value::Closure(name, mut closure_env, arguments, body) => {
if args.len() != arguments.len() {
return Err(EvalError::WrongArgCount(
loc.clone(),
name,
arguments.len(),
args.len(),
));
}
closure_env.new_scope();
for (name, value) in arguments.into_iter().zip(args.iter()) {
let value = value.eval(stdout, env)?;
closure_env.insert(name, value);
}
let result = body.eval(stdout, &mut closure_env)?;
closure_env.release_scope();
Ok(result)
}
_ => Err(EvalError::NotAFunction(loc.clone(), function)),
}
}
Expression::Block(_, stmts) => { Expression::Block(_, stmts) => {
let mut result = Value::Void; let mut result = Value::Void;

View File

@@ -205,9 +205,24 @@ UnaryExpression: Expression = {
Expression::Primitive(Location::new(file_idx, l..le), "-".to_string(), vec![e]), Expression::Primitive(Location::new(file_idx, l..le), "-".to_string(), vec![e]),
<l: @L> "<" <v:"<var>"> ">" <e:UnaryExpression> <le: @L> => <l: @L> "<" <v:"<var>"> ">" <e:UnaryExpression> <le: @L> =>
Expression::Cast(Location::new(file_idx, l..le), v.to_string(), Box::new(e)), Expression::Cast(Location::new(file_idx, l..le), v.to_string(), Box::new(e)),
CallExpression,
}
CallExpression: Expression = {
<s: @L> <f:CallExpression> "(" <args: CallArguments> ")" <e: @L> =>
Expression::Call(Location::new(file_idx, s..e), Box::new(f), args),
AtomicExpression, AtomicExpression,
} }
CallArguments: Vec<Expression> = {
=> vec![],
<e:Expression> => vec![e],
<mut args:CallArguments> "," <e:Expression> => {
args.push(e);
args
}
}
// finally, we describe our lowest-level expressions as "atomic", because // finally, we describe our lowest-level expressions as "atomic", because
// they cannot be further divided into parts // they cannot be further divided into parts
AtomicExpression: Expression = { AtomicExpression: Expression = {

View File

@@ -105,6 +105,11 @@ where
let comma_sepped_args = allocator.intersperse(args, CommaSep {}); let comma_sepped_args = allocator.intersperse(args, CommaSep {});
call.append(comma_sepped_args.parens()) call.append(comma_sepped_args.parens())
} }
Expression::Call(_, fun, args) => {
let args = args.iter().map(|x| x.pretty(allocator));
let comma_sepped_args = allocator.intersperse(args, CommaSep {});
fun.pretty(allocator).append(comma_sepped_args.parens())
}
Expression::Block(_, stmts) => match stmts.split_last() { Expression::Block(_, stmts) => match stmts.split_last() {
None => allocator.text("()"), None => allocator.text("()"),
Some((last, &[])) => last.pretty(allocator), Some((last, &[])) => last.pretty(allocator),
@@ -167,7 +172,7 @@ fn type_suffix(x: &Option<ConstantType>) -> &'static str {
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
struct CommaSep {} pub struct CommaSep {}
impl<'a, D, A> Pretty<'a, D, A> for CommaSep impl<'a, D, A> Pretty<'a, D, A> for CommaSep
where where

View File

@@ -212,6 +212,17 @@ impl Expression {
(errors, warnings) (errors, warnings)
} }
Expression::Call(_, func, args) => {
let (mut errors, mut warnings) = func.validate(variable_map);
for arg in args.iter() {
let (mut e, mut w) = arg.validate(variable_map);
errors.append(&mut e);
warnings.append(&mut w);
}
(errors, warnings)
}
Expression::Block(_, stmts) => { Expression::Block(_, stmts) => {
let mut errors = vec![]; let mut errors = vec![];
let mut warnings = vec![]; let mut warnings = vec![];

View File

@@ -286,9 +286,7 @@ fn convert_expression(
let (aexp, atype) = convert_expression(arg, constraint_db, renames, bindings); let (aexp, atype) = convert_expression(arg, constraint_db, renames, bindings);
let (aprereqs, asimple) = simplify_expr(aexp); let (aprereqs, asimple) = simplify_expr(aexp);
if let Some(prereq) = aprereqs { merge_prereq(&mut prereqs, aprereqs);
prereqs.push(prereq);
}
nargs.push(asimple); nargs.push(asimple);
atypes.push(atype); atypes.push(atype);
} }
@@ -313,6 +311,59 @@ fn convert_expression(
} }
} }
syntax::Expression::Call(loc, fun, args) => {
let return_type = ir::TypeOrVar::new();
let arg_types = args
.iter()
.map(|_| ir::TypeOrVar::new())
.collect::<Vec<_>>();
let (new_fun, new_fun_type) =
convert_expression(*fun, constraint_db, renames, bindings);
let target_fun_type =
ir::TypeOrVar::Function(arg_types.clone(), Box::new(return_type.clone()));
constraint_db.push(Constraint::Equivalent(
loc.clone(),
new_fun_type,
target_fun_type,
));
let mut prereqs = vec![];
let (fun_prereqs, fun) = simplify_expr(new_fun);
merge_prereq(&mut prereqs, fun_prereqs);
let new_args = args
.into_iter()
.zip(arg_types.into_iter())
.map(|(arg, target_type)| {
let (new_arg, inferred_type) =
convert_expression(arg, constraint_db, renames, bindings);
let location = new_arg.location().clone();
let (arg_prereq, new_valref) = simplify_expr(new_arg);
merge_prereq(&mut prereqs, arg_prereq);
constraint_db.push(Constraint::Equivalent(
location,
inferred_type,
target_type,
));
new_valref
})
.collect();
let last_call =
ir::Expression::Call(loc.clone(), return_type.clone(), Box::new(fun), new_args);
if prereqs.is_empty() {
(last_call, return_type)
} else {
prereqs.push(last_call);
(
ir::Expression::Block(loc, return_type.clone(), prereqs),
return_type,
)
}
}
syntax::Expression::Block(loc, stmts) => { syntax::Expression::Block(loc, stmts) => {
let mut ret_type = ir::TypeOrVar::Primitive(PrimitiveType::Void); let mut ret_type = ir::TypeOrVar::Primitive(PrimitiveType::Void);
let mut exprs = vec![]; let mut exprs = vec![];
@@ -381,6 +432,12 @@ fn finalize_name(
} }
} }
fn merge_prereq<T>(left: &mut Vec<T>, prereq: Option<T>) {
if let Some(item) = prereq {
left.push(item)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
// use super::*; // use super::*;

View File

@@ -91,6 +91,15 @@ fn finalize_expression(
Expression::Print(loc, var) => Expression::Print(loc, var), Expression::Print(loc, var) => Expression::Print(loc, var),
Expression::Call(loc, ty, fun, args) => Expression::Call(
loc,
finalize_type(ty, resolutions),
Box::new(finalize_val_or_ref(*fun, resolutions)),
args.into_iter()
.map(|x| finalize_val_or_ref(x, resolutions))
.collect(),
),
Expression::Bind(loc, var, ty, subexp) => Expression::Bind( Expression::Bind(loc, var, ty, subexp) => Expression::Bind(
loc, loc,
var, var,

View File

@@ -192,7 +192,7 @@ impl From<TypeInferenceError> for Diagnostic<usize> {
} }
TypeInferenceError::CouldNotSolve(Constraint::Equivalent(loc, a, b)) => { TypeInferenceError::CouldNotSolve(Constraint::Equivalent(loc, a, b)) => {
loc.labelled_error("internal error").with_message(format!( loc.labelled_error("internal error").with_message(format!(
"could not determine if {} and {:#?} were equivalent", "could not determine if {} and {} were equivalent",
a, b a, b
)) ))
} }
@@ -264,7 +264,7 @@ pub fn solve_constraints(
// constraints. Internal to the loop, we have a check that will make sure that we // constraints. Internal to the loop, we have a check that will make sure that we
// do (eventually) stop. // do (eventually) stop.
while changed_something && !constraint_db.is_empty() { while changed_something && !constraint_db.is_empty() {
println!("CONSTRAINT:"); println!("\n\n\nCONSTRAINT:");
for constraint in constraint_db.iter() { for constraint in constraint_db.iter() {
println!(" {}", constraint); println!(" {}", constraint);
} }
@@ -300,8 +300,9 @@ pub fn solve_constraints(
Constraint::IsSomething(_, TypeOrVar::Variable(_, ref name)) => { Constraint::IsSomething(_, TypeOrVar::Variable(_, ref name)) => {
if resolutions.get(name).is_none() { if resolutions.get(name).is_none() {
constraint_db.push(constraint); constraint_db.push(constraint);
} else {
changed_something = true;
} }
changed_something = true;
} }
// Case #1a: We have two primitive types. If they're equal, we've discharged this // Case #1a: We have two primitive types. If they're equal, we've discharged this
@@ -355,12 +356,15 @@ pub fn solve_constraints(
// type. // type.
Constraint::Equivalent(loc, t, TypeOrVar::Variable(vloc, name)) Constraint::Equivalent(loc, t, TypeOrVar::Variable(vloc, name))
| Constraint::Equivalent(loc, TypeOrVar::Variable(vloc, name), t) => { | Constraint::Equivalent(loc, TypeOrVar::Variable(vloc, name), t) => {
println!("IN THIS CASE with {}", name);
match resolutions.get(&name) { match resolutions.get(&name) {
None => match t.try_into() { None => match t.try_into() {
Ok(real_type) => { Ok(real_type) => {
println!(" HERE with {} and {}", name, real_type);
resolutions.insert(name, real_type); resolutions.insert(name, real_type);
} }
Err(variable_type) => { Err(variable_type) => {
println!(" REJECTED INTO RETURN with {} and {}", name, variable_type);
constraint_db.push(Constraint::Equivalent( constraint_db.push(Constraint::Equivalent(
loc, loc,
variable_type, variable_type,
@@ -369,7 +373,9 @@ pub fn solve_constraints(
continue; continue;
} }
}, },
Some(t2) if &t == t2 => {} Some(t2) if &t == t2 => {
println!(" MATCHED at {} == {}", t, t2);
}
Some(t2) => errors.push(TypeInferenceError::NotEquivalent( Some(t2) => errors.push(TypeInferenceError::NotEquivalent(
loc, loc,
t, t,