it runs! gets the wrong answer, but runs!

This commit is contained in:
2024-02-17 09:38:12 -08:00
parent 9d41cf0da7
commit 7edaf747aa
8 changed files with 491 additions and 26 deletions

View File

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

View File

@@ -491,19 +491,27 @@ impl<M: Module> Backend<M> {
panic!("Can't use a value for a function")
}
ValueOrRef::Ref(_, result_type, name) => match self.defined_functions.get(&name) {
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 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)),
[] => 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?"),
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"),
}
}
@@ -512,6 +520,7 @@ impl<M: Module> Backend<M> {
}
}
}
}
/// Compile a value or reference into Cranelift, returning the Cranelift Value for
/// the expression and its type.

View File

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

View File

@@ -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<String>, 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<Type> for TypeOrVar {

View File

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

View File

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

View File

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

View File

@@ -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<String>, 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<u64>),
/// 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<TypeInferenceError> for Diagnostic<usize> {
@@ -184,6 +219,11 @@ impl From<TypeInferenceError> for Diagnostic<usize> {
"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<TypeInferenceError> for Diagnostic<usize> {
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<TypeResolutions> {
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<Constraint>,
variable: &ArcIntern<String>,
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<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;
@@ -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"),
}
}