diff --git a/src/backend/eval.rs b/src/backend/eval.rs index 8e87f60..306c1fc 100644 --- a/src/backend/eval.rs +++ b/src/backend/eval.rs @@ -5,9 +5,9 @@ use crate::ir::Program; use crate::syntax::arbitrary::GenerationEnvironment; use cranelift_jit::JITModule; use cranelift_object::ObjectModule; -use std::path::Path; #[cfg(test)] use proptest::arbitrary::Arbitrary; +use std::path::Path; use target_lexicon::Triple; impl Backend { diff --git a/src/backend/runtime.rs b/src/backend/runtime.rs index ec5a2db..40907aa 100644 --- a/src/backend/runtime.rs +++ b/src/backend/runtime.rs @@ -121,9 +121,9 @@ extern "C" fn runtime_print( Err(_) => format!("{} = {}", reconstituted, value), }; - if let Some(output_buffer) = unsafe{ output_buffer.as_mut() } { + if let Some(output_buffer) = unsafe { output_buffer.as_mut() } { writeln!(output_buffer, "{}", output).unwrap(); } else { println!("{}", output); } -} \ No newline at end of file +} diff --git a/src/syntax/arbitrary.rs b/src/syntax/arbitrary.rs index 688a305..93759e9 100644 --- a/src/syntax/arbitrary.rs +++ b/src/syntax/arbitrary.rs @@ -13,12 +13,13 @@ const VALID_VARIABLE_NAMES: &str = r"[a-z][a-zA-Z0-9_]*"; impl ConstantType { fn get_operators(&self) -> &'static [(&'static str, usize)] { match self { - ConstantType::I8| ConstantType::I16 | ConstantType::I32 | ConstantType::I64 => - &[("+", 2), ("-", 1), ("-", 2), ("*", 2), ("/", 2)], - ConstantType::U8| ConstantType::U16 | ConstantType::U32 | ConstantType::U64 => - &[("+", 2), ("-", 2), ("*", 2), ("/", 2)], + ConstantType::I8 | ConstantType::I16 | ConstantType::I32 | ConstantType::I64 => { + &[("+", 2), ("-", 1), ("-", 2), ("*", 2), ("/", 2)] + } + ConstantType::U8 | ConstantType::U16 | ConstantType::U32 | ConstantType::U64 => { + &[("+", 2), ("-", 2), ("*", 2), ("/", 2)] + } } - } } @@ -55,38 +56,41 @@ impl Arbitrary for Program { type Strategy = BoxedStrategy; fn arbitrary_with(genenv: Self::Parameters) -> Self::Strategy { - proptest::collection::vec(ProgramStatementInfo::arbitrary(), genenv.block_length.clone()) - .prop_flat_map(move |mut items| { - let mut statements = Vec::new(); - let mut genenv = genenv.clone(); + proptest::collection::vec( + ProgramStatementInfo::arbitrary(), + genenv.block_length.clone(), + ) + .prop_flat_map(move |mut items| { + let mut statements = Vec::new(); + let mut genenv = genenv.clone(); - for psi in items.drain(..) { - if genenv.bindings.is_empty() || psi.should_be_binding { - genenv.return_type = psi.binding_type; - let expr = Expression::arbitrary_with(genenv.clone()); - genenv.bindings.insert(psi.name.clone(), psi.binding_type); - statements.push( - expr.prop_map(move |expr| { - Statement::Binding(Location::manufactured(), psi.name.clone(), expr) - }) - .boxed(), - ); - } else { - let printers = genenv.bindings.keys().map(|n| { - Just(Statement::Print( - Location::manufactured(), - Name::manufactured(n), - )) - }); - statements.push(Union::new(printers).boxed()); - } + for psi in items.drain(..) { + if genenv.bindings.is_empty() || psi.should_be_binding { + genenv.return_type = psi.binding_type; + let expr = Expression::arbitrary_with(genenv.clone()); + genenv.bindings.insert(psi.name.clone(), psi.binding_type); + statements.push( + expr.prop_map(move |expr| { + Statement::Binding(Location::manufactured(), psi.name.clone(), expr) + }) + .boxed(), + ); + } else { + let printers = genenv.bindings.keys().map(|n| { + Just(Statement::Print( + Location::manufactured(), + Name::manufactured(n), + )) + }); + statements.push(Union::new(printers).boxed()); } + } - statements - .prop_map(|statements| Program { statements }) - .boxed() - }) - .boxed() + statements + .prop_map(|statements| Program { statements }) + .boxed() + }) + .boxed() } } @@ -169,15 +173,18 @@ impl Arbitrary for Expression { // now we generate our recursive types, given our leaf strategy leaf_strategy .prop_recursive(3, 10, 2, move |strat| { - (select(genenv.return_type.get_operators()), strat.clone(), strat).prop_map( - |((oper, count), left, right)| { + ( + select(genenv.return_type.get_operators()), + strat.clone(), + strat, + ) + .prop_map(|((oper, count), left, right)| { let mut args = vec![left, right]; while args.len() > count { args.pop(); } Expression::Primitive(Location::manufactured(), oper.to_string(), args) - }, - ) + }) }) .boxed() } diff --git a/src/type_infer.rs b/src/type_infer.rs index d08ff53..1af1384 100644 --- a/src/type_infer.rs +++ b/src/type_infer.rs @@ -1,9 +1,9 @@ +/// A type inference pass for NGR mod ast; mod convert; mod finalize; mod solve; - use self::convert::convert_program; use self::finalize::finalize_program; use self::solve::solve_constraints; @@ -11,9 +11,9 @@ pub use self::solve::{TypeInferenceError, TypeInferenceResult, TypeInferenceWarn use crate::ir::ast as ir; use crate::syntax; #[cfg(test)] -use proptest::prelude::Arbitrary; -#[cfg(test)] use crate::syntax::arbitrary::GenerationEnvironment; +#[cfg(test)] +use proptest::prelude::Arbitrary; impl syntax::Program { /// Infer the types for the syntactic AST, returning either a type-checked program in diff --git a/src/type_infer/solve.rs b/src/type_infer/solve.rs index 4689bf8..ec33384 100644 --- a/src/type_infer/solve.rs +++ b/src/type_infer/solve.rs @@ -5,6 +5,7 @@ use codespan_reporting::diagnostic::Diagnostic; use internment::ArcIntern; use std::{collections::HashMap, fmt}; +/// A type inference constraint that we're going to need to solve. #[derive(Debug)] pub enum Constraint { /// The given type must be printable using the `print` built-in @@ -48,6 +49,11 @@ impl fmt::Display for Constraint { pub type TypeResolutions = HashMap, PrimitiveType>; +/// The results of type inference; like [`Result`], but with a bit more information. +/// +/// This result is parameterized, because sometimes it's handy to return slightly +/// different things; there's a [`TypeInferenceResult::map`] function for performing +/// those sorts of conversions. pub enum TypeInferenceResult { Success { result: Result, @@ -62,6 +68,8 @@ pub enum TypeInferenceResult { impl TypeInferenceResult { // If this was a successful type inference, run the function over the result to // create a new result. + // + // This is the moral equivalent of [`Result::map`], but for type inference results. pub fn map(self, f: F) -> TypeInferenceResult where F: FnOnce(R) -> U, @@ -89,11 +97,17 @@ impl TypeInferenceResult { } } +/// The various kinds of errors that can occur while doing type inference. pub enum TypeInferenceError { + /// The user provide a constant that is too large for its inferred type. ConstantTooLarge(Location, PrimitiveType, u64), + /// The two types needed to be equivalent, but weren't. NotEquivalent(Location, PrimitiveType, PrimitiveType), + /// We cannot safely cast the first type to the second type. CannotSafelyCast(Location, PrimitiveType, PrimitiveType), + /// The primitive invocation provided the wrong number of arguments. WrongPrimitiveArity(Location, ir::Primitive, usize, usize, usize), + /// We had a constraint we just couldn't solve. CouldNotSolve(Constraint), } @@ -164,6 +178,10 @@ impl From for Diagnostic { } } +/// Warnings that we might want to tell the user about. +/// +/// These are fine, probably, but could indicate some behavior the user might not +/// expect, and so they might want to do something about them. pub enum TypeInferenceWarning { DefaultedTo(Location, Type), } @@ -178,6 +196,16 @@ impl From for Diagnostic { } } +/// 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( mut constraint_db: Vec, ) -> TypeInferenceResult {