diff --git a/build.rs b/build.rs index f883c2b..50fe205 100644 --- a/build.rs +++ b/build.rs @@ -70,7 +70,7 @@ fn generate_tests(f: &mut File, path_so_far: PathBuf) -> std::io::Result<()> { writeln!(f, " let (errors, _) = syntax.validate();")?; writeln!( f, - " assert_eq!(errors.len(), 0, \"file should have no validation errors\");" + " assert_eq!(errors.len(), 0, \"file should have no validation errors, but saw: {{:?}}\", errors);" )?; writeln!(f, " let syntax_result = syntax.eval();")?; writeln!( diff --git a/src/backend/into_crane.rs b/src/backend/into_crane.rs index 740d101..ce1acad 100644 --- a/src/backend/into_crane.rs +++ b/src/backend/into_crane.rs @@ -482,7 +482,7 @@ impl Backend { let (arguments, _argument_types): (Vec<_>, Vec<_>) = args .into_iter() .map(|x| self.compile_value_or_ref(x, variables, builder)) - .collect::,BackendError>>()? + .collect::, BackendError>>()? .into_iter() .unzip(); @@ -491,20 +491,29 @@ impl Backend { panic!("Can't use a value for a function") } - ValueOrRef::Ref(_, result_type, name) => match self.defined_functions.get(&name) { - None => panic!("Couldn't find function {} to call", name), - Some(function) => { - let func_ref = self.module.declare_func_in_func(*function, builder.func); - let call = builder.ins().call(func_ref, &arguments); - let results = builder.inst_results(call); - - match results { - [] => Ok((builder.ins().iconst(types::I64, 0), ConstantType::Void)), - [result] => match result_type { - Type::Primitive(ct) => Ok((*result, ct.into())), - Type::Function(_, _) => panic!("return value is a function?"), + ValueOrRef::Ref(_, result_type, name) => { + match self.defined_functions.get(&name) { + None => panic!("Couldn't find function {} to call", name), + Some(function) => { + let func_ref = + self.module.declare_func_in_func(*function, builder.func); + let call = builder.ins().call(func_ref, &arguments); + let results = builder.inst_results(call); + + match results { + [] => Ok(( + builder.ins().iconst(types::I64, 0), + ConstantType::Void, + )), + [result] => match result_type { + Type::Primitive(ct) => Ok((*result, ct.into())), + Type::Function(_, rt) => match *rt { + Type::Function(_, _) => panic!("function returns a function?"), + Type::Primitive(ct) => Ok((*result, ct.into())), + } + }, + _ => panic!("don't support multi-value returns yet"), } - _ => panic!("don't support multi-value returns yet"), } } } diff --git a/src/bin/ngrun.rs b/src/bin/ngrun.rs index 1d5ca43..4bce462 100644 --- a/src/bin/ngrun.rs +++ b/src/bin/ngrun.rs @@ -58,6 +58,18 @@ fn main() { } }; + let (errors, warnings) = syntax.validate(); + let stop = !errors.is_empty(); + for error in errors { + emit(error.into()); + } + for warning in warnings { + emit(warning.into()); + } + if stop { + return; + } + if cli.interpreter == Interpreter::Syntax { match syntax.eval() { Err(e) => println!("Evaluation error: {}", e), diff --git a/src/ir/ast.rs b/src/ir/ast.rs index cfdc1f4..cc1d873 100644 --- a/src/ir/ast.rs +++ b/src/ir/ast.rs @@ -598,6 +598,38 @@ impl TypeOrVar { pub fn new_located(loc: Location) -> Self { TypeOrVar::Variable(loc, gensym("t")) } + + /// Try replacing the given type variable with the given type, returning true if anything + /// was changed. + pub fn replace(&mut self, name: &ArcIntern, replace_with: &TypeOrVar) -> bool { + match self { + TypeOrVar::Variable(_, var_name) if name == var_name => { + *self = replace_with.clone(); + true + } + + TypeOrVar::Variable(_, _) => false, + + TypeOrVar::Function(args, ret) => { + ret.replace(name, replace_with) + | args.iter_mut().any(|x| x.replace(name, replace_with)) + } + + TypeOrVar::Primitive(_) => false, + } + } + + /// Returns whether or not this type is resolved (meaning that it contains no type + /// variables.) + pub fn is_resolved(&self) -> bool { + match self { + TypeOrVar::Variable(_, _) => false, + TypeOrVar::Primitive(_) => true, + TypeOrVar::Function(args, ret) => { + args.iter().all(TypeOrVar::is_resolved) && ret.is_resolved() + } + } + } } impl PartialEq for TypeOrVar { @@ -675,7 +707,7 @@ impl TryFrom for Type { .map(Type::try_from) .collect::>(); let converted_ret = Type::try_from((*ret).clone()); - + if let Ok(args) = converted_args { if let Ok(ret) = converted_ret { return Ok(Type::Function(args, Box::new(ret))); diff --git a/src/ir/eval.rs b/src/ir/eval.rs index 27f1e84..4d5841f 100644 --- a/src/ir/eval.rs +++ b/src/ir/eval.rs @@ -108,7 +108,7 @@ where let function = fun.eval(env)?; match function { - Value::Closure(name, mut env, arguments, body) => { + Value::Closure(name, mut closure_env, arguments, body) => { if args.len() != arguments.len() { return Err(EvalError::WrongArgCount( loc.clone(), @@ -118,13 +118,13 @@ where )); } - env.new_scope(); - for (name, value) in arguments.into_iter().zip(args.into_iter()) { - let value = value.eval(&mut env)?; - env.insert(name, value); + closure_env.new_scope(); + for (name, value) in arguments.into_iter().zip(args) { + let value = value.eval(env)?; + closure_env.insert(name, value); } - let result = body.eval(&mut env, stdout)?; - env.release_scope(); + let result = body.eval(&mut closure_env, stdout)?; + closure_env.release_scope(); Ok(result) } _ => Err(EvalError::NotAFunction(loc.clone(), function)), diff --git a/src/syntax/validate.rs b/src/syntax/validate.rs index 331d5e6..9c89885 100644 --- a/src/syntax/validate.rs +++ b/src/syntax/validate.rs @@ -13,6 +13,7 @@ use std::str::FromStr; /// that we're not going to be able to work through. As with most /// of these errors, we recommend converting this to a [`Diagnostic`] /// and using [`codespan_reporting`] to present them to the user. +#[derive(Debug)] pub enum Error { UnboundVariable(Location, String), UnknownType(Location, String), @@ -83,6 +84,9 @@ impl Program { let mut warnings = vec![]; for stmt in self.items.iter() { + if let TopLevel::Function(Some(name), _, _) = stmt { + bound_variables.insert(name.to_string(), name.location.clone()); + } let (mut new_errors, mut new_warnings) = stmt.validate_with_bindings(bound_variables); errors.append(&mut new_errors); warnings.append(&mut new_warnings); diff --git a/src/type_infer/convert.rs b/src/type_infer/convert.rs index 9f2f98a..d93d811 100644 --- a/src/type_infer/convert.rs +++ b/src/type_infer/convert.rs @@ -334,7 +334,7 @@ fn convert_expression( let new_args = args .into_iter() - .zip(arg_types.into_iter()) + .zip(arg_types) .map(|(arg, target_type)| { let (new_arg, inferred_type) = convert_expression(arg, constraint_db, renames, bindings); diff --git a/src/type_infer/solve.rs b/src/type_infer/solve.rs index 88ea802..fb668d2 100644 --- a/src/type_infer/solve.rs +++ b/src/type_infer/solve.rs @@ -27,6 +27,8 @@ pub enum Constraint { Equivalent(Location, TypeOrVar, TypeOrVar), /// The given type can be resolved to something IsSomething(Location, TypeOrVar), + /// The given type can be negated + IsSigned(Location, TypeOrVar), } impl fmt::Display for Constraint { @@ -46,6 +48,34 @@ impl fmt::Display for Constraint { Constraint::ConstantNumericType(_, ty) => write!(f, "CONST_NUMERIC {}", ty), Constraint::Equivalent(_, ty, ty2) => write!(f, "EQUIVALENT {} => {}", ty, ty2), Constraint::IsSomething(_, ty) => write!(f, "SOMETHING {}", ty), + Constraint::IsSigned(_, ty) => write!(f, "SIGNED {}", ty), + } + } +} + +impl Constraint { + /// Replace all instances of the name (anywhere! including on the left hand side of equivalences!) + /// with the given type. + /// + /// Returns whether or not anything was changed in the constraint. + fn replace(&mut self, name: &ArcIntern, replace_with: &TypeOrVar) -> bool { + match self { + Constraint::Printable(_, ty) => ty.replace(name, replace_with), + Constraint::FitsInNumType(_, ty, _) => ty.replace(name, replace_with), + Constraint::CanCastTo(_, ty1, ty2) => { + ty1.replace(name, replace_with) || ty2.replace(name, replace_with) + } + Constraint::ConstantNumericType(_, ty) => ty.replace(name, replace_with), + Constraint::Equivalent(_, ty1, ty2) => { + ty1.replace(name, replace_with) || ty2.replace(name, replace_with) + } + Constraint::IsSigned(_, ty) => ty.replace(name, replace_with), + Constraint::IsSomething(_, ty) => ty.replace(name, replace_with), + Constraint::NumericType(_, ty) => ty.replace(name, replace_with), + Constraint::ProperPrimitiveArgs(_, _, args, ret) => { + ret.replace(name, replace_with) + | args.iter_mut().any(|x| x.replace(name, replace_with)) + } } } } @@ -112,7 +142,8 @@ pub enum TypeInferenceError { CannotSafelyCast(Location, PrimitiveType, PrimitiveType), /// The primitive invocation provided the wrong number of arguments. WrongPrimitiveArity(Location, Primitive, usize, usize, usize), - /// We cannot cast between function types at the moment. + /// We cannot cast between the given function types, usually because they + /// have different argument lengths CannotCastBetweenFunctinoTypes(Location, TypeOrVar, TypeOrVar), /// We cannot cast from a function type to something else. CannotCastFromFunctionType(Location, TypeOrVar), @@ -122,6 +153,10 @@ pub enum TypeInferenceError { CannotMakeNumberAFunction(Location, TypeOrVar, Option), /// We had a constraint we just couldn't solve. CouldNotSolve(Constraint), + /// Functions are not printable. + FunctionsAreNotPrintable(Location), + /// The given type isn't signed, and can't be negated + IsNotSigned(Location, PrimitiveType), } impl From for Diagnostic { @@ -184,6 +219,11 @@ impl From for Diagnostic { "cannot use a constant as a function type".to_string() }) .with_message(format!("function type was {}", t)), + TypeInferenceError::FunctionsAreNotPrintable(loc) => loc + .labelled_error("cannot print function values"), + TypeInferenceError::IsNotSigned(loc, pt) => loc + .labelled_error(format!("type {} is not signed", pt)) + .with_message("and so it cannot be negated"), TypeInferenceError::CouldNotSolve(Constraint::CanCastTo(loc, a, b)) => { loc.labelled_error("internal error").with_message(format!( "could not determine if it was safe to cast from {} to {:#?}", @@ -220,6 +260,9 @@ impl From for Diagnostic { loc.labelled_error("could not infer type") .with_message("Could not find *any* type information; is this an unused function argument?") } + TypeInferenceError::CouldNotSolve(Constraint::IsSigned(loc, t)) => loc + .labelled_error("internal error") + .with_message(format!("could not infer that type {} was signed", t)), } } } @@ -257,6 +300,366 @@ pub fn solve_constraints( ) -> TypeInferenceResult { let mut errors = vec![]; let mut warnings = vec![]; + + loop { + let mut changed_something = false; + let mut all_constraints_solved = true; + let mut new_constraints = vec![]; + + println!("\n\n\nCONSTRAINT:"); + for constraint in constraint_db.iter() { + println!(" {}", constraint); + } + + while let Some(constraint) = constraint_db.pop() { + match constraint { + // The basic philosophy of this match block is that, for each constraint, we're + // going to start seeing if we can just solve (or abandon) the constraint. Then, + // if we can't, we'll just chuck it back on our list for later. + + // Checks on whether we can cast from one thing to another! + 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; + } + + Constraint::CanCastTo( + loc, + TypeOrVar::Function(args1, ret1), + TypeOrVar::Function(args2, ret2), + ) => { + if args1.len() == args2.len() { + new_constraints.push(Constraint::Equivalent(loc.clone(), *ret1, *ret2)); + for (arg1, arg2) in args1.into_iter().zip(args2) { + new_constraints.push(Constraint::Equivalent(loc.clone(), arg1, arg2)) + } + all_constraints_solved = false; + } else { + errors.push(TypeInferenceError::CannotCastBetweenFunctinoTypes( + loc, + TypeOrVar::Function(args1, ret1), + TypeOrVar::Function(args2, ret2), + )); + } + changed_something = true; + } + + Constraint::CanCastTo( + loc, + TypeOrVar::Function(_, _), + pt @ TypeOrVar::Primitive(_), + ) => { + errors.push(TypeInferenceError::CannotCastFromFunctionType(loc, pt)); + changed_something = true; + } + + Constraint::CanCastTo( + loc, + pt @ TypeOrVar::Primitive(_), + TypeOrVar::Function(_, _), + ) => { + errors.push(TypeInferenceError::CannotCastToFunctionType(loc, pt)); + changed_something = true; + } + + // if we're testing if an actual primitive type is numeric, that's pretty easy + Constraint::ConstantNumericType(_, TypeOrVar::Primitive(_)) => { + changed_something = true; + } + + // if we're testing if a function type is numeric, then throw a useful warning + Constraint::ConstantNumericType(loc, t @ TypeOrVar::Function(_, _)) => { + errors.push(TypeInferenceError::CannotMakeNumberAFunction(loc, t, None)); + changed_something = true; + } + + // if we're testing if a number can fit into a numeric type, we can just do that! + 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're testing if a function type can fit into a numeric type, that's a problem + Constraint::FitsInNumType(loc, t @ TypeOrVar::Function(_, _), val) => { + errors.push(TypeInferenceError::CannotMakeNumberAFunction( + loc, + t, + Some(val), + )); + changed_something = true; + } + + // 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(_)) => { + changed_something = true; + } + + // if we want to know if something is signed, we can check its primitive type + Constraint::IsSigned(loc, TypeOrVar::Primitive(pt)) => { + if !pt.valid_operators().contains(&("-", 1)) { + errors.push(TypeInferenceError::IsNotSigned(loc, pt)); + } + changed_something = true; + } + + // again with the functions and the numbers + Constraint::IsSigned(loc, t @ TypeOrVar::Function(_, _)) => { + errors.push(TypeInferenceError::CannotCastFromFunctionType(loc, t)); + changed_something = true; + } + + // if we're testing if an actual primitive type is numeric, that's pretty easy + Constraint::NumericType(_, TypeOrVar::Primitive(_)) => { + changed_something = true; + } + + // if we're testing if a function type is numeric, then throw a useful warning + Constraint::NumericType(loc, t @ TypeOrVar::Function(_, _)) => { + errors.push(TypeInferenceError::CannotMakeNumberAFunction(loc, t, None)); + changed_something = true; + } + + // all of our primitive types are printable + Constraint::Printable(_, TypeOrVar::Primitive(_)) => { + changed_something = true; + } + + // function types are definitely not printable + Constraint::Printable(loc, TypeOrVar::Function(_, _)) => { + errors.push(TypeInferenceError::FunctionsAreNotPrintable(loc)); + changed_something = true; + } + + Constraint::ProperPrimitiveArgs(loc, prim, mut args, ret) => match prim { + Primitive::Plus | Primitive::Minus | Primitive::Times | Primitive::Divide + if args.len() == 2 => + { + let right = args.pop().expect("2>0"); + let left = args.pop().expect("2>1"); + + new_constraints.push(Constraint::NumericType(loc.clone(), left.clone())); + new_constraints.push(Constraint::Equivalent( + loc.clone(), + left.clone(), + right, + )); + new_constraints.push(Constraint::Equivalent(loc.clone(), left, ret)); + changed_something = true; + all_constraints_solved = false; + } + + Primitive::Minus if args.len() == 1 => { + let value = args.pop().expect("1>0"); + new_constraints.push(Constraint::NumericType(loc.clone(), value.clone())); + new_constraints.push(Constraint::IsSigned(loc.clone(), value.clone())); + new_constraints.push(Constraint::Equivalent(loc, value, ret)); + changed_something = true; + all_constraints_solved = false; + } + + Primitive::Plus | Primitive::Times | Primitive::Divide => { + errors.push(TypeInferenceError::WrongPrimitiveArity( + loc, + prim, + 2, + 2, + args.len(), + )); + changed_something = true; + } + + Primitive::Minus => { + errors.push(TypeInferenceError::WrongPrimitiveArity( + loc, + prim, + 1, + 2, + args.len(), + )); + changed_something = true; + } + }, + + // Some equivalences we can/should solve directly + Constraint::Equivalent( + loc, + TypeOrVar::Primitive(pt1), + TypeOrVar::Primitive(pt2), + ) => { + if pt1 != pt2 { + errors.push(TypeInferenceError::NotEquivalent( + loc, + TypeOrVar::Primitive(pt1), + TypeOrVar::Primitive(pt2), + )); + } + changed_something = true; + } + + Constraint::Equivalent( + loc, + pt @ TypeOrVar::Primitive(_), + ft @ TypeOrVar::Function(_, _), + ) + | Constraint::Equivalent( + loc, + ft @ TypeOrVar::Function(_, _), + pt @ TypeOrVar::Primitive(_), + ) => { + errors.push(TypeInferenceError::NotEquivalent(loc, pt, ft)); + changed_something = true; + } + + Constraint::Equivalent( + loc, + TypeOrVar::Function(args1, ret1), + TypeOrVar::Function(args2, ret2), + ) => { + if args1.len() != args2.len() { + let t1 = TypeOrVar::Function(args1, ret1); + let t2 = TypeOrVar::Function(args2, ret2); + errors.push(TypeInferenceError::NotEquivalent(loc, t1, t2)); + } else { + for (left, right) in args1.into_iter().zip(args2) { + new_constraints.push(Constraint::Equivalent(loc.clone(), left, right)); + } + new_constraints.push(Constraint::Equivalent(loc, *ret1, *ret2)); + all_constraints_solved = false; + } + + changed_something = true; + } + + Constraint::Equivalent(_, TypeOrVar::Variable(_, ref name), ref rhs) => { + changed_something |= replace_variable(&mut constraint_db, name, rhs); + changed_something |= replace_variable(&mut new_constraints, name, rhs); + all_constraints_solved &= rhs.is_resolved(); + new_constraints.push(constraint); + } + + Constraint::Equivalent(loc, lhs, rhs @ TypeOrVar::Variable(_, _)) => { + new_constraints.push(Constraint::Equivalent(loc, rhs, lhs)); + changed_something = true; + all_constraints_solved = false; + } + + Constraint::CanCastTo(_, TypeOrVar::Variable(_, _), _) + | Constraint::CanCastTo(_, _, TypeOrVar::Variable(_, _)) + | Constraint::ConstantNumericType(_, TypeOrVar::Variable(_, _)) + | Constraint::FitsInNumType(_, TypeOrVar::Variable(_, _), _) + | Constraint::IsSomething(_, TypeOrVar::Variable(_, _)) + | Constraint::IsSigned(_, TypeOrVar::Variable(_, _)) + | Constraint::NumericType(_, TypeOrVar::Variable(_, _)) + | Constraint::Printable(_, TypeOrVar::Variable(_, _)) => { + all_constraints_solved = false; + new_constraints.push(constraint); + } + } + } + + if all_constraints_solved { + let result = new_constraints + .into_iter() + .map(|constraint| match constraint { + Constraint::Equivalent(_, TypeOrVar::Variable(_, name), result) => { + match result.try_into() { + Err(e) => panic!("Ended up with complex type {}", e), + Ok(v) => (name, v), + } + } + _ => panic!("Had something that wasn't an equivalence left at the end!"), + }) + .collect(); + return TypeInferenceResult::Success { result, warnings }; + } + + if !changed_something { + let mut addendums = vec![]; + + new_constraints.retain(|x| { + if let Constraint::ConstantNumericType(loc, t) = x { + let resty = TypeOrVar::Primitive(PrimitiveType::U64); + addendums.push(Constraint::Equivalent( + loc.clone(), + t.clone(), + resty.clone(), + )); + warnings.push(TypeInferenceWarning::DefaultedTo(loc.clone(), resty)); + false + } else { + true + } + }); + + if addendums.is_empty() { + if errors.is_empty() { + errors = new_constraints + .into_iter() + .map(TypeInferenceError::CouldNotSolve) + .collect(); + } + return TypeInferenceResult::Failure { errors, warnings }; + } + + new_constraints.append(&mut addendums); + } + + constraint_db = new_constraints; + } +} + +/// Replace the given variable with the given type everywhere in the list of contraints. +/// +/// Returns whether anything was changed, at all, anywhere in the database. Note that +/// this will destroy all instances of the name, both on the left and right hand sides, +/// so you better make sure you can re-introduce *something* about this name after this +/// runs if you don't want to lose it. (If you do want to lose it, of course, go ahead.) +fn replace_variable( + constraint_db: &mut Vec, + variable: &ArcIntern, + replace_with: &TypeOrVar, +) -> bool { + let mut changed_anything = false; + + for constraint in constraint_db { + changed_anything |= constraint.replace(variable, replace_with); + } + + 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; @@ -364,7 +767,10 @@ pub fn solve_constraints( resolutions.insert(name, real_type); } Err(variable_type) => { - println!(" REJECTED INTO RETURN with {} and {}", name, variable_type); + println!( + " REJECTED INTO RETURN with {} and {}", + name, variable_type + ); constraint_db.push(Constraint::Equivalent( loc, variable_type, @@ -702,6 +1108,8 @@ pub fn solve_constraints( changed_something = true; } }, + + _ => panic!("unexpected constraint"), } }