think that replaces all the printlns I care about
This commit is contained in:
@@ -260,6 +260,7 @@ impl<M: Module> Backend<M> {
|
|||||||
|
|
||||||
/// Compile an expression, returning the Cranelift Value for the expression and
|
/// Compile an expression, returning the Cranelift Value for the expression and
|
||||||
/// its type.
|
/// its type.
|
||||||
|
#[tracing::instrument(level = "trace", skip(self, variables, builder))]
|
||||||
fn compile_expression(
|
fn compile_expression(
|
||||||
&mut self,
|
&mut self,
|
||||||
expr: Expression<Type>,
|
expr: Expression<Type>,
|
||||||
@@ -456,10 +457,6 @@ impl<M: Module> Backend<M> {
|
|||||||
Some(ReferenceBuilder::Global(_, global_value)) => {
|
Some(ReferenceBuilder::Global(_, global_value)) => {
|
||||||
let pointer = self.module.target_config().pointer_type();
|
let pointer = self.module.target_config().pointer_type();
|
||||||
let pointer_to = builder.ins().symbol_value(pointer, *global_value);
|
let pointer_to = builder.ins().symbol_value(pointer, *global_value);
|
||||||
println!(
|
|
||||||
"STORE {}: cranelift_type {} origin type {:?}",
|
|
||||||
name, value, value_type
|
|
||||||
);
|
|
||||||
builder.ins().store(MemFlags::new(), value, pointer_to, 0);
|
builder.ins().store(MemFlags::new(), value, pointer_to, 0);
|
||||||
Ok((builder.ins().iconst(types::I64, 0), ConstantType::Void))
|
Ok((builder.ins().iconst(types::I64, 0), ConstantType::Void))
|
||||||
}
|
}
|
||||||
@@ -529,14 +526,14 @@ impl<M: Module> Backend<M> {
|
|||||||
|
|
||||||
/// Compile a value or reference into Cranelift, returning the Cranelift Value for
|
/// Compile a value or reference into Cranelift, returning the Cranelift Value for
|
||||||
/// the expression and its type.
|
/// the expression and its type.
|
||||||
|
#[tracing::instrument(level = "trace", skip(self, variables, builder))]
|
||||||
fn compile_value_or_ref(
|
fn compile_value_or_ref(
|
||||||
&self,
|
&self,
|
||||||
valref: ValueOrRef<Type>,
|
value_or_ref: ValueOrRef<Type>,
|
||||||
variables: &HashMap<Variable, ReferenceBuilder>,
|
variables: &HashMap<Variable, ReferenceBuilder>,
|
||||||
builder: &mut FunctionBuilder,
|
builder: &mut FunctionBuilder,
|
||||||
) -> Result<(entities::Value, ConstantType), BackendError> {
|
) -> Result<(entities::Value, ConstantType), BackendError> {
|
||||||
println!("compile_value_or_ref {:?}", valref);
|
match value_or_ref {
|
||||||
match valref {
|
|
||||||
ValueOrRef::Value(_, _, val) => match val {
|
ValueOrRef::Value(_, _, val) => match val {
|
||||||
Value::I8(_, v) => {
|
Value::I8(_, v) => {
|
||||||
// Cranelift does a funny thing where it checks you aren't using bits in the I64
|
// Cranelift does a funny thing where it checks you aren't using bits in the I64
|
||||||
@@ -585,10 +582,6 @@ impl<M: Module> Backend<M> {
|
|||||||
let pointer_to = self.module.target_config().pointer_type();
|
let pointer_to = self.module.target_config().pointer_type();
|
||||||
let pointer_value = builder.ins().symbol_value(pointer_to, *gv);
|
let pointer_value = builder.ins().symbol_value(pointer_to, *gv);
|
||||||
let cranelift_type = ir::Type::from(*ty);
|
let cranelift_type = ir::Type::from(*ty);
|
||||||
println!(
|
|
||||||
"READ {}: cranelift_type {} origin type {:?}",
|
|
||||||
name, cranelift_type, ty
|
|
||||||
);
|
|
||||||
let value =
|
let value =
|
||||||
builder
|
builder
|
||||||
.ins()
|
.ins()
|
||||||
|
|||||||
@@ -137,22 +137,12 @@ impl Compiler {
|
|||||||
let mut backend = Backend::object_file(Triple::host())?;
|
let mut backend = Backend::object_file(Triple::host())?;
|
||||||
let unknown = "<unknown>".to_string();
|
let unknown = "<unknown>".to_string();
|
||||||
backend.compile_program("gogogo", ir)?;
|
backend.compile_program("gogogo", ir)?;
|
||||||
println!("FINAL MODULE:");
|
|
||||||
println!(" FUNCTIONS:");
|
|
||||||
for (_, decl) in backend.module.declarations().get_functions() {
|
for (_, decl) in backend.module.declarations().get_functions() {
|
||||||
println!(
|
tracing::debug!(name = %decl.name.as_ref().unwrap_or(&unknown), linkage = ?decl.linkage, "function definition");
|
||||||
" {}: {:?}",
|
|
||||||
decl.name.as_ref().unwrap_or(&unknown),
|
|
||||||
decl.linkage
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
println!(" DATA:");
|
|
||||||
for (_, decl) in backend.module.declarations().get_data_objects() {
|
for (_, decl) in backend.module.declarations().get_data_objects() {
|
||||||
println!(
|
tracing::debug!(name = %decl.name.as_ref().unwrap_or(&unknown), linkage = ?decl.linkage, "data definition");
|
||||||
" {}: {:?}",
|
|
||||||
decl.name.as_ref().unwrap_or(&unknown),
|
|
||||||
decl.linkage
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Ok(Some(backend.bytes()?))
|
Ok(Some(backend.bytes()?))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::ir::{Expression, Primitive, Program, TopLevel, Type, TypeOrVar, Value, ValueOrRef};
|
use crate::ir::{Expression, Primitive, Program, TopLevel, Type, TypeOrVar, Value, ValueOrRef};
|
||||||
use crate::syntax::{self, ConstantType};
|
use crate::syntax::{self, ConstantType};
|
||||||
use crate::util::pretty::{Allocator, pretty_function_type, derived_display};
|
use crate::util::pretty::{derived_display, pretty_function_type, Allocator};
|
||||||
use pretty::{Arena, DocAllocator, DocBuilder};
|
use pretty::{Arena, DocAllocator, DocBuilder};
|
||||||
|
|
||||||
impl Program<Type> {
|
impl Program<Type> {
|
||||||
@@ -71,8 +71,7 @@ impl Expression<Type> {
|
|||||||
}
|
}
|
||||||
Expression::Call(_, _, fun, args) => {
|
Expression::Call(_, _, fun, args) => {
|
||||||
let args = args.iter().map(|x| x.pretty(allocator));
|
let args = args.iter().map(|x| x.pretty(allocator));
|
||||||
let comma_sepped_args =
|
let comma_sepped_args = allocator.intersperse(args, allocator.text(","));
|
||||||
allocator.intersperse(args, allocator.text(","));
|
|
||||||
fun.pretty(allocator).append(comma_sepped_args.parens())
|
fun.pretty(allocator).append(comma_sepped_args.parens())
|
||||||
}
|
}
|
||||||
Expression::Block(_, _, exprs) => match exprs.split_last() {
|
Expression::Block(_, _, exprs) => match exprs.split_last() {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::syntax::ast::{ConstantType, Expression, Program, Statement, TopLevel, Value};
|
use crate::syntax::ast::{ConstantType, Expression, Program, Statement, TopLevel, Value};
|
||||||
use crate::util::pretty::{Allocator, derived_display};
|
use crate::util::pretty::{derived_display, Allocator};
|
||||||
use pretty::{DocAllocator, DocBuilder};
|
use pretty::{DocAllocator, DocBuilder};
|
||||||
|
|
||||||
impl Program {
|
impl Program {
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ fn convert_statement(
|
|||||||
.get(&final_name)
|
.get(&final_name)
|
||||||
.expect("print variable defined before use")
|
.expect("print variable defined before use")
|
||||||
.clone();
|
.clone();
|
||||||
println!("varty for {} is {}", final_name, varty);
|
|
||||||
|
|
||||||
constraint_db.push(Constraint::Printable(loc.clone(), varty.clone()));
|
constraint_db.push(Constraint::Printable(loc.clone(), varty.clone()));
|
||||||
|
|
||||||
@@ -257,7 +256,6 @@ fn convert_expression(
|
|||||||
.get(&final_name)
|
.get(&final_name)
|
||||||
.cloned()
|
.cloned()
|
||||||
.expect("variable bound before use");
|
.expect("variable bound before use");
|
||||||
println!("rtype for {} is {}", final_name, rtype);
|
|
||||||
let refexp =
|
let refexp =
|
||||||
ir::Expression::Atomic(ir::ValueOrRef::Ref(loc, rtype.clone(), final_name));
|
ir::Expression::Atomic(ir::ValueOrRef::Ref(loc, rtype.clone(), final_name));
|
||||||
|
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ fn finalize_type(ty: TypeOrVar, resolutions: &TypeResolutions) -> Type {
|
|||||||
TypeOrVar::Variable(_, tvar) => match resolutions.get(&tvar) {
|
TypeOrVar::Variable(_, tvar) => match resolutions.get(&tvar) {
|
||||||
None => panic!("Did not resolve type for type variable {}", tvar),
|
None => panic!("Did not resolve type for type variable {}", tvar),
|
||||||
Some(pt) => {
|
Some(pt) => {
|
||||||
println!("Finalizing {} to {}", tvar, pt);
|
tracing::trace!(type_variable = %tvar, final_type = %pt, "finalizing variable type");
|
||||||
pt.clone()
|
pt.clone()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -300,16 +300,18 @@ pub fn solve_constraints(
|
|||||||
) -> TypeInferenceResult<TypeResolutions> {
|
) -> TypeInferenceResult<TypeResolutions> {
|
||||||
let mut errors = vec![];
|
let mut errors = vec![];
|
||||||
let mut warnings = vec![];
|
let mut warnings = vec![];
|
||||||
|
let mut iteration = 0u64;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut changed_something = false;
|
let mut changed_something = false;
|
||||||
let mut all_constraints_solved = true;
|
let mut all_constraints_solved = true;
|
||||||
let mut new_constraints = vec![];
|
let mut new_constraints = vec![];
|
||||||
|
|
||||||
println!("\n\n\nCONSTRAINT:");
|
tracing::debug!(iteration, "Restarting constraint solving loop");
|
||||||
for constraint in constraint_db.iter() {
|
for constraint in constraint_db.iter() {
|
||||||
println!(" {}", constraint);
|
tracing::debug!(%constraint, "remaining constraint");
|
||||||
}
|
}
|
||||||
|
iteration += 1;
|
||||||
|
|
||||||
while let Some(constraint) = constraint_db.pop() {
|
while let Some(constraint) = constraint_db.pop() {
|
||||||
match constraint {
|
match constraint {
|
||||||
@@ -328,7 +330,7 @@ pub fn solve_constraints(
|
|||||||
loc, from_type, to_type,
|
loc, from_type, to_type,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
println!("changed something because finished CanCastTo");
|
tracing::trace!(form = %from_type, to = %to_type, "changed something because we can determine if we can do the cast");
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,41 +352,44 @@ pub fn solve_constraints(
|
|||||||
TypeOrVar::Function(args2, ret2),
|
TypeOrVar::Function(args2, ret2),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
println!("changed something because finished CanCastTo (2)");
|
tracing::trace!("changed something because we transferred CanCastTo to equivalence checks for function types");
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint::CanCastTo(
|
Constraint::CanCastTo(
|
||||||
loc,
|
loc,
|
||||||
TypeOrVar::Function(_, _),
|
ft @ TypeOrVar::Function(_, _),
|
||||||
pt @ TypeOrVar::Primitive(_),
|
pt @ TypeOrVar::Primitive(_),
|
||||||
) => {
|
) => {
|
||||||
|
tracing::trace!(function_type = %ft, primitive_type = %pt, "changed something because we can't cast a function type to a primitive type");
|
||||||
errors.push(TypeInferenceError::CannotCastFromFunctionType(loc, pt));
|
errors.push(TypeInferenceError::CannotCastFromFunctionType(loc, pt));
|
||||||
println!("changed something because finished CanCastTo (3)");
|
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint::CanCastTo(
|
Constraint::CanCastTo(
|
||||||
loc,
|
loc,
|
||||||
pt @ TypeOrVar::Primitive(_),
|
pt @ TypeOrVar::Primitive(_),
|
||||||
TypeOrVar::Function(_, _),
|
ft @ TypeOrVar::Function(_, _),
|
||||||
) => {
|
) => {
|
||||||
|
tracing::trace!(function_type = %ft, primitive_type = %pt, "changed something because we can't cast a primitive type to a function type");
|
||||||
errors.push(TypeInferenceError::CannotCastToFunctionType(loc, pt));
|
errors.push(TypeInferenceError::CannotCastToFunctionType(loc, pt));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because finished CanCastTo (4)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're testing if an actual primitive type is numeric, that's pretty easy
|
// if we're testing if an actual primitive type is numeric, that's pretty easy
|
||||||
Constraint::ConstantNumericType(_, TypeOrVar::Primitive(_)) => {
|
Constraint::ConstantNumericType(loc, TypeOrVar::Primitive(pt)) => {
|
||||||
|
tracing::trace!(primitive_type = %pt, "changed something because its easy to tell if a constant number can be a primitive type");
|
||||||
|
if pt.max_value().is_none() {
|
||||||
|
errors.push(TypeInferenceError::NotANumber(loc, pt))
|
||||||
|
}
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because constant numeric type (1)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're testing if a function type is numeric, then throw a useful warning
|
// if we're testing if a function type is numeric, then throw a useful warning
|
||||||
Constraint::ConstantNumericType(loc, t @ TypeOrVar::Function(_, _)) => {
|
Constraint::ConstantNumericType(loc, t @ TypeOrVar::Function(_, _)) => {
|
||||||
|
tracing::trace!(function_type = %t, "changed something because functions can't be constant numbers");
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAFunction(loc, t, None));
|
errors.push(TypeInferenceError::CannotMakeNumberAFunction(loc, t, None));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because constant numeric type (2)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're testing if a number can fit into a numeric type, we can just do that!
|
// if we're testing if a number can fit into a numeric type, we can just do that!
|
||||||
@@ -399,67 +404,70 @@ pub fn solve_constraints(
|
|||||||
Some(_) => {}
|
Some(_) => {}
|
||||||
}
|
}
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because fits in numeric type (1)");
|
tracing::trace!(primitive_type = %ctype, value = val, "changed something because we can test for a value fitting in a primitive type");
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're testing if a function type can fit into a numeric type, that's a problem
|
// if we're testing if a function type can fit into a numeric type, that's a problem
|
||||||
Constraint::FitsInNumType(loc, t @ TypeOrVar::Function(_, _), val) => {
|
Constraint::FitsInNumType(loc, t @ TypeOrVar::Function(_, _), val) => {
|
||||||
|
tracing::trace!(function_type = %t, "changed something because values don't fit in function types");
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAFunction(
|
errors.push(TypeInferenceError::CannotMakeNumberAFunction(
|
||||||
loc,
|
loc,
|
||||||
t,
|
t,
|
||||||
Some(val),
|
Some(val),
|
||||||
));
|
));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because fits in numeric type (2)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we want to know if a type is something, and it is something, then we're done
|
// if we want to know if a type is something, and it is something, then we're done
|
||||||
Constraint::IsSomething(_, TypeOrVar::Function(_, _))
|
Constraint::IsSomething(_, t @ TypeOrVar::Function(_, _))
|
||||||
| Constraint::IsSomething(_, TypeOrVar::Primitive(_)) => {
|
| Constraint::IsSomething(_, t @ TypeOrVar::Primitive(_)) => {
|
||||||
|
tracing::trace!(tested_type = %t, "changed something because type is definitely something");
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 1");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we want to know if something is signed, we can check its primitive type
|
// if we want to know if something is signed, we can check its primitive type
|
||||||
Constraint::IsSigned(loc, TypeOrVar::Primitive(pt)) => {
|
Constraint::IsSigned(loc, TypeOrVar::Primitive(pt)) => {
|
||||||
|
tracing::trace!(primitive_type = %pt, "changed something because we can check if a primitive is signed");
|
||||||
if !pt.valid_operators().contains(&("-", 1)) {
|
if !pt.valid_operators().contains(&("-", 1)) {
|
||||||
errors.push(TypeInferenceError::IsNotSigned(loc, pt));
|
errors.push(TypeInferenceError::IsNotSigned(loc, pt));
|
||||||
}
|
}
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 2");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// again with the functions and the numbers
|
// again with the functions and the numbers
|
||||||
Constraint::IsSigned(loc, t @ TypeOrVar::Function(_, _)) => {
|
Constraint::IsSigned(loc, t @ TypeOrVar::Function(_, _)) => {
|
||||||
|
tracing::trace!(function_type = %t, "changed something because functions are not signed");
|
||||||
errors.push(TypeInferenceError::CannotCastFromFunctionType(loc, t));
|
errors.push(TypeInferenceError::CannotCastFromFunctionType(loc, t));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 3");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're testing if an actual primitive type is numeric, that's pretty easy
|
// if we're testing if an actual primitive type is numeric, that's pretty easy
|
||||||
Constraint::NumericType(_, TypeOrVar::Primitive(_)) => {
|
Constraint::NumericType(loc, TypeOrVar::Primitive(pt)) => {
|
||||||
|
tracing::trace!(primitive_type = %pt, "changed something because its easy to tell if a primitive type is numeric");
|
||||||
|
if pt.max_value().is_none() {
|
||||||
|
errors.push(TypeInferenceError::NotANumber(loc, pt))
|
||||||
|
}
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 4");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we're testing if a function type is numeric, then throw a useful warning
|
// if we're testing if a function type is numeric, then throw a useful warning
|
||||||
Constraint::NumericType(loc, t @ TypeOrVar::Function(_, _)) => {
|
Constraint::NumericType(loc, t @ TypeOrVar::Function(_, _)) => {
|
||||||
|
tracing::trace!(function_type = %t, "changed something because function types aren't numeric");
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAFunction(loc, t, None));
|
errors.push(TypeInferenceError::CannotMakeNumberAFunction(loc, t, None));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 5");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// all of our primitive types are printable
|
// all of our primitive types are printable
|
||||||
Constraint::Printable(_, TypeOrVar::Primitive(_)) => {
|
Constraint::Printable(_, TypeOrVar::Primitive(pt)) => {
|
||||||
|
tracing::trace!(primitive_type = %pt, "changed something because primitive types are printable");
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 6");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// function types are definitely not printable
|
// function types are definitely not printable
|
||||||
Constraint::Printable(loc, TypeOrVar::Function(_, _)) => {
|
Constraint::Printable(loc, ft @ TypeOrVar::Function(_, _)) => {
|
||||||
|
tracing::trace!(function_type = %ft, "changed something because function types are not printable");
|
||||||
errors.push(TypeInferenceError::FunctionsAreNotPrintable(loc));
|
errors.push(TypeInferenceError::FunctionsAreNotPrintable(loc));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 7");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint::ProperPrimitiveArgs(loc, prim, mut args, ret) => match prim {
|
Constraint::ProperPrimitiveArgs(loc, prim, mut args, ret) => match prim {
|
||||||
@@ -478,7 +486,7 @@ pub fn solve_constraints(
|
|||||||
new_constraints.push(Constraint::Equivalent(loc.clone(), left, ret));
|
new_constraints.push(Constraint::Equivalent(loc.clone(), left, ret));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
all_constraints_solved = false;
|
all_constraints_solved = false;
|
||||||
println!("changed something because 8");
|
tracing::trace!(primitive = %prim, "changed something because we expanded out a binary primitive operation");
|
||||||
}
|
}
|
||||||
|
|
||||||
Primitive::Minus if args.len() == 1 => {
|
Primitive::Minus if args.len() == 1 => {
|
||||||
@@ -488,7 +496,7 @@ pub fn solve_constraints(
|
|||||||
new_constraints.push(Constraint::Equivalent(loc, value, ret));
|
new_constraints.push(Constraint::Equivalent(loc, value, ret));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
all_constraints_solved = false;
|
all_constraints_solved = false;
|
||||||
println!("changed something because 9");
|
tracing::trace!(primitive = %prim, "changed something because we expanded out a unary primitive operation");
|
||||||
}
|
}
|
||||||
|
|
||||||
Primitive::Plus | Primitive::Times | Primitive::Divide => {
|
Primitive::Plus | Primitive::Times | Primitive::Divide => {
|
||||||
@@ -500,7 +508,7 @@ pub fn solve_constraints(
|
|||||||
args.len(),
|
args.len(),
|
||||||
));
|
));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 10");
|
tracing::trace!(primitive = %prim, provided_arity = args.len(), "changed something because binary primitive operation arity is wrong");
|
||||||
}
|
}
|
||||||
|
|
||||||
Primitive::Minus => {
|
Primitive::Minus => {
|
||||||
@@ -512,7 +520,7 @@ pub fn solve_constraints(
|
|||||||
args.len(),
|
args.len(),
|
||||||
));
|
));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 11");
|
tracing::trace!(primitive = %prim, provided_arity = args.len(), "changed something because unary primitive operation arity is wrong");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -530,7 +538,7 @@ pub fn solve_constraints(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 12");
|
tracing::trace!(primitive_type1 = %pt1, primitive_type2 = %pt2, "changed something because we checked for primitive type equivalence");
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint::Equivalent(
|
Constraint::Equivalent(
|
||||||
@@ -543,9 +551,9 @@ pub fn solve_constraints(
|
|||||||
ft @ TypeOrVar::Function(_, _),
|
ft @ TypeOrVar::Function(_, _),
|
||||||
pt @ TypeOrVar::Primitive(_),
|
pt @ TypeOrVar::Primitive(_),
|
||||||
) => {
|
) => {
|
||||||
|
tracing::trace!(primitive_type = %pt, function_type = %ft, "changed something because function and primitive types cannot be equivalent");
|
||||||
errors.push(TypeInferenceError::NotEquivalent(loc, pt, ft));
|
errors.push(TypeInferenceError::NotEquivalent(loc, pt, ft));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 13");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint::Equivalent(
|
Constraint::Equivalent(
|
||||||
@@ -553,6 +561,7 @@ pub fn solve_constraints(
|
|||||||
TypeOrVar::Variable(_, name1),
|
TypeOrVar::Variable(_, name1),
|
||||||
TypeOrVar::Variable(_, name2),
|
TypeOrVar::Variable(_, name2),
|
||||||
) if name1 == name2 => {
|
) if name1 == name2 => {
|
||||||
|
tracing::trace!(name = %name1, "changed something because variable is equivalent to itself");
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -574,7 +583,7 @@ pub fn solve_constraints(
|
|||||||
}
|
}
|
||||||
|
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
println!("changed something because 14");
|
tracing::trace!("changed something because we checked/rewrote if function types are equivalent");
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint::Equivalent(_, TypeOrVar::Variable(_, ref name), ref rhs) => {
|
Constraint::Equivalent(_, TypeOrVar::Variable(_, ref name), ref rhs) => {
|
||||||
@@ -582,16 +591,16 @@ pub fn solve_constraints(
|
|||||||
changed_something |= replace_variable(&mut new_constraints, name, rhs);
|
changed_something |= replace_variable(&mut new_constraints, name, rhs);
|
||||||
all_constraints_solved &= rhs.is_resolved();
|
all_constraints_solved &= rhs.is_resolved();
|
||||||
if changed_something {
|
if changed_something {
|
||||||
println!("changed something because 15, maybe (name is {})", name);
|
tracing::trace!(%name, new_type = %rhs, "changed something because we were able to rewrite name somewhere");
|
||||||
}
|
}
|
||||||
new_constraints.push(constraint);
|
new_constraints.push(constraint);
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint::Equivalent(loc, lhs, rhs @ TypeOrVar::Variable(_, _)) => {
|
Constraint::Equivalent(loc, lhs, rhs @ TypeOrVar::Variable(_, _)) => {
|
||||||
|
tracing::trace!(new_left = %rhs, new_right = %lhs, "changed something because we flipped the order on an equivalence");
|
||||||
new_constraints.push(Constraint::Equivalent(loc, rhs, lhs));
|
new_constraints.push(Constraint::Equivalent(loc, rhs, lhs));
|
||||||
changed_something = true;
|
changed_something = true;
|
||||||
all_constraints_solved = false;
|
all_constraints_solved = false;
|
||||||
println!("changed something because 16");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Constraint::CanCastTo(_, TypeOrVar::Variable(_, _), _)
|
Constraint::CanCastTo(_, TypeOrVar::Variable(_, _), _)
|
||||||
@@ -636,7 +645,7 @@ pub fn solve_constraints(
|
|||||||
resty.clone(),
|
resty.clone(),
|
||||||
));
|
));
|
||||||
warnings.push(TypeInferenceWarning::DefaultedTo(loc.clone(), resty));
|
warnings.push(TypeInferenceWarning::DefaultedTo(loc.clone(), resty));
|
||||||
println!("Adding number equivalence");
|
tracing::trace!("Adding number equivalence");
|
||||||
false
|
false
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
@@ -679,520 +688,3 @@ fn replace_variable(
|
|||||||
|
|
||||||
changed_anything
|
changed_anything
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Solve all the constraints in the provided database.
|
|
||||||
///
|
|
||||||
/// This process can take a bit, so you might not want to do it multiple times. Basically,
|
|
||||||
/// it's going to grind on these constraints until either it figures them out, or it stops
|
|
||||||
/// making progress. I haven't done the math on the constraints to even figure out if this
|
|
||||||
/// is guaranteed to halt, though, let alone terminate in some reasonable amount of time.
|
|
||||||
///
|
|
||||||
/// The return value is a type inference result, which pairs some warnings with either a
|
|
||||||
/// successful set of type resolutions (mappings from type variables to their values), or
|
|
||||||
/// a series of inference errors.
|
|
||||||
pub fn _solve_constraints_old(
|
|
||||||
mut constraint_db: Vec<Constraint>,
|
|
||||||
) -> TypeInferenceResult<TypeResolutions> {
|
|
||||||
let mut errors = vec![];
|
|
||||||
let mut warnings = vec![];
|
|
||||||
let mut resolutions: HashMap<ArcIntern<String>, Type> = HashMap::new();
|
|
||||||
let mut changed_something = true;
|
|
||||||
|
|
||||||
// We want to run this inference endlessly, until either we have solved all of our
|
|
||||||
// constraints. Internal to the loop, we have a check that will make sure that we
|
|
||||||
// do (eventually) stop.
|
|
||||||
while changed_something && !constraint_db.is_empty() {
|
|
||||||
println!("\n\n\nCONSTRAINT:");
|
|
||||||
for constraint in constraint_db.iter() {
|
|
||||||
println!(" {}", constraint);
|
|
||||||
}
|
|
||||||
println!("RESOLUTIONS:");
|
|
||||||
for (name, ty) in resolutions.iter() {
|
|
||||||
println!(" {} = {}", name, ty);
|
|
||||||
}
|
|
||||||
// Set this to false at the top of the loop. We'll set this to true if we make
|
|
||||||
// progress in any way further down, but having this here prevents us from going
|
|
||||||
// into an infinite look when we can't figure stuff out.
|
|
||||||
changed_something = false;
|
|
||||||
// This is sort of a double-buffering thing; we're going to rename constraint_db
|
|
||||||
// and then set it to a new empty vector, which we'll add to as we find new
|
|
||||||
// constraints or find ourselves unable to solve existing ones.
|
|
||||||
let mut local_constraints = constraint_db;
|
|
||||||
constraint_db = vec![];
|
|
||||||
|
|
||||||
// OK. First thing we're going to do is run through all of our constraints,
|
|
||||||
// and see if we can solve any, or reduce them to theoretically more simple
|
|
||||||
// constraints.
|
|
||||||
for constraint in local_constraints.drain(..) {
|
|
||||||
match constraint {
|
|
||||||
// Currently, all of our types are printable
|
|
||||||
Constraint::Printable(_loc, _ty) => changed_something = true,
|
|
||||||
|
|
||||||
// If we're looking for a type to be something (anything!), and it's not a type
|
|
||||||
// variable, then yay, we've solved it.
|
|
||||||
Constraint::IsSomething(_, TypeOrVar::Function(_, _))
|
|
||||||
| Constraint::IsSomething(_, TypeOrVar::Primitive(_)) => changed_something = true,
|
|
||||||
|
|
||||||
// Otherwise, see if we've resolved this variable to anything. If not, add it
|
|
||||||
// back.
|
|
||||||
Constraint::IsSomething(_, TypeOrVar::Variable(_, ref name)) => {
|
|
||||||
if resolutions.get(name).is_none() {
|
|
||||||
constraint_db.push(constraint);
|
|
||||||
} else {
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case #1a: We have two primitive types. If they're equal, we've discharged this
|
|
||||||
// constraint! We can just continue. If they're not equal, add an error and then
|
|
||||||
// see what else we come up with.
|
|
||||||
Constraint::Equivalent(
|
|
||||||
loc,
|
|
||||||
a @ TypeOrVar::Primitive(_),
|
|
||||||
b @ TypeOrVar::Primitive(_),
|
|
||||||
) => {
|
|
||||||
if a != b {
|
|
||||||
errors.push(TypeInferenceError::NotEquivalent(loc, a, b));
|
|
||||||
}
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case #2: They're both variables. In which case, we'll have to do much the same
|
|
||||||
// check, but now on their resolutions.
|
|
||||||
Constraint::Equivalent(
|
|
||||||
ref loc,
|
|
||||||
TypeOrVar::Variable(_, ref name1),
|
|
||||||
TypeOrVar::Variable(_, ref name2),
|
|
||||||
) => match (resolutions.get(name1), resolutions.get(name2)) {
|
|
||||||
(None, None) => {
|
|
||||||
constraint_db.push(constraint);
|
|
||||||
}
|
|
||||||
(Some(pt), None) => {
|
|
||||||
resolutions.insert(name2.clone(), pt.clone());
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
(None, Some(pt)) => {
|
|
||||||
resolutions.insert(name1.clone(), pt.clone());
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
(Some(pt1), Some(pt2)) if pt1 == pt2 => {
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
(Some(pt1), Some(pt2)) => {
|
|
||||||
errors.push(TypeInferenceError::NotEquivalent(
|
|
||||||
loc.clone(),
|
|
||||||
pt1.clone().into(),
|
|
||||||
pt2.clone().into(),
|
|
||||||
));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// Case #3: One of the two constraints is a primitive, and the other is a variable.
|
|
||||||
// In this case, we'll check to see if we've resolved the variable, and check for
|
|
||||||
// equivalence if we have. If we haven't, we'll set that variable to be primitive
|
|
||||||
// type.
|
|
||||||
Constraint::Equivalent(loc, t, TypeOrVar::Variable(vloc, name))
|
|
||||||
| Constraint::Equivalent(loc, TypeOrVar::Variable(vloc, name), t) => {
|
|
||||||
println!("IN THIS CASE with {}", name);
|
|
||||||
match resolutions.get(&name) {
|
|
||||||
None => match t.try_into() {
|
|
||||||
Ok(real_type) => {
|
|
||||||
println!(" HERE with {} and {}", name, real_type);
|
|
||||||
resolutions.insert(name, real_type);
|
|
||||||
}
|
|
||||||
Err(variable_type) => {
|
|
||||||
println!(
|
|
||||||
" REJECTED INTO RETURN with {} and {}",
|
|
||||||
name, variable_type
|
|
||||||
);
|
|
||||||
constraint_db.push(Constraint::Equivalent(
|
|
||||||
loc,
|
|
||||||
variable_type,
|
|
||||||
TypeOrVar::Variable(vloc, name),
|
|
||||||
));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(t2) if &t == t2 => {
|
|
||||||
println!(" MATCHED at {} == {}", t, t2);
|
|
||||||
}
|
|
||||||
Some(t2) => errors.push(TypeInferenceError::NotEquivalent(
|
|
||||||
loc,
|
|
||||||
t,
|
|
||||||
t2.clone().into(),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case #4: Like primitives, but for function types. This is a little complicated, because
|
|
||||||
// we first want to resolve all the type variables in the two types, and then see if they're
|
|
||||||
// equivalent. Fortunately, though, we can cheat a bit. What we're going to do is first see
|
|
||||||
// if the two types have the same arity (the same number of arguments). If not, we know the
|
|
||||||
// types don't match. If they do, then we're going to just turn this into a bunch of different
|
|
||||||
// equivalence constraints by matching up each of the arguments as well as the return type, and
|
|
||||||
// then restarting the type checking loop. That will cause any of those type variables to be
|
|
||||||
// handled appropriately. This even works recursively, so we can support arbitrarily nested
|
|
||||||
// function types.
|
|
||||||
Constraint::Equivalent(
|
|
||||||
loc,
|
|
||||||
ref a @ TypeOrVar::Function(ref args1, ref ret1),
|
|
||||||
ref b @ TypeOrVar::Function(ref args2, ref ret2),
|
|
||||||
) => {
|
|
||||||
if args1.len() != args2.len() {
|
|
||||||
errors.push(TypeInferenceError::NotEquivalent(
|
|
||||||
loc.clone(),
|
|
||||||
a.clone(),
|
|
||||||
b.clone(),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
for (left, right) in args1.iter().zip(args2.iter()) {
|
|
||||||
constraint_db.push(Constraint::Equivalent(
|
|
||||||
loc.clone(),
|
|
||||||
left.clone(),
|
|
||||||
right.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
constraint_db.push(Constraint::Equivalent(
|
|
||||||
loc,
|
|
||||||
ret1.as_ref().clone(),
|
|
||||||
ret2.as_ref().clone(),
|
|
||||||
));
|
|
||||||
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Case #5: They're just totally the wrong types. In which case, we're done with
|
|
||||||
// this one; emit the error and drop the constraint.
|
|
||||||
Constraint::Equivalent(loc, a, b) => {
|
|
||||||
errors.push(TypeInferenceError::NotEquivalent(loc, a, b));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that the provided number fits within the provided constant type. For the
|
|
||||||
// moment, we're going to call an error here a failure, although this could be a
|
|
||||||
// warning in the future.
|
|
||||||
Constraint::FitsInNumType(loc, TypeOrVar::Primitive(ctype), val) => {
|
|
||||||
match ctype.max_value() {
|
|
||||||
None => {
|
|
||||||
errors.push(TypeInferenceError::NotANumber(loc, ctype));
|
|
||||||
}
|
|
||||||
Some(max_value) if max_value < val => {
|
|
||||||
errors.push(TypeInferenceError::ConstantTooLarge(loc, ctype, val));
|
|
||||||
}
|
|
||||||
Some(_) => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we have a non-constant type, then let's see if we can advance this to a constant
|
|
||||||
// type
|
|
||||||
Constraint::FitsInNumType(loc, TypeOrVar::Variable(vloc, var), val) => {
|
|
||||||
match resolutions.get(&var) {
|
|
||||||
None => constraint_db.push(Constraint::FitsInNumType(
|
|
||||||
loc,
|
|
||||||
TypeOrVar::Variable(vloc, var),
|
|
||||||
val,
|
|
||||||
)),
|
|
||||||
Some(nt) => {
|
|
||||||
constraint_db.push(Constraint::FitsInNumType(
|
|
||||||
loc,
|
|
||||||
nt.clone().into(),
|
|
||||||
val,
|
|
||||||
));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function types definitely do not fit in numeric types
|
|
||||||
Constraint::FitsInNumType(loc, t @ TypeOrVar::Function(_, _), val) => {
|
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAFunction(
|
|
||||||
loc,
|
|
||||||
t.clone(),
|
|
||||||
Some(val),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the left type in a "can cast to" check is a variable, let's see if we can advance
|
|
||||||
// it into something more tangible
|
|
||||||
Constraint::CanCastTo(loc, TypeOrVar::Variable(vloc, var), to_type) => {
|
|
||||||
match resolutions.get(&var) {
|
|
||||||
None => constraint_db.push(Constraint::CanCastTo(
|
|
||||||
loc,
|
|
||||||
TypeOrVar::Variable(vloc, var),
|
|
||||||
to_type,
|
|
||||||
)),
|
|
||||||
Some(nt) => {
|
|
||||||
constraint_db.push(Constraint::CanCastTo(
|
|
||||||
loc,
|
|
||||||
nt.clone().into(),
|
|
||||||
to_type,
|
|
||||||
));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the right type in a "can cast to" check is a variable, same deal
|
|
||||||
Constraint::CanCastTo(loc, from_type, TypeOrVar::Variable(vloc, var)) => {
|
|
||||||
match resolutions.get(&var) {
|
|
||||||
None => constraint_db.push(Constraint::CanCastTo(
|
|
||||||
loc,
|
|
||||||
from_type,
|
|
||||||
TypeOrVar::Variable(vloc, var),
|
|
||||||
)),
|
|
||||||
Some(nt) => {
|
|
||||||
constraint_db.push(Constraint::CanCastTo(
|
|
||||||
loc,
|
|
||||||
from_type,
|
|
||||||
nt.clone().into(),
|
|
||||||
));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If both of them are types, then we can actually do the test. yay!
|
|
||||||
Constraint::CanCastTo(
|
|
||||||
loc,
|
|
||||||
TypeOrVar::Primitive(from_type),
|
|
||||||
TypeOrVar::Primitive(to_type),
|
|
||||||
) => {
|
|
||||||
if !from_type.can_cast_to(&to_type) {
|
|
||||||
errors.push(TypeInferenceError::CannotSafelyCast(
|
|
||||||
loc, from_type, to_type,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If either type is a function type, then we can only cast if the two types
|
|
||||||
// are equivalent.
|
|
||||||
Constraint::CanCastTo(
|
|
||||||
loc,
|
|
||||||
t1 @ TypeOrVar::Function(_, _),
|
|
||||||
t2 @ TypeOrVar::Function(_, _),
|
|
||||||
) => {
|
|
||||||
if t1 != t2 {
|
|
||||||
errors.push(TypeInferenceError::CannotCastBetweenFunctinoTypes(
|
|
||||||
loc,
|
|
||||||
t1.clone(),
|
|
||||||
t2.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Constraint::CanCastTo(
|
|
||||||
loc,
|
|
||||||
t @ TypeOrVar::Function(_, _),
|
|
||||||
TypeOrVar::Primitive(_),
|
|
||||||
) => {
|
|
||||||
errors.push(TypeInferenceError::CannotCastFromFunctionType(
|
|
||||||
loc,
|
|
||||||
t.clone(),
|
|
||||||
));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Constraint::CanCastTo(
|
|
||||||
loc,
|
|
||||||
TypeOrVar::Primitive(_),
|
|
||||||
t @ TypeOrVar::Function(_, _),
|
|
||||||
) => {
|
|
||||||
errors.push(TypeInferenceError::CannotCastToFunctionType(loc, t.clone()));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// As per usual, if we're trying to test if a type variable is numeric, first
|
|
||||||
// we try to advance it to a primitive
|
|
||||||
Constraint::NumericType(loc, TypeOrVar::Variable(vloc, var)) => {
|
|
||||||
match resolutions.get(&var) {
|
|
||||||
None => constraint_db
|
|
||||||
.push(Constraint::NumericType(loc, TypeOrVar::Variable(vloc, var))),
|
|
||||||
Some(nt) => {
|
|
||||||
constraint_db.push(Constraint::NumericType(loc, nt.clone().into()));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Of course, if we get to a primitive type, then it's true, because all of our
|
|
||||||
// primitive types are numbers
|
|
||||||
Constraint::NumericType(_, TypeOrVar::Primitive(_)) => {
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// But functions are definitely not numbers
|
|
||||||
Constraint::NumericType(loc, t @ TypeOrVar::Function(_, _)) => {
|
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAFunction(
|
|
||||||
loc,
|
|
||||||
t.clone(),
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// As per usual, if we're trying to test if a type variable is numeric, first
|
|
||||||
// we try to advance it to a primitive
|
|
||||||
Constraint::ConstantNumericType(loc, TypeOrVar::Variable(vloc, var)) => {
|
|
||||||
match resolutions.get(&var) {
|
|
||||||
None => constraint_db.push(Constraint::ConstantNumericType(
|
|
||||||
loc,
|
|
||||||
TypeOrVar::Variable(vloc, var),
|
|
||||||
)),
|
|
||||||
Some(nt) => {
|
|
||||||
constraint_db
|
|
||||||
.push(Constraint::ConstantNumericType(loc, nt.clone().into()));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Of course, if we get to a primitive type, then it's true, because all of our
|
|
||||||
// primitive types are numbers
|
|
||||||
Constraint::ConstantNumericType(_, TypeOrVar::Primitive(_)) => {
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// But functions are definitely not numbers
|
|
||||||
Constraint::ConstantNumericType(loc, t @ TypeOrVar::Function(_, _)) => {
|
|
||||||
errors.push(TypeInferenceError::CannotMakeNumberAFunction(
|
|
||||||
loc,
|
|
||||||
t.clone(),
|
|
||||||
None,
|
|
||||||
));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// OK, this one could be a little tricky if we tried to do it all at once, but
|
|
||||||
// instead what we're going to do here is just use this constraint to generate
|
|
||||||
// a bunch more constraints, and then go have the engine solve those. The only
|
|
||||||
// real errors we're going to come up with here are "arity errors"; errors we
|
|
||||||
// find by discovering that the number of arguments provided doesn't make sense
|
|
||||||
// given the primitive being used.
|
|
||||||
Constraint::ProperPrimitiveArgs(loc, prim, mut args, ret) => match prim {
|
|
||||||
Primitive::Plus | Primitive::Times | Primitive::Divide if args.len() != 2 => {
|
|
||||||
errors.push(TypeInferenceError::WrongPrimitiveArity(
|
|
||||||
loc,
|
|
||||||
prim,
|
|
||||||
2,
|
|
||||||
2,
|
|
||||||
args.len(),
|
|
||||||
));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Primitive::Plus | Primitive::Times | Primitive::Divide => {
|
|
||||||
let right = args.pop().expect("2 > 0");
|
|
||||||
let left = args.pop().expect("2 > 1");
|
|
||||||
|
|
||||||
// technically testing that both are numeric is redundant, but it might give
|
|
||||||
// a slightly helpful type error if we do both.
|
|
||||||
constraint_db.push(Constraint::NumericType(loc.clone(), left.clone()));
|
|
||||||
constraint_db.push(Constraint::NumericType(loc.clone(), right.clone()));
|
|
||||||
constraint_db.push(Constraint::NumericType(loc.clone(), ret.clone()));
|
|
||||||
constraint_db.push(Constraint::Equivalent(
|
|
||||||
loc.clone(),
|
|
||||||
left.clone(),
|
|
||||||
right,
|
|
||||||
));
|
|
||||||
constraint_db.push(Constraint::Equivalent(loc, left, ret));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Primitive::Minus if args.is_empty() || args.len() > 2 => {
|
|
||||||
errors.push(TypeInferenceError::WrongPrimitiveArity(
|
|
||||||
loc,
|
|
||||||
prim,
|
|
||||||
1,
|
|
||||||
2,
|
|
||||||
args.len(),
|
|
||||||
));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Primitive::Minus if args.len() == 1 => {
|
|
||||||
let arg = args.pop().expect("1 > 0");
|
|
||||||
constraint_db.push(Constraint::NumericType(loc.clone(), arg.clone()));
|
|
||||||
constraint_db.push(Constraint::NumericType(loc.clone(), ret.clone()));
|
|
||||||
constraint_db.push(Constraint::Equivalent(loc, arg, ret));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Primitive::Minus => {
|
|
||||||
let right = args.pop().expect("2 > 0");
|
|
||||||
let left = args.pop().expect("2 > 1");
|
|
||||||
|
|
||||||
// technically testing that both are numeric is redundant, but it might give
|
|
||||||
// a slightly helpful type error if we do both.
|
|
||||||
constraint_db.push(Constraint::NumericType(loc.clone(), left.clone()));
|
|
||||||
constraint_db.push(Constraint::NumericType(loc.clone(), right.clone()));
|
|
||||||
constraint_db.push(Constraint::NumericType(loc.clone(), ret.clone()));
|
|
||||||
constraint_db.push(Constraint::Equivalent(
|
|
||||||
loc.clone(),
|
|
||||||
left.clone(),
|
|
||||||
right,
|
|
||||||
));
|
|
||||||
constraint_db.push(Constraint::Equivalent(loc.clone(), left, ret));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => panic!("unexpected constraint"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If that didn't actually come up with anything, and we just recycled all the constraints
|
|
||||||
// back into the database unchanged, then let's take a look for cases in which we just
|
|
||||||
// wanted something we didn't know to be a number. Basically, those are cases where the
|
|
||||||
// user just wrote a number, but didn't tell us what type it was, and there isn't enough
|
|
||||||
// information in the context to tell us. If that happens, we'll just set that type to
|
|
||||||
// be u64, and warn the user that we did so.
|
|
||||||
if !changed_something && !constraint_db.is_empty() {
|
|
||||||
local_constraints = constraint_db;
|
|
||||||
constraint_db = vec![];
|
|
||||||
|
|
||||||
for constraint in local_constraints.drain(..) {
|
|
||||||
match constraint {
|
|
||||||
Constraint::ConstantNumericType(loc, t @ TypeOrVar::Variable(_, _)) => {
|
|
||||||
let resty = TypeOrVar::Primitive(PrimitiveType::U64);
|
|
||||||
constraint_db.push(Constraint::Equivalent(
|
|
||||||
loc.clone(),
|
|
||||||
t,
|
|
||||||
TypeOrVar::Primitive(PrimitiveType::U64),
|
|
||||||
));
|
|
||||||
warnings.push(TypeInferenceWarning::DefaultedTo(loc, resty));
|
|
||||||
changed_something = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => constraint_db.push(constraint),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OK, we left our loop. Which means that either we solved everything, or we didn't.
|
|
||||||
// If we didn't, turn the unsolved constraints into type inference errors, and add
|
|
||||||
// them to our error list.
|
|
||||||
let mut unsolved_constraint_errors = constraint_db
|
|
||||||
.drain(..)
|
|
||||||
.map(TypeInferenceError::CouldNotSolve)
|
|
||||||
.collect();
|
|
||||||
errors.append(&mut unsolved_constraint_errors);
|
|
||||||
|
|
||||||
// How'd we do?
|
|
||||||
if errors.is_empty() {
|
|
||||||
TypeInferenceResult::Success {
|
|
||||||
result: resolutions,
|
|
||||||
warnings,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
TypeInferenceResult::Failure { errors, warnings }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -31,5 +31,5 @@ macro_rules! derived_display {
|
|||||||
|
|
||||||
// this is a dumb Rust trick to export the functions to the rest
|
// this is a dumb Rust trick to export the functions to the rest
|
||||||
// of the crate, but not globally.
|
// of the crate, but not globally.
|
||||||
pub(crate) use pretty_function_type;
|
|
||||||
pub(crate) use derived_display;
|
pub(crate) use derived_display;
|
||||||
|
pub(crate) use pretty_function_type;
|
||||||
|
|||||||
Reference in New Issue
Block a user