think that replaces all the printlns I care about

This commit is contained in:
2024-03-02 21:52:56 -08:00
parent e9fbd275a2
commit 745e263b69
8 changed files with 57 additions and 585 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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