diff --git a/src/backend/into_crane.rs b/src/backend/into_crane.rs index b260927..3806c8a 100644 --- a/src/backend/into_crane.rs +++ b/src/backend/into_crane.rs @@ -260,6 +260,7 @@ impl Backend { /// Compile an expression, returning the Cranelift Value for the expression and /// its type. + #[tracing::instrument(level = "trace", skip(self, variables, builder))] fn compile_expression( &mut self, expr: Expression, @@ -456,10 +457,6 @@ impl Backend { Some(ReferenceBuilder::Global(_, global_value)) => { let pointer = self.module.target_config().pointer_type(); 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); Ok((builder.ins().iconst(types::I64, 0), ConstantType::Void)) } @@ -529,14 +526,14 @@ impl Backend { /// Compile a value or reference into Cranelift, returning the Cranelift Value for /// the expression and its type. + #[tracing::instrument(level = "trace", skip(self, variables, builder))] fn compile_value_or_ref( &self, - valref: ValueOrRef, + value_or_ref: ValueOrRef, variables: &HashMap, builder: &mut FunctionBuilder, ) -> Result<(entities::Value, ConstantType), BackendError> { - println!("compile_value_or_ref {:?}", valref); - match valref { + match value_or_ref { ValueOrRef::Value(_, _, val) => match val { Value::I8(_, v) => { // Cranelift does a funny thing where it checks you aren't using bits in the I64 @@ -585,10 +582,6 @@ impl Backend { let pointer_to = self.module.target_config().pointer_type(); let pointer_value = builder.ins().symbol_value(pointer_to, *gv); let cranelift_type = ir::Type::from(*ty); - println!( - "READ {}: cranelift_type {} origin type {:?}", - name, cranelift_type, ty - ); let value = builder .ins() diff --git a/src/compiler.rs b/src/compiler.rs index 388fccc..8e8e5ec 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -137,22 +137,12 @@ impl Compiler { let mut backend = Backend::object_file(Triple::host())?; let unknown = "".to_string(); backend.compile_program("gogogo", ir)?; - println!("FINAL MODULE:"); - println!(" FUNCTIONS:"); + for (_, decl) in backend.module.declarations().get_functions() { - println!( - " {}: {:?}", - decl.name.as_ref().unwrap_or(&unknown), - decl.linkage - ); + tracing::debug!(name = %decl.name.as_ref().unwrap_or(&unknown), linkage = ?decl.linkage, "function definition"); } - println!(" DATA:"); for (_, decl) in backend.module.declarations().get_data_objects() { - println!( - " {}: {:?}", - decl.name.as_ref().unwrap_or(&unknown), - decl.linkage - ); + tracing::debug!(name = %decl.name.as_ref().unwrap_or(&unknown), linkage = ?decl.linkage, "data definition"); } Ok(Some(backend.bytes()?)) } diff --git a/src/ir/pretty.rs b/src/ir/pretty.rs index 7b948fe..ae0ce15 100644 --- a/src/ir/pretty.rs +++ b/src/ir/pretty.rs @@ -1,6 +1,6 @@ use crate::ir::{Expression, Primitive, Program, TopLevel, Type, TypeOrVar, Value, ValueOrRef}; 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}; impl Program { @@ -71,8 +71,7 @@ impl Expression { } Expression::Call(_, _, fun, args) => { let args = args.iter().map(|x| x.pretty(allocator)); - let comma_sepped_args = - allocator.intersperse(args, allocator.text(",")); + let comma_sepped_args = allocator.intersperse(args, allocator.text(",")); fun.pretty(allocator).append(comma_sepped_args.parens()) } Expression::Block(_, _, exprs) => match exprs.split_last() { diff --git a/src/syntax/pretty.rs b/src/syntax/pretty.rs index c4c1a56..3186b63 100644 --- a/src/syntax/pretty.rs +++ b/src/syntax/pretty.rs @@ -1,5 +1,5 @@ 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}; impl Program { @@ -154,4 +154,4 @@ derived_display!(Program); derived_display!(TopLevel); derived_display!(Statement); derived_display!(Expression); -derived_display!(Value); \ No newline at end of file +derived_display!(Value); diff --git a/src/type_infer/convert.rs b/src/type_infer/convert.rs index 3c11de8..0c2410a 100644 --- a/src/type_infer/convert.rs +++ b/src/type_infer/convert.rs @@ -150,7 +150,6 @@ fn convert_statement( .get(&final_name) .expect("print variable defined before use") .clone(); - println!("varty for {} is {}", final_name, varty); constraint_db.push(Constraint::Printable(loc.clone(), varty.clone())); @@ -257,7 +256,6 @@ fn convert_expression( .get(&final_name) .cloned() .expect("variable bound before use"); - println!("rtype for {} is {}", final_name, rtype); let refexp = ir::Expression::Atomic(ir::ValueOrRef::Ref(loc, rtype.clone(), final_name)); diff --git a/src/type_infer/finalize.rs b/src/type_infer/finalize.rs index 3765946..883f2a2 100644 --- a/src/type_infer/finalize.rs +++ b/src/type_infer/finalize.rs @@ -101,7 +101,7 @@ fn finalize_type(ty: TypeOrVar, resolutions: &TypeResolutions) -> Type { TypeOrVar::Variable(_, tvar) => match resolutions.get(&tvar) { None => panic!("Did not resolve type for type variable {}", tvar), Some(pt) => { - println!("Finalizing {} to {}", tvar, pt); + tracing::trace!(type_variable = %tvar, final_type = %pt, "finalizing variable type"); pt.clone() } }, diff --git a/src/type_infer/solve.rs b/src/type_infer/solve.rs index fc9b849..2301d1d 100644 --- a/src/type_infer/solve.rs +++ b/src/type_infer/solve.rs @@ -300,16 +300,18 @@ pub fn solve_constraints( ) -> TypeInferenceResult { let mut errors = vec![]; let mut warnings = vec![]; + let mut iteration = 0u64; loop { let mut changed_something = false; let mut all_constraints_solved = true; let mut new_constraints = vec![]; - println!("\n\n\nCONSTRAINT:"); + tracing::debug!(iteration, "Restarting constraint solving loop"); for constraint in constraint_db.iter() { - println!(" {}", constraint); + tracing::debug!(%constraint, "remaining constraint"); } + iteration += 1; while let Some(constraint) = constraint_db.pop() { match constraint { @@ -328,7 +330,7 @@ pub fn solve_constraints( 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; } @@ -350,41 +352,44 @@ pub fn solve_constraints( 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; } Constraint::CanCastTo( loc, - TypeOrVar::Function(_, _), + ft @ TypeOrVar::Function(_, _), 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)); - println!("changed something because finished CanCastTo (3)"); changed_something = true; } Constraint::CanCastTo( loc, 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)); 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 - 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; - println!("changed something because constant numeric type (1)"); } // if we're testing if a function type is numeric, then throw a useful warning 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)); 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! @@ -399,67 +404,70 @@ pub fn solve_constraints( Some(_) => {} } 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 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( loc, t, Some(val), )); 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 - Constraint::IsSomething(_, TypeOrVar::Function(_, _)) - | Constraint::IsSomething(_, TypeOrVar::Primitive(_)) => { + Constraint::IsSomething(_, t @ TypeOrVar::Function(_, _)) + | Constraint::IsSomething(_, t @ TypeOrVar::Primitive(_)) => { + tracing::trace!(tested_type = %t, "changed something because type is definitely something"); changed_something = true; - println!("changed something because 1"); } // if we want to know if something is signed, we can check its primitive type 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)) { errors.push(TypeInferenceError::IsNotSigned(loc, pt)); } changed_something = true; - println!("changed something because 2"); } // again with the functions and the numbers Constraint::IsSigned(loc, t @ TypeOrVar::Function(_, _)) => { + tracing::trace!(function_type = %t, "changed something because functions are not signed"); errors.push(TypeInferenceError::CannotCastFromFunctionType(loc, t)); changed_something = true; - println!("changed something because 3"); } // 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; - println!("changed something because 4"); } // if we're testing if a function type is numeric, then throw a useful warning 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)); changed_something = true; - println!("changed something because 5"); } // 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; - println!("changed something because 6"); } // 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)); changed_something = true; - println!("changed something because 7"); } 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)); changed_something = true; 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 => { @@ -488,7 +496,7 @@ pub fn solve_constraints( new_constraints.push(Constraint::Equivalent(loc, value, ret)); changed_something = true; 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 => { @@ -500,7 +508,7 @@ pub fn solve_constraints( args.len(), )); 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 => { @@ -512,7 +520,7 @@ pub fn solve_constraints( args.len(), )); 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; - println!("changed something because 12"); + tracing::trace!(primitive_type1 = %pt1, primitive_type2 = %pt2, "changed something because we checked for primitive type equivalence"); } Constraint::Equivalent( @@ -543,9 +551,9 @@ pub fn solve_constraints( ft @ TypeOrVar::Function(_, _), 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)); changed_something = true; - println!("changed something because 13"); } Constraint::Equivalent( @@ -553,6 +561,7 @@ pub fn solve_constraints( TypeOrVar::Variable(_, name1), TypeOrVar::Variable(_, name2), ) if name1 == name2 => { + tracing::trace!(name = %name1, "changed something because variable is equivalent to itself"); changed_something = true; } @@ -574,7 +583,7 @@ pub fn solve_constraints( } 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) => { @@ -582,16 +591,16 @@ pub fn solve_constraints( changed_something |= replace_variable(&mut new_constraints, name, rhs); all_constraints_solved &= rhs.is_resolved(); 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); } 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)); changed_something = true; all_constraints_solved = false; - println!("changed something because 16"); } Constraint::CanCastTo(_, TypeOrVar::Variable(_, _), _) @@ -636,7 +645,7 @@ pub fn solve_constraints( resty.clone(), )); warnings.push(TypeInferenceWarning::DefaultedTo(loc.clone(), resty)); - println!("Adding number equivalence"); + tracing::trace!("Adding number equivalence"); false } else { true @@ -679,520 +688,3 @@ fn replace_variable( 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, -) -> TypeInferenceResult { - let mut errors = vec![]; - let mut warnings = vec![]; - let mut resolutions: HashMap, 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 } - } -} diff --git a/src/util/pretty.rs b/src/util/pretty.rs index 1816dae..88b2789 100644 --- a/src/util/pretty.rs +++ b/src/util/pretty.rs @@ -31,5 +31,5 @@ macro_rules! derived_display { // this is a dumb Rust trick to export the functions to the rest // of the crate, but not globally. +pub(crate) use derived_display; pub(crate) use pretty_function_type; -pub(crate) use derived_display; \ No newline at end of file